/* 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 "system.h"

#include <math.h>
#include <cstring>
#include <cstdlib>


namespace MDFN_IEN_WSWAN
{
	void Memory::Write20(uint32 A, uint8 V)
	{
		uint32 offset, bank;

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

		if(!bank) /*RAM*/
		{
			sys->sound.CheckRAMWrite(offset);
			wsRAM[offset] = V;

			sys->gfx.InvalidByAddr(offset);

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



	uint8 Memory::Read20(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]);
			}
		}
	}

	void Memory::CheckDMA()
	{
		if(DMAControl & 0x80)
		{
			while(DMALength)
			{
				Write20(DMADest, Read20(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 Memory::CheckSoundDMA()
	{
		if(SoundDMAControl & 0x80)
		{
			if(SoundDMALength)
			{
				uint8 zebyte = Read20(SoundDMASource);

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

				if(SoundDMAControl & 0x10)
					sys->sound.Write(0x95, zebyte); // Pick a port, any port?!
				else
					sys->sound.Write(0x89, zebyte);

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

	uint8 Memory::readport(uint32 number)
	{
		number &= 0xFF;

		if(number >= 0x80 && number <= 0x9F)
			return(sys->sound.Read(number));
		else if(number <= 0x3F || (number >= 0xA0 && number <= 0xAF) || (number == 0x60))
			return(sys->gfx.Read(number));
		else if((number >= 0xBA && number <= 0xBE) || (number >= 0xC4 && number <= 0xC8))
			return(sys->eeprom.Read(number));
		else if(number >= 0xCA && number <= 0xCB)
			return(sys->rtc.Read(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(sys->interrupt.Read(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: 
			{
				Lagged = false;
				if (ButtonHook)
					ButtonHook();
				uint8 ret = (ButtonWhich << 4) | ButtonReadLatch;
				return(ret);
			}
		}

		if(number >= 0xC8)
			return language ? 0xD1 : 0xD0;
			//return(0xD0 | language); // is this right?

		return(0);
	}

	void Memory::writeport(uint32 IOPort, uint8 V)
	{
		IOPort &= 0xFF;

		if(IOPort >= 0x80 && IOPort <= 0x9F)
		{
			sys->sound.Write(IOPort, V);
		}
		else if((IOPort >= 0x00 && IOPort <= 0x3F) || (IOPort >= 0xA0 && IOPort <= 0xAF) || (IOPort == 0x60))
		{
			sys->gfx.Write(IOPort, V);
		}
		else if((IOPort >= 0xBA && IOPort <= 0xBE) || (IOPort >= 0xC4 && IOPort <= 0xC8))
			sys->eeprom.Write(IOPort, V);
		else if(IOPort >= 0xCA && IOPort <= 0xCB)
			sys->rtc.Write(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); 
			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: sys->interrupt.Write(IOPort, V); break;

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

		case 0xb5: ButtonWhich = V >> 4;
			// Lagged = false; // why was this being set here?
			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;
		}
	}

	Memory::~Memory()
	{
		if (wsCartROM)
		{
			std::free(wsCartROM);
			wsCartROM = nullptr;
		}
		if (wsSRAM)
		{
			std::free(wsSRAM);
			wsSRAM = nullptr;
		}
	}
	
	void Memory::Init(const SyncSettings &settings)
	{
		char tmpname[17];
		std::memcpy(tmpname, settings.name, 16);
		tmpname[16] = 0;

		language = settings.language;

		// WSwan_EEPROMInit() will also clear wsEEPROM
		sys->eeprom.Init(tmpname, settings.byear, settings.bmonth, settings.bday, settings.sex, settings.blood);

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

	void Memory::Reset()
	{
		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;

		std::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;
	}

	SYNCFUNC(Memory)
	{
		NSS(wsRAM);
		//NSS(rom_size);
		//PSS(wsCartROM, rom_size);
		NSS(sram_size);
		PSS(wsSRAM, sram_size);

		NSS(WSButtonStatus);
		NSS(Lagged);

		NSS(ButtonWhich);
		NSS(ButtonReadLatch);

		NSS(DMASource);
		NSS(DMADest);
		NSS(DMALength);
		NSS(DMAControl);

		NSS(SoundDMASource);
		NSS(SoundDMALength);
		NSS(SoundDMAControl);

		NSS(BankSelector);

		NSS(CommControl);
		NSS(CommData);

		NSS(language);
	}
}
