#include <errno.h>
#include <string.h>

#include "pcfx.h"
#include "vdc.h"
#include "soundbox.h"
#include "pad.h"
#include "king.h"
#include "timer.h"
#include "interrupt.h"

#include "../mempatcher.h"
#include "../netplay.h"

static uint8 *BIOSROM; // 1MB
static uint8 *RAM; // 2MB

static fx_vdc_t *vdc_chips[2];

uint8 mem_rbyte(uint32 A)
{
 if(A >= 0xFFF00000 && A <= 0xFFFFFFFF)
 {
  return(BIOSROM[A & 0xFFFFF]);
 }

 if(A <= 0x1FFFFF)
 {
  return(RAM[A & 0x1FFFFF]);
 }
 printf("RBYTE Unknown: %08x\n", A);
}

uint16 mem_rhword(uint32 A)
{
 if(A >= 0xFFF00000 && A <= 0xFFFFFFFF)
 {
  return(*(uint16 *)&BIOSROM[A & 0xFFFFF]);
 }

 if(A <= 0x1FFFFF)
 {
  return(*(uint16*)&RAM[A & 0x1FFFFF]);
 }

 printf("RHWORD Unknown: %08x\n", A);
 meowpc();
 return(0);
}

uint32 mem_rword(uint32 A)
{
 if(A >= 0xFFF00000 && A <= 0xFFFFFFFF)
 {
  return(*(uint32 *)&BIOSROM[A & 0xFFFFF]);
 }

 if(A <= 0x1FFFFF)
 {
  return(*(uint32*)&RAM[A & 0x1FFFFF]);
 }
 printf("RWORD Unknown: %08x\n", A);
 meowpc();
 return(0);
}

void mem_wbyte(uint32 A, uint8 V)
{
 if(A <= 0x1FFFFF)
 {
  RAM[A & 0x1FFFFF] = V;
 }
}

void mem_whword(uint32 A, uint16 V)
{
 if(A <= 0x1FFFFF)
 {
  *(uint16*)&RAM[A & 0x1FFFFF] = V;
 }
}

void mem_wword(uint32 A, uint32 V)
{
 if(A <= 0x1FFFFF)
 {
  *(uint32*)&RAM[A & 0x1FFFFF] = V;
 }
}

uint8 port_rbyte(uint32 A)
{
 //printf("PreadB: %04x\n", A);
  if(A >= 0x300 && A <= 0x3FF) // FXVCE
  {
   return(FXVCE_Read8(A));
  }
  if(A >= 0x400 && A <= 0x4FF) // VDC-A
  {
   return(FXVDC_Read(vdc_chips[0], ((A & 4) >> 1) | (A & 1)));
  }

  if(A >= 0x500 && A <= 0x5FF) // VDC-B
  {
   return(FXVDC_Read(vdc_chips[1], ((A & 4) >> 1) | (A & 1)));
  }

 printf("Unknown byte pread: %04x\n", A);
 return(0xFF);
}

uint16 port_rhword(uint32 A)
{
 if(A >= 0x000 && A <= 0x0FF)
  return(FXPAD_Read16(A));
 //else
  //printf("PreadH: %04x\n", A);

 if(A >= 0x300 && A <= 0x3FF)
 {
  return(FXVCE_Read8(A) | (FXVCE_Read8(A | 1) << 8));
 }

 if(A >= 0x400 && A <= 0x4FF) // VDC-A
 {
  return(FXVDC_Read(vdc_chips[0], ((A & 4) >> 1) | 0) | (FXVDC_Read(vdc_chips[0], ((A & 4) >> 1) | 1) << 8) );
 }

 if(A >= 0x500 && A <= 0x5FF) // VDC-B
 {
  return(FXVDC_Read(vdc_chips[1], ((A & 4) >> 1) | 0) | (FXVDC_Read(vdc_chips[1], ((A & 4) >> 1) | 1) << 8) );
 }

 if(A >= 0x600 && A <= 0x6FF)
 {
  return(KING_Read16(A));
 }

 if(A >= 0xe00 && A <= 0xeff)
 {
  return(PCFXIRQ_Read16(A));
 }
 if(A >= 0xf00 && A <= 0xfff)
 {
  return(FXTIMER_Read16(A));
 }
 printf("Unknown hword pread: %04x\n", A);
 return(0xFF);
}

uint32 port_rword(uint32 A)
{
 if(A >= 0x000 && A <= 0x0FF)
  return(FXPAD_Read32(A));
 if(A >= 0x600 && A <= 0x6FF)
  return(KING_Read32(A));

 printf("Unknown word pread: %04x\n", A);
 return(0xFF);
}

void port_wbyte(uint32 A, uint8 V)
{
  //printf("PortB: %08x %02x\n", A, V);
  if(A >= 0x100 && A <= 0x1FF)
  {
   SoundBox_Write(A, V);
  }
  if(A >= 0x300 && A <= 0x3FF) // FXVCE
  {
   FXVCE_Write8(A, V);
  }

  if(A >= 0x400 && A <= 0x4FF) // VDC-A
  {
   FXVDC_Write(vdc_chips[0], ((A & 4) >> 1) | (A & 1), V);
  }

  if(A >= 0x500 && A <= 0x5FF) // VDC-B
  {
   FXVDC_Write(vdc_chips[1], ((A & 4) >> 1) | (A & 1), V);
  }
}

void port_whword(uint32 A, uint16 V)
{
  //if(A >= 0x400 && A <= 0x5FF)
  // printf("PORTHW: %08x %04x\n", A, V);

  if(A >= 0x000 && A <= 0x0FF)
   FXPAD_Write16(A, V);
  else if(A >= 0x300 && A <= 0x3FF)
  {
   FXVCE_Write8(A, V);
   FXVCE_Write8(A | 1, V >> 8);
  }
  else if(A >= 0x400 && A <= 0x4FF) // VDC-A
  {
   FXVDC_Write(vdc_chips[0], ((A & 4) >> 1) | 0, V);
   FXVDC_Write(vdc_chips[0], ((A & 4) >> 1) | 1, V >> 8);
  }
  else if(A >= 0x500 && A <= 0x5FF) // VDC-B
  {
   FXVDC_Write(vdc_chips[1], ((A & 4) >> 1) | 0, V);
   FXVDC_Write(vdc_chips[1], ((A & 4) >> 1) | 1, V >> 8);
  }
  else if(A >= 0x600 && A <= 0x6FF)
  {
   KING_Write16(A, V);
  }
  else if(A >= 0xe00 && A <= 0xeff)
  { 
   PCFXIRQ_Write16(A, V);
  }
  else if(A >= 0xF00 && A <= 0xFFF)
  {
   FXTIMER_Write16(A, V);
  }
}

void port_wword(uint32 A, uint32 V)
{
  //printf("PortW: %08x %08x\n", A, V);

  if(A >= 0x000 && A <= 0x0FF)
   FXPAD_Write32(A, V);
  if(A >= 0x300 && A <= 0x5FF)
  {
   port_whword(A, V);
   port_whword(A | 2, V >> 16);
  }

  if(A >= 0x600 && A <= 0x6FF)
  {
   KING_Write32(A, V);
  }
}


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;
 FXPAD_Frame();

 didrew = MDFN_StateEvil(needrew);

 MDFNMP_ApplyPeriodicCheats();


 KING_RunFrame(vdc_chips, pXBuf, LineWidths, skip);
 *SoundBuf = SoundBox_Flush(SoundBufSize, didrew);

 needrew = 0;
}

static void PCFX_Reset(void)
{
 FXVDC_Reset(vdc_chips[0]);
 FXVDC_Reset(vdc_chips[1]);
 v810_reset();
}

static void PCFX_Power(void)
{
 FXVDC_Power(vdc_chips[0]);
 FXVDC_Power(vdc_chips[1]);
 v810_reset();
}

static int LoadCD(void)
{
 FILE *fp;
 std::string biospath = MDFN_GetSettingS("pcfx.bios");

 if(!(fp = fopen(biospath.c_str(), "rb")))
 {
  puts("ACKACK");
  MDFN_PrintError(_("Could not open BIOS file \"%s\": %s\n"), biospath.c_str(), strerror(errno));
  return(0);
 }

 BIOSROM = (uint8 *)malloc(1024 * 1024); // 1MB
 RAM = (uint8 *)malloc(2048 * 1024); // 2MB

 if(fread(BIOSROM, 1024 * 1024, 1, fp) < 1)
 {
  MDFN_PrintError(_("Error reading BIOS file \"%s\": %s\n"), biospath.c_str(), strerror(errno));
  free(BIOSROM);
  free(RAM);
  return(0);
 }
 fclose(fp);

 vdc_chips[0] = FXVDC_Init(12);
 vdc_chips[1] = FXVDC_Init(14);
 KING_Init();
 SoundBox_Init();

 PCFX_Power();

 MDFNGameInfo->fps = (uint32)((double)7159090.90909090 / 455 / 263 * 65536 * 256);
 MDFNGameInfo->soundchan = 2;

 return(1);
}

static void CloseGame(void)
{


}

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

static int StateAction(StateMem *sm, int load, int data_only)
{
 SFORMAT StateRegs[] =
 {
  SFARRAY(RAM, 0x200000),
  SFEND
 };

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

 ret &= FXVDC_StateAction(sm, load, data_only, vdc_chips[0], "VDC0");
 ret &= FXVDC_StateAction(sm, load, data_only, vdc_chips[1], "VDC1");

 if(load)
 {

 }

 return(ret);
}

static MDFNSetting PCFXSettings[] =
{
  { "pcfx.bios", "Path to the ROM BIOS", MDFNST_STRING, "pcfx.bios PATH NOT SET" },
  { NULL }
};

MDFNGI EmulatedPCFX =
{
 GISYS_PCFX,
 "pcfx",
 NULL,
 NULL,
 LoadCD,
 CloseGame,
 NULL,
 NULL,
 NULL,
 NULL,
 NULL,
 StateAction,
 DoRewind,
 Emulate,
 KING_SetPixelFormat,
 FXPAD_SetInput,
 SoundBox_SetSoundMultiplier,
 SoundBox_SetSoundVolume,
 SoundBox_Sound,
 DoSimpleCommand,
 NULL, //StartNetplay, // No netplay yet!
 PCFXSettings,
 0,
 NULL,
 320,
 232,
 1024 * sizeof(uint32),
 { 0, 4, 320, 232 },
};

