/* Mednafen - Multi-system Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 1998 BERO 
 *  Copyright (C) 2002 Xodnizel
 *
 * 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 <string.h>

#include "nes.h"
#include "x6502.h"
#include "sound.h"
#include "input.h"
#include "vsuni.h"
#include "fds.h"

extern INPUTC *MDFN_InitZapper(int w);
extern INPUTC *MDFN_InitPowerpadA(int w);
extern INPUTC *MDFN_InitPowerpadB(int w);
extern INPUTC *MDFN_InitArkanoid(int w);

extern INPUTCFC *MDFN_InitArkanoidFC(void);
extern INPUTCFC *MDFN_InitSpaceShadow(void);
extern INPUTCFC *MDFN_InitFKB(void);
extern INPUTCFC *MDFN_InitHS(void);
extern INPUTCFC *MDFN_InitMahjong(void);
extern INPUTCFC *MDFN_InitQuizKing(void);
extern INPUTCFC *MDFN_InitFamilyTrainerA(void);
extern INPUTCFC *MDFN_InitFamilyTrainerB(void);
extern INPUTCFC *MDFN_InitOekaKids(void);
extern INPUTCFC *MDFN_InitTopRider(void);
extern INPUTCFC *MDFN_InitBarcodeWorld(void);

static uint8 joy_readbit[2];
//static 
uint8 joy[4]={0,0,0,0};
static uint8 LastStrobe;

/* This function is a quick hack to get the NSF player to use emulated gamepad
   input.
*/
uint8 MDFN_GetJoyJoy(void)
{
 return(joy[0]|joy[1]|joy[2]|joy[3]);
}
extern uint8 coinon;

static int FSDisable=0;	/* Set to 1 if NES-style four-player adapter is disabled. */
static int JPAttrib[2]={0,0};
static int JPType[2]={0,0};
static void *InputDataPtr[2];

static int JPAttribFC=0;
static int JPTypeFC=0;
static void *InputDataPtrFC;

void (*InputScanlineHook)(uint8 *bg, uint8 *spr, uint32 linets, int final);


static INPUTC DummyJPort = {0, 0, 0, 0, 0, NULL, 0, 0};
static INPUTC *JPorts[2]={&DummyJPort,&DummyJPort};
static INPUTCFC *FCExp=0;

static uint8 ReadGPVS(int w)
{
                uint8 ret=0;
  
                if(joy_readbit[w]>=8)
                 ret=1;
                else
                {
                 ret = ((joy[w]>>(joy_readbit[w]))&1);
                 if(!fceuindbg)
                  joy_readbit[w]++;
                }
                return ret;
}

static uint8 ReadGP(int w)
{
                uint8 ret;

                if(joy_readbit[w]>=8)
                 ret = ((joy[2+w]>>(joy_readbit[w]&7))&1);
                else
                 ret = ((joy[w]>>(joy_readbit[w]))&1);
                if(joy_readbit[w]>=16) ret=0;
                if(FSDisable)
		{
	  	 if(joy_readbit[w]>=8) ret|=1;
		}
		else
		{
                 if(joy_readbit[w]==19-w) ret|=1;
		}
		if(!fceuindbg)
		 joy_readbit[w]++;
                return ret;
}

static DECLFR(JPRead)
{
	uint8 ret=0;

	if(JPorts[A&1]->Read)
	 ret|=JPorts[A&1]->Read(A&1);
	
	if(FCExp)
	{
	 if(FCExp->Read)
	 {
	  ret=FCExp->Read(A&1,ret);
	 }
	}
	ret|=X.DB&0xC0;
	return(ret);
}

static DECLFW(B4016)
{
	if(FCExp)
	 if(FCExp->Write)
	  FCExp->Write(V&7);

	if(JPorts[0]->Write)
	 JPorts[0]->Write(V&1);
        if(JPorts[1]->Write)
         JPorts[1]->Write(V&1);

        if((LastStrobe&1) && (!(V&1)))
        {
	 /* This strobe code is just for convenience.  If it were
	    with the code in input / *.c, it would more accurately represent
	    what's really going on.  But who wants accuracy? ;)
	    Seriously, though, this shouldn't be a problem.
	 */
	 if(JPorts[0]->Strobe)
	  JPorts[0]->Strobe(0);
         if(JPorts[1]->Strobe)
          JPorts[1]->Strobe(1);
	 if(FCExp)
	  if(FCExp->Strobe)
	   FCExp->Strobe();
	 }
         LastStrobe=V&0x1;
}

static void StrobeGP(int w)
{
	joy_readbit[w]=0;
}

static int StateActionGP(int w, StateMem *sm, int load, int data_only)
{
 SFORMAT StateRegs[] =
 {
  SFVAR(joy_readbit[w]),
  SFVAR(joy[w + 0]),
  SFVAR(joy[w + 2]),
  SFEND
 };
 int ret = MDFNSS_StateAction(sm, load, data_only, StateRegs, w ? "INP1" : "INP0");
 if(load)
 {

 }
 return(ret);
}

static uint8 F4ReadBit[2];
static int StateActionGPFC(StateMem *sm, int load, int data_only)
{
 SFORMAT StateRegs[] =
 {
  SFARRAY(F4ReadBit, 2),
  SFARRAY(joy, 4),
  SFEND
 };
 int ret = MDFNSS_StateAction(sm, load, data_only, StateRegs,"INPF");
 if(load)
 {

 }
 return(ret);
}

static void StrobeFami4(void)
{
 F4ReadBit[0]=F4ReadBit[1]=0;
}

static uint8 ReadFami4(int w, uint8 ret)
{
 ret&=1;

 ret |= ((joy[2+w]>>(F4ReadBit[w]))&1)<<1;
 if(F4ReadBit[w]>=8) ret|=2;
 else F4ReadBit[w]++;

 return(ret);
}

static INPUTCFC FAMI4C = { ReadFami4,0,StrobeFami4,0,0,0, StateActionGPFC, 4, 1 };
static INPUTC GPC={ReadGP,0,StrobeGP,0,0,0, StateActionGP, 4, 1};
static INPUTC GPCVS={ReadGPVS,0,StrobeGP,0,0,0, StateActionGP, 4, 1};

void MDFN_DrawInput(uint32 *buf)
{
 int x;

 for(x=0;x<2;x++)
  if(JPorts[x]->Draw)
   JPorts[x]->Draw(x,buf,JPAttrib[x]);
 if(FCExp)
  if(FCExp->Draw)
   FCExp->Draw(buf,JPAttribFC);
}

#define SFMODATOA16(_x) { (_x)->s = ((_x)->s & 0xFFFFFF) * sizeof(uint16); (_x)->s |= MDFNSTATE_RLSB16; }
#define SFMODATOA32(_x) { (_x)->s = ((_x)->s & 0xFFFFFF) * sizeof(uint32); (_x)->s |= MDFNSTATE_RLSB32; }

void MDFN_UpdateInput(void)
{
	int x;

        SFORMAT MovieRegs[] =
        {
         SFARRAY((uint8 *)InputDataPtr[0], JPorts[0]->InDataElements),
         SFARRAY((uint8 *)InputDataPtr[1], JPorts[1]->InDataElements),
	 SFARRAY((uint8 *)InputDataPtrFC, FCExp ? FCExp->InDataElements : 0),
         SFEND
        };
	if(JPorts[0]->InDataElementSize == 2) // 16-bit
	 SFMODATOA16(&MovieRegs[0])
	else if(JPorts[0]->InDataElementSize == 4) // 32-bit
	 SFMODATOA32(&MovieRegs[0])
        if(JPorts[1]->InDataElementSize == 2) // 16-bit
         SFMODATOA16(&MovieRegs[1])
        else if(JPorts[1]->InDataElementSize == 4) // 32-bit
         SFMODATOA32(&MovieRegs[1])
	if(FCExp)
	{
         if(FCExp->InDataElementSize == 2) // 16-bit
          SFMODATOA16(&MovieRegs[2])
         else if(FCExp->InDataElementSize == 4) // 32-bit
          SFMODATOA32(&MovieRegs[2])
	}

	// Optimization to not save the same data twice(or thrice) for gamepads:
	if(InputDataPtr[0] == InputDataPtr[1]) 
	 MovieRegs[1].v = NULL;
	if(FCExp && (InputDataPtrFC == InputDataPtr[0] || InputDataPtrFC == InputDataPtr[1]))
	 MovieRegs[2].v = NULL;

	/* netplay must be called before mdfnmov_addjoy() so that input from the network server can
	   be recorded in the movie file.
	*/
        #ifdef NETWORK
        if(MDFNnetplay) 
	{
	 if(JPorts[0] == &GPC || JPorts[0] == &GPCVS)
 	  NetplayUpdate((uint8 *)InputDataPtr[0]);
	 else if(JPorts[1] == &GPC || JPorts[1] == &GPCVS)
	  NetplayUpdate((uint8 *)InputDataPtr[1]);
	 else if(FCExp == &FAMI4C)
	  NetplayUpdate((uint8 *)InputDataPtrFC);
	}
        #endif

        MDFNMOV_AddJoy(MovieRegs);
	//printf("%02x\n", *(uint8 *)(InputDataPtr[0]));

	for(x=0;x<2;x++)
	{
	 switch(JPType[x])
	 {
	  case SI_GAMEPAD:
                if(!x)
                {
                 joy[0] = ((uint8 *)InputDataPtr[0])[0];
                 joy[2] = ((uint8 *)InputDataPtr[0])[2];
                }
                else
                {
                 joy[1]=((uint8 *)InputDataPtr[0])[1];
                 joy[3]=((uint8 *)InputDataPtr[0])[3];
                }
		break;
	  default:
		if(JPorts[x]->Update)
		 JPorts[x]->Update(x,InputDataPtr[x],JPAttrib[x]);
		break;
	 }
	}
	if(FCExp)
	 if(FCExp->Update)
	  FCExp->Update(InputDataPtrFC,JPAttribFC);

        if(MDFNGameInfo->nes.type==GIT_VSUNI)
	 if(coinon) coinon--;

        if(MDFNGameInfo->nes.type==GIT_VSUNI)
	 MDFN_VSUniSwap(&joy[0], &joy[1]);
}

extern uint8 vsdip;	// FIXME

static DECLFR(VSUNIRead0)
{ 
        uint8 ret=0; 
  
        if(JPorts[0]->Read)   
         ret|=(JPorts[0]->Read(0))&1;
 
        ret|=(vsdip&3)<<3;
        if(coinon)
         ret|=0x4;
        return ret;
}
 
static DECLFR(VSUNIRead1)
{
        uint8 ret=0;
 
        if(JPorts[1]->Read)
         ret|=(JPorts[1]->Read(1))&1;
        ret|=vsdip&0xFC;   
        return ret;
} 

static void SLHLHook(uint8 *bg, uint8 *spr, uint32 linets, int final)
{
 int x;

 for(x=0;x<2;x++)
  if(JPorts[x]->SLHook)
   JPorts[x]->SLHook(x,bg,spr,linets,final);
 if(FCExp) 
  if(FCExp->SLHook)
   FCExp->SLHook(bg,spr,linets,final);
}

static void CheckSLHook(void)
{
        InputScanlineHook=0;
        if(JPorts[0]->SLHook || JPorts[1]->SLHook)
         InputScanlineHook=SLHLHook;
        if(FCExp)
         if(FCExp->SLHook)
          InputScanlineHook=SLHLHook;
}

static void SetInputStuff(int x)
{
 	 switch(JPType[x])
	 {
	  case SI_GAMEPAD:
           if(MDFNGameInfo->nes.type==GIT_VSUNI)
	    JPorts[x] = &GPCVS;
	   else
	    JPorts[x]=&GPC;
	  break;
	  case SI_ARKANOID:JPorts[x]=MDFN_InitArkanoid(x);break;
	  case SI_ZAPPER:JPorts[x]=MDFN_InitZapper(x);break;
          case SI_POWERPADA:JPorts[x]=MDFN_InitPowerpadA(x);break;
	  case SI_POWERPADB:JPorts[x]=MDFN_InitPowerpadB(x);break;
	  case SI_NONE:JPorts[x]=&DummyJPort;break;
         }

	CheckSLHook();
}

static void SetInputStuffFC(void)
{
        switch(JPTypeFC)
        {
         case SIFC_NONE:FCExp=0;break;
         case SIFC_ARKANOID:FCExp=MDFN_InitArkanoidFC();break;
	 case SIFC_SHADOW:FCExp=MDFN_InitSpaceShadow();break;
	 case SIFC_OEKAKIDS:FCExp=MDFN_InitOekaKids();break;
         case SIFC_4PLAYER:FCExp=&FAMI4C;memset(&F4ReadBit,0,sizeof(F4ReadBit));break;
	 case SIFC_FKB:FCExp=MDFN_InitFKB();break;
	 case SIFC_HYPERSHOT:FCExp=MDFN_InitHS();break;
	 case SIFC_MAHJONG:FCExp=MDFN_InitMahjong();break;
	 case SIFC_QUIZKING:FCExp=MDFN_InitQuizKing();break;
	 case SIFC_FTRAINERA:FCExp=MDFN_InitFamilyTrainerA();break;
	 case SIFC_FTRAINERB:FCExp=MDFN_InitFamilyTrainerB();break;
	 case SIFC_BWORLD:FCExp=MDFN_InitBarcodeWorld();break;
	 case SIFC_TOPRIDER:FCExp=MDFN_InitTopRider();break;
        }
	CheckSLHook();
}

void NESINPUT_Power(void)
{ 
	memset(joy_readbit,0,sizeof(joy_readbit));
        memset(joy,0,sizeof(joy));
	LastStrobe = 0;

        if(MDFNGameInfo->nes.type==GIT_VSUNI)
        {
         SetReadHandler(0x4016,0x4016,VSUNIRead0);
         SetReadHandler(0x4017,0x4017,VSUNIRead1);
        } 
        else
         SetReadHandler(0x4016,0x4017,JPRead);

        SetWriteHandler(0x4016,0x4016,B4016);

        SetInputStuff(0);
        SetInputStuff(1);
	SetInputStuffFC();
}

void MDFNNES_SetInput(int port, int type, void *ptr, int attrib)
{
 JPAttrib[port]=attrib;
 JPType[port]=type;
 InputDataPtr[port]=ptr;
 SetInputStuff(port);
}

void MDFNI_SetInputFC(int type, void *ptr, int attrib)
{
 JPAttribFC=attrib;
 JPTypeFC=type;
 InputDataPtrFC=ptr;
 SetInputStuffFC();
}


int NESINPUT_StateAction(StateMem *sm, int load, int data_only)
{
 SFORMAT StateRegs[] =
 {
   SFVARN(LastStrobe, "LSTS"),
   SFEND
 };
 int ret = MDFNSS_StateAction(sm, load, data_only, StateRegs, "INPT");

 if(JPorts[0]->StateAction)
  ret &= JPorts[0]->StateAction(0, sm, load, data_only);
 if(JPorts[1]->StateAction)
  ret &= JPorts[1]->StateAction(1, sm, load, data_only);
 if(FCExp && FCExp->StateAction)
  ret &= FCExp->StateAction(sm, load, data_only);

 if(load)
 {

 }
 return(ret);
}

void NESINPUT_Init(void)
{
 FSDisable = MDFN_GetSettingB("nes.nofs");
}



void MDFNNES_DoSimpleCommand(int cmd)
{
 switch(cmd)
 {
   case MDFNNPCMD_FDSINSERT: MDFN_FDSInsert(-1);break;
   case MDFNNPCMD_FDSSELECT: MDFN_FDSSelect();break;
   case MDFNNPCMD_FDSEJECT: MDFN_FDSEject();break;
   case MDFNNPCMD_VSUNICOIN: MDFN_VSUniCoin(); break;
   case MDFNNPCMD_VSUNIDIP0 ... (MDFNNPCMD_VSUNIDIP0 + 7): MDFN_VSUniToggleDIP(cmd - MDFNNPCMD_VSUNIDIP0);break;
   case MDFNNPCMD_POWER: PowerNES();break;
   case MDFNNPCMD_RESET: ResetNES();break;
 }
}

void MDFNI_FDSSelect(void)
{ 
 MDFN_QSimpleCommand(MDFNNPCMD_FDSSELECT);
} 

int MDFNI_FDSInsert(int oride)
{ 
 MDFN_QSimpleCommand(MDFNNPCMD_FDSINSERT);
 return(1);
}


int MDFNI_FDSEject(void)
{
        MDFN_QSimpleCommand(MDFNNPCMD_FDSEJECT);
        return(1);
}

void MDFNI_VSUniToggleDIP(int w)
{
 MDFN_QSimpleCommand(MDFNNPCMD_VSUNIDIP0 + w);
}

void MDFNI_VSUniCoin(void)
{
	MDFN_QSimpleCommand(MDFNNPCMD_VSUNICOIN);
}

void MDFNNES_Reset(void)
{
	MDFN_QSimpleCommand(MDFNNPCMD_RESET);
}
  
void MDFNNES_Power(void)
{
        MDFN_QSimpleCommand(MDFNNPCMD_POWER);
}

