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

#include <stdio.h>
#include <getopt.h>

#include "klondike.h"

unsigned char	column[COLUMNS];		/* First card of column */
unsigned char	stock, pile;			/* First card of stock, pile */
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];

int stockcycled;		/* The number of times stock has been cycled */


void main(int argc, char **argv)
{
    short cmd, dest;
    int cycle = 0, stockmove = 1;
    int c, err = 0;

    while ((c = getopt(argc, argv, "cm:")) != -1) {
	switch (c) {
	  case 'c':
	    cycle = 1;
	    break;
	  case 'm':
	    stockmove = atoi(optarg);
	    if (stockmove == 0) {
		err++;
	    }
	    break;
	  case '?':
	    err++;
	    break;
	}
    }
    if (err) {
	fprintf(stderr, "Usage: %s [-c] [-m N]", argv[0]);
	fprintf(stderr, "\t-c    cycle through stock forever\n");
	fprintf(stderr, "\t-m N  move through stock N cards at a time\n");
	exit(1);
    }


    
    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(cycle, stockmove);
	}
	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;

    /* Initialise the deck */
    for (i = 0; i < NUMCARDS; i++) {
	cards[i] = NOPOSN;
	next[i] = NOCARD;
    }
    for (i = 0; i < NUMSUITS; i++)
	foundation[i] = ACE;
    pile = NOCARD;
		
    /* 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 {
		for (j = 0; cards[j] != POSN(col, row - 1); j++)
		    ;
		next[j] = r;
	    }
	    if (col == row) {
		hidden[r] = 0;
		col++; 
		row = 0;
	    }
	    else {
		hidden[r] = 1;
		row++;
	    }
	}
	else {
	    if (row == 0)
		stock = r;
	    else {
		for (j = 0; cards[j] != POSN(col, row - 1); j++)
		    ;
		next[j] = r;
	    }
	    hidden[r] = 0;
	    row++;
	}
    }

    stockcycled = 0;

    for (i = 0; i < COLUMNS; i++)
	DisplayColumn((short)i);
    DisplayFoundations();
    DisplayStockPile();

    return;
}


short FindDest(short card)
{
    unsigned char i, c;
    short move;

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

    move = CanMoveOff((unsigned char)card);
    if (move == DESIRABLE)
	return MOVEOFF;

    if (TYPE(card) == KING) {
	if (ROW(cards[card]) == 0 && COL(cards[card]) != PILE) {
	    if (move == POSSIBLE)
		return MOVEOFF;
	    else
		return NOPOSN;
	}
	for (i = 0; i < COLUMNS; i++)
	    if (column[i] == NOCARD)
		return (short)POSN(i, 0);
    }
    else {
	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) &&
		    ((ISBLACK(c) && ISRED(card)) || 
		     (ISRED(c) && ISBLACK(card)))) {
		    return (short)POSN(COL(cards[c]), ROW(cards[c]) + 1);
		}
	    }
    }

    if (move == POSSIBLE)
	return MOVEOFF;
    else
	return NOPOSN;
}


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

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

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

    return NOPOSN;
}


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

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

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

    if (pile == card) {
	pile = next[card];
	next[card] = NOCARD;
	oldcol = PILE;
    }
    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;
	    next[i] = NOCARD;
	    oldcol = COL(cards[i]);
	}
    }

    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(int cycle, int stockmove)
{
    unsigned char oldpile, oldstock;
    int i;

    if (stock != NOCARD) {
	for (i = 0; i < stockmove && stock != NOCARD; i++) {
	    oldpile = pile;
	    pile = stock;
	    stock = next[stock];
	    next[pile] = oldpile;
	    cards[pile] = POSN(PILE, 0);
	}

	DisplayStockPile();

    } else if ((++stockcycled) / stockmove == 0 || cycle) {
	while (pile != NOCARD) {
	    oldstock = stock;
	    stock = pile;
	    pile = next[pile];
	    next[stock] = oldstock;
	    cards[stock] = POSN(STOCK, 0);
	    hidden[stock] = 0;
	}

	DisplayStockPile();

    }

    return;
}


void MoveOff(unsigned char card)
{
    unsigned char i;
    short oldcol;

    if (card == pile && TYPE(card) == foundation[SUIT(card)]) {
	pile = next[card];
	next[card] = NOCARD;
	cards[card] = POSN(FOUNDATION, 0);
	foundation[SUIT(card)]++;

	DisplayFoundations();
	DisplayStockPile();
    }
    else if (next[card] == NOCARD && TYPE(card) == foundation[SUIT(card)]) {
	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;
		next[i] = NOCARD;
		oldcol = COL(cards[i]);
	    }
	}

	cards[card] = POSN(FOUNDATION, 0);
	foundation[SUIT(card)]++;

	DisplayFoundations();
	DisplayColumn((short)oldcol);
    }

    return;
}


short CanMoveOff(unsigned char card)
{
    if (foundation[SUIT(card)] != TYPE(card) || 
	(COL(cards[card]) != PILE && next[card] != NOCARD))
	return IMPOSSIBLE;

    if (LowestFoundation((unsigned char)SUIT(card)))
	return DESIRABLE;

    return POSSIBLE;
}


unsigned char LowestFoundation(unsigned char suit)
{
    unsigned char i;

    for (i = 0; i < NUMSUITS; i++)
	if (foundation[i] < foundation[suit])
	    return FALSE;
    return TRUE;
}
