/*****************************************************************************/
/*									     */
/*									     */
/*	X patience - events.c						     */
/*									     */
/*	rewrite of events.c from Spider (see below)			     */
/*	by Heiko Eissfeldt and Michael Bischoff				     */
/*									     */
/*	24-Feb-1993: First release (0.1)				     */
/*									     */
/*									     */
/*****************************************************************************/

/*
 *	Spider
 *
 *	(c) Copyright 1989, Donald R. Woods and Sun Microsystems, Inc.
 *	(c) Copyright 1990, David Lemke and Network Computing Devices Inc.
 *
 *	See copyright.h for the terms of the copyright.
 *
 *	@(#)events.c	2.6	91/05/09
 *
 *      extremely modified by Heiko Eissfeldt and Michael Bischoff
 */

/*
 * Spider event handlers
 */

#include "xpat.h"
#include "xpatx11.h"
#include "version.h"

#define CANCEL_SELECTION       hit_pile(-1, 0)
#define SPEEDUP

void show_message(const char *str)
{   static char last_message[512] = " ";

    if (str) {
	strcpy(last_message, str);
    }
    XClearWindow(dpy, message_win);
    XDrawImageString(dpy, message_win, textgc, MESSAGE_X, message_y,
	last_message, strlen(last_message));
}

static void (*check_button_list(struct singlebutton *p, int num,
    XButtonPressedEvent *xev))(void)
{   while (num--) {
	if (xev->x >= p->x && xev->x < p->x + p->w &&
	    xev->y >= p->y && xev->y < p->y + p->h) {
	    switch (xev->button) {
	    case Button1:
		return p->left;
	    case Button2:
		return p->middle;
	    case Button3:
		return p->right;
	    }
	}
	++p;
    }
    return NULL;
}

static void conf_button_press(XButtonPressedEvent *xev)
{   void (*func)(void);
    func = check_button_list(confirmbuttons, 2, xev);
    if (func)
	(*func)();
}


static void hit_pile(int pile, int y)
{   static int srcpile;		/* temp. variable to hold getpile(srcind) */

    hint(RESET_HINTS);
    if (game.srcind >= 0) {
	Cardindex h;
	h = game.srcind;
	game.srcind = -1;
	/* do move */
	show_mark(False);
	if (pile < 0) {
	    show_message("");
	    return;
	}
	if (pile != srcpile) {	/* else just unselect slot */
	    if (game.piletype[pile] == FacedownDeck)
		try_give_cards();
	    else {
		if (move_valid(h, pile)) {
		    store_move(do_move(h, pile));
		    show_message("");
		} else
		    show_message("invalid move");
	    }
	}
    } else {		/* no mark to clear, set srcpile and game.srcind */
	if (pile < 0) {
	    show_message("");
	    return;
	}
	if (game.piletype[pile] == FacedownDeck)
	    try_give_cards();
	else {
	    if ((game.srcind = select_max((srcpile = pile), y)) == -1)
	        show_message("empty or invalid source pile");
	    else {
		show_message("src selected");
		if (game.srcind >= 0 && getpile(game.srcind) == srcpile)
		    show_mark(True);
	    }
	}
    }
}


void do_move_to_stack(void)
{
    if (game.srcind < 0) {	/* all to stack */
	if (!all_to_stack())
	    show_message("no moves to stack possible");
	else
	    show_message("cards moved to stack");
    } else {
	show_mark(False);
	if (!move_to_stack(getpile(game.srcind)))
	    show_message("not possible");
	else
	    show_message("OK");
	game.srcind = -1;
    }
}


static void redraw_confirm(XExposeEvent *xev)
{   const char *s = NULL;
    XFillRectangle(dpy, confirm.win, whitegc, 0, 0, confirm.w-1, confirm.h-1);
    /* draw the text */
    XDrawImageString(dpy, confirm.win, button.gc,
        graphic.xgap, button.by+button.font->ascent, "Please confirm", 14);
    switch (confirm.request) {
    case RESTART_GAME:
    case NONE:	s = " <<err>>";
		break;
    case ANOTHER_GAME:
		s = " \"Another Game\"";
		break;
    case QUIT_GAME:
		s = " \"Quit Game\"";
		break;
    }
    XDrawImageString(dpy, confirm.win, button.gc, graphic.xgap,
        2 * button.by+button.font->ascent + button.fontheight, s, strlen(s));
    redraw_buttons(0, 0, confirm.w, confirm.h, confirm.win, 2, confirmbuttons);
}


static void resize_event(XConfigureEvent *xev)
{
    if (graphic.height == xev->height && graphic.width == xev->width)
	return;		/* no change of size */
    hint(RESET_HINTS);
    if (graphic.height < xev->height)
	/* window is greater now */
	XClearArea(dpy, table, 0, graphic.height, graphic.width, xev->height - graphic.height, False);
    graphic.height = xev->height;
    if (graphic.width < xev->width)
	/* window is greater now */
	XClearArea(dpy, table, graphic.width, 0, xev->width - graphic.width, graphic.height, False);
    graphic.width = xev->width;

    /* adjust message window */
    XMoveResizeWindow(dpy, message_win, 0, (graphic.height - 2 * TABLE_BW - 
	(message_font->ascent + message_font->descent)),
	(graphic.width - 2 * TABLE_BW),
	(message_font->ascent + message_font->descent));

    confirm.x = (graphic.width - confirm.w) / 2;
    confirm.y = (graphic.height - confirm.h) / 2;
    XMoveWindow(dpy, confirm.win, confirm.x, confirm.y);

    if (!xev->type)
	return;			/* comes from change rules */

    if (graphic.autolayout) {
	do_layout();	/* change everything */
    } else {
	Pileindex i;
	/* fix piles */
    	for (i = 0; i < rules.numslots; ++i)
	    graphic.pile[SLOT(i)].maxheight = graphic.height - graphic.pile[SLOT(i)].y;

	for (i = FIRST_SLOT; i <= LAST_SLOT; ++i)
	    if (pile_resize(i))
	        draw_pileupdate(i, 0);
    }
    /* XFlush(dpy); */       /* XNextEvent forces it */
}


static void change_rules(const char *new_rules_name)
{
    CANCEL_SELECTION;
    new_rules(new_rules_name, -1, -1, -1, -1, -1, -1, -1);	/* std rules */
    XStoreName(dpy, table, rules.longname);	/* new window name */
    XClearArea(dpy, table, 0, 0, 0, 0, True);	/* force redraw */

    /* should we do a resize table? I don't know */
    {   XSizeHints xsh;
	XConfigureEvent xev;
	
	/* compute minimum size */
	(*rules.minwindow)((XSize_t *)&xsh.min_width, (XSize_t *)&xsh.min_height);
	xsh.flags = PMinSize;
	XSetWMNormalHints(dpy, table, &xsh);

	xev.width = max(graphic.width, xsh.min_width);
	xev.height = max(graphic.height, xsh.min_height);
	if (xev.width != graphic.width || xev.height != graphic.height) {
	    XResizeWindow(dpy, table, xev.width, xev.height);
	    xev.type = 0;
	    resize_event(&xev);
	}
    }
    init_layout();			/* new setup */
    newgame(-1L);			/* new game with random seed */
}

static void key_press(XKeyPressedEvent *xev)
{   char str[32];
    char buf[512];
    int num;

#define	get_name_field()	get_selection()

    num = XLookupString(xev, str, 32, NULL, NULL);
    if (num == 0)
	return;

    if (confirm.request != NONE) {	/* reply confirmer */
	switch (str[0]) {
	case 'y': case 'Y':
	    confirm.result = YES;
	    return;
	case 'n': case 'N':
	    confirm.result = NO;
	    return;
        }
	return;       /* do not accept other commands now */
    }
 
    show_exposed_card(False);

    if (isdigit(str[0])) {
	int c;
	c = str[0] - '1';
	if (c < 0)
	    c = 9;
#if 0
	if ((cmd_state & PREFIX) == STACK_PREFIX) {
	    if (c >= rules.numstacks) {
		show_message("not so many stacks");
		cmd_state = 0;
		return;
	    }
	} else
#endif
	{	/* slot */
	    if (c >= rules.numslots) {
		show_message("not so many slots");
		return;
	    }
	    c += rules.numstacks;
	}
	hit_pile(c, 0);
	return;
    } else switch (str[0]) {
    case '\014':hint(RESET_HINTS);	/* complicated to save */
	        XClearArea(dpy, table, 0, 0, 0, 0, True);
		break;
    case 27:	/* abort cmd */
		CANCEL_SELECTION;
		show_message("aborted");
		return;
    case '?':	do_give_hints();
		break;
    case ' ':   do_last_hint();
                break;
    case 'a':	/* Spider: same game again */
    case 'A':	do_restart_game();
		break;

    case 'D':	/* Spider: deal cards */
    case 'd':	try_give_cards();
		break;

    case 'F':
	change_rules("FreeCell");
	break;
    case 'G':
	change_rules("Gypsy");
	break;
    case 'H':
	change_rules("Seahaven");
	break;
    case 'K':
	change_rules("Klondike");
	break;
    case 'M':	/* make all possible moves (greedy) */
	do_all_moves();
	break;

    case 'l':	do_layout();
		break;
    case 'N':	/* Spider: new game */
    case 'n':	do_another_game();
		break;

    case 'O':	/* mOve all to stacks */
		CANCEL_SELECTION;	/* unmark any block */
		do_move_to_stack();
		break;
    case 'o':	/* one card to stack */
		if (game.srcind < 0)
		    show_message("no source pile selected");
		else
		    do_move_to_stack();
		break;
    case 'Q':	/* Spider: quit game */
    case 'q':   leave_pat();
		break;
    case 'r':	/* redo move */
    case 'R':
	try_redo_move();
	break;
    case 's':			/* Spider: score */
 	show_score();
	break;
    case 'S':
	change_rules("Spider");
	break;
    case 'U':			/* Spider: Undo */
    case 'u':
	try_undo_move();
	break;
    case 'V':
    case 'v':			/* Spider: Version */
	sprintf(buf, "xpat version " VERSION);
	show_message(buf);
	break;
    default:
	str[num] = '\0';	/* NULL terminate it */
	(void) sprintf(buf, "Unknown command: '%s'", str);
	show_message(buf);
	break;
    }
}

static void handle_confirm_event(XEvent	*xev)
{    switch(xev->xany.type) {
     case ButtonPress:	conf_button_press((XButtonPressedEvent *)xev);
			break;
     case KeyPress:     key_press((XKeyPressedEvent *)xev); /* go same window */
                        break;
     }
}

static int initial_event = True;
static int replay_replay_to = 0;
static int replay_cheat_count = 0;
static int replay_request = 0;

void do_replay_game(void)
{
    replay_cheat_count = game.cheat_count;
    replay_replay_to = game.n_moves;

    newgame(game.seed);
    if (!initial_event)		/* this function called by main() ? */
	XClearArea(dpy, table, 0, 0, 0, 0, True); /* the first time, events */
						  /* are generated anyway */
    replay_request = 1;	/* replay after last expose done */
}



/* SPEEDUP does not work yet */
#ifdef SPEEDUP
static int pile_is_drawn[MAXPILES] = { 0, 0 };
static int buttons_are_drawn = 0;
#endif

static void redraw_table(XExposeEvent *xev)
{   int i;
    /* fprintf(stderr, "Expose (%d,%d,   %d,%d) called, count = %d\n",
	xev->x, xev->y, xev->width, xev->height, xev->count); */
#ifdef SPEEDUP
    if (!buttons_are_drawn++)
#endif
	redraw_buttons(xev->x, xev->y, xev->width, xev->height, table, button.num, button.b);

    for (i = 0; i < game.numpiles; ++i) {
	struct pile *p;
#ifdef SPEEDUP
	if (pile_is_drawn[i]++)
	    continue;
#endif
	p = graphic.pile + i;
	if (xev->y >= p->y + p->totalheight || xev->y + xev->height <= p->y ||
	    xev->x >= p->x + CARD_WIDTH  || xev->x + xev->width <= p->x)
	    continue;	/* this pile is not affected */
	draw_pileupdate(i, 0);
    }
    if (!xev->count && replay_request == 1) {
	XFlush(dpy);
	replay_request = 2;			/* nun aber los! */
    }
    if (!xev->count)
	show_arrow(2);				/* Update hint arrow */
#ifdef SPEEDUP
    if (xev->count)
	return;
    /* was last event, unmark piles */
    for (i = 0; i < MAXPILES; ++i)
	pile_is_drawn[i] = 0;
    buttons_are_drawn = 0;
#endif
}

/* the pointer is somewhere in the area of pile i */
/* check, if a card has to be exposed */

static void expose_card(Pileindex i, int y)
{   struct pile *p;
    Cardindex ind;

    if (EMPTY(i))
	return;
    p = graphic.pile + i;
    ind = game.ind[i];
    while (ind != INDEX_OF_LAST_CARD(i) && y >=	p->y + graphic.cardy[ind+1])
        ++ind;
    if (ind == INDEX_OF_LAST_CARD(i))   /* the most bottom card don't has to */
        ind = -1;                       /* be put on foreground */
    if (graphic.zoomed_card != ind) {   /* change of state */
	show_exposed_card(False);	/* hide it */
        /* graphic.zoomed_card is now -1 */
        if (ind >= 0) {
  	    graphic.zoomed_card = ind;
	    show_exposed_card(True);
        }
    }
}

static void button3_moved(XPointerMovedEvent *xev)
{
    Pileindex i;
    /* find new pile */
    for (i = FIRST_SLOT; i <= LAST_SLOT; ++i) {
	struct pile *p;
	p = graphic.pile+i;
	if (xev->x >= p->x && xev->x < p->x + CARD_WIDTH &&
	    xev->y >= p->y && xev->y < p->y + p->maxheight) {
	    /* yeah, a slot is hit */
	    /* find out which card is the target */
	    expose_card(i, xev->y);
	    return;
        }
    }
    /* pointer moved out of scope: */
    show_exposed_card(False);
}

static void button_press(XButtonPressedEvent *xev)
{   Pileindex i;
    void (*func)(void);

    show_exposed_card(False);
#if 0
    if (confirm.request != NONE) {
	func = check_button_list(button.b, 2, xev); 
	if (!func)
	    return;	/* no pile may be touched now */
    } else
#endif
        func = check_button_list(button.b, button.num, xev); 

    /* check for hit button */
    if (func) {
	(*func)();
	return;
    }
    for (i = 0; i < game.numpiles; ++i) {
	struct pile *p;
	p = graphic.pile+i;
	if (NOT_DISPLAYED(p))
	    continue;
	if (xev->x >= p->x && xev->x < p->x + CARD_WIDTH &&
	    xev->y >= p->y && xev->y < p->y + p->maxheight) {

	    /* yeah, hit a pile */
	    switch (xev->button) {
	    case Button1:            /* quick move */
		CANCEL_SELECTION;
		hint(RESET_HINTS);
		switch (game.piletype[i]) {
		case FacedownDeck:
		    hit_pile(i, xev->y);
		    break;
		case FaceupDeck:
		case Slot:
		case Stack:
		case Tmp:
		    {   Cardindex ind;
			if ((ind = select_max(i, xev->y)) < 0)
			    show_message("empty or illegal source pile");
			else if ((*rules.automove)(ind))
			    show_message("OK");
			else
			    show_message("no move possible");
		    }
		    break;	/* no action */
		}
		break;
	    case Button2:            /* specified move */
		hit_pile(i, xev->y);
		break;
	    case Button3:
		if (game.piletype[i] == Slot)
		    expose_card(i, xev->y);
		break;
	    }
	}
    }
}



/*
 * event on table
 */


static void handle_table_event(XEvent *xev)
{    switch(xev->xany.type) {
     case ButtonPress:	button_press((XButtonPressedEvent *)xev);
			break;
     case ButtonRelease:show_exposed_card(False);
                        break;
     case MotionNotify: button3_moved((XPointerMovedEvent *)xev);
                        break;
     case KeyPress:	key_press((XKeyPressedEvent *)xev);
			break;
     case ConfigureNotify:resize_event((XConfigureEvent *)xev);
			break;
     }
}

static void redraw_finwin(XExposeEvent *xev)
{
    XFillRectangle(dpy, finished_win, whitegc, 0, 0,
		   FINISHED_W-1, FINISHED_H-1);
    /* draw the text */
    XDrawImageString(dpy, finished_win, button.gc,
		     (FINISHED_W - XTextWidth(button.font, "GONZO!", 6))/2,
		     FINISHED_H * 2 / 3, "GONZO!", 6);
    XFlush(dpy);
}

void handle_expose_event(XExposeEvent *xev)
{   if (xev->window == message_win)
	show_message(NULL);
    else if (xev->window == confirm.win)
    	redraw_confirm(xev);
    else if (xev->window == table)
	redraw_table(xev);
    else if (xev->window == finished_win)
	redraw_finwin(xev);
}

void event_loop(void)
{   XEvent xev;

    initial_event = False;
    CANCEL_SELECTION;	/* reset cmd state */
    while (1) {
	if (game.ind[FIRST_SLOT] == rules.cards_per_color * rules.numstacks
	    && !game.finished) {
	    /* all cards on the stacks and not yet notified */
	    game.finished = True;
	    XMoveWindow(dpy, finished_win, (graphic.width - FINISHED_W) / 2,
			(graphic.height - FINISHED_H) / 2);
	    XMapWindow(dpy, finished_win);
	    redraw_finwin((XExposeEvent *)0);
	    show_message("You did it!");
	    write_log_file();
	    do_music(&xev);	/* play until next event */
	    XUnmapWindow(dpy, finished_win);
	} else
	    XNextEvent(dpy, &xev);
	if (xev.xany.type == Expose) /* expose events are treated separately */
	    handle_expose_event((XExposeEvent *)&xev);
	else if (xev.xany.window == table)
	    handle_table_event(&xev);
	else if (xev.xany.window == confirm.win)
	    handle_confirm_event(&xev);

	if (confirm.request != NONE && confirm.result != UNDEFINED) {
	    XUnmapWindow(dpy, confirm.win);
	    if (confirm.result == YES)
	        switch (confirm.request) {
		case QUIT_GAME:
		    sun_sound("goodbye");
		    exit(0);
		case ANOTHER_GAME: 
		    if (!game.finished)
			sun_sound("giveup");
		    newgame(-1L);
		    XClearArea(dpy, table, 0, 0, 0, 0, True);
		    break;
		case RESTART_GAME:
		    /* (*rules.new_game)(game.seed); */	/* already done */
		    XClearArea(dpy, table, 0, 0, 0, 0, True);
		    break;
		case NONE:	/* this can't happen */
		    ;
		}
	    confirm.request = NONE;
	}
        if (replay_request == 2) {
	    while (game.n_moves != replay_replay_to)
		redo_move();
	    replay_request = 0;
	    game.cheat_count = replay_cheat_count;
	}
    }
}

void try_undo_move(void)
{   CANCEL_SELECTION;
    switch (undo_move()) {
    case 0:	show_message("undo not possible!");
		break;
    case 1:	show_message("undo!");
		break;
    case 2:	show_message("undo (cheat!)");
		break;
    }
}

void try_redo_move(void)
{   CANCEL_SELECTION;
    switch (redo_move()) {
    case 0:	show_message("redo not possible!");
		break;
    case 1:	show_message("redo!");
		break;
    case 2:	show_message("redo (& uncheat)");
		break;
    }
}
