/* 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 "pcfx.h"
#include "interrupt.h"
#include "pad.h"
#include "v810_cpu.h"


// D0 = TRG, trigger bit
// D1 = MOD, multi-tap clear mode?
// D2 = IOS, data direction.  0 = output, 1 = input

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];

#ifdef WANT_DEBUGGER
bool FXPAD_GetRegister(const std::string &name, uint32 &value, std::string *special)
{
 if(name == "KPCTRL0" || name == "KPCTRL1")
 {
  value = control[name[6] - '0'];
  if(special)
  {
   char buf[256];
   //*special = std::string(buf);
  }
  return(TRUE);
 }
 return(FALSE);
}
#endif

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;

 switch(A & 0xC2)
 {
  case 0x40: ret = data_latch[0]; latched[0] = 0; break;
  case 0x42: ret = 0xF << 12; latched[0] = 0; break;
  case 0xC0: ret = data_latch[1]; latched[1] = 0; break;
  case 0xC2: ret = 0xF << 12; latched[1] = 0; break;

  case 0x80:
  case 0x00: 
	    {
	     int w = (A & 0x80) >> 7;

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

 if(!latched[0] && !latched[1])
   PCFXIRQ_Assert(11, FALSE);

 return(ret);
}

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

 //PCFXIRQ_Assert(11, FALSE);
 //if(V != 7 && V != 5)
 //printf("PAD Write16: %04x %04x %d\n", A, V, v810_timestamp);

 switch(A & 0xC0)
 {
  case 0x80:
  case 0x00:
	    {
	     int w = (A & 0x80) >> 7;

	     if((V & 0x1) && !(control[w] & 0x1))
	     {
	      LatchPending[w] = 100; //2560; //100;
	      SetEvent();
	     }
	     control[w] = V & 0x7;
	    }
	    break;
 }
}

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

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;
   control[0] &= ~1;
   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;
   control[1] &= ~1;
   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
};

