#include "misc.h"
#include "file.h"
#include "xy.h"
#include "nsc.h"
#include "genitem.h"
#include "edb.h"
#include "edbproc.h"	/* public  structures */
#include "edbpriv.h"	/* private structures */
#include <assert.h>
#include <stdio.h>
#include <ctype.h>
#include <malloc.h>

#ifdef Debug
#define HelpMe
#define DebugPrint(x) printf x;
#define DIAG(x)	x
#else
#define DebugPrint(x)
#define DIAG(x)
#endif

#define MAXNAMELEN 80

extern ObjectDescriptor objTable[];

/*
	findObject -- find object type in our object table
*/
ObjectDescriptor *findObject(int code)
{
	ObjectDescriptor *p;
	for (p=objTable; p->objtype!=EF_BAD; p++)
		if (p->objtype==code)
			return p;
	return 0;
}


/*
	 newField -- allocate a new Field structure
 */
Field *newField(s_char *name, int kind)
{
	Field *f;
	s_char *fname;
	int namelen;
	fname = (s_char *) malloc(strlen(name)+1);
	assert(fname!=0);
	strcpy(	fname, name);
	f = (Field *) malloc(sizeof(Field));
	assert(f!=0);
	f->fldname = fname;
	f->fldtype = kind;
	f->next = 0;
	return f;
}

/*
	copyField -- copy constructor for Field structure
*/
Field *copyField(Field *aField)
{
	return newField(aField->fldname, aField->fldtype);
}

/*
	newDict -- allocate a new Dictionary structure
*/
Dictionary *newDict(int num)
{
	Dictionary *d;
	d = (Dictionary *) malloc(sizeof(Dictionary));
	assert(d!=0);
	d->next = 0;
	d->objnum = num;
	d->flds = 0;
	return d;
}

/*
	findDict -- find a Dictionary entry in the database
*/
Dictionary *findDict(Empire_Database *edb, int code)
{
	Dictionary *p;
	for (p = (Dictionary *)edb->dictionary ; p ; p=p->next) {
		if (p->objnum==code) return p;
	}
	return 0;
}
	
/*
	insertDict -- insert a Dictionary entry into the database
	(simple push down with no check for duplicates)
*/
void insertDict(Empire_Database *edb, Dictionary *p)
{
	assert(edb!=0);
	if (!p) return;
	p->next = (Dictionary *) edb->dictionary;
	edb->dictionary = p;
}

/*
	insertField -- add a field to an object definition
*/
void insertField(Dictionary *d, Field *f)
{
	Field *prev, *p;

	assert(d!=0);

	if (!f) return;
	
	for (prev=0,p=d->flds; p; prev=p,p=p->next)
		/**/ ;
	if (prev)
		prev->next = f;
	else
		d->flds = f;
	f->next = 0;
}

Field *findField(Dictionary *d, s_char *name)
{
	Field *p;
	assert(d!=0);
	for (p=d->flds; p; p=p->next)
		if (strcmp(name,p->fldname)==0)
			return p;
	return NULL;
}

/*
	getnum -- parse a number in the C_SYNC message
*/
int getnum(s_char **pp)
{
	s_char *p;
	int n;
	p = *pp;
	while (*p==' ') p++;
	n = 0;
	while (isdigit(*p)) {
		n = n*10 + (*p++) - '0';
	}
	*pp = p;
	return n;
}

/*
	parseField -- parse a field entry in an object definition
	allocates a Field structure and return it if successful.
*/

void parseField(Dictionary *dict, s_char **msg)
{
	/* we're parsing entries that look like this */
	/*   fieldname,num,num:fieldname,num,num */
	char namebuf[MAXNAMELEN];
	Field *fld;
	s_char *p, *nameptr;
	int namelen, fieldkind;
	nameptr = p = *msg;
	/* find end of fieldname */
	while ( *p!=0 && *p!=':' && *p!=',') p++;
	namelen = p - *msg;
	if (namelen>=MAXNAMELEN-5)
		namelen = MAXNAMELEN-5;
	strncpy(namebuf,nameptr,namelen);
	namebuf[namelen] = 0;
	if (*p!=',') return;	/* probably bogus */
	p++;	/* skip comma */
	fieldkind = getnum(&p);
	while ( *p!=0 && *p!=':') p++;	/* skip any other junk */
	*msg = p;
	if (findField(dict, namebuf)) {
		DebugPrint(("csync: warning: duplicate field def for object %d: %s\n",
				dict->objnum, namebuf))
		strcat(namebuf,"_dup");
	}
	fld = newField( namebuf, fieldkind);
	insertField(dict, fld);
}

/*
	findObjectField -- match fields received with fields known
	This is done only when definitions are received, not
	when objects are received.
*/
struct castr *findObjectField(struct castr *objca, s_char *fldname)
{
	for (;objca->ca_code>0;objca++)
		if (objca->ca_name==0)
			return 0;
		else if (strcmp(objca->ca_name, fldname)==0)
			return objca;
	return 0;
}

void bindFieldDefinitions(Dictionary *dict)
{
	ObjectDescriptor *obj;
	Field *f;
	dict->objdesc = obj = findObject(dict->objnum);
	if (!obj) {
		DebugPrint(("csync: warning: unknown object type %d\n", dict->objnum));
		return;	/* an object we cannot handle */
	}
	for (f=dict->flds;f;f=f->next) {
		f->boundto = findObjectField(obj->obj_ca, f->fldname);
		if (!f->boundto) {
			DebugPrint(("csync: warning: field not bound: %s\n", f->fldname));
		}
	}
}


/*
	parse_typedef -- parse a C_SYNC type definition
*/
void parse_typedef(Empire_Database *edb, s_char *msg)
{
	int objnum, baseobjnum;
	Dictionary *baseobj;
	Dictionary *obj;
	Field *fld;
	ObjectDescriptor *od;

	baseobjnum = getnum(&msg);
	objnum = getnum(&msg);
	if (baseobjnum==objnum)
		baseobj = NULL;
	else {
		baseobj = findDict(edb, baseobjnum);
		DIAG( if (!baseobj) {
			DebugPrint(("csync: typedef, unknown base object %d for object %d\n",
				baseobjnum, objnum));
		} )
	}
	if (findDict(edb, objnum)) return;	/* already done */

	DIAG(
		od = findObject(objnum);

		printf("csync: defining object type: %d (%s)\n",
			objnum, od?od->objdescr:"unknown");
	)
	
	obj = newDict(objnum);
	
#if (0)
	/* base object fields are included with the definition for now */
	if (baseobj) {
		for (fld=baseobj->flds;fld;fld=fld->next) {
			insertField(obj, copyField(fld));
		}
	}
#endif
	/* now parse fields and field types */
	while (*msg==' ') msg++;
	for (;;) {
		 parseField(obj, &msg);
		if (*msg != ':') break;
		msg++;
	}
	/* bind fields in the csync message to fields in the database */
	bindFieldDefinitions(obj);

	insertDict(edb, obj);

}

void storeField(struct genobject *item, Field *fld, s_char *buf)
{
	/* msg is a string delimited with a ":" or a newline or a null */
	
	int fldoffs;
	long fldkind;
	typedef union {
		s_char **ccp;
		s_char *cp;
		u_char *up;
		int *ip;
		short *sp;
		unsigned short *usp;
		long *lp;
		float *fp;
	} Thing;
	
	Thing thing;
	struct castr *captr;
	
	/* get object descriptor */	
	captr = fld->boundto;
	if (!captr) {
		return;
	}

	fldoffs = (captr->ca_code)&0xffff;
	fldkind = (captr->ca_code)&NSC_TMASK;
	
	thing.cp = ((char *)item) + fldoffs;
	switch (fldkind) {
		case NSC_CHAR:
			*thing.cp = atoi(buf);
			break;
		case NSC_UCHAR:
			*thing.up = atoi(buf);
			break;
		case NSC_SHORT:
			*thing.sp = atoi(buf);
			break;
		case NSC_INT:
			*thing.ip = atoi(buf);
			break;
		case NSC_USHORT:
			*thing.usp = atoi(buf);
			break;
		case NSC_LONG:
			*thing.lp = atol(buf);
			break;
		case NSC_XCOORD:
		case NSC_YCOORD:
			*thing.sp = atoi(buf);
			break;
		case NSC_FLOAT:
			*thing.fp = atof(buf);
			break;
		case NSC_CHARP:
			if (*thing.ccp) free(*thing.ccp);
			*thing.ccp = strdup(buf);
			break;
		default:
			DIAG(
				printf("unknown field kind: %s %08lx\n", 
					fld->fldname, fldkind);
			)
			break;
	}
/*	printf("%4d: %10s = %-10s\n", fldoffs, fld->fldname, buf); */
}

void sprintField(char *pbuf, struct genobject *item, Field *fld)
{
	/* msg is a string delimited with a ":" or a newline or a null */
	
	int fldoffs;
	long fldkind;
	typedef union {
		s_char **ccp;
		s_char *cp;
		u_char *up;
		int *ip;
		short *sp;
		unsigned short *usp;
		long *lp;
		float *fp;
	} Thing;
	
	Thing thing;
	struct castr *captr;
	
	/* get object descriptor */	
	captr = fld->boundto;
	if (!captr) {
		return;
	}

	fldoffs = (captr->ca_code)&0xffff;
	fldkind = (captr->ca_code)&NSC_TMASK;
	
	thing.cp = ((char *)item) + fldoffs;
	switch (fldkind) {
		case NSC_CHAR:
			sprintf(pbuf, "%d", *thing.cp);
			break;
		case NSC_UCHAR:
			sprintf(pbuf, "%d", *thing.up);
			break;
		case NSC_SHORT:
			sprintf(pbuf, "%d", *thing.sp);
			break;
		case NSC_INT:
			sprintf(pbuf, "%d", *thing.ip);
			break;
		case NSC_USHORT:
			sprintf(pbuf, "%d", *thing.usp);
			break;
		case NSC_LONG:
			sprintf(pbuf, "%ld", *thing.lp);
			break;
		case NSC_XCOORD:
		case NSC_YCOORD:
			sprintf(pbuf, "%d", *thing.sp);
			break;
		case NSC_FLOAT:
			sprintf(pbuf, "%f", *thing.fp);
			break;
		case NSC_CHARP:
			strcpy(pbuf, *thing.ccp);
			break;
		default:
			strcpy(pbuf, "*unknown format*");
			break;
	}
}

void storeFields(Empire_Database *edb, Dictionary *dict,
		 char *msg, struct genobject *item, int keysonly)
{
	Field *fld;
	int i;
	s_char *bp;
	s_char *cp;
	s_char c;
	char buf[128];

	/* store fields one by one */
	for (fld=dict->flds;fld;fld=fld->next) {
#ifdef TAGFIELDS
		/* a minor optimization would be possible if we know key fields.. */
		/* this requires that we know whether a field is key */
		if (keysonly&&!fld->iskey) {
			for (;;) {
				c = *msg++;
				if (c==0||c==':'||c=='\n') break;
			}
			if (*msg==':') msg++;
			continue;
		}
#endif
		/* extract field by copying to buf */
		bp = buf;
		for (i=0;i<127;i++) {
			c = *msg++;
			if (c==0||c==':'||c=='\n') break;
			*bp++ = c;
		}
		*bp++ = '\0';
		if (bp!=buf)
			storeField(item, fld, buf);
		if (*msg==':') msg++;
	}
	
}

struct genobject *newGenObject(Empire_Database *edb, ObjectDescriptor *desc)
{
	struct genobject *item;
	/* allocate memory for a new object */
	item = (struct genobject *) malloc(desc->objsize);
	if (item)
		memset(item, 0, desc->objsize);	/* initialize it */
	return item;
}

void freeGenObject(Empire_Database *edb, Dictionary *dict, struct genobject *item)
{
	/*
	   actually we need to go through the dictionary and free string fields 
	   to prevent memory leaks.
	*/
	free(item);
}

void storeObject(Empire_Database *edb, int code, char *msg)
{
	Dictionary *dict;
	ObjectDescriptor *desc;
	struct genobject *item, *orig_item;
	dict = findDict(edb, code);
	if (!dict) {
		/* we never received a definition */
		DebugPrint(("csync: unknown csync type: %d\n", code));
		return;
	}
	desc = dict->objdesc;

	/* if we cannot store these kind of objects, just ignore */
	if (!desc) {
		DebugPrint(("csync: no physical representation for object: %d\n", code));
		return;
	}

	item = newGenObject(edb, desc);
	item->ef_type = code;

	/* pack the key fields into the object */
	storeFields(edb, dict, msg, item, 1);

	/* see if the object is already in the database */
	orig_item = desc->find(desc, edb, item);

	if (orig_item) {
		/* already there... Just update it with new values */
		storeFields(edb, dict, msg, orig_item, 0);
		item = orig_item;
	}
	else {
		/* it's a whole new object */
		/* now store the item in the database... */
#ifdef TAGFIELDS
		storeFields(edb, dict, msg, item, 0);
#endif
		desc->store(desc, edb, item);
	}
}

/* edb_parse_c_sync -- process a Csync message and update the database */
void edb_parse_c_sync(Empire_Database *edb, s_char *msg)
{
	int code;
	assert(edb!=0);
	assert(msg!=0);
	
	msg += 2;	/* skip over protocol character and blank */
	
	code = getnum(&msg);
	if (code==EDB_TYPE_DEF) 
		parse_typedef(edb, msg+1);
	else
		storeObject(edb, code, msg+1);
}

void dumpDict(Empire_Database *edb)
{
	Dictionary *dict;
	Field *fld;
	for (dict=(Dictionary *)edb->dictionary; dict; dict=dict->next) {
		printf("csync: object dictionary for type %d\n", dict->objnum);
		for (fld=dict->flds;fld;fld=fld->next) {
			printf("	%10s: %d\n", fld->fldname, fld->fldtype);
		}
	}
}

void dumpObject(Empire_Database *edb, struct genobject *thing)
{
	Dictionary *dict;
	Field *fld;
	char buf[80];
	if (!thing) {
		printf("dumpObject: NULL object\n");
		return;
	}
	dict = findDict(edb, thing->ef_type);
	if (!dict) {
		printf("dumpObject: unknown object type: %d\n", thing->ef_type);
		return;
	}
	printf("	object type = %d\n", dict->objnum);
	for (fld=dict->flds;fld;fld=fld->next) {
		sprintField(buf, thing, fld);
		printf("	%10s: %s\n", fld->fldname, buf);
	}
}

#ifdef HelpMe

#define MAXTYPE 8
char *typenames[] = {
	"bad",
	"char *",
	"u_char",
	"short",
	"u_short",
	"int",
	"long",
	"float",
	"double" };
	
char *nscnames[] = {
	"bad",
	"NSC_CHARP",
	"NSC_UCHAR",
	"NSC_SHORT",
	"NSC_USHORT",
	"NSC_INT",
	"NSC_LONG",
	"NSC_FLOAT",
	"NSC_FLOAT" };

void describeType(Dictionary *dict)
{
	Field *f;
	char *typename;
	printf("/* Structure definition */\n");
	printf("struct Object_type_%d {\n", dict->objnum);
	printf("	short ef_type;\n");
	for (f=dict->flds;f;f=f->next) {
		if (f->fldtype>0 && f->fldtype<=MAXTYPE)
			typename = typenames[f->fldtype];
		else
			typename = typenames[0];
		printf("	%-10s fld_%-16s; /* %d */\n", 
			typename, f->fldname, f->fldtype);
	}
	printf("};\n\n");
}

void makeNscDefinition(Dictionary *dict)
{
	Field *f;
	char *typename;
	printf("/* NSC table definition */\n");
	printf("struct castr Object_type_%d_ca[] = {\n", dict->objnum);
	for (f=dict->flds;f;f=f->next) {
		if (f->fldtype>0 && f->fldtype<=MAXTYPE)
			typename = nscnames[f->fldtype];
		else
			typename = nscnames[0];
		printf("	%-10s| fldoff(Object_type_%d, fld_%-16s), \"%s\",0,0,\n",
			typename, dict->objnum, f->fldname, f->fldname);
	}
	printf("	0,0,0,0\n");
	printf("};\n\n");
}

void dumpUnknownTypes(Empire_Database *edb)
{
	Dictionary *dict;
	Field *fld;
	for (dict=(Dictionary *)edb->dictionary; dict; dict=dict->next) 
		if (!dict->objdesc)
			describeType(dict);
	for (dict=(Dictionary *)edb->dictionary; dict; dict=dict->next) 
		if (!dict->objdesc)
			makeNscDefinition(dict);
}

#endif
