/*
 * Spider
 *
 * Copyright (C) Evan Harris, 1991, 1993, 1994
 *
 * Permission is granted to freely redistribute and modify this code,
 * providing the author(s) get credit for having written it.
 */

#include "spider.h"
#include <stdlib.h>


#define U_MOVE			1
#define U_MOVEOFF		2
#define U_MOVE_EXPOSE		3
#define U_MOVEOFF_EXPOSE	4
#define U_FROMSTOCK		5

struct undo {
    unsigned char type;
    unsigned char card;
    short from;
    struct undo	*next;
};


unsigned char	column[COLUMNS];		/* First card of column */
unsigned char	stock;
short		cards[NUMCARDS];		/* Positions of cards */
unsigned char	next[NUMCARDS];			/* Card underneath */
unsigned char	hidden[NUMCARDS];		/* Cards which are face down */
unsigned char	foundation[NUMSUITS];
struct undo	*undoinfo = NULL;


void main(int argc, char **argv)
{
    short cmd, dest;

    InitDisplay(argc, argv);

    InitRandom(NEW);
    Deal();

    for (;;) {
	cmd = GetCmd();
	if (cmd == QUIT) {
	    EndDisplay();
	    exit(0);
	}
	else if (cmd == NEWGAME) {
	    InitRandom(NEW);
	    Deal();
	}
	else if (cmd == RESTART) {
	    InitRandom(LAST);
	    Deal();
	}
	else if (cmd == FROMSTOCK) {
	    FromStock();
	}
	else if (cmd == UNDO) {
	    Undo();
	}
	else if (ISCARD(NOHINT(cmd))) {
	    dest = FindDest(cmd);
	    if (dest != NOPOSN)
		MakeMove((unsigned char)NOHINT(cmd), dest);
	}
    }

    /* Never reached */
}


void Deal()
{
    unsigned char i, j;
    unsigned char row, col;
    short r, lastr = 0;
    struct undo *u;

    /* Initialise the deck */
    for (i = 0; i < NUMCARDS; i++) {
	cards[i] = NOPOSN;
	next[i] = NOCARD;
    }
    /* Initialise foundations */
    for (i = 0; i < NUMSUITS; i++) {
	foundation[i] = 0;
    }
		
    /* Remove undo information */
    while (undoinfo) {
	u = undoinfo->next;
	free(undoinfo);
	undoinfo = u;
    }

    /* Deal the deck */
    row = 0;
    col = 0;
    for (i = 0; i < NUMCARDS; i++) {
	r = Random(NUMCARDS - i);
	for (j = 0; j < NUMCARDS && r >= 0; j++) {
	    if (cards[j] == NOPOSN)
		r--;
	}
	r = j - 1;

	cards[r] = POSN(col, row);
	if (col != STOCK) {
	    if (row == 0)
		column[col] = r;
	    else
		next[lastr] = r;
	    lastr = r;

	    if ((col < 4 && row < 5) || (row < 4)) {
		hidden[r] = 1;
		row++;
	    }
	    else {
		hidden[r] = 0;
		col++; 
		row = 0;
	    }
	}
	else {
	    if (row == 0)
		stock = r;
	    else
		next[lastr] = r;
	    lastr = r;
	    hidden[r] = 0;
	    row++;
	}
    }
    for (i = 0; i < COLUMNS; i++)
	DisplayColumn((short)i);
    DisplayFoundations();
    DisplayStockPile();

    return;
}


short FindDest(short card)
{
    unsigned char i, c;
    short first = -1;

    c = next[NOHINT(card)];
    i = 1;
    while (c != NOCARD) {
	if (SUIT(c) != SUIT(NOHINT(card)) || TYPE(c) != TYPE(NOHINT(card)) - i) {
	    return NOPOSN;
	}
	c = next[c];
	i++;
    }

    if (HASHINT(card))
	return FindHintedDest((short)NOHINT(card), (short)HINT(card));

    if (CanMoveOff((unsigned char)card))
	return MOVEOFF;

    c = column[COL(cards[card])];
    if (c != card) {
	while (next[c] != card)
	    c = next[c];
	if (!hidden[c] && TYPE(c) == TYPE(card) + 1 && SUIT(c) == SUIT(card)) {
	    return NOPOSN;
	}
    }

    if (TYPE(card) != KING) {
	for (i = 0; i < COLUMNS; i++) {
	    if (i != COL(cards[card]) && column[i] != NOCARD) {
		c = column[i];
		while (next[c] != NOCARD)
		    c = next[c];
		if (TYPE(c) == TYPE(card) + 1) {
		    if (SUIT(c) == SUIT(card)) {
			return (short)POSN(COL(cards[c]), ROW(cards[c]) + 1);
		    }
		    else if (first == -1)
			first = c;
		}
	    }
	}
	if (first != -1) {
	    return (short)POSN(COL(cards[first]), ROW(cards[first]) + 1);
	}
    }
    if (ROW(cards[card]) == 0) {
	return NOPOSN;
    }
    for (i = 0; i < COLUMNS; i++) {
	if (column[i] == NOCARD) {
	    return (short)POSN(i, 0);
	}
    }

    return NOPOSN;
}


short FindHintedDest(short card, short hint)
{
    unsigned char c;

    if (hint == FOUNDATION) {
	if (CanMoveOff((unsigned char)card))
	    return MOVEOFF;
	else
	    return NOPOSN;
    }

    if (TYPE(card) != KING && hint != COL(cards[card])) {
	c = column[hint];
	if (c != NOCARD) {
	    while (next[c] != NOCARD)
		c = next[c];
	    if (TYPE(c) == TYPE(card) + 1) {
		return (short)POSN(COL(cards[c]), ROW(cards[c]) + 1);
	    }
	}
    }
    if (ROW(cards[card]) == 0)
	return NOPOSN;
    if (column[hint] == NOCARD)
	return (short)POSN(hint, 0);

    return NOPOSN;
}


void MakeMove(unsigned char card, short dest)
{
    short col, row, oldcol;
    unsigned char c, i;
    unsigned char h = 0;

    if (dest == MOVEOFF) {
	MoveOff(card);
	return;
    }

    col = COL(dest);
    row = ROW(dest);
    oldcol = -1;

    for (i = 0; oldcol == -1 && i < COLUMNS; i++) {
	if (column[i] == card) {
	    column[i] = NOCARD;
	    oldcol = i;
	}
    }
    for (i = 0; oldcol == -1 && i < NUMCARDS; i++) {
	if (next[i] == card) {
	    if (hidden[i]) {
		hidden[i] = 0;
		h = 1;
	    }
	    next[i] = NOCARD;
	    oldcol = COL(cards[i]);
	}
    }

    if (h)
	AddUndo(U_MOVE_EXPOSE, card, cards[card]);
    else
	AddUndo(U_MOVE, card, cards[card]);

    if (row > 0) {
	c = column[col];
	while (next[c] != NOCARD)
	    c = next[c];
	next[c] = card;
    }
    else
	column[col] = card;
    while (card != NOCARD) {
	cards[card] = POSN(col, row++);
	card = next[card];
    }

    DisplayColumn(oldcol);
    DisplayColumn(col);

    return;
}


void FromStock()
{
    unsigned char c, col, row;

    if (stock != NOCARD) {
	for (col = 0; col < COLUMNS; col++) {
	    if (column[col] == NOCARD) {
		return;
	    }
	}
	for (col = 0; col < COLUMNS; col++) {
	    c = column[col];
	    row = 1;
	    while (next[c] != NOCARD) {
		c = next[c];
		row++;
	    }
	    next[c] = stock;
	    stock = next[stock];
	    cards[next[c]] = POSN(col, row);
	    next[next[c]] = NOCARD;

	    DisplayColumn((short)col);
	}

	if (stock == NOCARD)
	    DisplayStockPile();

	AddUndo(U_FROMSTOCK, 0, 0);
    }

    return;
}


void MoveOff(unsigned char card)
{
    unsigned char c, lastc;
    unsigned char h = 0;
    short oldcol;

    oldcol = COL(cards[card]);
    c = column[oldcol];
    lastc = NOCARD;
    while (hidden[c]) {
	lastc = c;
	c = next[c];
    }

    for (;;) {
	while (c != NOCARD && TYPE(c) != KING && SUIT(c) != SUIT(card)) {
	    lastc = c;
	    c = next[c];
	}
	if (CheckFullTerminatingSuit(c)) {
	    if (lastc == NOCARD)
		column[oldcol] = NOCARD;
	    else {
		next[lastc] = NOCARD;
		if (hidden[lastc]) {
		    hidden[lastc] = 0;
		    h = 1;
		}
	    }
	    if (h)
		AddUndo(U_MOVEOFF_EXPOSE, c, cards[c]);
	    else
		AddUndo(U_MOVEOFF, c, cards[c]);
	    while (c != NOCARD) {
		cards[c] = POSN(FOUNDATION, 0);
		c = next[c];
	    }

	    if (foundation[SUIT(card)])
		foundation[SUIT(card) + 4] = 1;
	    else
		foundation[SUIT(card)] = 1;
	    DisplayFoundations();
	    DisplayColumn(oldcol);

	    return;
	}
	else {
	    lastc = c;
	    c = next[c];
	}
    }
}


short CanMoveOff(unsigned char card)
{
    unsigned char c;

    c = column[COL(cards[card])];
    while (hidden[c])
	c = next[c];
    while (c != NOCARD) {
	while (c != NOCARD && TYPE(c) != KING && SUIT(c) != SUIT(card))
	    c = next[c];
	if (CheckFullTerminatingSuit(c)) 
	    return TRUE;
	else
	    c = next[c];
    }
    return FALSE;
}


short CheckFullTerminatingSuit(unsigned char card)
{
    unsigned char c;
    short type;

    c = card;
    type = KING;
    while (c != NOCARD && SUIT(c) == SUIT(card) && TYPE(c) == type) {
	c = next[c];
	type--;
    }

    if (c == NOCARD && type == ACE - 1)
	return TRUE;
    else
	return FALSE;
}


void AddUndo(unsigned char type, unsigned char card, short from)
{
    struct undo *undo;

    undo = (struct undo *)malloc(sizeof(struct undo));
    undo->type = type;
    undo->card = card;
    undo->from = from;
    undo->next = undoinfo;
    undoinfo = undo;

    return;
}


void Undo()
{
    struct undo *undo = undoinfo;

    if (undo == NULL)
	return;

    switch (undo->type) {
      case U_MOVE:
	UndoMove(undo->card, (short)COL(undo->from), 0);
	break;
      case U_MOVE_EXPOSE:
	UndoMove(undo->card, (short)COL(undo->from), 1);
	break;
      case U_MOVEOFF:
	UndoMoveOff(undo->card, (short)COL(undo->from), 0);
	break;
      case U_MOVEOFF_EXPOSE:
	UndoMoveOff(undo->card, (short)COL(undo->from), 1);
	break;
      case U_FROMSTOCK:
	UndoFromStock();
	break;
    }

    undoinfo = undoinfo->next;
    free(undo);

    return;
}


void UndoMove(unsigned char card, short col, unsigned char expose)
{
    unsigned char c, row;
    short oldcol;

    oldcol = COL(cards[card]);
    c = column[oldcol];
    if (c == card) {
	column[oldcol] = NOCARD;
    } else {
	while (next[c] != card)
	    c = next[c];
	next[c] = NOCARD;
    }

    c = column[col];
    row = 0;
    if (c == NOCARD) {
	column[col] = card;
	cards[card] = POSN(col, row);
	c = next[card];
    } else {
	while (next[c] != NOCARD) {
	    c = next[c];
	    row++;
	}
	if (expose)
	    hidden[c] = 1;
	next[c] = card;
	cards[card] = POSN(col, ++row);
	c = next[card];
    }
    while (c != NOCARD) {
	cards[c] = POSN(col, ++row);
	c = next[c];
    }
    DisplayColumn(oldcol);
    DisplayColumn(col);

    return;
}


void UndoMoveOff(unsigned char card, short col, unsigned char expose)
{
    unsigned char c, row;

    c = column[col];
    row = 0;
    if (c == NOCARD) {
	column[col] = card;
	c = card;
	while (c != NOCARD) {
	    cards[c] = POSN(col, row++);
	    c = next[c];
	}
    } else {
	while (next[c] != NOCARD) {
	    c = next[c];
	    row++;
	}
	if (expose)
	    hidden[c] = 1;
	next[c] = card;
	c = next[c];
	while (c != NOCARD) {
	    cards[c] = POSN(col, row++);
	    c = next[c];
	}
    }
    if (foundation[SUIT(card) + 4])
	foundation[SUIT(card) + 4] = 0;
    else
	foundation[SUIT(card)] = 0;
    DisplayColumn((short)col);
    DisplayFoundations();

    return;
}


void UndoFromStock()
{
    unsigned char oldstock = stock;
    unsigned char c;
    short col;

    for (col = COLUMNS - 1; col >= 0; col--) {
	c = column[col];
	while (next[next[c]] != NOCARD) {
	    c = next[c];
	}
	next[next[c]] = stock;
	stock = next[c];
	cards[stock] = POSN(STOCK, 0);
	next[c] = NOCARD;

	DisplayColumn((short)col);
    }

    if (oldstock == NOCARD)
	DisplayStockPile();

    return;
}
