/*
Copyright (C) 1997-2008 ZSNES Team ( zsKnight, _Demo_, pagefault, Nach )

http://www.zsnes.com
http://sourceforge.net/projects/zsnes
https://zsnes.bountysource.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.

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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#ifdef __UNIXSDL__
#include "gblhdr.h"
#else
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#endif
#include "zpath.h"

#ifndef __GNUC__
#define strcasecmp stricmp
#define strncasecmp strnicmp
#endif

#define BUFFER_SIZE 2048

extern int maxromspace;
extern int curromspace;
extern int NumofBytes;
extern int NumofBanks;
extern unsigned int* romdata;
extern bool IPSPatched;
extern unsigned char Header512;
extern bool AutoPatch;

struct
{
    unsigned int file_size;
    unsigned char* data;
    unsigned char* current;
    unsigned int buffer_total;
    unsigned int proccessed;

    unzFile zipfile;
    FILE* fp;
} IPSPatch;

bool reloadBuffer()
{
    if (IPSPatch.proccessed == IPSPatch.file_size) {
        return (false);
    }

    IPSPatch.buffer_total = IPSPatch.fp ?
                                        /* Regular Files */ fread(IPSPatch.data, 1, BUFFER_SIZE, IPSPatch.fp)
                                        :
                                        /* Zip Files     */ (unsigned int)unzReadCurrentFile(IPSPatch.zipfile, IPSPatch.data, BUFFER_SIZE);

    IPSPatch.current = IPSPatch.data;
    if (IPSPatch.buffer_total && (IPSPatch.buffer_total <= BUFFER_SIZE)) {
        return (true);
    }

    IPSPatch.buffer_total = 0;
    return (false);
}

int IPSget()
{
    int retVal;
    if (IPSPatch.current == IPSPatch.data + IPSPatch.buffer_total) {
        if (!reloadBuffer()) {
            return (-1);
        }
    }
    IPSPatch.proccessed++;
    retVal = *IPSPatch.current;
    IPSPatch.current++;
    return (retVal);
}

bool initPatch(const char* ext)
{
    memset(&IPSPatch, 0, sizeof(IPSPatch));
    setextension(ZSaveName, ext);

    IPSPatch.fp = fopen_dir(ZIpsPath, ZSaveName, "rb");
    if (!IPSPatch.fp) {
        IPSPatch.fp = fopen_dir(ZRomPath, ZSaveName, "rb");
    }
    if (!IPSPatch.fp) {
        return (false);
    }

    fseek(IPSPatch.fp, 0, SEEK_END);
    IPSPatch.file_size = (unsigned int)ftell(IPSPatch.fp);
    rewind(IPSPatch.fp);

    if ((IPSPatch.data = (unsigned char*)malloc(BUFFER_SIZE))) {
        return (reloadBuffer());
    }
    return (false);
}

void deinitPatch()
{
    if (IPSPatch.data) {
        free(IPSPatch.data);
        IPSPatch.data = 0;
    }

    if (IPSPatch.fp) {
        fclose(IPSPatch.fp);
        IPSPatch.fp = 0;
    }

    if (IPSPatch.zipfile) {
        unzCloseCurrentFile(IPSPatch.zipfile);
        unzClose(IPSPatch.zipfile);
        IPSPatch.zipfile = 0;
    }
}

bool PatchUsingIPS(const char* ext)
{
    unsigned char* ROM = (unsigned char*)romdata;
    int location = 0, length = 0, last = 0;
    int sub = Header512 ? 512 : 0;

    if (!AutoPatch) {
        deinitPatch(); // Needed if the call to this function was done from findZipIPS()
        return (false);
    }

    if (!IPSPatch.zipfile) // Regular file, not Zip
    {
        if (!initPatch(ext)) {
            deinitPatch(); // Needed because if it didn't fully init, some things could have
            return (false);
        }
    }

    // Yup, it's goto! :)
    // See 'IPSDone:' for explanation
    if (IPSget() != 'P') {
        goto IPSDone;
    }
    if (IPSget() != 'A') {
        goto IPSDone;
    }
    if (IPSget() != 'T') {
        goto IPSDone;
    }
    if (IPSget() != 'C') {
        goto IPSDone;
    }
    if (IPSget() != 'H') {
        goto IPSDone;
    }

    while (IPSPatch.proccessed != IPSPatch.file_size) {
        // Location is a 3 byte value (max 16MB)
        int inloc = (IPSget() << 16) | (IPSget() << 8) | IPSget();

        if (inloc == 0x454f46) // EOF
        {
            break;
        }

        // Offset by size of ROM header
        location = inloc - sub;

        // Length is a 2 byte value (max 64KB)
        length = (IPSget() << 8) | IPSget();

        if (length) // Not RLE
        {
            int i;
            for (i = 0; i < length; i++, location++) {
                if (location >= 0) {
                    if (location >= maxromspace) {
                        goto IPSDone;
                    }
                    ROM[location] = (unsigned char)IPSget();
                    if (location > last) {
                        last = location;
                    }
                } else {
                    IPSget(); // Need to skip the bytes that write to header
                }
            }
        } else // RLE
        {
            int i;
            unsigned char newVal;
            length = (IPSget() << 8) | IPSget();
            newVal = (unsigned char)IPSget();
            for (i = 0; i < length; i++, location++) {
                if (location >= 0) {
                    if (location >= maxromspace) {
                        goto IPSDone;
                    }
                    ROM[location] = newVal;
                    if (location > last) {
                        last = location;
                    }
                }
            }
        }
    }

// We use gotos to break out of the nested loops,
// as well as a simple way to check for 'PATCH' in
// some cases like this one, goto is the way to go.
IPSDone:

    deinitPatch();

    IPSPatched = true;

    // Adjust size values if the ROM was expanded
    if (last >= curromspace) {
        NumofBytes = curromspace = last + 1;
        NumofBanks = NumofBytes / 32768;
    }

    /*
  //Write out patched ROM
  {
    FILE *fp = 0;
    fp = fopen_dir(ZCfgPath, "zsnes.rom", "wb");
    if (!fp) { perror("zsnes.rom"); __asm__ volatile("int $3"); }
    fwrite(ROM, 1, curromspace, fp);
    fclose(fp);
  }
  */

    return (true);
}

bool findZipIPS(char* compressedfile, const char* ext)
{
    bool FoundIPS = false;
    unz_file_info cFileInfo; // Create variable to hold info for a compressed file
    int cFile;

    memset(&IPSPatch, 0, sizeof(IPSPatch));

    IPSPatch.zipfile = unzopen_dir(ZRomPath, compressedfile); // Open zip file
    cFile = unzGoToFirstFile(IPSPatch.zipfile); // Set cFile to first compressed file

    while (cFile == UNZ_OK) // While not at end of compressed file list
    {
        // Temporary char array for file name
        char cFileName[256];

        // Gets info on current file, and places it in cFileInfo
        unzGetCurrentFileInfo(IPSPatch.zipfile, &cFileInfo, cFileName, 256, NULL, 0, NULL, 0);

        // Find IPS file
        if (isextension(cFileName, ext)) {
            FoundIPS = true;
            break;
        }

        // Go to next file in zip file
        cFile = unzGoToNextFile(IPSPatch.zipfile);
    }

    if (FoundIPS) {
        // Open file
        unzOpenCurrentFile(IPSPatch.zipfile);

        IPSPatch.file_size = (unsigned int)cFileInfo.uncompressed_size;
        if ((IPSPatch.data = (unsigned char*)malloc(BUFFER_SIZE))) {
            reloadBuffer();
            return (PatchUsingIPS(0));
        } else {
            deinitPatch();
        }
    } else {
        unzClose(IPSPatch.zipfile);
        IPSPatch.zipfile = 0;
    }
    return (false);
}
