/*
 * Mines
 *
 * Copyright (C) Evan Harris, 1994, 1995.
 *
 * 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 <unistd.h>
#include <vga.h>
#ifndef NOMOUSE
#include <vgamouse.h>
#endif

#include "mines.h"
#include "misquare.h"
#include "vga16.h"
#include "mouse.h"
#include "key.h"


#if !defined(CLOCKS_PER_SECOND) && defined(CLOCKS_PER_SEC)
#define CLOCKS_PER_SECOND CLOCKS_PER_SEC
#endif


#define SCREENMODE G640x480x16
#define SCREENWIDTH 640
#define SCREENHEIGHT 480

#define BOARDWIDTH 480
#define BOARDHEIGHT SCREENHEIGHT

#define VERSION "MINES v1.1"
#define VERSIONLEFT (SCREENWIDTH - 80)
#define VERSIONBOTTOM 168

#define QUITLEFT (SCREENWIDTH - 80)
#define QUITBOTTOM 76
#define QUITTOP (QUITBOTTOM - 15)
#define QUITRIGHT (QUITLEFT + 79)

#define NEWGAMELEFT QUITLEFT
#define NEWGAMEBOTTOM 48
#define NEWGAMETOP (NEWGAMEBOTTOM - 15)
#define NEWGAMERIGHT (NEWGAMELEFT + 79)

#define WINLEFT (SCREENWIDTH - 72)
#define WINBOTTOM 312

#define MINESLEFT (SCREENWIDTH - 80)
#define MINESBOTTOM 248

#define TIMELEFT (SCREENWIDTH - 40)
#define TIMEBOTTOM 280

#define SQUARESIZE 16

#define MOUSEKEYMOVE 8

#define PALETTESIZE 12

int palette[PALETTESIZE * 3] = {
    0x00, 0x00, 0x00,		/* black */
    0x20, 0x20, 0x20,		/* grey */
    0x18, 0x18, 0x18,		/* dark grey */
    0x00, 0x00, 0x3f,		/* blue */
    0x3f, 0x00, 0x00,		/* red */
    0x3f, 0x3f, 0x00,		/* yellow */
    0x3f, 0x3f, 0x3f,		/* white */
    0x00, 0x3f, 0x00,		/* green */
    0x00, 0x3f, 0x3f,		/* cyan */
    0x3f, 0x00, 0x3f,		/* magenta */
    0x3f, 0x29, 0x00,		/* orange */
    0x3b, 0x20, 0x3b,		/* violet */
};

#define TEXTFG 6
#define TEXTBG 0
#define BUTTONFG 0
#define BUTTONBG 1
#define MINESFG 1
#define TIMEFG 5
#define MOUSEFG 4
#define MOUSEDARKFG 5

#define BTNNONE 0
#define BTNLEFT 1
#define BTNRIGHT 2
#define BTNMIDDLE 3
#define BTNBOTH 4

#define UNKNOWN -1

#ifndef MOUSESAMPLERATE
#define MOUSESAMPLERATE MOUSE_DEFAULTSAMPLERATE
#endif

#define USLEEP_TIME 30000


void ShowTime(long t);


static int boardleft, boardtop, boardxsize, boardysize;


void
InitDisplay(int width, int height)
{
    vga_disabledriverreport();
    vga_init();
#if !defined(USEMOUSEFUNCS) && !defined(NOMOUSE)
    vga_setmousesupport(1);
#endif

    if (vga_setmode(SCREENMODE) != 0) {
	fprintf(stderr, "Mode %s not available!\n",
		vga_getmodename(SCREENMODE));
	exit(1);
    }

#if defined(USEMOUSEFUNCS) && !defined(NOMOUSE)
    mouse_init("/dev/mouse", vga_getmousetype(), MOUSESAMPLERATE);
    mouse_setxrange(0, SCREENWIDTH - 1);
    mouse_setyrange(0, SCREENHEIGHT - 1);
    mouse_setwrap(MOUSE_NOWRAP);
#endif

    vga16_init();

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

    vga16_text(NEWGAMELEFT, NEWGAMEBOTTOM, " NEW GAME ", BUTTONFG, BUTTONBG);
    vga16_text(QUITLEFT, QUITBOTTOM, "   QUIT   ", BUTTONFG, BUTTONBG);
    vga16_text(VERSIONLEFT, VERSIONBOTTOM, VERSION, TEXTFG, TEXTBG);

    boardleft = ((BOARDWIDTH - width * SQUARESIZE) / 16) * 8;
    boardtop = (BOARDHEIGHT - height * SQUARESIZE) / 2;
    boardxsize = width;
    boardysize = height;
}


void
EndDisplay()
{
    vga_setmode(TEXT);
#if defined(USEMOUSEFUNCS) && !defined(NOMOUSE)
    mouse_close();
#endif    
}


static time_t starttime;
static int stoptime;


void
NewGame(int w, int h)
{
    int x, y;
    
    vga16_text(WINLEFT, WINBOTTOM, "        ", MINESFG, TEXTBG);
    for (x = 0; x < w; x++) {
	for (y = 0; y < h; y++) {
	    ShowSquare(x, y, SQ_COVERED);
	}
    }
    starttime = time(NULL);
    stoptime = 0;
    ShowTime(0);
}


void
GameOver(int status)
{
    stoptime = 1;

    if (status == WIN) {
	vga16_text(WINLEFT, WINBOTTOM, "YOU WIN!", MINESFG, TEXTBG);
    } else if (status == LOSE) {
	vga16_text(WINLEFT, WINBOTTOM, "YOU LOSE", MINESFG, TEXTBG);
    }
}


void
MinesLeft(int m)
{
    char s[16];
    
    sprintf(s, "Mines: %3d", m);
    vga16_text(MINESLEFT, MINESBOTTOM, s, MINESFG, TEXTBG);
}


void
ShowTime(long t)
{
    static long lastt = -1;
    char buf[16];

    if (!stoptime && t != lastt) {
	if (t >= 3600) {
	    sprintf(buf, "%ld:%02ld:%02ld", t / 3600, (t % 3600) / 60, t % 60);
	} else if (t >= 60) {
	    sprintf(buf, "%ld:%02ld", t / 60, t % 60);
	} else {
	    sprintf(buf, "%ld", t);
	}
	if (t == 0) {
	    vga16_text(TIMELEFT - 40, TIMEBOTTOM, "          ",
		       TIMEFG, TEXTBG);
	}
	vga16_text(TIMELEFT - 4 * strlen(buf), TIMEBOTTOM, buf,
		   TIMEFG, TEXTBG);
	lastt = t;
    }
}


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


void
MoveMousePointer(int x, int y)
{
    if (x != oldx || y != oldy) {
	if (oldx != -1) {
	    RestoreUnderMousePointer(oldx, oldy, SCREENWIDTH, SCREENHEIGHT,
				     oldcolour);
	}
	SaveUnderMousePointer(x, y, SCREENWIDTH, SCREENHEIGHT, oldcolour);
	RenderMousePointer(x, y, MOUSEFG, MOUSEDARKFG,
			   SCREENWIDTH, SCREENHEIGHT);
	oldx = x;
	oldy = y;
    }
}


int
ParseMousePosition(int x, int y, int btn)
{
    if (btn == BTNLEFT
	&& x >= NEWGAMELEFT && x <= NEWGAMERIGHT
	&& y >= NEWGAMETOP && y <= NEWGAMEBOTTOM) {
	return packcmd(NEWGAME, 0, 0);
    } else if (btn == BTNLEFT
	       && x >= QUITLEFT && x <= QUITRIGHT
	       && y >= QUITTOP && y <= QUITBOTTOM) {
	return packcmd(QUIT, 0, 0);
    } else if ((x - boardleft) / SQUARESIZE >= 0
	       && (x - boardleft) / SQUARESIZE < boardxsize
	       && (y - boardtop) / SQUARESIZE >= 0
	       && (y - boardtop) / SQUARESIZE < boardysize) {
	if (btn == BTNRIGHT) {
	    return packcmd(PROTECT, (x - boardleft) / SQUARESIZE,
			   (y - boardtop) / SQUARESIZE);
	} else if (btn == BTNMIDDLE || btn == BTNBOTH) {
	    return packcmd(CHECK_AROUND, (x - boardleft) / SQUARESIZE,
			   (y - boardtop) / SQUARESIZE);
	} else if (btn == BTNNONE) {
	    return packcmd(UNCHECK_AROUND, (x - boardleft) / SQUARESIZE,
			   (y - boardtop) / SQUARESIZE);
	} else {
	    return packcmd(DETONATE, (x - boardleft) / SQUARESIZE,
			   (y - boardtop) / SQUARESIZE);
	}
    }
    return UNKNOWN;
}


int
GetMove()
{
    int move = UNKNOWN, key;

#if !defined(NOMOUSE)
    static int lastbutton = 0, b_x = 0, b_y = 0;
    int x, y, button;
    
    if (oldx == -1) {
	x = mouse_getx();
	y = mouse_gety();
	MoveMousePointer(x, y);
    }
#else
    MoveMousePointer(0, 0);
#endif
    
    while (move == UNKNOWN) {
	usleep(USLEEP_TIME);	/* don't chew up as much CPU */
	
#if !defined(NOMOUSE)
	if (mouse_update()) {
	    x = mouse_getx();
	    y = mouse_gety();
	    button = mouse_getbutton();

	    MoveMousePointer(x, y);

	    if (lastbutton == 0 ||
		((lastbutton & (MOUSE_LEFTBUTTON | MOUSE_RIGHTBUTTON))
		 &&
		 ((button & (MOUSE_LEFTBUTTON | MOUSE_RIGHTBUTTON))
		  == (MOUSE_LEFTBUTTON | MOUSE_RIGHTBUTTON)))) {
		
		if ((button & (MOUSE_LEFTBUTTON | MOUSE_RIGHTBUTTON))
		    == (MOUSE_LEFTBUTTON | MOUSE_RIGHTBUTTON)) {
		    move = ParseMousePosition(x, y, BTNBOTH);
		    b_x = x;
		    b_y = y;
		} else if (button & MOUSE_LEFTBUTTON) {
		    move = ParseMousePosition(x, y, BTNLEFT);
		} else if (button & MOUSE_RIGHTBUTTON) {
		    move = ParseMousePosition(x, y, BTNRIGHT);
		} else if (button & MOUSE_MIDDLEBUTTON) {
		    move = ParseMousePosition(x, y, BTNMIDDLE);
		    b_x = x;
		    b_y = y;
		}
	    } else if (((lastbutton & MOUSE_MIDDLEBUTTON)
			&& (button & MOUSE_MIDDLEBUTTON) == 0)
		       ||
		       (((lastbutton & (MOUSE_LEFTBUTTON | MOUSE_RIGHTBUTTON))
			 == (MOUSE_LEFTBUTTON | MOUSE_RIGHTBUTTON))
			&& ((button & (MOUSE_LEFTBUTTON | MOUSE_RIGHTBUTTON))
			    != (MOUSE_LEFTBUTTON | MOUSE_RIGHTBUTTON)))) {
		move = ParseMousePosition(b_x, b_y, BTNNONE);
	    }
	    lastbutton = button;
	}
#endif

	if ((key = key_getkey()) != -1 ) {
	    switch (key) {
	      case 'n':
	      case 'N':
		move = packcmd(NEWGAME, 0, 0);
		break;
	      case 'q':
	      case 'Q':
		move = packcmd(QUIT, 0, 0);
		break;
	      case KEY_CURSORUP:
		if (oldy >= MOUSEKEYMOVE) {
		    MoveMousePointer(oldx, oldy - MOUSEKEYMOVE);
		}
		break;
	      case KEY_CURSORDOWN:
		if (oldy < SCREENHEIGHT - MOUSEKEYMOVE) {
		    MoveMousePointer(oldx, oldy + MOUSEKEYMOVE);
		}
		break;
	      case KEY_CURSORLEFT:
		if (oldx >= MOUSEKEYMOVE) {
		    MoveMousePointer(oldx - MOUSEKEYMOVE, oldy);
		}
		break;
	      case KEY_CURSORRIGHT:
		if (oldx < SCREENWIDTH - MOUSEKEYMOVE) {
		    MoveMousePointer(oldx + MOUSEKEYMOVE, oldy);
		}
		break;
	      case '\n':
	      case ',':
		move = ParseMousePosition(oldx, oldy, BTNLEFT);
		break;
	      case ' ':
	      case '/':
		move = ParseMousePosition(oldx, oldy, BTNRIGHT);
		break;
	      default:
		break;
	    }
	}

	ShowTime(time(NULL) - starttime);
    }

    return move;
}


void
ShowSquare(int x, int y, int sq)
{
    int l;
    int renderpointer = 0;
    
    if (oldx != -1
	&& oldx >= boardleft + x * SQUARESIZE - 8
	&& oldx <= boardleft + (x + 1) * SQUARESIZE
	&& oldy >= boardtop + y * SQUARESIZE - 8
	&& oldy <= boardtop + (y + 1) * SQUARESIZE) {
	RestoreUnderMousePointer(oldx, oldy, SCREENWIDTH, SCREENHEIGHT,
				 oldcolour);
	renderpointer = 1;
    }

    for (l = 0; l < SQUARESIZE; l++) {
	vga16_drawscansegment(squarelines[square[sq][l]],
			      boardleft + x * SQUARESIZE,
			      boardtop + y * SQUARESIZE + l, SQUARESIZE);

    }

    if (renderpointer) {
	SaveUnderMousePointer(oldx, oldy, SCREENWIDTH, SCREENHEIGHT,
			      oldcolour);
	RenderMousePointer(oldx, oldy, MOUSEFG, MOUSEDARKFG,
			   SCREENWIDTH, SCREENHEIGHT);
    }
}
