// Meteor - A Nintendo Gameboy Advance emulator
// Copyright (C) 2009-2011 Philippe Daouadi
//
// 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 3 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, see <http://www.gnu.org/licenses/>.

#include "ameteor/io.hpp"
#include "ameteor/dma.hpp"
#include "globals.hpp"
#include "ameteor.hpp"
#include <cstring>

#include "debug.hpp"

#define W8(add, val) \
	m_iomem[(add) & 0xFFF] = (val)
#define W16(add, val) \
	*(uint16_t*)(m_iomem + ((add) & 0xFFF)) = (val)
#define W32(add, val) \
	*(uint32_t*)(m_iomem + ((add) & 0xFFF)) = (val)

namespace AMeteor
{
	Io::Io ()
	{
		m_iomem = new uint8_t[IO_SIZE];
		Reset ();
	}

	Io::~Io ()
	{
		delete [] m_iomem;
	}

	void Io::Reset ()
	{
		std::memset(m_iomem, 0, IO_SIZE);

		// TODO use clears intead

		// TODO DISPCNT should be 0x80 by default
		// TODO do it also for clears
		// TODO lcd should draw white lines when hblank forced
		// TODO when passing disabling hblank forced, lcd should start drawing from
		// first line
		W16(SOUNDBIAS, 0x0200); // default value
		W16(KEYINPUT, 0x03FF); // all keys released
		W8(HALTCNT, 0xFF); // normal mode (internal)
		W16(DISPSTAT, 0x0004); // vcount match (since default vcount is 0)
		W16(BG2PA, 0x0100);
		W16(BG2PD, 0x0100);
		W16(BG3PA, 0x0100);
		W16(BG3PD, 0x0100);
		W16(RCNT, 0x8000); // we start in general purpose mode
	}

	void Io::ClearSio ()
	{
		// TODO
		W16(RCNT, 0x8000);
	}

	void Io::ClearSound ()
	{
		// TODO
	}

	void Io::ClearOthers ()
	{
		// FIXME !! shouldn't we call Write*() ?
		// lcd
		for (uint8_t i = 0x0; i < 0x56; i += 2)
			Write16(i, 0x0000);
		// dma
		for (uint8_t i = 0xB0; i < 0xE0; i += 4)
			Write32(i, 0x0000);
		// FIXME : should timers be set to 0 too ? (vba does not)
		W8(HALTCNT, 0xFF); // normal mode (internal)
		W16(IE, 0x0000);
		W16(IF, 0x0000);
		W16(IME, 0x0000);
		Write16(WAITCNT, 0x0000);
		W16(BG2PA, 0x0100);
		W16(BG2PD, 0x0100);
		W16(BG3PA, 0x0100);
		W16(BG3PD, 0x0100);
	}

	// TODO implement unreadable or write-only io
	uint8_t Io::Read8 (uint32_t add)
	{
		if ((add & 0xFFE) == KEYINPUT)
			keyupdate_bizhawk();
		//debug ("IO Read8 at " << IOS_ADD << add << " of " << IOS_ADD << (int)*(uint8_t*)(m_iomem + (add & 0xFFF)));
		if ((add & 0xFF0) == 0x100)
			switch (add & 0xF)
			{
				case 0x0:
				case 0x4:
				case 0x8:
				case 0xC:
					met_abort("Misaligned reading of timers");
			}
		return m_iomem[add & 0xFFF];
	}

	uint16_t Io::Read16 (uint32_t add)
	{
		if ((add & 0xFFE) == KEYINPUT)
			keyupdate_bizhawk();
		//debug ("IO Read16 at " << IOS_ADD << add << " of " << IOS_ADD << *(uint16_t*)(m_iomem + (add & 0xFFF)));
		// special case, reading timers
		if ((add & 0xFF0) == 0x100)
			switch (add & 0xF)
			{
				case 0x0: return TIMER0.GetCount();
				case 0x4: return TIMER1.GetCount();
				case 0x8: return TIMER2.GetCount();
				case 0xC: return TIMER3.GetCount();
			}
		return *(uint16_t*)(m_iomem + (add & 0xFFF));
	}

	uint32_t Io::Read32 (uint32_t add)
	{
		if ((add & 0xFFC) == KEYINPUT)
			keyupdate_bizhawk();
		//debug ("IO Read32 at " << IOS_ADD << add << " of " << IOS_ADD << *(uint32_t*)(m_iomem + (add & 0xFFF)));
		// special case, reading timers
		if ((add & 0xFF0) == 0x100)
			switch (add & 0xF)
			{
				case 0x0: return TIMER0.GetCount();
				case 0x4: return TIMER1.GetCount();
				case 0x8: return TIMER2.GetCount();
				case 0xC: return TIMER3.GetCount();
			}
		return *(uint32_t*)(m_iomem + (add & 0xFFF));
	}

	void Io::Write8 (uint32_t add, uint8_t val)
	{
		//debug ("IO Write8 at " << IOS_ADD << add << " of " << IOS_ADD << (int)val);
		switch (add & 0xFFF)
		{
			case NR10+1:
			case NR52+1:
			case NR52+2:
			case NR52+3:
				break;
			case NR10:
			case NR11:
			case NR13:
			case NR21:
			case NR23:
			case NR41:
			case NR43:
			case NR50:
			case NR51:
			case SOUNDCNT_H:
				W8(add, val);
				break;
			case NR12:
				W8(add, val);
				if (!(val & (0xF << 4)))
					SOUND.ResetSound1Envelope();
				break;
			case NR14:
				W8(add, val & 0xC7);
				if (val & (0x1 << 7))
					SOUND.ResetSound1();
				break;
			case NR22:
				W8(add, val);
				if (!(val & (0xF << 4)))
					SOUND.ResetSound2Envelope();
				break;
			case NR24:
				W8(add, val & 0xC7);
				if (val & (0x1 << 7))
					SOUND.ResetSound2();
				break;
			case NR42:
				W8(add, val);
				if (!(val & (0xF << 4)))
					SOUND.ResetSound4Envelope();
				break;
			case NR44:
				W8(add, val & 0xC7);
				if (val & (0x1 << 7))
					SOUND.ResetSound4();
				break;
			case SOUNDCNT_H+1:
				W8(add, val);
				SOUND.UpdateCntH1(val);
				break;
			case NR52:
				// this will also reset the sound on flags
				W8(add, val & 0x80);
				break;
			case POSTFLG:
				// FIXME is this right, i have no idea about why i should do that
				if (val)
					val &= 0xFE;
				W8(add, val);
				break;
			case HALTCNT:
				W8(add, val);
				break;
			default:
				//W8(add, val);
				// TODO make a function which will apply masks to IO memory and trigger
				// the update functions, this function will be called by write8 and
				// write16
#if 1
				add &= 0xFFF;
				if (add % 2)
					Write16(add & ~0x1, (val << 8) | m_iomem[add & ~0x1]);
				else
					Write16(add, (m_iomem[add | 0x1] << 8) | val);
#endif
				break;
		}
	}

	void Io::Write16 (uint32_t add, uint16_t val)
	{
		//debug ("IO Write16 at " << IOS_ADD << add << " of " << IOS_ADD << val);
		//*(uint16_t*)(m_iomem + (add & 0xFFF)) = val;

		switch (add & 0xFFF)
		{
			case KEYINPUT:
			case VCOUNT:
				break;
			case DMA0CNT_L:
			case DMA1CNT_L:
			case DMA2CNT_L:
			case DMA3CNT_L:
				//W16(add, val);
				DMA.SetReload(((add & 0xFFF) - DMA0CNT_L) / DMA_CHANSIZE, val);
				break;
			case KEYCNT:
				W16(add, val & 0xC3FF);
				break;
			case IME:
				W16(add, val & 0x0001);
				CPU.CheckInterrupt();
				break;
			case IE:
				W16(add, val & 0x3FFF);
				CPU.CheckInterrupt();
				break;
			case IF:
				*((uint16_t*)(m_iomem+IF)) ^= (val & (*((uint16_t*)(m_iomem+IF))));
				CPU.CheckInterrupt();
				break;
			case BG0CNT:
				W16(add, val & 0xFFCF);
				LCD.UpdateBg0Cnt(val & 0xFFCF);
				break;
			case BG1CNT:
				W16(add, val & 0xFFCF);
				LCD.UpdateBg1Cnt(val & 0xFFCF);
				break;
			case BG2CNT:
				W16(add, val & 0xFFCF);
				LCD.UpdateBg2Cnt(val & 0xFFCF);
				break;
			case BG3CNT:
				W16(add, val & 0xFFCF);
				LCD.UpdateBg3Cnt(val & 0xFFCF);
				break;
			case DISPSTAT:
				// the first 3 bits are read only and they are used by the lcd
				// NOTE : we are in LITTLE ENDIAN
				// FIXME : if vcount setting has changed to current vcount, we should
				// update the vcounter flag and eventually trigger an interrupt
				W16(add, (val & 0xFFF8) | (m_iomem[add & 0xFFF] & 0x07));
				break;
			// The BG*OFS are write-only, we don't need to W16()
			// You do if you're ever going to load them from a savestate...
			case BG0HOFS:
				W16(add, val & 0x1FF);
				LCD.UpdateBg0XOff(val & 0x1FF);
				break;
			case BG0VOFS:
				W16(add, val & 0x1FF);
				LCD.UpdateBg0YOff(val & 0x1FF);
				break;
			case BG1HOFS:
				W16(add, val & 0x1FF);
				LCD.UpdateBg1XOff(val & 0x1FF);
				break;
			case BG1VOFS:
				W16(add, val & 0x1FF);
				LCD.UpdateBg1YOff(val & 0x1FF);
				break;
			case BG2HOFS:
				W16(add, val & 0x1FF);
				LCD.UpdateBg2XOff(val & 0x1FF);
				break;
			case BG2VOFS:
				W16(add, val & 0x1FF);
				LCD.UpdateBg2YOff(val & 0x1FF);
				break;
			case BG3HOFS:
				W16(add, val & 0x1FF);
				LCD.UpdateBg3XOff(val & 0x1FF);
				break;
			case BG3VOFS:
				W16(add, val & 0x1FF);
				LCD.UpdateBg3YOff(val & 0x1FF);
				break;
			case BG2X_H:
				val &= 0x0FFF;
			case BG2X_L:
				W16(add, val);
				LCD.UpdateBg2RefX(IO.DRead32(Io::BG2X_L));
				break;
			case BG2Y_H:
				val &= 0x0FFF;
			case BG2Y_L:
				W16(add, val);
				LCD.UpdateBg2RefY(IO.DRead32(Io::BG2Y_L));
				break;
			case BG3X_H:
				val &= 0x0FFF;
			case BG3X_L:
				W16(add, val);
				LCD.UpdateBg3RefX(IO.DRead32(Io::BG3X_L));
				break;
			case BG3Y_H:
				val &= 0x0FFF;
			case BG3Y_L:
				W16(add, val);
				LCD.UpdateBg3RefY(IO.DRead32(Io::BG3Y_L));
				break;
			case WIN0H:
			case WIN1H:
			case WIN0V:
			case WIN1V:
			case WININ:
			case WINOUT:
				W16(add, val);
				break;
			case BLDCNT:
				W16(add, val);
				break;
			case MOSAIC:
				W16(add, val);
				break;
			case DISPCNT:
				W16(add, val);
				LCD.UpdateDispCnt(val);
				break;
			case DMA0CNT_H:
			case DMA1CNT_H:
			case DMA2CNT_H:
			case DMA3CNT_H:
				W16(add, val & 0xFFE0);
				DMA.UpdateCnt(((add & 0xFFF) - DMA0CNT_H) / DMA_CHANSIZE);
				break;
			case WAITCNT:
				W16(add, val & 0xDFFF);
				MEM.UpdateWaitStates (val & 0xDFFF);
				break;
			case SOUND1CNT_L:
			case SOUND1CNT_H:
			case SOUND1CNT_X:
			case SOUND2CNT_L:
			case SOUND2CNT_H:
			case SOUND4CNT_L:
			case SOUND4CNT_H:
			case SOUNDCNT_L:
			case SOUNDCNT_H:
			case SOUNDCNT_X:
			case POSTFLG:
				Write8(add, val & 0xFF);
				Write8(add + 1, val >> 8);
				break;
			case TM0CNT_L:
				TIMER0.SetReload(val);
				break;
			case TM1CNT_L:
				TIMER1.SetReload(val);
				break;
			case TM2CNT_L:
				TIMER2.SetReload(val);
				break;
			case TM3CNT_L:
				TIMER3.SetReload(val);
				break;
			case TM0CNT_H:
				W16(add, val & 0x00C7);
				TIMER0.Reload();
				break;
			case TM1CNT_H:
				W16(add, val & 0x00C7);
				TIMER1.Reload();
				break;
			case TM2CNT_H:
				W16(add, val & 0x00C7);
				TIMER2.Reload();
				break;
			case TM3CNT_H:
				W16(add, val & 0x00C7);
				TIMER3.Reload();
				break;
			default:
				//met_abort("Unknown IO at " << IOS_ADD << add);
				W16(add, val);
				break;
		}
	}

	void Io::Write32 (uint32_t add, uint32_t val)
	{
		//debug ("IO Write32 at " << IOS_ADD << add << " of " << IOS_ADD << val);
		switch (add & 0xFF)
		{
			case DMA1DAD:
			case DMA0SAD:
			case DMA1SAD:
			case DMA2SAD:
			case DMA3SAD:
			case DMA0DAD:
			case DMA2DAD:
			case DMA3DAD:
				W32(add, val);
				break;
			case BG0HOFS:
			case BG1HOFS:
			case BG2HOFS:
			case BG3HOFS:
				Write16(add, val & 0xFFFF);
				Write16(add+2, val >> 16);
				break;
			case BG2X_L:
				W32(add, val & 0x0FFFFFFF);
				LCD.UpdateBg2RefX(IO.DRead32(Io::BG2X_L));
				break;
			case BG2Y_L:
				W32(add, val & 0x0FFFFFFF);
				LCD.UpdateBg2RefY(IO.DRead32(Io::BG2Y_L));
				break;
			case BG3X_L:
				W32(add, val & 0x0FFFFFFF);
				LCD.UpdateBg3RefX(IO.DRead32(Io::BG3X_L));
				break;
			case BG3Y_L:
				W32(add, val & 0x0FFFFFFF);
				LCD.UpdateBg3RefY(IO.DRead32(Io::BG3Y_L));
				break;
			case BG2PA:
			case BG2PC:
			case BG3PA:
			case BG3PC:
			case WIN0H:
			case WIN0V:
			case WININ:
				Write16(add, val & 0xFFFF);
				Write16(add+2, val >> 16);
				break;
			case DMA0CNT_L:
			case DMA1CNT_L:
			case DMA2CNT_L:
			case DMA3CNT_L:
				Write16(add, val & 0xFFFF);
				Write16(add+2, val >> 16);
				break;
			case FIFO_A:
			case FIFO_B:
				// TODO
				break;
			default:
				//met_abort("Unknown IO at " << IOS_ADD << add);
				//*(uint32_t*)(m_iomem + (add & 0xFFF)) = val;
				Write16(add, val & 0xFFFF);
				Write16(add+2, val >> 16);
				break;
		}
	}

	bool Io::SaveState (std::ostream& stream)
	{
		SS_WRITE_DATA(m_iomem, IO_SIZE);

		return true;
	}

	bool Io::LoadState (std::istream& stream)
	{
		SS_READ_DATA(m_iomem, IO_SIZE);

		return true;
	}
}
