#ifndef CODE_XCLASSES_CXX
#define CODE_XCLASSES_CXX

/*-----------------------------------------------------------------------
 * Xclasses code file
 * ($Id: Xclasses.cxx,v 1.4 1999/03/08 15:59:42 schmitzj Exp $)
 * 
 * Copyright (C) 1998 Jürgen Schmitz
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 *-----------------------------------------------------------------------
 */

#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>

#include "types.h"
#include "../include/defaults.h"
#include "../include/XCprogram_p.h"
#include <Xclasses/Xclasses_class.h>
#include <Xclasses/dlist.h>
#include "include/Xclasses_p.h"

#define HASHSIZE	64 	// 26
#define HASH2SIZE	64
#define USEHASH

static void addObject(class pXclasses *obj);
static void remObject(class pXclasses *obj);
static void updateObjectList(int flags=0);

static class dlist *allObjects=NULL;
static int goodCache=0;
static int objChanged=0;

static class dlist *hash=NULL;
static int calcHash(char *lastname);
static class dlist *hash2=NULL;
static int calcHash2(Xclasses *);

short OBJDEBUGMODE=0;

char *splitName(char *n,int p)
{
	static char buf[200];
	int i=0,c=0;
	while (i<=p && *n)
	{
		if (*n=='.')
		{
			i++;
		}
		else
		{
			if (i==p)
			{
				buf[c++]=*n;
			}
		}
		n++;
	}
	buf[c]=0;
	return buf;
}
char *splitLastName(char *n)
{
	char *c=n;
	while (*n)
	{
		if (*n=='.')
		{
			c=n+1;
		}
		n++;
	}
	return c;
}

Xclasses::Xclasses(void) : callable()
{
	while (!(p=new pXclasses))
	{
		outOfMemory("Xclasses");
	}
	p->o=this;
	classdata=NULL;
	addObject(p);
}
Xclasses::Xclasses(char *name) : callable()
{
	while (!(p=new pXclasses))
	{
		outOfMemory("Xclasses");
	}
	p->o=this;
	classdata=NULL;
	addObject(p);
	Name(name);
}
Xclasses::~Xclasses()
{
	if (p) delete p;
	p=NULL;
}
pXclasses::pXclasses()
{
	o=NULL;
	objname=NULL;
	parentobj=NULL;
	pathbuf=NULL;
	alllist=0;
	level=0;
	changed=0;
	hashvalue=-1;
	hash2value=-1;
	mynode.text=(char*)this;
	nexthash.text=(char*)this;
	nexthash2.text=(char*)this;
}
pXclasses::~pXclasses()
{
	remObject(this);
	if (pathbuf) delete pathbuf;
	pathbuf=NULL;
	if (objname) ::free(objname);
	for(int i=0;i<alllist;i++)
	{
		if (allnames[i]) ::free(allnames[i]);
	}
}

void Xclasses::Name(char *n)
{
	if (p->hashvalue>=0)
	{
		hash[p->hashvalue].Remove(&p->nexthash);
		p->hashvalue=-1;
	}

	if (p->objname) ::free(p->objname);
	if (n)
	{
		p->objname=strdup(n);
		TranslateName(p->objname);
		if (p->alllist<MAXNAMES)
		{
			p->allnames[p->alllist++]=strdup(p->objname);
		}
  		p->hashvalue=calcHash(p->objname);
  		hash[p->hashvalue].AddTail(&p->nexthash);
  		p->nexthash.text=(char*)p;
	}
	else
		p->objname=NULL;
	if (p->pathbuf) delete p->pathbuf;
	p->pathbuf=NULL;
	goodCache=0;
	p->changed=1;
}
char *Xclasses::Name(void)
{
	return p->objname;
}
char *Xclasses::FullName(void)
{
	if (!p->pathbuf)
	{
		Xclasses *parent=p->parentobj;
		if (!p->parentobj && thisProgram && this!=thisProgram->o) parent=thisProgram->o;
		if (parent && (parent==thisProgram->o || p->parentExists()))
		{
			char *b=parent->FullName();
			p->level=parent->p->level+1;
			int s=strlen(b);
			if (p->objname)
				s+=strlen(p->objname);

			p->pathbuf=new char[s+2];
			*p->pathbuf=0;
			strcpy(p->pathbuf,b);
			if (p->objname)
			{
				strcat(p->pathbuf,".");
				strcat(p->pathbuf,p->objname);
			}
		}
		else
		{
			int s=0;
			if (p->objname)
				s=strlen(p->objname);

			p->level=0;
			p->pathbuf=new char[s+2];
			*p->pathbuf=0;
			if (p->objname)
			{
				strcpy(p->pathbuf,p->objname);
			}
		}
	}
	return p->pathbuf;
}
void Xclasses::ParentClass(Xclasses *g)
{
	p->parentobj=g;
	if (p->pathbuf) delete p->pathbuf;
	p->pathbuf=NULL;
	goodCache=0;
	p->changed=1;
}
Xclasses *Xclasses::ParentClass(void)
{
	return p->parentobj;
}
Xclasses *Xclasses::ParentClassType(char *n)
{
	class Xclasses *cl=p->parentobj;
	while (cl)
	{
		if (cl->IsClassType(n)) return cl;
		cl=cl->ParentClass();
	}
	return NULL;
}

int Xclasses::IsClassType(char *n)
{
	int r=0;
	for(int i=0;i<p->alllist;i++)
	{
		if (!strcmp(n,p->allnames[i])) return 1;
	}
	return r;
}
char *Xclasses::GetResource(char *res)
{
	char *ret=NULL;
	int i;
	int l=strlen(FullName());

	if (l<50) l=50;
	char buf[l+strlen(res)+2];

	strcpy(buf,FullName());
	strcat(buf,".");
	strcat(buf,res);
	i=p->alllist-1;
	do
	{
		ret=getResource(buf,NULL);
		if (!ret)
		{
			strcpy(buf,thisProgram->name->Name());
			strcat(buf,".");
			strcat(buf,p->allnames[i]);
			strcat(buf,".");
			strcat(buf,res);
			i--;
		}
	}
	while (!ret && i>=0);
	return ret;
}
void Xclasses::SetResource(char *res,char *val)
{
	if (*res=='*')
	{
		char buf[strlen(thisProgram->o->FullName())+strlen(res)+2];
		strcpy(buf,thisProgram->o->FullName());
		strcat(buf,"*");
		strcat(buf,res);
		setResource(buf,val);
	}
	else
	{
		char buf[strlen(FullName())+strlen(res)+2];
		strcpy(buf,FullName());
		if (*res=='.')
		{
			strcat(buf,"*");
			strcat(buf,res+1);
		}
		else
		{
			strcat(buf,".");
			strcat(buf,res);
		}
		setResource(buf,val);
	}
}

class Xclasses *Xclasses::ScanObjects(char *base,class Xclasses *old)
{
	struct node *n=allObjects->head;
	if (old) n=old->p->mynode.next;

	{
		if (!base) base="";
		char oldbase[strlen(base)+1];
		strcpy(oldbase,base);
		updateObjectList(0);
		base=oldbase;
	}
	int blen=strlen(base);

	if (base[0]=='.')
	{ // letzten Zeichen
		// ????
	}
	else
	{ // gesamter Name
		char *c;
		while (n->next)
		{
			c=((pXclasses*)n->text)->o->FullName();
			if (strcmp(splitName(c,1),INTERNALOBJS) && !strncmp(base,c,blen))
			{
				return ((pXclasses*)n->text)->o;
			}
			n=n->next;
		}
	}
	return NULL;
}
int pXclasses::parentExists(void)
{
	if (!parentobj) return 0;
	register struct node *n=hash2[calcHash2(parentobj)].head;

	while (n->next)
	{
		if (((pXclasses*)n->text)->o==parentobj) return 1;
		n=n->next;
	}
	if (OBJDEBUGMODE)
		fprintf(stderr,"Parent object not found:\nName: %s\n",objname);
	parentobj=NULL;
	changed=1;
	return 0;
}
int pXclasses::checkChange(void)
{
	int c=changed;

 	if (pathbuf && parentobj && parentExists())
 		c|=parentobj->p->checkChange();
 	if (c && pathbuf)
 	{
 		delete pathbuf;
 		pathbuf=NULL;
 	}
 	changed=0;
	return c;
}


// -------------------------------------------------------------

void addObject(class pXclasses *obj)
{
	if (!allObjects)
	{
		while (!(allObjects=new dlist))
		{
			outOfMemory("Xclasses");
		}
	}
	if (!hash)
	{
		while (!(hash=new dlist[HASHSIZE]))
		{
			outOfMemory("Xclasses");
		}
	}
	if (!hash2)
	{
		while (!(hash2=new dlist[HASH2SIZE]))
		{
			outOfMemory("Xclasses");
		}
	}
	allObjects->AddTail(&obj->mynode);
	obj->hash2value=calcHash2(obj->o);
	hash2[obj->hash2value].AddTail(&obj->nexthash2);
	objChanged=1;
}
void remObject(class pXclasses *obj)
{
	hash2[obj->hash2value].Remove(&obj->nexthash2);
	allObjects->Remove(&obj->mynode);
	if (obj->hashvalue>=0)
	{
		hash[obj->hashvalue].Remove(&obj->nexthash);
		obj->hashvalue=-1;
	}
	goodCache=0;
	if (allObjects->Count()==0)
	{
		delete allObjects;
		allObjects=NULL;
		delete [] hash;
		hash=NULL;
		delete [] hash2;
		hash2=NULL;
	}
	objChanged=1;
}

#ifdef USEHASH
// -------------
static int ohashmaximum(void)
{
	return HASH2SIZE-1;
}
static int ohashauslastung(int h)
{
	return hash2[h].Count();
}
int ohashtest(void)
{
	int t,n=0,m=0,h,z=0;
	for(int i=0;i<=ohashmaximum();i++)
	{
		printf("%02d ",i);
		n+=ohashauslastung(i);
	}
	t=n;
	printf("\n");
	for(int i=0;i<=ohashmaximum();i++)
	{
		h=ohashauslastung(i);
		if (h>m) m=h;
		if (h<t) t=h;
		if (h==0) z++;
		printf("%02d ",h);
	}
	printf("\n\nTotal items %d, Hash cells %d, Min fill %d, Max fill %d, Free cells %d (%d%%)\n",n,ohashmaximum()+1,t,m,z,(100*z/(ohashmaximum()+1)));
// 	if ((m-t)>2)
// 	{
// 		printf("Hash table is bad used.\n");
// 		return 0;
// 	}
// 	else
// 	{
// 		printf("Hash table is well used!\n");
// 		return 1;
// 	}
	return 0;
}
// -------------
#endif

static int calcHash(char *lastname)
{
	register unsigned int l=strlen(lastname),h=l;

	for (int i=0;i<l;i++)
	{
		h+=lastname[i]+(i & 1?toupper(lastname[i])*i:1);
	}
	return h%HASHSIZE;
}
static int calcHash2(Xclasses *c)
{
	return (((unsigned int)c)>>4 )%HASH2SIZE;
}

static void updateObjectList(int flags)
{ // 1: only check
  // 0: FullName all

	if (goodCache==2) return;
	if (!goodCache)
	{
		register struct node *n=allObjects->tail;
		while (n->prev)
		{
			((pXclasses*)n->text)->checkChange();
			n=n->prev;
		}

		if (flags==0)
		{
			n=allObjects->head;
			while (n->next)
			{
				((pXclasses*)n->text)->o->FullName();
				n=n->next;
			}
		}
	}
	goodCache=1;
}

void writeObjectTree(char *filename)
{
	FILE *datei;
	updateObjectList(0);
	if (datei=fopen(filename,"w"))
	{
		struct node *n=allObjects->head;
		int i;

		fprintf(datei,"## Xclasses objects list file\n");
		fprintf(datei,"#version: 1.0\n");
		fprintf(datei,"#program: %s\n",prgname);
		fprintf(datei,"#objects: %d\n",allObjects->Count());
		while (n->next)
		{
			fprintf(datei,"object:\n");

			if (((pXclasses*)n->text)->o->Name())
			{
				fprintf(datei,"%s\n",((pXclasses*)n->text)->o->Name());
			}
			else
			{
				fprintf(datei,"unnamed\n");
			}
			fprintf(datei,"P%s\n",((pXclasses*)n->text)->o->FullName());
			for(i=0;i<((pXclasses*)n->text)->alllist;i++)
			{
				fprintf(datei,">%s",((pXclasses*)n->text)->allnames[i]);
			}
			if (((pXclasses*)n->text)->alllist) fprintf(datei,"\n");
			fprintf(datei,"H%d\n",((pXclasses*)n->text)->hashvalue);

			fprintf(datei,"endobject\n");
			n=n->next;
		}
		fclose(datei);
		fprintf(stderr,"Class view saved\n");
	}
	else
	{
		fprintf(stderr,"Can't save class view as %s\n",filename);
	}
}
void writeObjectTree(void)
{
	char buf[200];
	sprintf(buf,"/tmp/Xclasses_%s.Xcobjf",thisProgram->name->Name());
	writeObjectTree(buf);
	sprintf(buf,"/tmp/Xclasses_%s.resbase",thisProgram->name->Name());
	writeObjectDatabase(buf);
}

void resetObjectCheck(void)
{
	objChanged=0;
}
int objectsChanged(void)
{
	return objChanged;
}
int objectExists(Xclasses *obj)
{
	if (objChanged)
	{
		struct node *n=allObjects->head;
		while (n->next)
		{
			if (((pXclasses*)n->text)->o==obj) return 1;
			n=n->next;
		}
		return 0;
	}
	return 1;
}

class Xclasses *FindObject(char *name)
{
	updateObjectList(1);

	register struct node *n=hash[calcHash(splitLastName(name))].head;
	if (name[0]=='.')
	{ // letzten Zeichen
		register int l=strlen(name),pl;
		register char *c;
		while (n->next)
		{
			c=((pXclasses*)n->text)->o->FullName();
			pl=strlen(c)-l;
			if (pl>=0)
			{
				if (!strcmp(name,c+pl))
				{
					return ((pXclasses*)n->text)->o;
				}
			}
			n=n->next;
		}
	}
	else
	{ // gesamter Name
		char *c;
		while (n->next)
		{
			c=((pXclasses*)n->text)->o->FullName();
			if (!strcmp(name,c))
			{
				return ((pXclasses*)n->text)->o;
			}
			n=n->next;
		}
	}
	if (OBJDEBUGMODE)
		fprintf(stderr,"Object not found:\n%s\n",name);
	return NULL;
}

class Xclasses *FindObject(char *base,char *name)
{
	if (!base) base="";
	char oldbase[strlen(base)+1];
	strcpy(oldbase,base);
	updateObjectList(1);
	base=oldbase;
	int blen=strlen(base);

	register struct node *n=hash[calcHash(splitLastName(name))].head;
	if (name[0]=='.')
	{ // letzten Zeichen
		register int l=strlen(name),pl;
		register char *c;
		while (n->next)
		{
			c=((pXclasses*)n->text)->o->FullName();
			pl=strlen(c)-l;
			if (pl>=0)
			{
				if (!strcmp(name,c+pl) && !strncmp(base,c,blen))
				{
					return ((pXclasses*)n->text)->o;
				}
			}
			n=n->next;
		}
	}
	else
	{ // gesamter Name
		register char *c;
		while (n->next)
		{
			c=((pXclasses*)n->text)->o->FullName();
			if (!strncmp(base,c,blen) && (((int)strlen(c)-(blen+1))>0) && !strcmp(name,c+blen+1))
			{
				return ((pXclasses*)n->text)->o;
			}
			n=n->next;
		}
	}
	if (OBJDEBUGMODE)
		fprintf(stderr,"Object not found:\nBase: %s\nName: %s\n",base,name);
	return NULL;
}
void DeleteObjectTree(char *base,int flags)
{
	int dobase=!(flags&DOT_NOTBASE);

	if (!base) base="";
	char oldbase[strlen(base)+1];
	strcpy(oldbase,base);

	updateObjectList(0);

	struct node *n=allObjects->head;
	base=oldbase;
	int blen=strlen(base);

	char *c;
	while (n && n->next)
	{
		c=((pXclasses*)n->text)->o->FullName();
		if (c && strcmp(splitName(c,1),INTERNALOBJS) && !strncmp(base,c,blen) && (c[blen]=='.' || (dobase && c[blen]==0)))
		{
			goodCache=1;	// hack to be mostly reentrant
			if (OBJDEBUGMODE)
				fprintf(stderr,"Delete object %s\n",((pXclasses*)n->text)->pathbuf);
			delete ((pXclasses*)n->text)->o;
			n=allObjects->head;
		}
		else
		{
			n=n->next;
		}
	}
}

char *TranslateName(char *name)
{
	register char *c=name;
	while (*c)
	{
		if (!isalnum(*c))
		{
			*c='_';
		}
		c++;
	}
	return name;
}
#endif
