/* Mednafen - Multi-system Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2002 Xodnizel
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

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

#include <zlib.h>
#include "unzip.h"

#include "types.h"
#include "mednafen.h"

#include "file.h"
#include "endian.h"
#include "memory.h"
#include "general.h"

void ApplyIPS(FILE *ips, MDFNFILE *dest, const char *path)
{
 uint8 header[5];
 uint32 count=0;
 
 MDFN_printf(_("Applying IPS file \"%s\"...\n"), path);
 MDFN_indent(1);
 if(fread(header,1,5,ips) != 5 || memcmp(header, "PATCH", 5))
 {
  fclose(ips);
  MDFN_printf("Not a valid IPS file!");
  MDFN_indent(-1);
  return;
 }

 while(fread(header,1,3,ips)==3)
 {
  uint32 offset=(header[0]<<16)|(header[1]<<8)|header[2];
  uint16 size;

  if(!memcmp(header,"EOF",3))
  {
   MDFN_printf(_("IPS EOF:  Did %d patches\n\n"),count);
   MDFN_indent(-1);
   fclose(ips);
   return;
  }

  size=fgetc(ips)<<8;
  size|=fgetc(ips);
  if(!size)	/* RLE */
  {
   uint8 *start;
   uint8 b;
   size=fgetc(ips)<<8;
   size|=fgetc(ips);

   //MDFN_printf("  Offset: %8d  Size: %5d RLE\n",offset,size);

   if((offset+size)>dest->size)
   {
    uint8 *tmp;

    // Probably a little slow.
    tmp=(uint8 *)realloc(dest->data,offset+size);
    if(!tmp)
    {
     MDFN_printf("%m\n", errno);
     fclose(ips);
     return;
    }
    dest->size=offset+size;
    dest->data=tmp;
    memset(dest->data+dest->size,0,offset+size-dest->size);
   }
   b=fgetc(ips);
   start=dest->data+offset;
   do
   {
    *start=b;
    start++;
   } while(--size);
  }
  else		/* Normal patch */
  {
   //MDFN_printf("  Offset: %8d  Size: %5d\n",offset,size);
   if((offset+size)>dest->size)
   {
    uint8 *tmp;
    
    // Probably a little slow.
    tmp=(uint8 *)realloc(dest->data,offset+size);
    if(!tmp)
    {
     MDFN_printf("%m\n", errno);
     fclose(ips);
     MDFN_indent(-1);
     return;
    }
    dest->data=tmp;
    memset(dest->data+dest->size,0,offset+size-dest->size);
   }
   fread(dest->data+offset,1,size,ips);
  }
  count++;
 }
 fclose(ips);
 MDFN_printf(_("Warning:  IPS ended without an EOF chunk.\n"));
 MDFN_indent(1);
}

static MDFNFILE *MakeMemWrap(void *tz, int type)
{
 MDFNFILE *tmp;

 if(!(tmp=(MDFNFILE *)MDFN_malloc(sizeof(MDFNFILE))))
  goto doret;
 tmp->location=0;

 if(type==0)
 {
  fseek((FILE *)tz,0,SEEK_END);
  tmp->size=ftell((FILE *)tz);
  fseek((FILE *)tz,0,SEEK_SET);
  if(!(tmp->data=(uint8*)MDFN_malloc(tmp->size)))
  {
   free(tmp);
   tmp=0;
   goto doret;
  }
  fread(tmp->data,1,tmp->size,(FILE *)tz);
 }
 else if(type==1)
 {
  /* Bleck.  The gzip file format has the size of the uncompressed data,
     but I can't get to the info with the zlib interface(?). */
  for(tmp->size=0; gzgetc(tz) != EOF; tmp->size++);
  gzseek(tz,0,SEEK_SET);
  if(!(tmp->data=(uint8 *)MDFN_malloc(tmp->size)))
  {
   free(tmp);
   tmp=0;
   goto doret;
  }
  gzread(tz,tmp->data,tmp->size);
 }
 else if(type==2)
 {
  unz_file_info ufo; 
  unzGetCurrentFileInfo(tz,&ufo,0,0,0,0,0,0);  

  tmp->size=ufo.uncompressed_size;
  if(!(tmp->data=(uint8 *)MDFN_malloc(ufo.uncompressed_size)))
  {
   free(tmp);
   tmp=0;
   goto doret;
  }
  unzReadCurrentFile(tz,tmp->data,ufo.uncompressed_size);
 }

 doret:
 if(type==0)
 {
  fclose((FILE *)tz);
 }
 else if(type==1)
 {
  gzclose(tz);
 }
 else if(type==2)
 {
  unzCloseCurrentFile(tz);
  unzClose(tz);
 }
 return tmp;
}

#ifndef __GNUC__
 #define strcasecmp strcmp
#endif


MDFNFILE * MDFN_fopen(const char *path, const char *ipsfn, char *mode, char *ext)
{
 FILE *ipsfile=0;
 MDFNFILE *fceufp = 0;
 void *t;

 if(strchr(mode,'r') && ipsfn)
  ipsfile=fopen(ipsfn,"rb");

 {
  unzFile tz;
  if((tz=unzOpen(path)))  // If it's not a zip file, use regular file handlers.
			  // Assuming file type by extension usually works,
			  // but I don't like it. :)
  {
   char tempu[1024];

   if(unzGoToFirstFile(tz)==UNZ_OK)
   {
    for(;;)
    {
     unzGetCurrentFileInfo(tz,0,tempu,1024,0,0,0,0);
     tempu[1023]=0;
     if(strlen(tempu)>=4)
     {
      char *za=tempu+strlen(tempu)-4;
      
      if(!ext)
      {
       if(!strcasecmp(za,".nes") || !strcasecmp(za,".fds") ||
          !strcasecmp(za,".nsf") || !strcasecmp(za,".unf") ||
          !strcasecmp(za,".nez") || !strcasecmp(za + 1, ".gb") ||
	  !strcasecmp(za, ".gbc") || !strcasecmp(za, ".gba") ||
	  !strcasecmp(za, ".agb") || !strcasecmp(za, ".cgb") || !strcasecmp(za, ".bin")
	  || !strcasecmp(za, ".pce") || !strcasecmp(za, ".hes") || !strcasecmp(za, ".ngp") || !strcasecmp(za, ".ngc"))
        break;
      }
      else if(!strcasecmp(za,ext)) 
       break;
     }
     if(strlen(tempu)>=5)
     {
      if(!strcasecmp(tempu+strlen(tempu)-5,".unif"))
       break;
     }
     if(unzGoToNextFile(tz)!=UNZ_OK)
     { 
      if(unzGoToFirstFile(tz)!=UNZ_OK) goto zpfail;
      break;     
     }
    }
    if(unzOpenCurrentFile(tz)!=UNZ_OK)
     goto zpfail;       
   }
   else
   {
    zpfail:
    free(fceufp);
    unzClose(tz);
    return 0;
   }
   if(!(fceufp=MakeMemWrap(tz,2)))
   {
    return(0);
   }
   char *ld = strrchr(tempu, '.');
   fceufp->ext = strdup(ld ? ld + 1 : "");
   if(ipsfile)
    ApplyIPS(ipsfile,fceufp, ipsfn);
   return(fceufp);
  }
 }

 if((t=fopen(path,"rb")))
 {
  uint32 magic;

  magic=fgetc((FILE *)t);
  magic|=fgetc((FILE *)t)<<8;
  magic|=fgetc((FILE *)t)<<16;

  if(magic!=0x088b1f)   /* Not gzip... */
   fclose((FILE *)t);
  else                  /* Probably gzip */
  {
   int fd;

   fd = dup(fileno( (FILE *)t));

   fclose((FILE *)t);

   lseek(fd, 0, SEEK_SET);

   if((t=gzdopen(fd,mode)))
   {
    fceufp = MakeMemWrap(t, 1);

    if(ipsfile)
     ApplyIPS(ipsfile, fceufp, ipsfn);

    char *tmp_path = strdup(path);
    char *ld = strrchr(tmp_path, '.');

    if(ld && ld > tmp_path)
    {
     char *last_ld = ld;
     *ld = 0;
     ld = strrchr(tmp_path, '.');
     if(!ld) { *last_ld = '.'; ld = last_ld; }
    }
	//printf("%s\n",ld);
    fceufp->ext = strdup(ld ? ld + 1 : "");
    free(tmp_path);
    return(fceufp);
   }
   close(fd);
  }
 }

 if((t=fopen(path,mode)))
 {
  fseek((FILE *)t,0,SEEK_SET);
 
  fceufp = MakeMemWrap(t, 0);

  if(ipsfile)
   ApplyIPS(ipsfile,fceufp, ipsfn);

  char *ld = strrchr(path, '.');
  fceufp->ext = strdup(ld ? ld + 1 : "");
  return(fceufp);
 }

 return 0;
}

int MDFN_fclose(MDFNFILE *fp)
{
 free(fp->ext);
 free(fp->data);
 free(fp);
 return 1;
}

uint64 MDFN_fread(void *ptr, size_t size, size_t nmemb, MDFNFILE *fp)
{
 uint32 total=size*nmemb;

 if(fp->location>=fp->size) return 0;

 if((fp->location+total)>fp->size)
 {
  int ak=fp->size-fp->location;
  memcpy((uint8*)ptr,fp->data+fp->location,ak);
  fp->location=fp->size;
  return(ak/size);
 }
 else
 {
  memcpy((uint8*)ptr,fp->data+fp->location,total);
  fp->location+=total;
  return nmemb;
 }
}

uint64 MDFN_fwrite(void *ptr, size_t size, size_t nmemb, MDFNFILE *fp)
{
 return(0); // TODO
}

int MDFN_fseek(MDFNFILE *fp, int64 offset, int whence)
{
  switch(whence)
  {
   case SEEK_SET:if(offset>=fp->size)
                  return(-1);
                 fp->location=offset;break;
   case SEEK_CUR:if(offset+fp->location>fp->size)
                  return (-1);
                 fp->location+=offset;
                 break;
  }    
  return 0;
}

uint64 MDFN_ftell(MDFNFILE *fp)
{
 return(fp->location);
}

void MDFN_rewind(MDFNFILE *fp)
{
 fp->location = 0;
}

int MDFN_read16le(uint16 *val, MDFNFILE *fp)
{
 uint8 t[2];

 if(fp->location+2>fp->size)
 {return 0;}
 *(uint32 *)t=*(uint32 *)(fp->data+fp->location);
 fp->location+=2;

 *val=t[0]|(t[1]<<8);
 return(1);
}

int MDFN_read32le(uint32 *Bufo, MDFNFILE *fp)
{
 uint8 *t;
 uint8 *t_buf = (uint8 *)Bufo;

 if(fp->location+4>fp->size)
 {
  return 0;
 }

 t=fp->data + fp->location;
 fp->location+=4;

 #ifndef LSB_FIRST
 t_buf[0]=t[3];
 t_buf[1]=t[2];
 t_buf[2]=t[1];
 t_buf[3]=t[0];
 #else
 t_buf[0] = t[0];
 t_buf[1] = t[1];
 t_buf[2] = t[2];
 t_buf[3] = t[3];
 #endif

 return 1;
}

char *MDFN_fgets(char *s, int size, MDFNFILE *fp)
{
 int pos = 0;

 if(!size) return(NULL);

 if(fp->location >= fp->size) return(NULL);

 while(pos < (size - 1) && fp->location < fp->size)
 {
  int v = fp->data[fp->location];
  s[pos] = v;
  fp->location++;
  pos++;
  if(v == '\n') break;
 }

 if(size)
  s[pos] = 0;
 return(s);
}

int MDFN_fgetc(MDFNFILE *fp)
{
 if(fp->location<fp->size)
  return fp->data[fp->location++];
 return EOF;
}

uint8 *MDFN_fgetmem(MDFNFILE *fp)
{
 return fp->data;
}

uint64 MDFN_fgetsize(MDFNFILE *fp)
{
 return fp->size;
}

int MDFN_fisarchive(MDFNFILE *fp)
{
 return 0;
}
