#ifndef lint
static char rcsid[] = "$Header: /usr/brule/guest/empire/empire/emprcs/lib/common/path.c,v 2.1 1995/02/07 05:27:25 empire Exp $";
#endif

/*
 * path.c
 *
 * Empire/A* interface code.  Provides callbacks for A* code and
 * a sector cache to speed things up.  Define BP_STATS for sector
 * cache statistics, AS_STATS for A* statistics.
 *
 * Phil Lapsley & Dave Pare, 1991
 * hastily hacked by ts, 1993
 */

#include <stdio.h>
#include "../as/as.h"
#include "misc.h"
#include "path.h"
#include "xy.h"
#include "sect.h"
#include "file.h"

static	int bp_path();
static	int bp_neighbors();
static	double bp_lbcost();
static	double bp_realcost();
static	double bp_seccost();
static	int bp_coord_hash();
static	struct sctstr *bp_getsect();
static	struct sctstr *bp_sctcache_get();
static	void bp_sctcache_set();
static	void bp_sctcache_zap();

/* XXX won't need sector hash when sect file is memory mapped */

#define	BP_SCTHASHSIZE	128		/* sector cache hash table size */
#define	BP_ASHASHSIZE	128		/* A* queue hash table size */
#define	BP_NEIGHBORS	6		/* max number of neighbors */

struct	sctcache {
	coord	x, y;
	struct	sctstr *sp;
	struct	sctcache *next;
};

struct bestp {
	struct	sctcache *sctcachetab[BP_SCTHASHSIZE];
	int	sctcache_hits;
	int	sctcache_misses;
	struct	as_data *adp;
};

struct empfile *ep;

s_char *
bp_init()
{
	struct	bestp *bp;

	ep = &empfile[EF_SECTOR];

	bp = (struct bestp *) malloc(sizeof(*bp));
	bzero(bp, sizeof(*bp));
	bp->adp = as_init(BP_NEIGHBORS, BP_ASHASHSIZE, bp_coord_hash,
		bp_neighbors, bp_lbcost, bp_realcost,
		bp_seccost, (s_char *)bp);

	if (bp->adp == NULL)
		return NULL;

	return (s_char *) bp;
}

/*
 * Find the best path from sector to to sector, and put the Empire movement
 * string in path.  Return 0 on success, -1 on error.
 */
int
best_path(from, to, path)
	struct	sctstr *from;
	struct	sctstr *to;
	s_char	*path;
{
	static struct bestp *mybestpath;
	struct	as_data *adp;

	if (mybestpath == 0) 
		mybestpath = (struct bestp *)bp_init();
	adp = mybestpath->adp;
	bp_sctcache_zap(mybestpath);
	adp->from.x = from->sct_x;
	adp->from.y = from->sct_y;
	adp->to.x = to->sct_x;
	adp->to.y = to->sct_y;

	if (as_search(adp) < 0)
		return -1;

	if (bp_path(adp->path, path) < 0)
		return -1;

#ifdef AS_STATS
	as_stats(adp, stderr);
#endif /* AS_STATS */
#ifdef BP_STATS
	fprintf(stderr, "best path %s\n", path);
	fprintf(stderr, "cache hits/misses: %d/%d\n",
		bp->sctcache_hits, bp->sctcache_misses);
#endif /* BP_STATS */
	return 0;
}

/*
 * Translate an A* path into an empire movement string.  Return 0 on
 * success, -1 on failure.
 */
static int
bp_path(pp, buf)
	struct	as_path *pp;
	s_char	*buf;
{
	struct	as_path *np;
	s_char	*cp = buf;
	int	dx, dy;
	int	n;

	np = pp->next;
	while (np) {
		dx = np->c.x - pp->c.x;
		/* deal with wraparound from non-neg coords */
		if (dx < -2)
			dx += WORLD_X;
		if (dx > 2)
			dx -= WORLD_X;
		dy = np->c.y - pp->c.y;
		if (dy < -1)
			dy += WORLD_Y;
		if (dy > 1)
			dy -= WORLD_Y;
		for (n=1;n<=6;n++)
			if (dx == diroff[n][0] && dy == diroff[n][1])
				break;
		if (n > 6)
			return -1;

		*cp++ = dirch[n];
		pp = np;
		np = np->next;
	}
	*cp = '\0';
	return 0;
}

/*
 * Find coords neighboring this sector; return number of such
 * coords, and coordinartes themselves in an array pointed
 * to by *cpp.
 * XXX need to check ownership, sector types, etc.
 */
static int
bp_neighbors(c, cp, pp)
	struct	as_coord c;
	struct	as_coord *cp;
	s_char	*pp;
{
	struct	bestp *bp = (struct bestp *) pp;
	coord	x, y;
	coord	nx, ny;
	int	n = 0, q;
	struct	sctstr *sp, *from;

	x = c.x;
	y = c.y;
	if ((ep->flags & EFF_MEM) == 0) {
		from = bp_getsect(bp, x, y);
	}else{
		int	Nx, Ny, offset;
		if (x < 0)
			Nx = (WORLD_X - (-x % WORLD_X)) % WORLD_X;
		else
			Nx = x % WORLD_X;

		if (y < 0)
			Ny = (WORLD_Y - (-y % WORLD_Y)) % WORLD_Y;
		else
			Ny = y % WORLD_Y;

		offset = (Ny * WORLD_X + Nx)/2; 
		from = (struct sctstr *) (ep->cache + ep->size * offset);
	}

	for(q=1;q<=6;q++){
		nx = x + diroff[q][0];
		ny = y + diroff[q][1];
		if ((ep->flags & EFF_MEM) == 0) {
			sp = bp_getsect(bp, nx, ny);
		}else{
			int	Nx, Ny, offset;
			if (nx < 0)
				Nx = (WORLD_X - (-nx % WORLD_X)) % WORLD_X;
			else
				Nx = nx % WORLD_X;
	
			if (ny < 0)
				Ny = (WORLD_Y - (-ny % WORLD_Y)) % WORLD_Y;
			else
				Ny = ny % WORLD_Y;
	
			offset = (Ny * WORLD_X + Nx)/2; 
			sp = (struct sctstr *) (ep->cache + ep->size * offset);
		}
		if (sector_mcost(sp->sct_type,sp->sct_effic) < 0)
			continue;
		if (sp->sct_own != from->sct_own)
			continue;
		cp[n].x = xnorm(nx);
		cp[n].y = ynorm(ny);
		n++;
	}
	return (n);
}

/*
 * Compute a lower-bound on the cost from "from" to "to".
 */
/*ARGSUSED*/
static double
bp_lbcost(from, to, pp)
	struct	as_coord from;
	struct	as_coord to;
	s_char	*pp;
{
	struct	bestp *bp = (struct bestp *) pp;
	struct	sctstr *ts;
	float	cost;

	if ((ep->flags & EFF_MEM) == 0) {
		ts = bp_getsect(bp, (coord)to.x, (coord)to.y);
	}else{
		int	Nx, Ny, offset, x, y;
		x = to.x; y = to.y;

		if (x < 0)
			Nx = (WORLD_X - (-x % WORLD_X)) % WORLD_X;
		else
			Nx = x % WORLD_X;

		if (y < 0)
			Ny = (WORLD_Y - (-y % WORLD_Y)) % WORLD_Y;
		else
			Ny = y % WORLD_Y;

		offset = (Ny * WORLD_X + Nx)/2; 
		ts = (struct sctstr *) (ep->cache + ep->size * offset);
	}
	cost = sector_mcost(ts->sct_type, ts->sct_effic);
	return (cost);
}

/*
 * Compute the real cost to move from "from" to "to".
 */
static double
bp_realcost(from, to, pp)
	struct	as_coord from;
	struct	as_coord to;
	s_char	*pp;
{
	return (bp_lbcost(from, to, pp));
}

/*
 * Tie breaker secondary metric (only used when lower bound costs
 * are equal).
 */
/*ARGSUSED*/
static double
bp_seccost(from, to, pp)
	struct	as_coord from;
	struct	as_coord to;
	s_char	*pp;
{
	return ((double) mapdist((coord)from.x, (coord)from.y,
		(coord)to.x, (coord)to.y));
}

/*
 * Get a sector from the cache.  If it's not in the cache,
 * get it from disk and add it to the cache.
 */
static struct sctstr *
bp_getsect(bp, x, y)
	struct	bestp *bp;
	coord	x, y;
{
	struct	sctstr *sp;

	sp = bp_sctcache_get(bp, x, y);
	if (sp == NULL) {
		sp = (struct sctstr *) malloc(sizeof(*sp));
		getsect(x, y, sp);
		bp_sctcache_set(bp, x, y, sp);
		bp->sctcache_misses++;
	} else {
		bp->sctcache_hits++;
	}
	return (sp);
}

/*
 * Get a sector from the cache; return NULL if it's not there.
 */
static struct sctstr *
bp_sctcache_get(bp, x, y)
	struct	bestp *bp;
	coord	x, y;
{
	int 	hashval;
	struct	as_coord c;
	struct	sctcache *hp;

	c.x = x;
	c.y = y;
	hashval = bp_coord_hash(c) % BP_SCTHASHSIZE;
	for (hp = bp->sctcachetab[hashval]; hp; hp = hp->next) {
		if (hp->x == x && hp->y == y)
			return (hp->sp);
	}
	return (NULL);
}

/*
 * Put a sector in the cache.
 */
static void
bp_sctcache_set(bp, x, y, sp)
	struct	bestp *bp;
	coord	x, y;
	struct	sctstr *sp;
{
	int	hashval;
	struct	as_coord c;
	struct	sctcache *hp;

	hp = (struct sctcache *) calloc(1, sizeof(*hp));
	hp->x = x;
	hp->y = y;
	hp->sp = sp;
	c.x = x;
	c.y = y;
	hashval = bp_coord_hash(c) % BP_SCTHASHSIZE;
	hp->next = bp->sctcachetab[hashval];
	bp->sctcachetab[hashval] = hp;
}

/*
 * Zap the cache and reset statistics.
 */
static void
bp_sctcache_zap(bp)
	struct	bestp *bp;
{
	register struct sctcache *hp;
	register struct sctcache *np;
	register int i;

	for (i = 0; i < BP_SCTHASHSIZE; i++) {
		for (hp = bp->sctcachetab[i]; hp; hp = np) {
			np = hp->next;
			free(hp->sp);
			free(hp);
		}
		bp->sctcachetab[i] = NULL;
	}
	bp->sctcache_hits = 0;
	bp->sctcache_misses = 0;
}

/*
 * Hash a coordinate into an integer.
 */
static int
bp_coord_hash(c)
	struct	as_coord c;
{
	return ((abs(c.x) + 1) << 3) ^ abs(c.y);
}

s_char * 
BestDistPath(path, from, to, cost)
	s_char *path;
	struct sctstr *from, *to;
	double *cost;
{
	s_char	*BestLandPath();

	return BestLandPath(path, from, to, cost);
}

s_char *
BestLandPath(path, from, to, cost)
	s_char	path[512];
	struct sctstr *from, *to;
	double *cost;
{
	double	pathcost();
	int length;

	*path = 0;
	*cost = 0.0;
	if (best_path(from,to,path) < 0)
		return (s_char *)0;
	*cost = pathcost(from,path);
	length = strlen (path);
	path[length] = 'h';
	path[length + 1] = '\0';
	return path;
}

double
pathcost(start,path)
	struct	sctstr *start;
	s_char	*path;
{
	register int n, o, len;
	register int cx, cy;
	double	cost=0.0;
	struct	sctstr sp;

	len = strlen(path);
	n = 0;
	cx = start->sct_x;
	cy = start->sct_y;

	while(n < len){
		for(o=1;o<=6;o++)
			if (dirch[o] == *(path+n))
				break;
		cx += diroff[o][0];
		cy += diroff[o][1];
		getsect(cx,cy,&sp);
		cost += sector_mcost(sp.sct_type,sp.sct_effic);
		n++;
	}

	return cost;
}
