#include "pcfx.h"
#include "interrupt.h"
#include "pad.h"
#include "v810_cpu.h"

static uint8 control[2];
static uint8 latched[2];

static int InputTypes[2];
static uint16 *data_ptr[2], data_save[2];
static uint16 data_latch[2];

static int32 LatchPending[2];

void snortus(void);

static INLINE int32 min(int32 a, int32 b, int32 c)
{
 int32 ret = a;

 if(b < ret)
  ret = b;
 if(c < ret)
  ret = c;

 return(ret);
}

static INLINE void SetEvent(void)
{
 v810_setevent(V810_EVENT_PAD, min(LatchPending[0] > 0 ? LatchPending[0] : V810_EVENT_NONONO, LatchPending[1] > 0 ? LatchPending[1] : V810_EVENT_NONONO, V810_EVENT_NONONO));
}

void FXPAD_SetInput(int port, const char *type, void *ptr)
{
 data_ptr[port] = (uint16*)ptr;

 if(!strcasecmp(type, "gamepad"))
  InputTypes[port] = 1;
 else
  InputTypes[port] = 0;
}

uint8 FXPAD_Read8(uint32 A)
{
 return(FXPAD_Read16(A &~1) >> ((A & 1) * 8));
}

uint16 FXPAD_Read16(uint32 A)
{
 FXPAD_Update();

 uint16 ret = 0;

 PCFXIRQ_Assert(11, FALSE);
 switch(A & 0xC0)
 {
  default: ret = FXPAD_Read32(A&~2) >> ((A & 2) * 8);
	   break;
  case 0x80:
  case 0x00: 
	    {
	     int w = (A & 0x80) >> 7;

	     if(LatchPending[w] > 0)
	      ret = 0x8;
	     else if(latched[w])
	     {
	      ret = 0x0;
	      latched[w] = 0;
	     }
	     else
	      ret = 0;
	    }
	    break;
 }
 return(ret);
}

uint32 FXPAD_Read32(uint32 A)
{
 uint32 ret = 0;
 FXPAD_Update();

 switch(A & 0xC0)
 {
  default: ret = FXPAD_Read16(A) | (FXPAD_Read16(A) << 16);
	   break;
  case 0x40: ret = data_latch[0] | (15 << 28); break;
  case 0xC0: ret = data_latch[1] | (15 << 28); break;
 }

 return(ret);
}


void FXPAD_Write16(uint32 A, uint16 V)
{
 FXPAD_Update();

 //PCFXIRQ_Assert(11, FALSE);
 //printf("PAD Write16: %04x %04x %d\n", A, V, v810_timestamp);

 switch(A & 0xC0)
 {
  case 0x80:
  case 0x00:
	    {
	     int w = (A & 0x80) >> 7;
	     control[w] = V;
	     if((V & 0x4))
	     {
	      LatchPending[w] = 100;
	      SetEvent();
	     }
	    }
	    break;
 }
}

void FXPAD_Write8(uint32 A, uint8 V)
{
 FXPAD_Write16(A, V); 
}

void FXPAD_Write32(uint32 A, uint32 V)
{
 switch(A & 0xC0)
 {
  case 0x40: break;
  case 0xC0: break;
  case 0x80:
  case 0x00:
	  FXPAD_Write16(A, V & 0xFFFF);
	  FXPAD_Write16(A | 2, V >> 16);
	  break;
 }
}

void FXPAD_Frame(void)
{
 if(InputTypes[0])
  data_save[0] = *data_ptr[0];
 if(InputTypes[1])
  data_save[1] = *data_ptr[1];
}

static uint32 lastts;

void FXPAD_Update(void)
{
 int32 run_time = v810_timestamp - lastts;

 if(LatchPending[0] > 0)
 {
  LatchPending[0] -= run_time;
  if(LatchPending[0] <= 0)
  {
   data_latch[0] = data_save[0];
   latched[0] = TRUE;
   PCFXIRQ_Assert(11, TRUE);
  }
 }

 if(LatchPending[1] > 0)
 {
  LatchPending[1] -= run_time;
  if(LatchPending[1] <= 0)
  {
   data_latch[1] = data_save[1];
   latched[1] = TRUE;
   PCFXIRQ_Assert(11, TRUE);
  }
 }
 lastts = v810_timestamp;

 SetEvent();
}

void FXPAD_ResetTS(void)
{
 lastts = 0;
}


int FXPAD_StateAction(StateMem *sm, int load, int data_only)
{
 SFORMAT StateRegs[] =
 {
  SFARRAY32(LatchPending, 2),
  SFARRAY(control, 2),
  SFARRAY(latched, 2),
  SFARRAY16(data_latch, 2),
  SFEND
 };

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

 if(load)
  SetEvent();

 return(ret);
}

static const InputDeviceInputInfoStruct GamepadIDII[] =
{
 { "i", "I", 11, IDIT_BUTTON, NULL },
 { "ii", "II", 10, IDIT_BUTTON, NULL },
 { "iii", "III", 9, IDIT_BUTTON, NULL },
 { "iv", "IV", 6, IDIT_BUTTON, NULL },
 { "v", "V", 7, IDIT_BUTTON, NULL },
 { "vi", "VI", 8, IDIT_BUTTON, NULL },
 { "select", "SELECT", 4, IDIT_BUTTON, NULL },
 { "run", "RUN", 5, IDIT_BUTTON, NULL },
 { "up", "UP ↑", 0, IDIT_BUTTON, "down" },
 { "right", "RIGHT →", 3, IDIT_BUTTON, "left" },
 { "down", "DOWN ↓", 1, IDIT_BUTTON, "up" },
 { "left", "LEFT ←", 2, IDIT_BUTTON, "right" },
};

static InputDeviceInfoStruct InputDeviceInfo[] =
{
 // None
 {
  "none",
  "none",
  NULL,
  0,
  NULL,
 },

 // Gamepad
 {
  "gamepad",
  "Gamepad",
  NULL,
  sizeof(GamepadIDII) / sizeof(InputDeviceInputInfoStruct),
  GamepadIDII,
 },
};

static const InputPortInfoStruct PortInfo[] =
{
 { 0, "port1", "Port 1", sizeof(InputDeviceInfo) / sizeof(InputDeviceInfoStruct), InputDeviceInfo},
 { 0, "port2", "Port 2", sizeof(InputDeviceInfo) / sizeof(InputDeviceInfoStruct), InputDeviceInfo },
};

InputInfoStruct PCFXInputInfo =
{
 sizeof(PortInfo) / sizeof(InputPortInfoStruct),
 PortInfo
};

