/***************************************************************************
 *
 * resman.c
 *
 ***************************************************************************
 *
 * run-time resource manager
 *
 ***************************************************************************/

/*
 * includes
 */

#include "generic.h"

#include "resman.h"
#include "memory.h"
#include "file.h"

/*
 * defines
 */

#define SET_ALLOC_USER		1

#define FLIP(x) { int _i,_t; for(_i=0;_i<sizeof((x))/2;_i++) { \
		  _t=((unsigned char *)(&(x)))[_i]; \
		  ((unsigned char *)(&(x)))[_i]=((unsigned char *)(&(x)))[sizeof((x))-_i-1]; \
		  ((unsigned char *)(&(x)))[sizeof((x))-_i-1]=_t; }}

#define TAG_BITS		2
#define TAG_MASK		((1<<TAG_BITS)-1)

#define TAG_BIT_PENDING_LOAD	1
#define TAG_BIT_USER		2

#define MAX_CONSTRUCTORS        16

/*
 * typedefs
 */

/*
 * structures
 */

struct cons
{
        char name[4];
        void (*function)(unsigned char **);
};

struct tag
{
        struct tag *next;
        char *name;
        unsigned long value;
        unsigned long offset;
};


/*
 * globals
 */

void (*resmanPoll)(void)=NULL;

int hiEndian=0;
int consValid=0;
struct cons constructors[MAX_CONSTRUCTORS];
unsigned char *resTmpBuffer;

/*
 * function prototypes
 */

unsigned char *ResOpenID(struct openSet *p,int id);

/*
 * code
 */

/***************************************************************************
 *
 * int ResHashName(char *name)
 *
 ***************************************************************************
 *
 * Generate a hash value for a tag name
 *
 ***************************************************************************/

int ResHashName(char *name)
{
        int i,j;

        for(i=0,j=0;i<strlen(name) && i<16;i++)
        {
                j+=toupper(name[i]);
        }
        return(j&0xFF);
}


/***************************************************************************
 *
 * void ResOpenSet(char *setName,struct openSet **pp)
 *
 ***************************************************************************
 *
 * Open a resource set by reading the .DIR and .TAG files
 *
 ***************************************************************************/

int ResOpenSet(char *setName,struct openSet **pp)
{
        char tmp[128];
        char name[16];
        FH stream;
        unsigned long q;
        int i,j;
        struct tag *t;
        struct openSet *p;
	int resCount;

	/*
	 * do endian check
	 */

	i=0;
	*((unsigned char *)(&i))=0xFF;
	if(i==0x000000FF) hiEndian=0;
	else if(i==0xFF000000) hiEndian=1;
	else
	{
		printf("I'm so confused\n");
		exit(1);
	}

        /*
         * open directory file
         */

        sprintf(tmp,"%s.DIR",setName);
        stream=FileOpen(tmp,"rb");
        if(!stream)
        {
                return(1);
        }

        /*
         * determine file length, and hence, number of resources
         */

        FileSeek(stream,0,SEEK_END); q=FileTell(stream); FileSeek(stream,0,SEEK_SET);
	resCount=q/4;

        /*
         * allocate resource set
         */

	if(!resTmpBuffer)
	{
        	p=(struct openSet *)MemMalloc(sizeof(struct openSet)+resCount*3*4,MEM_ANY);
        	if(!p)
        	{
                	return(2);
        	}
		p->flags=0;
	}
	else
	{
		p=(struct openSet *)resTmpBuffer;
		p->flags=SET_ALLOC_USER;
		resTmpBuffer=NULL;
	}

        ResInit(p);


        p->numResources=q/4;
        if(!p->numResources)
        {
                return(2);
        }

        /*
         * allocate and read the resource table
         */

        p->resourceHandles=(unsigned char **)(((unsigned char *)p)+sizeof(struct openSet));
        memset(p->resourceHandles,0,q);

        p->resOffsets=(unsigned long *)(((unsigned char *)p)+sizeof(struct openSet)+(resCount*4*2));
        FileRead(p->resOffsets,1,q,stream);

	if(hiEndian) for(i=0;i<p->numResources;i++) FLIP(p->resOffsets[i]);

        FileClose(stream);

        /*
         * now read the tag database
         */
#if 0
        sprintf(tmp,"%s.TAG",setName);
        stream=FileOpen(tmp,"rb");
        if(!stream)
        {
                MemFree(p->resOffsets);
                return(4);
        }

        /*
         * skip header
         */

        FileSeek(stream,1024,SEEK_SET);

        while(1)
        {
                FileRead(name,1,16,stream);
                if(FileEof(stream)) break;
                i=ResHashName(name);
                t=(struct tag *)MemMalloc(sizeof(struct tag),MEM_ANY);
                if(!t)
                {
                        FileClose(stream);
			MemFree(p->resOffsets);
                        return(5);
                }
                j=strlen(name);
                if(j>16) j=16;
                t->name=(char *)MemMalloc(j+1,MEM_ANY);
                if(!t->name)
                {
                        FileClose(stream);
                        MemFree(p->resOffsets);
                        return(6);
                }
                strncpy(t->name,name,16);
                FileRead(&t->offset,1,4,stream);
		if(hiEndian) FLIP(t->offset);
                FileRead(&t->value,1,4,stream);
		if(hiEndian) FLIP(t->value);

                t->next=p->tagList[i];
                p->tagList[i]=t;
        }

        FileClose(stream);
#endif

        strcpy(p->currentSetName,setName);
        sprintf(p->currentSetDataName,"%s.BIG",setName);

        *pp=p;

        return(0);
}


/***************************************************************************
 *
 * void ResInit(struct openSet *p)
 *
 ***************************************************************************
 *
 * Initialise the resource manager
 *
 ***************************************************************************/

void ResInit(struct openSet *p)
{
        int i;

        p->resLevel=0;
        p->resOffsets=NULL;
        p->numResources=0;
        for(i=0;i<256;i++)
        {
                p->tagList[i]=NULL;
        }
        p->resourceHandles=NULL;
	p->romOffset=0;		// invalidate
}


/***************************************************************************
 *
 * void ResCloseSet(void)
 *
 ***************************************************************************
 *
 * Free up all used memory
 *
 ***************************************************************************/

void ResCloseSet(struct openSet *p)
{
        int i;
        struct tag *t,*t2;

        for(i=0;i<p->numResources;i++)
        {
                ResPurgeID(p,i);
        }

        for(i=0;i<256;i++)
        {
                for(t=p->tagList[i];t!=NULL;)
                {
                        t2=t->next;
                        MemFree(t->name);
                        MemFree(t);
                        t=t2;
                }
        }

        if(!(p->flags & SET_ALLOC_USER))
	{
		MemFree(p);
	}
}


/***************************************************************************
 *
 * void ResPurge(unsigned char **handle)
 *
 ***************************************************************************
 *
 * Free the resource associated with the given handle
 *
 ***************************************************************************/

void ResPurge(unsigned char **handle)
{
        if(*handle)
        {
		if(!((unsigned long)(*handle) & TAG_BIT_USER))
		{
	                MemFree(*handle);
		}
                *handle=NULL;
        }
}


/***************************************************************************
 *
 * unsigned char *ResOpenName(struct openSet *p,char *name,int *id)
 *
 ***************************************************************************
 *
 * Open a resource by name
 *
 ***************************************************************************/

unsigned char *ResOpenName(struct openSet *p,char *name,int *id)
{
        struct tag *t;
        int h;
        unsigned char *q;

        h=ResHashName(name);
        for(t=p->tagList[h];t!=NULL;t=t->next)
        {
                if(!stricmp(t->name,name))
                {
                        break;
                }
        }
        if(!t)
        {
                printf("NO RESOURCE %s\n",name);
                exit(1);
                return(NULL);
        }

        if(id)
        {
                *id=t->value;
        }

        q=ResOpenID(p,t->value);
        return(q);
}


/***************************************************************************
 *
 * unsigned char *ResOpenID(struct openSet *p,int id)
 *
 ***************************************************************************
 *
 * Open the resource by ID
 *
 ***************************************************************************/

unsigned char *ResOpenID(struct openSet *p,int id)
{
        FH stream;
        unsigned long q,w,i,j;
        unsigned char *d;
        unsigned char pref[4];
	int	tmpLoad;

	if(resmanPoll) resmanPoll();

        /*
         * see if resource is already open
         */

        if(!(((unsigned long)p->resourceHandles[id]) & TAG_BIT_PENDING_LOAD) &&
           ((unsigned long)p->resourceHandles[id])!=0)
        {
                return(p->resourceHandles[id]);
        }

        p->resLevel++;

        /*
         * open data file
         */

        stream=FileOpen(p->currentSetDataName,"rb");
        if(!stream)
        {
                p->resLevel--;
                return(NULL);
        }

        FileSeek(stream,p->resOffsets[id],SEEK_SET);
        FileRead(pref,1,4,stream);

        FileRead(&q,1,4,stream);
	if(hiEndian) FLIP(q);

	if(resTmpBuffer)
	{
		d=resTmpBuffer;
		tmpLoad=1;
		resTmpBuffer=NULL;
	}
	else
	{
	        d=(unsigned char *)MemMalloc(q,MEM_ANY);
		tmpLoad=0;
	}

	if((unsigned long)d & TAG_MASK)
	{
		printf("Unaligned malloc: %p\n",d);
		FileClose(stream);
		return(NULL);
	}
        if(!d)
        {
                p->resLevel--;
                FileClose(stream);
                return(NULL);
        }

        /*
         * read data
         */

        w=FileRead(d,1,q,stream);
        if(w!=q)
        {
                MemFree(d);
                FileClose(stream);
                p->resLevel--;
                return(NULL);
        }

        /*
         * fix up tags
         */

        FileRead(&q,1,4,stream);
	if(hiEndian) FLIP(q);
        for(i=0;i<q;i++)
        {
                FileRead(&w,1,4,stream);
		if(hiEndian) FLIP(w);

                j=*((unsigned long *)(&d[w]));
		if(hiEndian) FLIP(j);

                *((unsigned char ***)(&d[w]))=&p->resourceHandles[j];

                if(!p->resourceHandles[j])
                {
                        p->resourceHandles[j]=(unsigned char *)(TAG_BIT_PENDING_LOAD | (p->resLevel<<TAG_BITS));
                }
        }

	if(tmpLoad)	p->resourceHandles[id]=(unsigned char *)((unsigned long)d | TAG_BIT_USER);
        else		p->resourceHandles[id]=d;

        FileClose(stream);


        /*
         * load all tags that require loading...
         */

        {
                for(i=0;i<p->numResources;i++)
                {
                        if(p->resourceHandles[i]==(unsigned char *)(TAG_BIT_PENDING_LOAD | (p->resLevel<<TAG_BITS)))
                        {
                                ResOpenID(p,i);
                                i=-1;
                        }
                }
        }

        /*
         * call constructor if there is one
         */

        for(i=0;i<MAX_CONSTRUCTORS;i++)
        {
                if(toupper(constructors[i].name[0])==toupper(pref[0]) &&
                   toupper(constructors[i].name[1])==toupper(pref[1]) &&
                   toupper(constructors[i].name[2])==toupper(pref[2]))
                {
                        constructors[i].function(&p->resourceHandles[id]);
                        break;
                }
        }

        d=p->resourceHandles[id];

        p->resLevel--;

        return(d);
}


/***************************************************************************
 *
 * long ResGetSize(struct openSet *p,int id)
 *
 ***************************************************************************
 *
 * Open the resource by ID
 *
 ***************************************************************************/

long ResGetSize(struct openSet *p,int id)
{
        FH stream;
        unsigned long q;
        unsigned char pref[4];

        /*
         * open data file
         */

        stream=FileOpen(p->currentSetDataName,"rb");
	if(!stream) return(-1);

        FileSeek(stream,p->resOffsets[id],SEEK_SET);
        FileRead(pref,1,4,stream);

        FileRead(&q,1,4,stream);
	if(hiEndian) FLIP(q);

	FileClose(stream);

	return(q);
}

/***************************************************************************
 *
 * void ResSetConstructor(char *pref,void (*construct)(unsigned char **))
 *
 ***************************************************************************
 *
 * Set a constructor for a particular 3-letter resource prefix
 *
 ***************************************************************************/

void ResSetConstructor(char *pref,void (*construct)(unsigned char **))
{
        int i;

        if(!consValid)
        {
                memset(constructors,0,sizeof(constructors));
                consValid=1;
        }

        for(i=0;i<MAX_CONSTRUCTORS;i++)
        {
                if(toupper(constructors[i].name[0])==toupper(pref[0]) &&
                   toupper(constructors[i].name[1])==toupper(pref[1]) &&
                   toupper(constructors[i].name[2])==toupper(pref[2]))
                {
                        break;
                }
        }
        if(i==MAX_CONSTRUCTORS)
        {
                for(i=0;i<MAX_CONSTRUCTORS;i++)
                {
                        if(!constructors[i].function) break;
                }
                if(i==MAX_CONSTRUCTORS)
                {
                        printf("too many constructors\n");
                        exit(1);
                }
        }

        memcpy(constructors[i].name,pref,3);
        constructors[i].function=construct;
}

/***************************************************************************
 *
 * int ResLookupTag(struct openSet *p,char *tag)
 *
 ***************************************************************************
 *
 * Read the GID file to find the given tag
 *
 ***************************************************************************/

int ResLookupTag(struct openSet *p,char *tag)
{
	char tmp[80];
	FH stream;
	int i,j;

	sprintf(tmp,"%s.gid",p->currentSetName);
	stream=FileOpen(tmp,"rb");
	if(!stream) return(-1);
	FileRead(&j,1,4,stream);
	if(hiEndian) FLIP(j);

	for(i=0;i<j;i++)
	{
		FileRead(tmp,1,16,stream);
		if(!stricmp(tag,tmp))
		{
			FileClose(stream);
			return(i);
		}
	}
	FileClose(stream);
	return(-1);
}


/***************************************************************************
 *
 *
 *
 ***************************************************************************
 *
 *
 *
 ***************************************************************************/

void ResSetTmpBuffer(unsigned char *tmp)
{
	resTmpBuffer=tmp;
}

/***************************************************************************
 *
 *
 *
 ***************************************************************************
 *
 *
 *
 ***************************************************************************/

void ResPurgeAll(struct openSet *s)
{
	int i;

        for(i=0;i<s->numResources;i++)
        {
                ResPurgeID(s,i);
        }
}

/***************************************************************************
 *
 *
 *
 ***************************************************************************
 *
 *
 *
 ***************************************************************************/


/***************************************************************************
 *
 *
 *
 ***************************************************************************
 *
 *
 *
 ***************************************************************************/

