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

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <vga.h>
#include <vgamouse.h>

#include "vga16.h"
#include "solitaire.h"


#define SO_SCREENMODE G640x480x16
#define SO_SCREENWIDTH 640
#define SO_SCREENHEIGHT 480

#define SO_TEXTFG 1
#define SO_TEXTBG 0
#define SO_BUTTONFG 2
#define SO_BUTTONBG 6
#define SO_MOUSEFG 5
#define SO_MOUSEDARKFG 2
#define SO_OUTLINE 6

#define NUMCARDIMAGES 52
#define CARDACTUALWIDTH 53
#define CARDIMAGEWIDTH 56
#define CARDIMAGEHEIGHT 80

#define VERSION "SOLITAIRE\n  v1.0"
#define VERSIONLEFT (SO_SCREENWIDTH - 76)

#ifndef CARDSFILE
#define CARDSFILE "Cards56x80"
#endif


#define CARDTOIMAGE(card)	(card % NUMCARDIMAGES)

#define BITWIDTH CARDIMAGEWIDTH /* multiple of 8 */
#define BITHEIGHT CARDIMAGEHEIGHT
#define BITSHOWLEFT 24		/* multiple of 8 */
#define BITTOP 24
#define COLWIDTH (BITWIDTH + 8)
#define TOPROWHEIGHT	0


#define SO_QUITLEFT (SO_SCREENWIDTH - 80)
#define SO_QUITRIGHT (SO_QUITLEFT + 80)
#define SO_QUITBOTTOM 132
#define SO_QUITTOP (SO_QUITBOTTOM - 16)

#define SO_NEWGAMELEFT SO_QUITLEFT
#define SO_NEWGAMERIGHT SO_QUITRIGHT
#define SO_NEWGAMEBOTTOM 48
#define SO_NEWGAMETOP (SO_NEWGAMEBOTTOM - 16)

#define SO_RESTARTLEFT SO_QUITLEFT
#define SO_RESTARTRIGHT SO_QUITRIGHT
#define SO_RESTARTBOTTOM 76
#define SO_RESTARTTOP (SO_RESTARTBOTTOM - 16)

#define SO_UNDOLEFT SO_QUITLEFT
#define SO_UNDORIGHT SO_QUITRIGHT
#define SO_UNDOBOTTOM 104
#define SO_UNDOTOP (SO_UNDOBOTTOM - 16)


#define SO_PALETTESIZE 7

int palette[SO_PALETTESIZE * 3] = {
    0x00, 0x20, 0x00,		/* green */
    0x3f, 0x3f, 0x3f,		/* white */
    0x00, 0x00, 0x00,		/* black */
    0x20, 0x00, 0x00,		/* red */
    0x00, 0x00, 0x20,		/* blue */
    0x3f, 0x3f, 0x00,		/* yellow */
    0x18, 0x18, 0x18,		/* grey */
};

#define MIN(a, b) ((a) < (b) ? (a) : (b))
static void LoadCards(char *file);
static long GetMouseButton(void);
static long WhileMouseButton(void);
static int ParsePos(long pos, int downcard, int buttonup);

static unsigned char ***imageCard;
static unsigned char **backCard;
static unsigned char **outlineCard;
static unsigned char *blankLine;


void
InitDisplay(int argc, char **argv)
{
    vga_disabledriverreport();
    vga_init();
    vga_setmousesupport(1);
    
    if (vga_setmode(SO_SCREENMODE) != 0) {
	fprintf(stderr, "Mode %s not available!\n",
		vga_getmodename(SO_SCREENMODE));
	exit(1);
    }

    vga16_init();

    LoadCards(CARDSFILE);

    blankLine = (unsigned char *)calloc(SO_SCREENWIDTH, sizeof(unsigned char));
    if (blankLine == NULL) {
	fprintf(stderr, "Error: cannot get memory for blankLine\n");
	exit(1);
    }

    vga_setpalvec(0, SO_PALETTESIZE, &palette[0]);

    vga16_text(SO_NEWGAMELEFT, SO_NEWGAMEBOTTOM, " NEW GAME ",
	       SO_BUTTONFG, SO_BUTTONBG);
    vga16_text(SO_RESTARTLEFT, SO_RESTARTBOTTOM, " RESTART  ",
	       SO_BUTTONFG, SO_BUTTONBG);
    vga16_text(SO_UNDOLEFT, SO_UNDOBOTTOM, "   UNDO   ",
	       SO_BUTTONFG, SO_BUTTONBG);
    vga16_text(SO_QUITLEFT, SO_QUITBOTTOM, "   QUIT   ",
	       SO_BUTTONFG, SO_BUTTONBG);

    vga16_text(VERSIONLEFT, 200, VERSION, SO_TEXTFG, SO_TEXTBG);
}


void
EndDisplay()
{
    vga_setmode(TEXT);
}


static void
LoadCards(char *file)
{
    int i, j, k, l, c, colour;
    FILE *f;

    f = fopen(file, "r");
    if (f == NULL) {
	fprintf(stderr, "Cannot find '%s'\n", file);
	exit(1);
    }

    imageCard = (unsigned char ***)malloc(NUMCARDIMAGES
					  * sizeof(unsigned char **));
    if (imageCard == NULL) {
	fprintf(stderr, "Error: cannot get memory for imageCard\n");
	exit(1);
    }
    for (i = 0; i < NUMCARDIMAGES; i++) {
	imageCard[i] = (unsigned char **)malloc(CARDIMAGEHEIGHT
						* sizeof(unsigned char *));
	if (imageCard == NULL) {
	    fprintf(stderr, "Error: cannot get memory for imageCard[%d]\n",
		    i);
	    exit(1);
	}
	for (j = 0; j < CARDIMAGEHEIGHT; j++) {
	    imageCard[i][j] = (unsigned char *)malloc(CARDIMAGEWIDTH
						      * sizeof(unsigned char));
	    if (imageCard[i][j] == NULL) { 
		fprintf(stderr,
			"Error: cannot get memory for imageCard[%d][%d]\n",
			i, j);
		exit(1);
	    }
	    
	    if (SUIT(i) == SPADES || SUIT(i) == CLUBS) {
		colour = 2;
	    } else {
		colour = 3;
	    }
	    if (TYPE(i) < JACK) {
		for (k = 0; k < CARDIMAGEWIDTH / 8; k++) {
		    if ((c = getc(f)) == EOF) {
			fprintf(stderr, "Unexpected EOF in '%s'\n", file);
			exit(1);
		    }
		    for (l = 0; l < 8; l++) {
			imageCard[i][j][8 * k + l] =
			    (c & 1 << (7 - l)) ? colour : 1;
		    }
		}
		for (k = CARDACTUALWIDTH; k < CARDIMAGEWIDTH; k++) {
		    imageCard[i][j][k] = 0;
		}
	    } else {
		for (k = 0; k < CARDIMAGEWIDTH / 2; k++) {
		    if ((c = getc(f)) == EOF) {
			fprintf(stderr, "Unexpected EOF in '%s'\n", file);
			exit(1);
		    }
		    imageCard[i][j][2 * k] = (unsigned char)c >> 4;
		    imageCard[i][j][2 * k + 1] = (unsigned char)c & 0xf;
		}
	    }
	}
    }

    fclose(f);

    backCard = malloc(CARDIMAGEHEIGHT * sizeof(unsigned char *));
    outlineCard = malloc(CARDIMAGEHEIGHT * sizeof(unsigned char *));
    if (backCard == NULL || outlineCard == NULL) {
	fprintf(stderr, "Error: cannot get memory for cards");
	exit(1);
    }
    for (i = 0; i < CARDIMAGEHEIGHT; i++) {
	backCard[i] = malloc(CARDIMAGEWIDTH * sizeof(unsigned char));
	outlineCard[i] = calloc(CARDIMAGEWIDTH, sizeof(unsigned char));
	if (backCard[i] == NULL || outlineCard[i] == NULL) {
	    fprintf(stderr, "Error: cannot get memory for cards");
	    exit(1);
	}
    }
    for (i = 0; i < CARDACTUALWIDTH; i++) {
	outlineCard[0][i] = SO_OUTLINE;
	outlineCard[CARDIMAGEHEIGHT - 1][i] = SO_OUTLINE;
    }
    for (i = 0; i < CARDIMAGEHEIGHT; i++) {
	outlineCard[i][0] = SO_OUTLINE;
	outlineCard[i][CARDACTUALWIDTH - 1] = SO_OUTLINE;
    }
    for (i = 0; i < CARDIMAGEHEIGHT; i++) {
	for (j = 0; j < CARDACTUALWIDTH; j++) {
	    if ((i / 4) % 2 == (j / 4) % 2) {
		backCard[i][j] = 4;
	    } else {
		backCard[i][j] = 1;
	    }
	}
	for (; j < CARDIMAGEWIDTH; j++) {
	    backCard[i][j] = 0;
	}
    }
}


void
InitRandom(unsigned char state)
{
    static long seed;
    
    if (state == NEW) {
	seed = time(NULL);
    }
    srand48(seed);
}


long
Random(long max)
{
    return lrand48() % max;
}


static unsigned char columnTop[COLUMNS];

/* We redisplay a whole column, rather than do anything tricky. */
void
DisplayColumn(short col)
{
    unsigned char **image;
    int card, length, row, height, line;
    int bittop = BITTOP;
	
    for (card = column[col], length = 0; card != NOCARD; card = next[card])
	length++;

    if (length > 0 && (SO_SCREENHEIGHT - TOPROWHEIGHT) / length < bittop)
	bittop = (SO_SCREENHEIGHT - TOPROWHEIGHT) / length;
    columnTop[col] = bittop;

    card = column[col];
    row = TOPROWHEIGHT;
    col = col * COLWIDTH;

    while (card != NOCARD && row < SO_SCREENHEIGHT - 1) {
	if (hidden[card])
	    image = backCard;
	else
	    image = imageCard[CARDTOIMAGE(card)];
	if (next[card] == NOCARD)
	    height = BITHEIGHT;
	else
	    height = bittop;
	if (row + height >= SO_SCREENHEIGHT)
	    height -= row + height - SO_SCREENHEIGHT + 1;

	for (line = 0; line < height; line++) {
	    vga16_drawscansegment(image[line], col, row + line, BITWIDTH);
	}

	row += height;
	card = next[card];
    }
    for (; row < SO_SCREENHEIGHT; row++) {
	vga16_drawscansegment(blankLine, col, row, BITWIDTH);
    }

    return;
}


short
GetCmd()
{
    int c, c1;
    
    for (c = NOCARD; c == NOCARD;) {
	c = GetMouseButton();
	c1 = WhileMouseButton();
	if (c >= 0) {
	    c = ParsePos(c, -1, 0);
	    c1 = ParsePos(c1, c, 1);

	    if (c == c1 && c >= 0) {
		return c;
	    }
	    c = NOCARD;
	}
    }

    return c;
}


static void
RenderMousePointer(int x, int y)
{
    vga16_setpixel(SO_MOUSEFG, x    , y);
    vga16_setpixel(SO_MOUSEFG, x + 1, y);
    vga16_setpixel(SO_MOUSEFG, x + 2, y);
    vga16_setpixel(SO_MOUSEFG, x + 3, y);
    vga16_setpixel(SO_MOUSEFG, x + 4, y);
    vga16_setpixel(SO_MOUSEFG, x + 5, y);
    vga16_setpixel(SO_MOUSEFG, x    , y + 1);
    vga16_setpixel(SO_MOUSEDARKFG, x + 1, y + 1);
    vga16_setpixel(SO_MOUSEDARKFG, x + 2, y + 1);
    vga16_setpixel(SO_MOUSEFG, x + 3, y + 1);
    vga16_setpixel(SO_MOUSEFG, x    , y + 2);
    vga16_setpixel(SO_MOUSEDARKFG, x + 1, y + 2);
    vga16_setpixel(SO_MOUSEDARKFG, x + 2, y + 2);
    vga16_setpixel(SO_MOUSEFG, x + 3, y + 2);
    vga16_setpixel(SO_MOUSEFG, x + 4, y + 2);
    vga16_setpixel(SO_MOUSEFG, x    , y + 3);
    vga16_setpixel(SO_MOUSEFG, x + 1, y + 3);
    vga16_setpixel(SO_MOUSEFG, x + 2, y + 3);
    vga16_setpixel(SO_MOUSEDARKFG, x + 3, y + 3);
    vga16_setpixel(SO_MOUSEFG, x + 4, y + 3);
    vga16_setpixel(SO_MOUSEFG, x + 5, y + 3);
    vga16_setpixel(SO_MOUSEFG, x    , y + 4);
    vga16_setpixel(SO_MOUSEFG, x + 2, y + 4);
    vga16_setpixel(SO_MOUSEFG, x + 3, y + 4);
    vga16_setpixel(SO_MOUSEDARKFG, x + 4, y + 4);
    vga16_setpixel(SO_MOUSEFG, x + 5, y + 4);
    vga16_setpixel(SO_MOUSEFG, x + 6, y + 4);
    vga16_setpixel(SO_MOUSEFG, x    , y + 5);
    vga16_setpixel(SO_MOUSEFG, x + 3, y + 5);
    vga16_setpixel(SO_MOUSEFG, x + 4, y + 5);
    vga16_setpixel(SO_MOUSEDARKFG, x + 5, y + 5);
    vga16_setpixel(SO_MOUSEFG, x + 6, y + 5);
    vga16_setpixel(SO_MOUSEFG, x + 7, y + 5);
    vga16_setpixel(SO_MOUSEFG, x + 4, y + 6);
    vga16_setpixel(SO_MOUSEFG, x + 5, y + 6);
    vga16_setpixel(SO_MOUSEFG, x + 6, y + 6);
    vga16_setpixel(SO_MOUSEFG, x + 5, y + 7);
}

static void
RestoreUnderMousePointer(int x, int y, int *colour)
{
    vga16_setpixel(colour[0], x    , y);
    vga16_setpixel(colour[1], x + 1, y);
    vga16_setpixel(colour[2], x + 2, y);
    vga16_setpixel(colour[3], x + 3, y);
    vga16_setpixel(colour[4], x + 4, y);
    vga16_setpixel(colour[5], x + 5, y);
    vga16_setpixel(colour[6], x    , y + 1);
    vga16_setpixel(colour[7], x + 1, y + 1);
    vga16_setpixel(colour[8], x + 2, y + 1);
    vga16_setpixel(colour[9], x + 3, y + 1);
    vga16_setpixel(colour[10], x    , y + 2);
    vga16_setpixel(colour[11], x + 1, y + 2);
    vga16_setpixel(colour[12], x + 2, y + 2);
    vga16_setpixel(colour[13], x + 3, y + 2);
    vga16_setpixel(colour[14], x + 4, y + 2);
    vga16_setpixel(colour[15], x    , y + 3);
    vga16_setpixel(colour[16], x + 1, y + 3);
    vga16_setpixel(colour[17], x + 2, y + 3);
    vga16_setpixel(colour[18], x + 3, y + 3);
    vga16_setpixel(colour[19], x + 4, y + 3);
    vga16_setpixel(colour[20], x + 5, y + 3);
    vga16_setpixel(colour[21], x    , y + 4);
    vga16_setpixel(colour[22], x + 2, y + 4);
    vga16_setpixel(colour[23], x + 3, y + 4);
    vga16_setpixel(colour[24], x + 4, y + 4);
    vga16_setpixel(colour[25], x + 5, y + 4);
    vga16_setpixel(colour[26], x + 6, y + 4);
    vga16_setpixel(colour[27], x    , y + 5);
    vga16_setpixel(colour[28], x + 3, y + 5);
    vga16_setpixel(colour[29], x + 4, y + 5);
    vga16_setpixel(colour[30], x + 5, y + 5);
    vga16_setpixel(colour[31], x + 6, y + 5);
    vga16_setpixel(colour[32], x + 7, y + 5);
    vga16_setpixel(colour[33], x + 4, y + 6);
    vga16_setpixel(colour[34], x + 5, y + 6);
    vga16_setpixel(colour[35], x + 6, y + 6);
    vga16_setpixel(colour[36], x + 5, y + 7);
}

static int
SaveUnderMousePointer(int x, int y, int *colour)
{
    colour[0] = vga16_getpixel(x    , y);
    colour[1] = vga16_getpixel(x + 1, y);
    colour[2] = vga16_getpixel(x + 2, y);
    colour[3] = vga16_getpixel(x + 3, y);
    colour[4] = vga16_getpixel(x + 4, y);
    colour[5] = vga16_getpixel(x + 5, y);
    colour[6] = vga16_getpixel(x    , y + 1);
    colour[7] = vga16_getpixel(x + 1, y + 1);
    colour[8] = vga16_getpixel(x + 2, y + 1);
    colour[9] = vga16_getpixel(x + 3, y + 1);
    colour[10] = vga16_getpixel(x    , y + 2);
    colour[11] = vga16_getpixel(x + 1, y + 2);
    colour[12] = vga16_getpixel(x + 2, y + 2);
    colour[13] = vga16_getpixel(x + 3, y + 2);
    colour[14] = vga16_getpixel(x + 4, y + 2);
    colour[15] = vga16_getpixel(x    , y + 3);
    colour[16] = vga16_getpixel(x + 1, y + 3);
    colour[17] = vga16_getpixel(x + 2, y + 3);
    colour[18] = vga16_getpixel(x + 3, y + 3);
    colour[19] = vga16_getpixel(x + 4, y + 3);
    colour[20] = vga16_getpixel(x + 5, y + 3);
    colour[21] = vga16_getpixel(x    , y + 4);
    colour[22] = vga16_getpixel(x + 2, y + 4);
    colour[23] = vga16_getpixel(x + 3, y + 4);
    colour[24] = vga16_getpixel(x + 4, y + 4);
    colour[25] = vga16_getpixel(x + 5, y + 4);
    colour[26] = vga16_getpixel(x + 6, y + 4);
    colour[27] = vga16_getpixel(x    , y + 5);
    colour[28] = vga16_getpixel(x + 3, y + 5);
    colour[29] = vga16_getpixel(x + 4, y + 5);
    colour[30] = vga16_getpixel(x + 5, y + 5);
    colour[31] = vga16_getpixel(x + 6, y + 5);
    colour[32] = vga16_getpixel(x + 7, y + 5);
    colour[33] = vga16_getpixel(x + 4, y + 6);
    colour[34] = vga16_getpixel(x + 5, y + 6);
    colour[35] = vga16_getpixel(x + 6, y + 6);
    colour[36] = vga16_getpixel(x + 5, y + 7);
}


static int oldx = -1, oldy, oldcolour[40];

static long
GetMouseButton()
{
    int x, y, button;

    for (;;) {
	mouse_update();
	x = mouse_getx();
	y = mouse_gety();
	button = mouse_getbutton();

	if (x != oldx || y != oldy) {
	    if (oldx != -1) {
		RestoreUnderMousePointer(oldx, oldy, oldcolour);
	    }
	    SaveUnderMousePointer(x, y, oldcolour);
	    RenderMousePointer(x, y);
	    oldx = x;
	    oldy = y;
	}
	
	if (button & MOUSE_LEFTBUTTON) {
	    break;
	}
    }

    if (x >= SO_NEWGAMELEFT && x < SO_NEWGAMERIGHT
	&& y > SO_NEWGAMETOP && y <= SO_NEWGAMEBOTTOM) {
	return NEWGAME;
    }
    if (x >= SO_RESTARTLEFT && x < SO_RESTARTRIGHT
	&& y > SO_RESTARTTOP && y <= SO_RESTARTBOTTOM) {
	return RESTART;
    }
    if (x >= SO_UNDOLEFT && x < SO_UNDORIGHT
	&& y > SO_UNDOTOP && y <= SO_UNDOBOTTOM) {
	return UNDO;
    }
    if (x >= SO_QUITLEFT && x < SO_QUITRIGHT
	&& y > SO_QUITTOP && y <= SO_QUITBOTTOM) {
	return QUIT;
    }

    return (x << 9) | y;
}


static long
WhileMouseButton()
{
    int x, y, button;

    for (;;) {
	mouse_update();
	x = mouse_getx();
	y = mouse_gety();
	button = mouse_getbutton();

	if (x != oldx || y != oldy) {
	    if (oldx != -1) {
		RestoreUnderMousePointer(oldx, oldy, oldcolour);
	    }
	    SaveUnderMousePointer(x, y, oldcolour);
	    RenderMousePointer(x, y);
	    oldx = x;
	    oldy = y;
	}
	
	if (!(button & MOUSE_LEFTBUTTON)) {
	    break;
	}
    }

    if (oldx != -1) {
	RestoreUnderMousePointer(oldx, oldy, oldcolour);
    }
    oldx = -1;

    return (x << 9) | y;
}


static int
ParsePos(long pos, int downcard, int buttonup)
{
    int x, y, card, row, bittop;
    
    x = pos >> 9;
    y = pos & 0x1ff;
    if (x / COLWIDTH > 6 || y < TOPROWHEIGHT ||
	(x % COLWIDTH) / BITWIDTH > 0) {
	return NOCARD;
    }

    card = column[x / COLWIDTH];
    bittop = columnTop[x / COLWIDTH];
    row = TOPROWHEIGHT;

    while (card != NOCARD) {
	if ((next[card] != NOCARD && y >= row && y < row + bittop)
	    || (next[card] == NOCARD && y >= row && y < row + BITHEIGHT)) {
	    if (!hidden[card] && (downcard == -1 || downcard == card)) {
		return card;
	    }
	}
	card = next[card];
	row += bittop;
    }

    return NOCARD;
}
