/* Cygne
 *
 * Copyright notice for this file:
 *  Copyright (C) 2002 Dox dox@space.pl
 *
 * 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 "wswan.h"
#include "gfx.h"
#include "memory.h"
#include "sound.h"
#include "eeprom.h"
#include "rtc.h"
#include "v30mz.h"
#include "../mempatcher.h"
#include <time.h>
#include <math.h>

uint32 wsRAMSize;
uint8 wsRAM[65536];
static uint8 *wsSRAM = NULL;

uint8 *wsCartROM;
static uint32 sram_size;
uint32 eeprom_size;

static uint8 ButtonWhich, ButtonReadLatch;

static uint32 DMASource, DMADest;
static uint16 DMALength;
static uint8 DMAControl;

static uint32 SoundDMASource;
static uint16 SoundDMALength;
static uint8 SoundDMAControl;

static uint8 BankSelector[4];

static uint8 CommControl, CommData;

extern uint16 WSButtonStatus;

void WSwan_writemem20(uint32 A, uint8 V)
{
 uint32 offset, bank;

 offset = A & 0xffff;
 bank = (A>>16) & 0xF;

 if(!bank) /*RAM*/
 {
  WSwan_SoundCheckRAMWrite(offset);
  wsRAM[offset] = V;

  WSWan_TCacheInvalidByAddr(offset);

  if(offset>=0xfe00) /*WSC palettes*/
   WSwan_GfxWSCPaletteRAMWrite(offset, V);
 }
 else if(bank == 1) /* SRAM */
 {	 
  if(sram_size)
  {
   wsSRAM[(offset | (BankSelector[1] << 16)) & (sram_size - 1)] = V;
  }
 }
}



uint8 WSwan_readmem20(uint32 A)
{
 uint32	offset, bank;

 offset = A & 0xFFFF;
 bank = (A >> 16) & 0xF;
  
 switch(bank)
 {
	case 0:  return wsRAM[offset];
	case 1:  if(sram_size)
		 {
		  return wsSRAM[(offset | (BankSelector[1] << 16)) & (sram_size - 1)];
		 }
		 else
		  return(0);

	case 2:
	case 3:  return wsCartROM[offset+((BankSelector[bank]&((rom_size>>16)-1))<<16)];

	default: 
		{
		 uint8 bank_num = ((BankSelector[0] & 0xF) << 4) | (bank & 0xf);
		 bank_num &= (rom_size >> 16) - 1;
		 return(wsCartROM[(bank_num << 16) | offset]);
		}
 }
}

static void ws_CheckDMA(void)
{
 if(DMAControl & 0x80)
 {
  while(DMALength)
  {
   WSwan_writemem20(DMADest, WSwan_readmem20(DMASource));

   DMASource++; // = ((DMASource + 1) & 0xFFFF) | (DMASource & 0xFF0000);
   //if(!(DMASource & 0xFFFF)) puts("Warning: DMA source bank crossed.");
   DMADest = ((DMADest + 1) & 0xFFFF) | (DMADest & 0xFF0000);
   DMALength--;
  }
 }
 DMAControl &= ~0x80;
}

void WSwan_CheckSoundDMA(void)
{
 if(SoundDMAControl & 0x80)
 {
  if(SoundDMALength)
  {
   uint8 zebyte = WSwan_readmem20(SoundDMASource);

   if(SoundDMAControl & 0x08)
    zebyte ^= 0x80;

   if(SoundDMAControl & 0x10)
    WSwan_SoundWrite(0x95, zebyte); // Pick a port, any port?!
   else
    WSwan_SoundWrite(0x89, zebyte);

   SoundDMASource++; // = ((SoundDMASource + 1) & 0xFFFF) | (SoundDMASource & 0xFF0000);
   //if(!(SoundDMASource & 0xFFFF)) puts("Warning:  Sound DMA source bank crossed.");
   SoundDMALength--;
  }
  if(!SoundDMALength)
   SoundDMAControl &= ~0x80;
 }
}

uint8 WSwan_readport(uint32 number)
{
  number &= 0xFF;

  if(number >= 0x80 && number <= 0x9F)
   return(WSwan_SoundRead(number));
  else if(number <= 0x3F || (number >= 0xA0 && number <= 0xAF) || (number == 0x60))
   return(WSwan_GfxRead(number));
  else if((number >= 0xBA && number <= 0xBE) || (number >= 0xC4 && number <= 0xC8))
   return(WSwan_EEPROMRead(number));
  else if(number >= 0xCA && number <= 0xCB)
   return(WSwan_RTCRead(number));
  else switch(number)
  {
   //default: printf("Read: %04x\n", number); break;
   case 0x40: return(DMASource >> 0);
   case 0x41: return(DMASource >> 8);
   case 0x42: return(DMASource >> 16);

   case 0x43: return(DMADest >> 16);
   case 0x44: return(DMADest >> 0);
   case 0x45: return(DMADest >> 8);

   case 0x46: return(DMALength >> 0);
   case 0x47: return(DMALength >> 8);

   case 0x48: return(DMAControl);

   case 0xB0:
   case 0xB2:
   case 0xB6: return(WSwan_InterruptRead(number));

   case 0xC0: return(BankSelector[0] | 0x20);
   case 0xC1: return(BankSelector[1]);
   case 0xC2: return(BankSelector[2]);
   case 0xC3: return(BankSelector[3]);

   case 0x4a: return(SoundDMASource >> 0);
   case 0x4b: return(SoundDMASource >> 8);
   case 0x4c: return(SoundDMASource >> 16);
   case 0x4e: return(SoundDMALength >> 0);
   case 0x4f: return(SoundDMALength >> 8);
   case 0x52: return(SoundDMAControl);

   case 0xB1: return(CommData);

   case 0xb3: 
	     {
	      uint8 ret = CommControl & 0xf0;

	      if(CommControl & 0x80)
	       ret |= 0x4; // Send complete

	      return(ret);
	     }
   case 0xb5: 
	     {
	      uint8 ret = (ButtonWhich << 4) | ButtonReadLatch;
  	      return(ret);
	     }
 }

 if(number >= 0xC8)
  return(0xD1);

 return(0);
}

void WSwan_writeport(uint32 IOPort, uint8 V)
{
 IOPort &= 0xFF;

	if(IOPort >= 0x80 && IOPort <= 0x9F)
	{
	 WSwan_SoundWrite(IOPort, V);
	}
	else if((IOPort >= 0x00 && IOPort <= 0x3F) || (IOPort >= 0xA0 && IOPort <= 0xAF) || (IOPort == 0x60))
	{
	 WSwan_GfxWrite(IOPort, V);
	}
	else if((IOPort >= 0xBA && IOPort <= 0xBE) || (IOPort >= 0xC4 && IOPort <= 0xC8))
	 WSwan_EEPROMWrite(IOPort, V);
	else if(IOPort >= 0xCA && IOPort <= 0xCB)
	 WSwan_RTCWrite(IOPort, V);
	else switch(IOPort)
	{
		//default: printf("%04x %02x\n", IOPort, V); break;

		case 0x40: DMASource &= 0xFFFF00; DMASource |= (V << 0); break;
		case 0x41: DMASource &= 0xFF00FF; DMASource |= (V << 8); break;
		case 0x42: DMASource &= 0x00FFFF; DMASource |= ((V & 0x0F) << 16); break;

		case 0x43: DMADest &= 0x00FFFF; DMADest |= ((V & 0x0F) << 16); break;
		case 0x44: DMADest &= 0xFFFF00; DMADest |= (V << 0); break;
		case 0x45: DMADest &= 0xFF00FF; DMADest |= (V << 8); break;

		case 0x46: DMALength &= 0xFF00; DMALength |= (V << 0); break;
		case 0x47: DMALength &= 0x00FF; DMALength |= (V << 8); break;

		case 0x48: DMAControl = V;
			   //if(V&0x80) 
			   // printf("DMA%02x: %08x %08x %08x\n", V, DMASource, DMADest, DMALength); 
			   ws_CheckDMA(); 
			   break;

                case 0x4a: SoundDMASource &= 0xFFFF00; SoundDMASource |= (V << 0); break;
                case 0x4b: SoundDMASource &= 0xFF00FF; SoundDMASource |= (V << 8); break;
                case 0x4c: SoundDMASource &= 0x00FFFF; SoundDMASource |= (V << 16); break;
		//case 0x4d: break; // Unused?
                case 0x4e: SoundDMALength &= 0xFF00; SoundDMALength |= (V << 0); break;
                case 0x4f: SoundDMALength &= 0x00FF; SoundDMALength |= (V << 8); break;
		//case 0x50: break; // Unused?
		//case 0x51: break; // Unused?
		case 0x52: SoundDMAControl = V; 
			   //if(V & 0x80) printf("Sound DMA: %02x, %08x %08x\n", V, SoundDMASource, SoundDMALength);
			   break;

	case 0xB0:
	case 0xB2:
	case 0xB6: WSwan_InterruptWrite(IOPort, V); break;

	case 0xB1: CommData = V; break;
	case 0xB3: CommControl = V & 0xF0; break;

	case 0xb5: ButtonWhich = V >> 4;
		   ButtonReadLatch = 0;

                   if(ButtonWhich & 0x4) /*buttons*/
		    ButtonReadLatch |= ((WSButtonStatus >> 8) << 1) & 0xF;

		   if(ButtonWhich & 0x2) /* H/X cursors */
		    ButtonReadLatch |= WSButtonStatus & 0xF;

		   if(ButtonWhich & 0x1) /* V/Y cursors */
		    ButtonReadLatch |= (WSButtonStatus >> 4) & 0xF;
                   break;

   case 0xC0: BankSelector[0] = V & 0xF; break;
   case 0xC1: BankSelector[1] = V; break;
   case 0xC2: BankSelector[2] = V; break;
   case 0xC3: BankSelector[3] = V; break;
 }
}

#ifdef WANT_DEBUGGER
static void GetAddressSpaceBytes(const char *name, uint32 Address, uint32 Length, uint8 *Buffer)
{
 if(!strcmp(name, "ram"))
 {
  while(Length--)
  {
   Address &= wsRAMSize - 1;
   *Buffer = wsRAM[Address];
   Address++;
   Buffer++;
  }
 }
 else if(!strcmp(name, "physical"))
 {
  while(Length--)
  {
   Address &= 0xFFFFF;
   *Buffer = WSwan_readmem20(Address);
   Address++;
   Buffer++;
  }
 }
}

static void PutAddressSpaceBytes(const char *name, uint32 Address, uint32 Length, uint32 Granularity, bool hl, const uint8 *Buffer)
{
 if(!strcmp(name, "ram"))
 {
  while(Length--)
  {
   Address &= wsRAMSize - 1;
   wsRAM[Address] = *Buffer;
   WSWan_TCacheInvalidByAddr(Address);
   if(Address >= 0xfe00)
    WSwan_GfxWSCPaletteRAMWrite(Address, *Buffer);

   Address++;
   Buffer++;
  }
 }
 else if(!strcmp(name, "physical"))
 {
  while(Length--)
  {
   uint32 offset, bank;

   Address &= 0xFFFFF;

   offset = Address & 0xFFFF;
   bank = (Address >> 16) & 0xF;
  
   switch(bank)
   {
    case 0:  wsRAM[offset & (wsRAMSize - 1)] = *Buffer;
	     WSWan_TCacheInvalidByAddr(offset & (wsRAMSize - 1));
	     if(Address >= 0xfe00)
	      WSwan_GfxWSCPaletteRAMWrite(offset & (wsRAMSize - 1), *Buffer);
	     break;
    case 1:  if(sram_size)
	       wsSRAM[(offset | (BankSelector[1] << 16)) & (sram_size - 1)] = *Buffer;
	     break;
    case 2:
    case 3:  wsCartROM[offset+((BankSelector[bank]&((rom_size>>16)-1))<<16)] = *Buffer;
	     break;

    default:
            {
             uint8 bank_num = ((BankSelector[0] & 0xF) << 4) | (bank & 0xf);
             bank_num &= (rom_size >> 16) - 1;
             wsCartROM[(bank_num << 16) | offset] = *Buffer;
            }
	    break;
   }

   Address++;
   Buffer++;
  }
 }
}

static RegType MiscRegs[] =
{
 { "ROMBBSLCT", "ROM Bank Base Selector for 64KiB banks 0x4-0xF", 1 },
 { "BNK1SLCT", "???", 1 },
 { "BNK2SLCT", "ROM Bank Selector for 64KiB bank 0x2", 1 },
 { "BNK3SLCT", "ROM Bank Selector for 64KiB bank 0x3", 1 },
 { "IStatus", "Interrupt Status", 1 },
 { "IEnable", "Interrupt Enable", 1 },
 { "IVectorBase", "Interrupt Vector Base", 1 },
 { "", "", 0 },
};

static uint32 GetRegister(const std::string &oname, std::string *special)
{
 if(oname == "ROMBBSLCT")
 {
  if(special)
  {
   char tmpstr[256];
   snprintf(tmpstr, 256, "((0x%02x * 0x100000) %% 0x%08x) + 20 bit address = 0x%08x + 20 bit address", BankSelector[0], rom_size, (BankSelector[0] * 0x100000) & (rom_size - 1));
   *special = std::string(tmpstr);
  }
  return(BankSelector[0]);
 }
 if(oname == "BNK1SLCT")
  return(BankSelector[1]);
 if(oname == "BNK2SLCT")
  return(BankSelector[2]);
 if(oname == "BNK3SLCT")
  return(BankSelector[3]);

 return WSwan_InterruptGetRegister(oname, special);
}

static void SetRegister(const std::string &oname, uint32 value)
{
 if(oname == "ROMBBSLCT")
  BankSelector[0] = value;
 else if(oname == "BNK1SLCT")
  BankSelector[1] = value;
 if(oname == "BNK2SLCT")
  BankSelector[2] = value;
 if(oname == "BNK3SLCT")
  BankSelector[3] = value;
 else
  WSwan_InterruptSetRegister(oname, value);
}

static RegGroupType MiscRegsGroup =
{
 MiscRegs,
 GetRegister,
 SetRegister,
};


#endif

void WSwan_MemoryKill(void)
{
 if(sram_size || eeprom_size)
 {
  gzFile fp;

  fp = gzopen(MDFN_MakeFName(MDFNMKF_SAV, 0, "sav").c_str(), "wb");
  if(fp)
  {
   if(eeprom_size)
    gzwrite(fp, wsEEPROM, eeprom_size);
   if(sram_size)
    gzwrite(fp, wsSRAM, sram_size);
   gzclose(fp);
  }
 }

 if(wsSRAM)
 {
  free(wsSRAM);
  wsSRAM = NULL;
 }
}

void WSwan_MemoryInit(bool IsWSC, uint32 ssize)
{
 wsRAMSize = 65536;
 sram_size = ssize;

 MDFNMP_AddRAM(wsRAMSize, 0x00000, wsRAM);

 if(sram_size)
  MDFNMP_AddRAM(sram_size, 0x10000, wsSRAM);

 #ifdef WANT_DEBUGGER
 MDFNDBG_AddRegGroup(&MiscRegsGroup);
 MDFNDBG_AddASpace(GetAddressSpaceBytes, PutAddressSpaceBytes, "physical", "CPU Physical", 20);
 MDFNDBG_AddASpace(GetAddressSpaceBytes, PutAddressSpaceBytes, "ram", "RAM", (int)(log(wsRAMSize) / log(2)));
 #endif

 uint16 byear = MDFN_GetSettingUI("wswan.byear");
 uint8 bmonth = MDFN_GetSettingUI("wswan.bmonth");
 uint8 bday = MDFN_GetSettingUI("wswan.bday");
 std::string sex_s = MDFN_GetSettingS("wswan.sex");
 std::string blood_s = MDFN_GetSettingS("wswan.blood");
 uint8 sex = 1, blood = 1;

 if(!strcasecmp(sex_s.c_str(), "m") || !strcasecmp(sex_s.c_str(), "male"))
  sex = 1;
 else if(!strcasecmp(sex_s.c_str(), "f") || !strcasecmp(sex_s.c_str(), "female"))
  sex = 2;
 else
 {
  int tmp_num;
  if(sscanf(sex_s.c_str(), "%u", &tmp_num) == 1)
  {
   sex = tmp_num;
  }
 }

 if(!strcasecmp(blood_s.c_str(), "a"))
  blood = 1;
 else if(!strcasecmp(blood_s.c_str(), "b"))
  blood = 2;
 else if(!strcasecmp(blood_s.c_str(), "o"))
  blood = 3;
 else if(!strcasecmp(blood_s.c_str(), "ab"))
  blood = 4;
 else
 {
  int tmp_num;
  if(sscanf(blood_s.c_str(), "%u", &tmp_num) == 1)
  {
   blood = tmp_num;
  }
 }

 WSwan_EEPROMInit(MDFN_GetSettingS("wswan.name").c_str(), byear, bmonth, bday, sex, blood);

 if(sram_size)
 {
  wsSRAM = (uint8*)malloc(sram_size);
  memset(wsSRAM, 0, sram_size);
 }

 if(sram_size || eeprom_size)
 {
  gzFile savegame_fp;

  savegame_fp = gzopen(MDFN_MakeFName(MDFNMKF_SAV, 0, "sav").c_str(), "rb");
  if(savegame_fp)
  {
   if(eeprom_size)
    gzread(savegame_fp, wsEEPROM, eeprom_size);
   if(sram_size)
    gzread(savegame_fp, wsSRAM, sram_size);
   gzclose(savegame_fp);
  }
 }


}

void WSwan_MemoryReset(void)
{
 memset(&wsRAM,0,65536);

 wsRAM[0x75AC] = 0x41;
 wsRAM[0x75AD] = 0x5F;
 wsRAM[0x75AE] = 0x43;
 wsRAM[0x75AF] = 0x31;
 wsRAM[0x75B0] = 0x6E;
 wsRAM[0x75B1] = 0x5F;
 wsRAM[0x75B2] = 0x63;
 wsRAM[0x75B3] = 0x31;

 memset(BankSelector, 0, sizeof(BankSelector));
 ButtonWhich = 0;
 ButtonReadLatch = 0;
 DMASource = 0;
 DMADest = 0;
 DMALength = 0;
 DMAControl = 0;

 SoundDMASource = 0;
 SoundDMALength = 0;
 SoundDMAControl = 0;

 CommControl = 0;
 CommData = 0;
}

int WSwan_MemoryStateAction(StateMem *sm, int load, int data_only)
{
 SFORMAT StateRegs[] =
 {
  SFARRAYN(wsRAM, 65536, "RAM"),
  SFARRAYN(sram_size ? wsSRAM : NULL, sram_size, "SRAM"),
  SFVAR(ButtonWhich),
  SFVAR(ButtonReadLatch),
  SFVAR(WSButtonStatus),
  SFVAR(DMASource),
  SFVAR(DMADest),
  SFVAR(DMALength),
  SFVAR(DMAControl),

  SFVAR(SoundDMASource),
  SFVAR(SoundDMALength),
  SFVAR(SoundDMAControl),

  SFVAR(CommControl),
  SFVAR(CommData),

  SFARRAY(BankSelector, 4),

  SFEND
 };

 if(!MDFNSS_StateAction(sm, load, data_only, StateRegs, "MEMR"))
  return(0);

 if(load)
 {
  for(uint32 A = 0xfe00; A <= 0xFFFF; A++)
  {
   WSwan_GfxWSCPaletteRAMWrite(A, wsRAM[A]);
  }
 }
 return(1);
}

