
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <zlib.h>

#include <StorageDefs.h>

#ifdef BSD43 /* old style directory handling */
#include <sys/types.h>
#include <sys/dir.h>
#define dirent direct
#endif

#include "osdepend.h"
 
int load_zipped_file (const char *zipfile,const char *filename, unsigned char **buf, unsigned int *length);
int checksum_zipped_file (const char *zipfile, const char *filename, unsigned int *length, unsigned int *sum);


#define FALSE	0
#define	TRUE	1

#define MAXPATHC 20 /* at most 20 path entries */
#define MAXPATHL	B_PATH_NAME_LENGTH

char buf1[MAXPATHL];
char buf2[MAXPATHL];


typedef enum
{
	kPlainFile,
	kRamFile
} eFileType;

typedef struct
{
	FILE		*file;
	unsigned char	*data;
	unsigned int	offset;
	unsigned int	length;
	unsigned int	crc;
 	eFileType	type;
} FakeFileHandle;

static char *rompathv[MAXPATHC];
static int   rompathc = 0;
char *samplepathv[MAXPATHC];
int samplepathc;

char *cfgdir, *hidir, *inpdir, *stadir, *memcarddir, *artworkdir, *screenshotdir;


char *alternate_name; /* for "-romdir" */


#define DEBUG(x)//	{ x }


/* unix helper functions */

///* helper function which decomposes a path list into a vector of paths */
//void init_rom_path(void)
//{
//	char *token;
//	/* has to be static since ptr's to it are kept after this function */
//	static char buf[MAXPATHL];
//#ifdef MESS
//	/* mess also searches the current dir and to allow absolute paths
//	   also the root dir */
//	static char *current_dir = ".";
//	static char    *root_dir = "/";
//	rompathv[0] = current_dir;
//	rompathv[1] = root_dir;
//	rompathc    = 2;
//#endif
//	
//	/* make a copy since strtok modifies rompath */
//	strncpy(buf, rompathv, MAXPATHL);
//	token = strtok(buf, ":");
//	while ((rompathc < MAXPATHC) && token)
//	{
//		rompathv[rompathc] = token;
//		rompathc++;
//		token = strtok (NULL, ":");
//	}
//}




/*
 * Search file caseinsensitively
 *
 * Arguments: 
 *	char * path - complete pathname to the desired file. The string will
 *	              be modified during search (and contains the final output).
 *
 * Return TRUE if found, FALSE otherwise.
 */
static int filesearch(char *path)
{
    DIR *dirp;
    struct dirent *de = NULL;
    char *ep, *dp, *fp;

    ep = strrchr(path, '/');
    if (ep) {
		*ep = '\0';	/* I guess root directory is not supported */
		dp = path;
		fp = ep + 1;
    } else {
		dp = "."; /* well, what should be the correct name for "current dir" */
		fp = path;
    }

    if (*fp == '\0') {
		return FALSE;
    }

    /* jamc: code to perform a upper/lowercase rom search */
    /* try to open directory */
DEBUG(printf("filesearch() - opening dir \"%s\"\n", dp);)
    if ((dirp = opendir(dp)) == (DIR *) 0) { 
		return FALSE;
    }

    /* search entry and upcasecompare to desired file name */
    for (de = readdir(dirp); de; de = readdir(dirp)) {
		if (!strcasecmp(de->d_name, fp))
			break;
	}

	if (de) strcpy(fp, de->d_name);
    closedir(dirp);
    
    if (ep) *ep = '/';

    if (de) return TRUE;
    return FALSE;
}

#define GZIP_BLOCK_SIZE 8192




void load_gzipped_file (FakeFileHandle *f)
{
    int read;
    
    f->data   = malloc(GZIP_BLOCK_SIZE);
    if (!f->data) goto gzip_error;
    
    while ((read = gzread(f->file, f->data+f->length, GZIP_BLOCK_SIZE)) == GZIP_BLOCK_SIZE)
    {
        f->length += GZIP_BLOCK_SIZE;
        f->data = realloc(f->data, f->length + GZIP_BLOCK_SIZE);
        if (!f->data) goto gzip_error;
    }
    if (read == -1) goto gzip_error;
    f->length += read;
    f->crc  = crc32(0L, f->data, f->length);
    f->type = kRamFile;
    gzclose(f->file);
    return;
    
gzip_error:
    if (f->data) free(f->data);
    gzclose(f->file);
    f->file=0;
    return;
}





/* osd functions */

/*
 * check if roms/samples for a game exist at all
 * return 1 on success, otherwise 0
 */
int osd_faccess(const char *filename, int filetype)
{
	char name[MAXPATHL];
	int i;
	
	switch (filetype)
	{
		case OSD_FILETYPE_ROM:
		case OSD_FILETYPE_SAMPLE:
		    for(i=0;i<rompathc;i++)
		    {
			sprintf(name,"%s/%s",rompathv[i],filename);
			if (access(name, F_OK) == 0) return 1;
			
			/* try with a .zip extension */
			if  (filetype == OSD_FILETYPE_ROM)
				sprintf(name,"%s/%s/%s.zip",rompathv[i],"roms", filename);
			else
				sprintf(name,"%s/%s/%s.zip",rompathv[i],"samples", filename);
			if (access(name, F_OK) == 0) return 1;
			
			
			/* try with a .gz extension */
			if  (filetype == OSD_FILETYPE_ROM)
				sprintf(name,"%s/%s/%s.gz",rompathv[i],"roms", filename);
			else
				sprintf(name,"%s/%s/%s.gz",rompathv[i],"samples", filename);
		    }
			if (access(name, F_OK) == 0) return 1;
		    break;
		case OSD_FILETYPE_SCREENSHOT:
			printf("Sorry, not implemented\n");
		    /*sprintf(name,"%s/%s.png",home_dir ,filename);
		    if (access(name, F_OK) == 0) return 1;*/
		    break;
	}
	
	return 0;
}





/*
 * file handling routines
 *
 * gamename holds the driver name, filename is only used for ROMs and samples.
 * if 'write' is not 0, the file is opened for write. Otherwise it is opened
 * for read.
 */
void *osd_fopen(const char *gamename, const char *filename, int filetype, 
 int write) {
	char name[256]; /* FIXME ?? possible buffer overrun */
	FakeFileHandle *f;
	int i;
#ifdef MESS
	char *pt;
#endif	

	f = (FakeFileHandle *)malloc(sizeof(FakeFileHandle));
	if (f == NULL) return f;
	
	memset(f,0,sizeof(FakeFileHandle));
	
	switch (filetype)
	{
#ifdef MESS
		case OSD_FILETYPE_ROM_CART:
		case OSD_FILETYPE_IMAGE:
#else
		case OSD_FILETYPE_ROM:
		case OSD_FILETYPE_SAMPLE:
#endif

		    if (write)
		    {
			/*if (errorlog)*/
			    fprintf(stderr, "Error trying to open rom/sample %s in write mode\n", filename);
			free(f); 
			return NULL;
		    }



		    for(i=0;i<rompathc;i++)
		    {
	DEBUG(printf("Trying ROM path \"%s\"\n", rompathv[i]);)
			    sprintf(name,"%s/%s/%s",rompathv[i],gamename,filename);
			    if (filesearch(name)) {
			    	f->file = fopen(name, "rb");
				} else {
	DEBUG(printf("Couldn't open %s/%s\n", gamename, filename););
				}
				if (f->file)
				{
				    /* make it a ram-file we need to load it anyway,
				       to get the crc */
				    f->length=osd_fsize(f);
				    if ( (f->data=malloc(f->length)) == NULL)
				    {
					free(f);
					return NULL;
				    }
				    if ( (fread(f->data, 1, f->length, f->file)) != f->length)
				    {
					free(f->data);
					free(f);
					return NULL;
				    }
				    f->crc = crc32(0, f->data, f->length);
				    f->type = kRamFile;
				    fclose(f->file);
				    return f;
				}

				/* try with a .zip extension */
				if  (filetype == OSD_FILETYPE_ROM)
					sprintf(name,"%s/%s/%s.zip",rompathv[i],"roms", gamename);
				else
					sprintf(name,"%s/%s/%s.zip",rompathv[i],"samples", gamename);
				f->file = fopen(name, "rb");

				if (f->file)
				{
					/*if (errorlog)
						fprintf(stderr, "using zip file for %s\n", filename);*/
					fclose(f->file);
					if (load_zipped_file(name, filename, &f->data, &f->length) == 0)
					{
	DEBUG(printf("Found %s in zip file\n", filename);)
						f->crc  = crc32(0L, f->data, f->length);
						f->type = kRamFile;
						return f;
					}
					else
						f->file   = 0;
				}
			
				/* try with a .gz extension */
			    sprintf(name,"%s/%s/%s.gz",rompathv[i],gamename,filename);
			    f->file = gzopen(name, "rb");

				if (f->file) load_gzipped_file(f);
				if (f->file) return f;
		    }
	DEBUG(printf("Ack, couldn't find it!\n");)
		    break;

		case OSD_FILETYPE_HIGHSCORE:
		    if (mame_highscore_enabled())
		    {
			sprintf(name,"%s/%s.hi", hidir, gamename);
			f->file = fopen(name,write ? "wb" : "rb");
		    }
		    break;

		case OSD_FILETYPE_CONFIG:
		    sprintf(name, "%s/%s.cfg", cfgdir, gamename);
			printf("Save config as %s\n", name);
		    f->file = fopen(name,write ? "wb" : "rb");
		    break;

		case OSD_FILETYPE_INPUTLOG:
		    f->file = fopen(filename,write ? "wb" : "rb");
		    break;

		case OSD_FILETYPE_STATE:
		    sprintf(name, "%s/%s.sta", stadir, /*NAME*/ gamename);
		    f->file = fopen(name,write ? "wb" : "rb");
		    break;

		case OSD_FILETYPE_ARTWORK:
		    /* only for reading */
		    if (write) break;
		    for(i=0;i<rompathc;i++)
		    {
			sprintf(name,"%s/artwork/%s",rompathv[i],filename);
			if (filesearch(name))
			{
				f->file = fopen(name, "rb");
				if (f->file) return f;
			}
		    }
		    break;

		case OSD_FILETYPE_MEMCARD:
		    sprintf(name, "%s/%s.mem", memcarddir, filename);
		    f->file = fopen(name,write ? "wb" : "rb");
		    break;

		case OSD_FILETYPE_SCREENSHOT:
		    /* only for writing */
		    if (!write) break;
		    
		    sprintf(name,"%s/%s.png", screenshotdir, filename);
		    f->file = fopen(name, "wb");
		    break;
	}

	if (f->file == NULL)
	{
		free(f); 
		return NULL;
	}

	return f;
}





int osd_fread(void *file,void *buffer,int length)
{
	FakeFileHandle *f = (FakeFileHandle *)file;

	switch (f->type)
	{
		case kPlainFile:
			return fread(buffer,1,length,f->file);
			break;
		case kRamFile:
			/* reading from the uncompressed image of a zipped file */
			if (f->data)
			{
				if (length + f->offset > f->length)
					length = f->length - f->offset;
				memcpy(buffer, f->offset + f->data, length);
				f->offset += length;
				return length;
			}
			break;
	}

	return 0;
}




int osd_fread_swap(void *file,void *buffer,int length)
{
	int i;
	unsigned char *buf;
	unsigned char temp;
	int res;

	res = osd_fread(file,buffer,length);

	buf = buffer;
	for (i = 0;i < length;i+=2)
	{
		temp = buf[i];
		buf[i] = buf[i+1];
		buf[i+1] = temp;
	}

	return res;
}




int osd_fread_scatter(void *file,void *buffer,int length,int increment)
{
	unsigned char *buf = buffer;
	FakeFileHandle *f = (FakeFileHandle *)file;
	unsigned char tempbuf[4096];
	int totread,r,i;

	switch (f->type)
	{
		case kPlainFile:
			totread = 0;
			while (length)
			{
				r = length;
				if (r > 4096) r = 4096;
				r = fread(tempbuf,1,r,f->file);
				if (r == 0) return totread;	/* error */
				for (i = 0;i < r;i++)
				{
					*buf = tempbuf[i];
					buf += increment;
				}
				totread += r;
				length -= r;
			}
			return totread;
			break;
		case kRamFile:
			/* reading from the RAM image of a file */
			if (f->data)
			{
				if (length + f->offset > f->length)
					length = f->length - f->offset;
				for (i = 0;i < length;i++)
				{
					*buf = f->data[f->offset + i];
					buf += increment;
				}
				f->offset += length;
				return length;
			}
			break;
	}

	return 0;
}




int osd_fwrite(void *file,const void *buffer,int length)
{
	FakeFileHandle *f = (FakeFileHandle *)file;

	switch (f->type)
	{
		case kPlainFile:
			return fwrite(buffer,1,length,f->file);
		default:
			return -1; /* note dos returns 0, but this is incorrect */
	}
}




int osd_fwrite_swap(void *file,const void *buffer,int length)
{
	int i;
	unsigned char *buf;
	unsigned char temp;
	int res;

	buf = (unsigned char *)buffer;
	for (i = 0;i < length;i+=2)
	{
		temp = buf[i];
		buf[i] = buf[i+1];
		buf[i+1] = temp;
	}

	res = osd_fwrite(file,buffer,length);

	for (i = 0;i < length;i+=2)
	{
		temp = buf[i];
		buf[i] = buf[i+1];
		buf[i+1] = temp;
	}

	return res;
}




int osd_fseek(void *file,int offset,int whence)
{
	FakeFileHandle *f = (FakeFileHandle *)file;

	switch (f->type)
	{
		case kPlainFile:
			return fseek(((FakeFileHandle *)file)->file,offset,whence);
			break;
		case kRamFile:
			/* seeking within the uncompressed image of a zipped file */
			switch (whence)
			{
				case SEEK_SET:
					f->offset = offset;
					return 0;
					break;
				case SEEK_CUR:
					f->offset += offset;
					return 0;
					break;
				case SEEK_END:
					f->offset = f->length + offset;
					return 0;
					break;
			}
			break;
	}

	return -1;
}



void osd_fclose(void *file)
{
	FakeFileHandle *f = (FakeFileHandle *) file;

	switch(f->type)
	{
		case kPlainFile:
			fclose(f->file);
			break;
		case kRamFile:
			if (f->data)
				free(f->data);
			break;
	}
	free(f);
}




int osd_fsize(void *file)
{
	int position, end;
	FakeFileHandle *f = (FakeFileHandle *) file;

	switch(f->type)
	{
		case kPlainFile:
			position = ftell(f->file);
			fseek(f->file, 0, SEEK_END);
			end = ftell(f->file);
			fseek(f->file, position, SEEK_SET);
			return end;
			break;
		case kRamFile:
			return f->length;
			break;
	}
	
	return 0;
}




unsigned int osd_fcrc (void *file)
{
	FakeFileHandle *f = (FakeFileHandle *)file;
	/* only supported for ramfiles */
	if (f->type == kRamFile) return f->crc;
	return 0;
}





int osd_fchecksum (const char *game, const char *filename, unsigned int *length, unsigned int *sum)
{
  char name[256];
  FakeFileHandle *f;
  int i;
  
  for(i=0;i<rompathc;i++)
  {
    sprintf(name,"%s/%s/%s.zip",rompathv[i],"roms", game);

    if (access(name, R_OK)==0)
    {
      if (checksum_zipped_file(name, filename, length, sum) == 0)
        return 0;
    }
  }

  f = osd_fopen(game, filename, OSD_FILETYPE_ROM, 0);
  if (f==NULL) return -1;
  *sum    = osd_fcrc(f);
  *length = osd_fsize(f);
  osd_fclose(f);
  return 0;
}




/* called while loading ROMs. It is called a last time with name == 0 to signal */
/* that the ROM loading process is finished. */
/* return non-zero to abort loading */
int osd_display_loading_rom_message(const char *name,int current,int total)
{
	if (name)
		fprintf(stdout,"loading %-12s\r",name);
	else
		fprintf(stdout,"                    \r");
	fflush(stdout);

	return 0;
}




/* helper function which decomposes a path list into a vector of paths */
int path2vector (char *path, char *buf, char **pathv)
{
	int i;
	char *token;

	/* copy the path variable, cause strtok will modify it */
	strncpy (buf, path, MAXPATHL-1);
	i = 0;
	token = strtok (buf, ";");
	while ((i < MAXPATHC) && token)
	{
		pathv[i] = token;
		i++;
		token = strtok (NULL, ";");
	}
	return i;
}




void decompose_rom_sample_path (char *rompath, char *samplepath)
{
	rompathc    = path2vector (rompath,    buf1, rompathv);
	samplepathc = path2vector (samplepath, buf2, samplepathv);

#ifdef FILE_CACHE
	/* AM 980919 */
	if (file_cache_max == 0) {
		/* (rom path directories + 1 buffer)==rompathc+1 */
		/* (dir + .zip + .zif)==3 */
		/* (clone+parent)==2 */
		cache_allocate((rompathc+1)*3*2);
	}
#endif

}










