/*
 * game.c - The freecell manager
 */

#include <stdlib.h>
#include <time.h>

#include "fc.h"
#include "err.h"

#include "card.h"
#include "cslot.h"
#include "cpile.h"
#include "cstack.h"

#include "game.h"
#include "rules.h"

/* ---------- Macros ---------- */
#define icode_what(i)  ((i) & 0xFF00)
#define icode_which(i) ((i) & 0x00FF)

/* ---------- Move table ---------- */
/* This table is a two dimensional function table.  Use jt_index() macro */
/* to turn an itemcode into an index into the jump table.  Index order is */
/* jt[from][to]. */
/* jt_index is heavily dependent on the values of the what_codes in game.h. */
/* If those should change, so should this function or the jump table or */
/* both. */
#define jt_index(i)  (((icode_what(i)>>8)-1))

/* The functions themselves take two args, the game, and the index of the */
/* item in src, dst order. */
static int cc_move(struct game *, int, int);
static int cs_move(struct game *, int, int);
static int ct_move(struct game *, int, int);
static int sc_move(struct game *, int, int);
static int ss_move(struct game *, int, int);
static int st_move(struct game *, int, int);
static int tc_move(struct game *, int, int);
static int ts_move(struct game *, int, int);
static int tt_move(struct game *, int, int);

/* Indices:  0 = CELL, 1 = SUITS, 3 = STACKS */
static int (*jt[4][4])(struct game *, int, int) = {
     { cc_move, cs_move, NULL, ct_move },
     { sc_move, ss_move, NULL, st_move },
     { NULL,    NULL,    NULL, NULL    },
     { tc_move, ts_move, NULL, tt_move }
};

/* ---------- Function declarations ---------- */
int game_move(struct game *, int, int);
int game_automove(struct game *);
int game_unselectitem(struct game *, int);
int game_selectitem(struct game *, int);
int jump(struct game *, int, int);
int tt_valid_split(struct game *, struct cstack *, struct cstack *, int *); 
int num_free_cells(struct game *);
int can_automove(struct game *, struct card *, int *);
int game_get_freecell(struct game *);

/* ---------------------------------------------------------------------- */
struct game *
game_create()
{
     struct game *ret;
     int i;
     
     ret = calloc(sizeof(*ret), 1);

     /* Assume everything works for now!!! */
     for (i = 0; i < GAME_NUM_SUITS; i++) {
	  ret->suits[i] = cpile_create();
     }
     for (i = 0; i < GAME_NUM_STACKS; i++) {
	  ret->stacks[i] = cstack_create();
     }
     for (i = 0; i < GAME_NUM_CELLS; i++) {
	  ret->cells[i] = cslot_create();
     }
     
     return(ret);
}

/* ---------------------------------------------------------------------- */
int
game_reinit(struct game *gp, int gamenum)
{
     struct card *cdp;
     int used[52], cnums[52], cnum;
     static int seeded = FALSE;
     int i;

     if (gamenum == 0) {
	  /* Use the current time as a seed for now. */
	  time((time_t *)&gamenum);
     }
     
     if (!seeded) {
	  seeded = TRUE;
	  srandom(gamenum);
     }
     
     /* Initialize these dudes, assume someone else has free'd them */
     for (i = 0; i < GAME_NUM_CELLS; i++) {
	  cslot_makeempty(gp->cells[i]);
     }
     for (i = 0; i < GAME_NUM_SUITS; i++) {
	  cpile_makeempty(gp->suits[i]);
     }
     for (i = 0; i < GAME_NUM_STACKS; i++) {
	  cstack_makeempty(gp->stacks[i], TRUE);
     }
     
     gp->selected_item = 0;
     for (i = 0; i < GAME_NUM_SUITS; i++) {
	  gp->topinsuit[i] = 0;
     }

     /* Shuffle and deal */
     for (i = 0; i < 52; i++) {
	  used[i] = FALSE;
     }
     for (i = 0; i < 52; i++) {
	  cnum = random() % 52;
	  while (used[cnum]) {
	       cnum = (cnum + 1) % 52;
	  }
	  used[cnum] = TRUE;
	  cnums[i] = cnum;
     }

     for (i = 0; i < 52; i++) {
	  cdp = card_create_from_int(cnums[i] + 1);
	  /* Potential memory leak here on failure !!! */
	  if (!cdp) return(1);
	  cstack_addcard(gp->stacks[i % 8], cdp);
     }

     return(0);
}

/* ---------------------------------------------------------------------- */
int
game_dclickitem(struct game *gp, int icode)
{
     struct cstack *csp;
     int freecell;

     if (icode != gp->selected_item || icode_what(icode) != GAME_STACKS) {  
	  /* Only the last clicked stack gets double clicked. */
	  game_clickitem(gp, icode);
	  return(0);
     }
     
     csp = gp->stacks[icode_which(icode)];
     freecell = game_get_freecell(gp);
     if (freecell < 0) return(1);
     game_move(gp, icode, freecell);
     while(game_automove(gp));
     return(0);
}

/* ---------------------------------------------------------------------- */
int 
game_clickitem(struct game *gp, int icode)
{
     if (icode == gp->selected_item) {
	  game_unselectitem(gp, icode);
     }
     else if (gp->selected_item == 0) {
	  game_selectitem(gp, icode);
     }
     else {
	  game_move(gp, gp->selected_item, icode);
	  while (game_automove(gp));
     }
     return(0);
}

#ifdef NOT_USED
/* ---------------------------------------------------------------------- */
/*
 *  game_clickunselectitem () -- unselect an item specifically by clicking
 *	on that item.
 *	As a general rule, simply dispatches the call to game_unselectitem().
 */
static int
game_clickunselectitem(struct game *gp, int icode)
{
     struct cstack *csp;
     int freecell;

     switch(icode_what(icode)) {
     case GAME_STACKS:
	  csp = gp->stacks[icode_which(icode)];
	  if (cstack_isdoubleclick (csp)) {
	       freecell = game_get_freecell (gp);
	       if (freecell < 0) break;
	       game_move (gp, icode, freecell);
	       while (game_automove(gp));
	       return (0);
	  }
	  break;
     }
     return (game_unselectitem (gp, icode));
}
#endif /* NOT_USED */

/* ---------------------------------------------------------------------- */
/*
 *  game_get_freecell () -- return the icode of a free cell,
 *	or (-1) if there are no free cells.
 */
int game_get_freecell(struct game *gp)
{
     int x;

     for (x = 0; x < GAME_NUM_CELLS; x++)
	  if (cslot_isempty (gp->cells[x]))
	       return (game_itemcode (GAME_CELLS, x));
     return (-1);
}

/* ---------------------------------------------------------------------- */
int
game_unselectitem(struct game *gp, int icode)
{
     switch(icode_what(icode)) {
     case GAME_CELLS:
	  cslot_setinvert(gp->cells[icode_which(icode)], FALSE);
	  break;
     case GAME_SUITS:
	  break;
     case GAME_STACKS:
	  cstack_setinvert(gp->stacks[icode_which(icode)], FALSE);
	  break;
     }
     gp->selected_item = 0;
     return(0);
}

/* ---------------------------------------------------------------------- */
int
game_selectitem(struct game *gp, int icode)
{
     switch(icode_what(icode)) {
     case GAME_CELLS:
	  if (cslot_isempty(gp->cells[icode_which(icode)])) {
	       icode = 0;
	  }
	  else {
	       cslot_setinvert(gp->cells[icode_which(icode)], TRUE);
	  }
	  break;
     case GAME_SUITS:
	  icode = 0;
	  break;
     case GAME_STACKS:
	  if (cstack_isempty(gp->stacks[icode_which(icode)])) {
	       icode = 0;
	  }
	  else {
	       cstack_setinvert(gp->stacks[icode_which(icode)], TRUE);
	  }
	  break;
     }
     gp->selected_item = icode;
     return(0);
}

/* ---------------------------------------------------------------------- */
int
game_move(struct game *gp, int from_icode, int to_icode)
{
     /* Start by clearing the selection */
     game_unselectitem(gp, from_icode);

     return(jump(gp, from_icode, to_icode));
}

/* ---------------------------------------------------------------------- */
int 
jump(struct game *gp, int from_icode, int to_icode)
{
     return jt[jt_index(from_icode)][jt_index(to_icode)]
	  (gp, icode_which(from_icode), icode_which(to_icode));
}


/* ---------------------------------------------------------------------- */
static int 
cc_move(struct game *gp, int from, int to)
{
     struct cslot *fctp = gp->cells[from];
     struct cslot *tctp = gp->cells[to];
     struct card *cdp;

     if (cslot_isempty(tctp)) {
	  cdp = cslot_removecard(fctp);
	  cslot_addcard(tctp, cdp);
     }

     return(0);
}

static int 
cs_move(struct game *gp, int from, int to)
{
     struct cslot *fctp = gp->cells[from];
     struct cpile *tclp = gp->suits[to];
     struct card *cdp, *cdp2;

     cdp = cslot_getcard(fctp);
     cdp2 = cpile_getcard(tclp);
     if (!r_cardonpile(cdp, cdp2)) return(1);

     cdp = cslot_removecard(fctp);
     cpile_addcard(tclp, cdp);

     gp->topinsuit[card_getsuit(cdp)] = card_getpips(cdp);

     return(0);
}

static int 
ct_move(struct game *gp, int from, int to)
{
     struct cslot *fctp = gp->cells[from];
     struct cstack *tcsp = gp->stacks[to];
     struct card *cdp, *cdp2;
     
     cdp = cslot_getcard(fctp);
     cdp2 = cstack_bottomcard(tcsp);
     if (!r_cardonstack(cdp, cdp2)) return(1);

     cdp = cslot_removecard(fctp);
     cstack_addcard(tcsp, cdp);

     return(0);
}

static int 
sc_move(struct game *gp, int from, int to)
{
     do_error(ERR_INVALIDMOVE, "in sc_move().");

     return(0);
}

static int 
ss_move(struct game *gp, int from, int to)
{
     do_error(ERR_INVALIDMOVE, "in ss_move().");
     return(0);
}

static int 
st_move(struct game *gp, int from, int to)
{
     do_error(ERR_INVALIDMOVE, "in st_move().");
     return(0);
}

static int 
tc_move(struct game *gp, int from, int to)
{
     struct cstack *fcsp = gp->stacks[from];
     struct cslot *tctp = gp->cells[to];
     struct card *cdp;

     if (cslot_isempty(tctp)) {
	  cdp = cstack_removecard(fcsp);
	  cslot_addcard(tctp, cdp);
     }

     return(0);
}

static int 
ts_move(struct game *gp, int from, int to)
{
     struct cstack *fcsp = gp->stacks[from];
     struct cpile *tclp = gp->suits[to];
     struct card *cdp, *cdp2;

     cdp = cstack_bottomcard(fcsp);
     cdp2 = cpile_getcard(tclp);
     if (!r_cardonpile(cdp, cdp2)) return(1);

     cdp = cstack_removecard(fcsp);
     cpile_addcard(tclp, cdp);

     gp->topinsuit[card_getsuit(cdp)] = card_getpips(cdp);

     return(0);
}

static int 
tt_move(struct game *gp, int from, int to)
{
     struct cstack *fcsp = gp->stacks[from];
     struct cstack *tcsp = gp->stacks[to];
     struct cstack *other;
     int split_level, num_in_split, i;
     int num_avail_stacks;

     if (!tt_valid_split(gp, fcsp, tcsp, &split_level)) {
	  return(1);
     }
     num_in_split = cstack_getnumcards(fcsp) - split_level;
     num_avail_stacks = 0;
     for (i = 0; i < GAME_NUM_STACKS; i++) {
	  if (i == from || i == to) continue;
	  if (cstack_isempty(gp->stacks[i])) {
	       num_avail_stacks++;
	  }
     }
     
     if (num_in_split > (num_free_cells(gp) + 1) * (num_avail_stacks + 1)) { 
	  return(1);
     }

     other = cstack_split(fcsp, split_level);
     cstack_join(tcsp, other);

     return(0);
}

/* ---------------------------------------------------------------------- */
int
game_automove(struct game *gp)
{
     struct card *cdp;
     int i, fromicode, toicode;

     /* Start by checking the stacks */
     for (i = 0; i < GAME_NUM_STACKS; i++) {
	  cdp = cstack_bottomcard(gp->stacks[i]);
	  if (can_automove(gp, cdp, &toicode)) {
	       fromicode = game_itemcode(GAME_STACKS, i);
	       goto got_one;
	  }
     }

     /* Then check the cells */
     for (i = 0; i < GAME_NUM_CELLS; i++) {
	  cdp = cslot_getcard(gp->cells[i]);
	  if (can_automove(gp, cdp, &toicode)) {
	       fromicode = game_itemcode(GAME_CELLS, i);
	       goto got_one;
	  }
     }

     return(0);

got_one:
     XFlush(dpy);
     game_move(gp, fromicode, toicode);
     return(1);
}

/* ---------------------------------------------------------------------- */
int
can_automove(struct game *gp, struct card *cdp, int *icodep)
{
     pipn_e pips;
     suit_e suit;
     int i;
     
     if (cdp == NULL) return(FALSE);

     suit = card_getsuit(cdp);
     pips = card_getpips(cdp);

     if (pips != gp->topinsuit[suit] + 1) return(FALSE);
     
     if (pips == ace) goto find_a_place;
     if (pips == 2) goto find_a_place;
     
     if (card_color(cdp) == red) {
	  if (pips > gp->topinsuit[spades] + 1) return(FALSE);
	  if (pips > gp->topinsuit[clubs] + 1) return(FALSE);
     }
     else {
	  if (pips > gp->topinsuit[hearts] + 1) return(FALSE);
	  if (pips > gp->topinsuit[diamonds] + 1) return(FALSE); 
     }

find_a_place:
     /* Now we need to find a place for the card. */
     for (i = 0; i < GAME_NUM_SUITS; i++) {
	  if (r_cardonpile(cdp, cpile_getcard(gp->suits[i]))) {
	       *icodep = game_itemcode(GAME_SUITS, i);
	       return(TRUE);
	  }
     }
     
     do_error(ERR_INVALIDSTATE, "Automove problems.");
     return(TRUE);
}

/* ---------------------------------------------------------------------- */
int
num_free_cells(struct game *gp)
{
     int i, ret = 0;

     for (i = 0; i < GAME_NUM_CELLS; i++) {
	  if (cslot_isempty(gp->cells[i])) ret++;
     }
     return(ret);
}

/* ---------------------------------------------------------------------- */
/*
 * tt_valid_split(game, from_stack, to_stack, splitp) --> ret
 * 
 * This routine goes through the from stack and tries to find a valid split
 * that can be moved from the from_stack to the to_stack.  Iff such a split 
 * can be found, return TRUE.  On a TRUE return, splitp will contain the
 * number of the card to split on (i.e., the argument to cstack_split()). 
 * 
 * N.B.: This does mean that the move can occur.  There may not be enough
 * free cells and free stacks to perform the movement.
 */
int
tt_valid_split(struct game *gp, struct cstack *fcsp, struct cstack *tcsp,
	       int *split_level)
{
     struct card *candidate_cardp;
     struct card *last_cardp = NULL;
     struct card *to_cardp;
     int level;

     /* This is the card the stack needs to be placed upon. */
     to_cardp = cstack_bottomcard(tcsp);

     /* Iterate through the stack looking for a valid stack that can be */
     /* moved. */
     for (level = cstack_getnumcards(fcsp) - 1; level >= 0; level--) {
	  candidate_cardp = cstack_getcard(fcsp, level);
	  if (last_cardp != NULL) {
	       /* Is it still a valid continguous stack */
	       if (!r_cardonstack(last_cardp, candidate_cardp)) {
		    return(FALSE);
	       }
	  }
	  /* Can we move this stack. */
	  if (r_cardonstack(candidate_cardp, to_cardp)) {
	       *split_level = level;
	       return(TRUE);
	  }
	  last_cardp = candidate_cardp;
     }

     return(FALSE);
}
