/*-
 * Copyright (c) 2002 by Edwin de Jong <mauddib@gmx.net>.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 */

/* this file is a part of pacman.c, and included via pacman.h.  It handles the
   AI of the ghosts and the pacman. */


/* fills array of DIRVECS size with possible directions, returns number of
   directions. 'posdirs' points to a possibly undefined array of four
   integers.  The vector will contain booleans wether the direction is
   a possible direction or not.  Reverse directions are deleted. */
static int
ghost_get_posdirs(pacmangamestruct *pp, int *posdirs, ghoststruct *g) 
{
	unsigned int i, nrdirs = 0;

	/* bit of black magic here */
	for (i = 0; i < DIRVECS; i++) {
		/* remove opposite */
		if (g->lastbox != NOWHERE && i == (g->lastbox + 2) % DIRVECS
				&& g->aistate != goingout) {
			posdirs[i] = 0;
			continue;
		}
		if (g->aistate == goingout && i == 1) {
			posdirs[i] = 0;
			continue;
		}
		/* check if possible direction */
		if ((posdirs[i] = 
			check_pos(pp, g->row + dirvecs[i].dy, 
				g->col + dirvecs[i].dx, 
				g->aistate == goingout ? True : False)) == 
				True) {
			nrdirs++;
		}
	}

	return nrdirs;
}

/* Directs ghost to a random direction, exluding opposite (except in the
   impossible situation that there is only one valid direction). */
static void
ghost_random(pacmangamestruct *pp, ghoststruct *g) 
{
	int posdirs[DIRVECS], nrdirs = 0, i, dir = 0;

	nrdirs = ghost_get_posdirs(pp, posdirs, g);
	for (i = 0; i < DIRVECS; i++)
		if (posdirs[i] == True) dir = i;

	if (nrdirs == 0) dir = (g->lastbox + 2) % DIRVECS;
	else if (nrdirs > 1)
		for (i = 0; i < DIRVECS; i++) {
			if (posdirs[i] == True && NRAND(nrdirs) == 0) {
				dir = i;
				break;
			}
		}

	g->nextrow = g->row + dirvecs[dir].dy;
	g->nextcol = g->col + dirvecs[dir].dx;
	g->lastbox = dir;
}

/* Determines best direction to chase the pacman and goes that direction. */
static void
ghost_chasing(pacmangamestruct *pp, ghoststruct *g) 
{
	int posdirs[DIRVECS], nrdirs = 0, i, dir = 0, highest = -100000, 
		thisvecx, thisvecy, thisvec;

	nrdirs = ghost_get_posdirs(pp, posdirs, g);
	for (i = 0; i < DIRVECS; i++)
		if (posdirs[i] == True) dir = i;

	if (nrdirs == 0) dir = (g->lastbox + 2) % DIRVECS;
	else if (nrdirs > 1)
		for (i = 0; i < DIRVECS; i++) {
			if (posdirs[i] == False) continue;
			thisvecx = (pp->pacman.col - g->col) * dirvecs[i].dx;
			thisvecy = (pp->pacman.row - g->row) * dirvecs[i].dy;
			thisvec = thisvecx + thisvecy;
			if (thisvec >= highest) {
				dir = i;
				highest = thisvec;
			}
		}

	g->nextrow = g->row + dirvecs[dir].dy;
	g->nextcol = g->col + dirvecs[dir].dx;
	g->lastbox = dir;
}

/* Determines the best direction to go away from the pacman, and goes that
   direction. */
static void
ghost_hiding(pacmangamestruct *pp, ghoststruct *g) 
{
	int posdirs[DIRVECS], nrdirs = 0, i, dir = 0, highest = -100000,
		thisvecx, thisvecy, thisvec;

	nrdirs = ghost_get_posdirs(pp, posdirs, g);
	for (i = 0; i < DIRVECS; i++)
		if (posdirs[i] == True) dir = i;

	if (nrdirs == 0) dir = (g->lastbox + 2) % DIRVECS;
	else if (nrdirs > 1)
		for (i = 0; i < DIRVECS; i++) {
			if (posdirs[i] == False) continue;
			thisvecx = (g->col - pp->pacman.col) * dirvecs[i].dx;
			thisvecy = (g->row - pp->pacman.row) * dirvecs[i].dy;
			thisvec = thisvecx + thisvecy;
			if (thisvec >= highest)
				dir = i;
		}

	g->nextrow = g->row + dirvecs[dir].dy;
	g->nextcol = g->col + dirvecs[dir].dx;
	g->lastbox = dir;
}

/* Determines a vector from the pacman position, towards all dots.  The vector
   is inversely proportional to the square of the distance of each dot.  
   (so, close dots attract more than far away dots). */
static void 
pac_dot_vec(pacmangamestruct *pp, pacmanstruct *p, long *vx, long *vy) 
{
	int x, y, bx = 0, by = 0, ex = LEVWIDTH, ey = LEVHEIGHT;
	long dx, dy, dist, top = 0;

#if 0
	int rnr = NRAND(50);
	/* determine begin and end vectors */

	switch (rnr) {
		case 0: ex = LEVHEIGHT/2; break;
		case 1: bx = LEVHEIGHT/2; break;
		case 2: ey = LEVHEIGHT/2; break;
		case 3: by = LEVHEIGHT/2; break;
	}
#endif
	*vx = *vy = 0;

	for (y = by; y < ey; y++) 
		for (x = bx; x < ex; x++)
			if (check_dot(pp, x, y) == 1) {
				dx = (long)x - (long)(p->col);
				dy = (long)y - (long)(p->row);
				dist = dx * dx + dy * dy;
				if (dist > top)
					top = dist;
				*vx += (dx * ((long)LEVWIDTH * (long)LEVHEIGHT))
				       / dist;
				*vy += (dy * ((long)LEVWIDTH * (long)LEVHEIGHT))
				       / dist;
			}
}

/* Determine a vector towards the closest ghost (in one loop, so we spare a
   couple of cycles which we can spoil somewhere else just as fast). */
static int
pac_ghost_prox_and_vector(pacmangamestruct *pp, pacmanstruct *p, 
		int *vx, int *vy) 
{
	int dx, dy, dist, closest = 100000;
	unsigned int g;

	if (vx != NULL)
		*vx = *vy = 0;

	for (g = 0; g < pp->nghosts; g++) {
		if (pp->ghosts[g].dead    == True  || 
		    pp->ghosts[g].aistate == inbox ||
		    pp->ghosts[g].aistate == goingout)
			continue;
		dx = pp->ghosts[g].col + /*dirvecs[pp->ghosts[g].lastbox].dx*/ -
			p->col;
		dy = pp->ghosts[g].row + /*dirvecs[pp->ghosts[g].lastbox].dy*/ -
		       	p->row;
		dist = dx * dx + dy * dy;
		if (dist < closest) {
			closest = dist;
			if (vx != NULL) {
				*vx = dx; *vy = dy;
			}
		}
	}
	return closest;
}

/* fills array of DIRVECS size with possible directions, returns number of
   directions.  posdirs should point to an array of 4 integers. */
static int
pac_get_posdirs(pacmangamestruct *pp, pacmanstruct *p, int *posdirs)
{
	int nrdirs = 0;
	unsigned int i;

	for (i = 0; i < DIRVECS; i++) {
		/* if we just ate, or we are in a statechange, it is allowed 
		   to go the opposite direction */
		if (p->justate == 0 &&
		    p->state_change == 0 && 
		    p->lastbox != NOWHERE && 
		    i == (p->lastbox + 2) % DIRVECS) {
			posdirs[i] = 0;
		} else if ((posdirs[i] = 
			check_pos(pp, p->row + dirvecs[i].dy, 
				p->col + dirvecs[i].dx, 0)) == 1)
			nrdirs++;
	}
	p->state_change = 0;

	return nrdirs;
}

/* Clears the trace of vectors. */
static void
pac_clear_trace(pacmanstruct *p) 
{
	int i;

	for(i = 0; i < TRACEVECS; i++) {
		p->trace[i].vx = NOWHERE; p->trace[i].vy = NOWHERE;
	}
	p->cur_trace = 0;
}

/* Adds a new vector to the trace. */
static void
pac_save_trace(pacmanstruct *p, const int vx, const int vy) 
{
	if (!(vx == NOWHERE && vy == NOWHERE)) {
		p->trace[p->cur_trace].vx = vx;
		p->trace[p->cur_trace].vy = vy;
		p->cur_trace = (p->cur_trace + 1) % TRACEVECS;
	}
}

/* Check if a vector can be found in the trace. */
static int
pac_check_trace(const pacmanstruct *p, const int vx, const int vy) 
{
	int i, curel;

	for (i = 1; i < TRACEVECS; i++) {
		curel = (p->cur_trace - i + TRACEVECS) % TRACEVECS;
		if (p->trace[curel].vx == NOWHERE &&
		    p->trace[curel].vy == NOWHERE)
			continue;
		if (p->trace[curel].vx == vx &&
		    p->trace[curel].vy == vy)
			return 1;
	}


	return 0;
}

/* AI mode "Eating" for pacman. Tries to eat as many dots as possible, goes
   to "hiding" if too close to a ghost. If in state "hiding" the vectors
   of the ghosts are also taken into account (thus not running towards them
   is the general idea). */
static void
pac_eating(pacmangamestruct *pp, pacmanstruct *p) 
{
	int posdirs[DIRVECS], nrdirs, i, highest = -(1 << 16), 
		score, dir = 0, dotfound = 0, prox, worst = 0;
	long vx, vy;

	if ((prox = pac_ghost_prox_and_vector(pp, p, (int *)&vx, (int *)&vy)) <
				4 * 4 && p->aistate == ps_eating) {
		p->aistate = ps_hiding;
		p->state_change = 1;
		pac_clear_trace(p);
	}

	if (prox > 6 * 6 && p->aistate == ps_hiding) {
		p->aistate = ps_eating;
		if (p->justate == 0) p->state_change = 1;
		pac_clear_trace(p);
	}

	if (prox < 3 * 3) p->state_change = 1;

	nrdirs = pac_get_posdirs(pp, p, posdirs);
	
	/* remove directions which lead to ghosts */
	if (p->aistate == ps_hiding) {
		for (i = 0; i < DIRVECS; i++) {
			if (posdirs[i] == 0) continue;
			score = vx * dirvecs[i].dx + vy * dirvecs[i].dy;
			if (score > highest) {
				worst = i;
				highest = score;
			}
			dir = i;
		}
		nrdirs--;
		posdirs[worst] = 0;
		highest = -(1 << 16);
	}

	/* get last possible direction if all else fails */
	for (i = 0; i < DIRVECS; i++)
		if (posdirs[i] != 0) dir = i;

	pac_dot_vec(pp, p, &vx, &vy);

	if (vx != NOWHERE && vy != NOWHERE && pac_check_trace(p, vx, vy) > 0) {
		p->roundscore++;
		if (p->roundscore >= 12) {
			p->roundscore = 0;
			p->aistate = ps_random;
			pac_clear_trace(p);
		}
	} else
		p->roundscore = 0;

	if (p->justate == 0) pac_save_trace(p, vx, vy);

	for (i = 0; i < DIRVECS; i++) {
		if (posdirs[i] == 0) continue;
		score = dirvecs[i].dx * vx + dirvecs[i].dy * vy;
		if (check_dot(pp, p->col + dirvecs[i].dx, 
				  p->row + dirvecs[i].dy) == 1) {
			if (dotfound == 0) {
				highest = score;
				dir = i;
				dotfound = 1;
			} else if (score > highest) {
				highest = score;
				dir = i;
			}
		} else if (score > highest && dotfound == 0) {
			dir = i;
			highest = score;
		}
	}

	p->nextrow = p->row + dirvecs[dir].dy;
	p->nextcol = p->col + dirvecs[dir].dx;
	p->lastbox = dir;
}

#if 0
/* Tries to catch the ghosts. */
static void
pac_chasing(pacmangamestruct *pp, pacmanstruct *p) 
{
}
#endif

/* Goes completely random, but not in the opposite direction.  Used when a
   loop is detected. */
static void
pac_random(pacmangamestruct *pp, pacmanstruct *p) 
{
	int posdirs[DIRVECS], nrdirs, i, dir = -1, lastdir = 0;

	if (pac_ghost_prox_and_vector(pp, p, NULL, NULL) < 5 * 5) {
		p->aistate = ps_hiding;
		p->state_change = 1;
	}
	if (NRAND(20) == 0) {
		p->aistate = ps_eating;
		p->state_change = 1;
		pac_clear_trace(p);
	}

	nrdirs = pac_get_posdirs(pp, p, posdirs);

	for (i = 0; i < DIRVECS; i++) {
		if (posdirs[i] == 0) continue;
		lastdir = i;
		if (check_dot(pp, p->col + dirvecs[i].dx, 
			p->row + dirvecs[i].dy) == 1) {
			dir = i;
			p->aistate = ps_eating;
			p->state_change = 1;
			pac_clear_trace(p);
			break;
		} else if (NRAND(nrdirs) == 0)
			dir = i;
	}

	if (dir == -1) dir = lastdir;

	p->nextrow = p->row + dirvecs[dir].dy;
	p->nextcol = p->col + dirvecs[dir].dx;
	p->lastbox = dir;
}

static int
pac_get_vector_screen(pacmangamestruct *pp, pacmanstruct *p, 
		const int x, const int y, int *vx, int *vy) 
{
	int lx, ly;

	lx = (x - pp->xb) / pp->xs;
	ly = (y - pp->yb) / pp->ys;

	if (lx < 0) lx = 0;
	else if ((unsigned int) lx > LEVWIDTH) lx = LEVWIDTH - 1;

	if (ly < 0) ly = 0;
	else if ((unsigned int) ly > LEVHEIGHT) ly = LEVHEIGHT - 1;

	*vx = lx - p->col;
	*vy = ly - p->row;

	if (lx == p->oldlx && ly == p->oldly) return 0;
	p->oldlx = lx; p->oldly = ly;
	return 1;
}

static int
pac_trackmouse(ModeInfo * mi, pacmangamestruct *pp, pacmanstruct *p) 
{
	int dx, dy, cx, cy, vx, vy;
	unsigned int m;
	int posdirs[DIRVECS], i, dir = -1, highest = -(2 << 16), score;
	Window r, c;

	(void) XQueryPointer(MI_DISPLAY(mi), MI_WINDOW(mi),
			 &r, &c, &dx, &dy, &cx, &cy, &m);

	if (cx <= 0 || cy <= 0 ||
                    cx >= MI_WIDTH(mi) - 1 ||
                    cy >= MI_HEIGHT(mi) - 1)
		return 0;
	/* get vector */
	p->state_change = pac_get_vector_screen(pp, p, cx, cy, &vx, &vy);

	(void) pac_get_posdirs(pp, p, posdirs);

	for (i = 0; i < DIRVECS; i++) {
		if (posdirs[i] == 0) continue;
		score = dirvecs[i].dx * vx + dirvecs[i].dy * vy;
		if (score > highest) {
			highest = score;
			dir = i;
		}
	}

	p->nextrow = p->row + dirvecs[dir].dy;
	p->nextcol = p->col + dirvecs[dir].dx;
	p->lastbox = dir;
	return 1;
}

/* Calls correct state function, and changes between states. */
static void
ghost_update(pacmangamestruct *pp, ghoststruct *g)
{

	if (!(g->nextrow == NOWHERE && g->nextcol == NOWHERE)) {
		g->row = g->nextrow;
		g->col = g->nextcol;
	}

	/* update ghost */
	if (g->dead == True) /* dead ghosts are dead, 
				so they don't run around */
		return;

	if ((g->aistate == randdir || g->aistate == chasing) && 
			NRAND(10) == 0) {
		switch (NRAND(3)) {
			case 0:
				g->aistate = randdir; break;
			case 1:
				g->aistate = chasing; break;
			case 2:
				g->aistate = chasing; break;
		}

	} else if (g->aistate == inbox) {
		if (g->timeleft < 0)
			g->aistate = goingout;
		else
			g->timeleft--;

	} else if (g->aistate == goingout) {
		if (g->col < LEVWIDTH/2 - JAILWIDTH/2 ||
		    g->col > LEVWIDTH/2 + JAILWIDTH/2 ||
		    g->row < LEVHEIGHT/2 - JAILHEIGHT/2 ||
		    g->row > LEVHEIGHT/2 + JAILHEIGHT/2 )
			g->aistate = randdir;
	}
		   
	switch (g->aistate) {
		case inbox:
		case goingout:
		case randdir:
			ghost_random(pp, g);
			break;
		case chasing:
			ghost_chasing(pp, g);
			break;
		case hiding:
			ghost_hiding(pp, g);
			break;
	}

	g->cfactor = GETFACTOR(g->nextcol, g->col);
	g->rfactor = GETFACTOR(g->nextrow, g->row);
}

/* Calls correct pacman state function. */
static void
pac_update(ModeInfo *mi, pacmangamestruct *pp, pacmanstruct *p) 
{
	if (!(p->nextrow == NOWHERE && p->nextcol == NOWHERE)) {
		p->row = p->nextrow;
		p->col = p->nextcol;
	}

	if (pp->level[p->row * LEVWIDTH + p->col] == '.') { 
		pp->level[p->row * LEVWIDTH + p->col] = ' ';
		if (!trackmouse)
			p->justate = 1;
		pp->dotsleft--;
	} else if (!trackmouse) {
		p->justate = 0;
	}

	if (!(trackmouse && pac_trackmouse(mi, pp, p))) {
		/* update pacman */
		switch (p->aistate) {
			case ps_eating:
				pac_eating(pp, p);
				break;
			case ps_hiding:
				pac_eating(pp, p);
				break;
			case ps_random:
				pac_random(pp, p);
				break;
			case ps_chasing:
#if 0
				pac_chasing(pp, p);
#endif
				break;
		}
	}

	p->cfactor = GETFACTOR(p->nextcol, p->col);
	p->rfactor = GETFACTOR(p->nextrow, p->row);
}
