/* Mednafen - Multi-system Emulator
 *
 * 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 <zlib.h>
#include "pce.h"
#include "vdc.h"
#include "psg.h"
#include "input.h"
#include "huc.h"
#include "cdrom.h"
#include "hes.h"
#include "debug.h"
#include "../netplay.h"
#include "../mempatcher.h"

static RegType SGXRegsVDC[] =
{
        { "MAWR-B", 2 },
        { "MARR-B", 2 },
        { "CR-B", 2 },
        { "RCR-B", 2 },
        { "BXR-B", 2 },
        { "BYR-B", 2 },
        { "MWR-B", 2 },

        { "HSR-B", 2 },
        { "HDR-B", 2 },
        { "VSR-B", 2 },
        { "VDR-B", 2 },

        { "VCR-B", 2 },
        { "DCR-B", 2 },
        { "SOUR-B", 2 },
        { "DESR-B", 2 },
        { "LENR-B", 2 },
        { "SATB-B", 2 },
        { "", 0 },
};

bool PCE_InDebug = 0;

extern MDFNGI EmulatedPCE;
static bool IsSGX;
static bool IsHES;
int pce_overclocked;

static uint8 BaseRAM[32768]; // 8KB for PCE, 32KB for Super Grafx

uint8 PCEIODataBuffer;
readfunc PCERead[0x100], NonCheatPCERead[0x100];
writefunc PCEWrite[0x100];

static DECLFR(PCEBusRead)
{
 //printf("BUS Read: %02x %04x\n", A >> 13, A);
 return(0xFF);
}

static DECLFW(PCENullWrite)
{
 //printf("Null Write: %02x, %08x %02x\n", A >> 13, A, V);
}

static DECLFR(BaseRAMReadSGX)
{
 return(BaseRAM[A & 0x7FFF]);
}

static DECLFW(BaseRAMWriteSGX)
{
 BaseRAM[A & 0x7FFF] = V;
}

static DECLFR(BaseRAMRead)
{
 return(BaseRAM[A & 0x1FFF]);
}

static DECLFW(BaseRAMWrite)
{
 BaseRAM[A & 0x1FFF] = V;
}

static DECLFR(IORead)
{
 A &= 0x1FFF;

 switch(A & 0x1c00)
 {
  case 0x0000: if(!PCE_InDebug) HuC6280_StealCycles(1); 
	       return(VDC_Read(A));
  case 0x0400: if(!PCE_InDebug) HuC6280_StealCycles(1); 
	       return(VCE_Read(A));
  case 0x0800: if(HuCPU.in_block_move)
		return(0);
	       return(PCEIODataBuffer); 
  case 0x0c00: if(HuCPU.in_block_move)
                return(0);
	       {
		uint8 ret = HuC6280_TimerRead(A);
                if(!PCE_InDebug) PCEIODataBuffer = ret;
                return(ret);
               }
  case 0x1000: if(HuCPU.in_block_move)
                return(0);
	       {
	        uint8 ret = INPUT_Read(A);
                if(!PCE_InDebug) PCEIODataBuffer = ret;
                return(ret);
               }
  case 0x1400: if(HuCPU.in_block_move)
                return(0);
	       {
	        uint8 ret = HuC6280_IRQStatusRead(A);
		if(!PCE_InDebug) PCEIODataBuffer = ret;
	        return(ret);
	       }
  case 0x1800: if(!PCE_IsCD) break;
	       if((A & 0x1E00) == 0x1A00)
		return(PCE_ACRead(A));
	       else
		return(CDROM_Read(A));
  case 0x1C00: if(IsHES) return(ReadIBP(A)); 
	       break; // Expansion
  //default: printf("Eeep\n");break;
 }
 return(0xFF);
}

static DECLFW(IOWrite)
{
 A &= 0x1FFF;
  
 switch(A & 0x1c00)
 {
  case 0x0000: if(!PCE_InDebug) HuC6280_StealCycles(1); 
	       VDC_Write(A, V); break;
  case 0x0400: if(!PCE_InDebug) HuC6280_StealCycles(1); 
	       VCE_Write(A, V); break;
  case 0x0800: PCEIODataBuffer = V; PSG_Write(A, V); break;
  case 0x0c00: PCEIODataBuffer = V; HuC6280_TimerWrite(A, V); break;
  case 0x1000: PCEIODataBuffer = V; INPUT_Write(A, V); break;
  case 0x1400: PCEIODataBuffer = V; HuC6280_IRQStatusWrite(A, V); break;
  case 0x1800: if(!PCE_IsCD) break;
	       if((A & 0x1E00) == 0x1A00)
		PCE_ACWrite(A, V);
	       else
	        CDROM_Write(A, V); 
	       break;
  //case 0x1C00: break; // Expansion
  //default: printf("Eep: %04x\n", A); break;
 }

}

static int LoadCommon(void);
static int Load(const char *name, MDFNFILE *fp)
{
 uint32 headerlen = 0;
 uint32 r_size;

 memset(HuCPUFastMap, 0, sizeof(HuCPUFastMap));

 IsHES = 0;

 if(!memcmp(fp->data, "HESM", 4))
  IsHES = 1;

 if(strcasecmp(fp->ext, "pce") && !IsHES)
  return(-1);

 if(!IsHES)
 {
  if(fp->size & 0x200) // 512 byte header!
   headerlen = 512;
 }

 MDFNMP_Init(1024, (1 << 21) / 1024);

 r_size = fp->size - headerlen;
 if(r_size > 4096 * 1024) r_size = 4096 * 1024;

 for(int x = 0; x < 0x100; x++)
 {
  PCERead[x] = PCEBusRead;
  PCEWrite[x] = PCENullWrite;
 }

 uint32 crc = crc32(0, fp->data + headerlen, fp->size - headerlen);


 if(IsHES)
 {
  if(!PCE_HESLoad(fp->data, fp->size))
   return(0);
 }
 else
  HuCLoad(fp->data + headerlen, fp->size - headerlen, crc);

 if(fp->size >= 8192 && !memcmp(fp->data + headerlen, "DARIUS Version 1.11b", strlen("DARIUS VERSION 1.11b")))
 {
  MDFN_printf("SuperGfx:  Darius Plus\n");
  IsSGX = 1;
 }

 if(crc == 0x4c2126b0)
 {
  MDFN_printf("SuperGfx:  Aldynes\n");
  IsSGX = 1;
 }

 if(crc == 0x8c4588e2)
 {
  MDFN_printf("SuperGfx:  1941 - Counter Attack\n");
  IsSGX = 1;
 }
 if(crc == 0x1f041166)
 {
  MDFN_printf("SuperGfx:  Madouou Granzort\n");
  IsSGX = 1;
 }
 if(crc == 0xb486a8ed)
 {
  MDFN_printf("SuperGfx:  Daimakaimura\n");
  IsSGX = 1;
 }
 if(crc == 0x3b13af61)
 {
  MDFN_printf("SuperGfx:  Battle Ace\n");
  IsSGX = 1;
 }

 return(LoadCommon());
}

static int LoadCommon(void)
{ 
 IsSGX |= MDFN_GetSettingB("pce.forcesgx") ? 1 : 0;
 if(IsHES)
  IsSGX = 1;

 pce_overclocked = MDFN_GetSettingUI("pce.ocmultiplier");
 if(pce_overclocked > 1)
  MDFN_printf("CPU overclock: %dx\n", pce_overclocked);

 VDC_Init(IsSGX);

 if(IsSGX)
 {
  MDFN_printf("SuperGrafx Emulation Enabled.\n");
  PCERead[0xF8] = PCERead[0xF9] = PCERead[0xFA] = PCERead[0xFB] = BaseRAMReadSGX;
  PCEWrite[0xF8] = PCEWrite[0xF9] = PCEWrite[0xFA] = PCEWrite[0xFB] = BaseRAMWriteSGX;

  for(int x = 0xf8; x < 0xfb; x++)
   HuCPUFastMap[x] = BaseRAM - 0xf8 * 8192;
 }
 else
 {
  PCERead[0xF8] = PCERead[0xF9] = PCERead[0xFA] = PCERead[0xFB] = BaseRAMRead;
  PCEWrite[0xF8] = PCEWrite[0xF9] = PCEWrite[0xFA] = PCEWrite[0xFB] = BaseRAMWrite;

  for(int x = 0xf8; x < 0xfb; x++)
   HuCPUFastMap[x] = BaseRAM - x * 8192;
 }

 MDFNMP_AddRAM(IsSGX ? 32768 : 8192, 0xf8 * 8192, BaseRAM);

 PCERead[0xFF] = IORead;
 PCEWrite[0xFF] = IOWrite;

 HuC6280_Init();
 PSG_Init();
 PCE_Power();

 MDFNGameInfo->pitch = 1024 * sizeof(uint32);
 MDFNGameInfo->soundchan = 2;
 MDFNGameInfo->LayerNames = IsSGX ? "BG0\0SPR0\0BG1\0SPR1\0" : "Background\0Sprites\0";
 MDFNGameInfo->fps = (uint32)((double)7159090.90909090 / 455 / 263 * 65536 * 256);


 memcpy(NonCheatPCERead, PCERead, sizeof(PCERead));

 MDFNGameInfo->Debugger->Regs[2] = IsSGX ? SGXRegsVDC : NULL;

 MDFN_LoadGameCheats(0);
 MDFNMP_InstallReadPatches();
 return(1);
}

static int LoadCD(void)
{
 IsHES = 0;
 IsSGX = 0;

 for(int x = 0; x < 0x100; x++)
 {
  PCERead[x] = PCEBusRead;
  PCEWrite[x] = PCENullWrite;
 }

 MDFNMP_Init(1024, (1 << 21) / 1024);

 if(!HuCLoadCD(MDFN_GetSettingS("pce.cdbios").c_str()))
  return(0);

 return(LoadCommon());
}


static void CloseGame(void)
{
 if(IsHES)
  HES_Close();
 else
 {
  MDFN_FlushGameCheats(0);
  HuCClose();
 }
}

static int needrew = 0;

static void DoRewind(void)
{
 needrew = 1;
}

static void Emulate(uint32 *pXBuf, MDFN_Rect *LineWidths, int16 **SoundBuf, int32 *SoundBufSize, int skip)
{
 int didrew;

 MDFNGameInfo->fb = pXBuf;
 INPUT_Frame();

 didrew = MDFN_StateEvil(needrew);

 MDFNMP_ApplyPeriodicCheats();

 VDC_RunFrame(pXBuf, LineWidths, IsHES ? 1 : skip);

 *SoundBuf = PSG_Flush(SoundBufSize, didrew);

 needrew = 0;

 if(IsHES && !skip)
  HES_Draw(pXBuf, *SoundBuf, *SoundBufSize);
}

static int StateAction(StateMem *sm, int load, int data_only)
{
 SFORMAT StateRegs[] =
 {
  SFARRAY(BaseRAM, IsSGX? 32768 : 8192),
  SFVAR(PCEIODataBuffer),
  SFEND
 };

 int ret = MDFNSS_StateAction(sm, load, data_only, StateRegs, "MAIN");

 ret &= HuC6280_StateAction(sm, load, data_only);
 ret &= VDC_StateAction(sm, load, data_only);
 ret &= PSG_StateAction(sm, load, data_only);
 ret &= INPUT_StateAction(sm, load, data_only);
 ret &= HuC_StateAction(sm, load, data_only);

 if(load)
 {

 }

 return(ret);
}

static void PCE_Reset(void)
{
 PCEIODataBuffer = 0xFF;
 HuC6280_Reset();
 VDC_Reset();
 PSG_Reset();

 if(IsHES)
  HES_Reset();
 if(PCE_IsCD)
  CDROM_Reset();
}

void PCE_Power(void)
{
 memset(BaseRAM, 0x00, sizeof(BaseRAM));
 PCEIODataBuffer = 0xFF;

 HuC6280_Power();
 VDC_Power();
 PSG_Power();
 HuC_Power();
 if(IsHES)
  HES_Reset();
 if(PCE_IsCD)
  CDROM_Reset();
}

static void DoSimpleCommand(int cmd)
{
 switch(cmd)
 {
  case MDFNNPCMD_RESET: PCE_Reset(); break;
  case MDFNNPCMD_POWER: PCE_Power(); break;
 }
}

static MDFNSetting PCESettings[] = 
{
  { "pce.forcesgx", "Force SuperGrafx emulation.", MDFNST_BOOL, "0" },
  { "pce.ocmultiplier", "CPU overclock multiplier.", MDFNST_UINT, "1", "1", "50"},
  { "pce.nospritelimit", "No 16-sprites-per-scanline limit option.", MDFNST_BOOL, "0" },
  { "pce.cdbios", "Path to the CD BIOS", MDFNST_STRING, "pce.cdbios PATH NOT SET" },
  { NULL }
};

static bool StartNetplay(NetplaySystemInfoStruct *info)
{
 info->total_controllers = 5;
 info->controller_data_type = CONTROLLERDT_UINT8;
 return(1);
}

static DECLFR(CheatReadFunc)
{
  std::vector<SUBCHEAT>::iterator chit;
  uint8 retval = NonCheatPCERead[(A / 8192) & 0xFF](A);

  for(chit = SubCheats[A & 0x7].begin(); chit != SubCheats[A & 0x7].end(); chit++)
  {
   if(A == chit->addr)
   {
    if(chit->compare == -1 || chit->compare == retval)
    {
     retval = chit->value;
     break;
    }
   }
  }
 return(retval);
}

static uint8 MemRead(uint32 addr)
{
 return(NonCheatPCERead[(addr / 8192) & 0xFF](addr));
}

static void InstallReadPatch(uint32 address)
{
 HuCPUFastMap[address / 8192] = NULL;
 PCERead[address / 8192] = CheatReadFunc;
}

static void RemoveReadPatches(void)
{
 for(int x = 0; x < 0x100; x++)
  PCERead[x] = NonCheatPCERead[x];
}

static RegType PCERegs[] =
{
        { "PC", 2 },
        { "A", 1 },
        { "X", 1 },
        { "Y", 1 },
        { "SP", 1 },
        { "P", 1 },
	{ "MPR0", 1},
	{ "MPR1", 1},
	{ "MPR2", 1},
	{ "MPR3", 1},
	{ "MPR4", 1},
	{ "MPR5", 1},
	{ "MPR6", 1},
	{ "MPR7", 1},
	{ "SPD", 1 },
	{ "IRQM", 1},
	{ "TIMS", 1},
	{ "TIMV", 1},
	{ "TIML", 1},
	{ "TIMD", 2},
	{ "", 0 },
};

static RegType PCERegsVDC[] =
{
	{ "MAWR", 2 },
        { "MARR", 2 },
        { "CR", 2 },
        { "RCR", 2 },
        { "BXR", 2 },
        { "BYR", 2 },
        { "MWR", 2 },

        { "HSR", 2 },
        { "HDR", 2 },
        { "VSR", 2 },
        { "VDR", 2 },

        { "VCR", 2 },
        { "DCR", 2 },
        { "SOUR", 2 },
        { "DESR", 2 },
        { "LENR", 2 },
        { "SATB", 2 },
	
	{ "Frm Cntr", 2 },
        { "", 0 },
};

static DebuggerInfoStruct DBGInfo =
{
 { PCERegs, PCERegsVDC, NULL, NULL },
 7,
 16,
 21,
 0x2000,
 PCEDBG_MemPeek,
 PCEDBG_MemPoke,
 PCEDBG_Disassemble,
 NULL, //NESDBG_IRQ,
 NULL, //NESDBG_GetVector,
 PCEDBG_GetRegister,
 PCEDBG_SetRegister,
 PCEDBG_FlushBreakPoints,
 PCEDBG_AddBreakPoint,
 PCEDBG_SetCPUCallback,
 PCEDBG_SetBPCallback,
 PCEDBG_GetBranchTrace,
};

MDFNGI EmulatedPCE =
{
 GISYS_PCE,
 "pce",
 &DBGInfo,
 Load,
 LoadCD,
 CloseGame,
 VDC_ToggleLayer,
 NULL,
 InstallReadPatch,
 RemoveReadPatches,
 MemRead,
 StateAction,
 DoRewind,
 Emulate,
 VDC_SetPixelFormat,
 PCEINPUT_SetInput,
 PSG_SetSoundMultiplier,
 PSG_SetSoundVolume,
 PSG_Sound,
 DoSimpleCommand,
 StartNetplay,
 PCESettings,
 0,
 NULL,
 320,
 232,
 1024 * sizeof(uint32),
 { 0, 4, 320, 232 },
};

