// 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/dma.hpp"
#include "ameteor/io.hpp"
#include "ameteor/memory.hpp"
#include "globals.hpp"
#include "ameteor.hpp"

#include "debug.hpp"
#include <sstream>

namespace AMeteor
{
	void Dma::Reset ()
	{
		for (Channel* chan = m_chans; chan < m_chans+4; ++chan)
		{
			chan->src = 0;
			chan->dest = 0;
			chan->count = 0;
			chan->control.w = 0;
		}
		m_graphic = 0;
	}

	void Dma::UpdateCnt (uint8_t channum)
	{
		Channel& chan = m_chans[channum];

		if (chan.control.w !=
				IO.DRead16(Io::DMA0CNT_H + channum * Io::DMA_CHANSIZE))
		{
			if (!chan.control.b.enable &&
					(IO.DRead16(Io::DMA0CNT_H + channum * Io::DMA_CHANSIZE)
					 & (0x1 << 15)))
				// if we changed enable from 0 to 1
			{
				chan.dest = IO.DRead32(Io::DMA0DAD + channum * Io::DMA_CHANSIZE);
				if (channum == 3)
					chan.dest &= 0x0FFFFFFF;
				else
					chan.dest &= 0x07FFFFFF;

				chan.src = IO.DRead32(Io::DMA0SAD + channum * Io::DMA_CHANSIZE);
				if (channum == 0)
					chan.src &= 0x07FFFFFF;
				else
					chan.src &= 0x0FFFFFFF;

				chan.count = chan.reload;
				if (channum != 3)
					chan.count &= 0x3FFF;

				chan.control.w =
					IO.DRead16(Io::DMA0CNT_H + channum * Io::DMA_CHANSIZE);

				Check(channum, Immediately);
			}
			else
				chan.control.w =
					IO.DRead16(Io::DMA0CNT_H + channum * Io::DMA_CHANSIZE);

			if (chan.control.b.start == Special)
			{
				switch (channum)
				{
					case 0:
						met_abort("prohibited !");
						break;
					case 1:
					case 2:
						// sound dma
						if (chan.dest != 0x040000A0 && chan.dest != 0x040000A4)
							met_abort("Special DMA 1 or 2 with unauthorized address : "
									<< IOS_ADD << chan.dest);
						if (!chan.control.b.repeat)
							met_abort("Special DMA 1 or 2 without repeat");

						// 4 words of 32 bits
						chan.count = 4;
						chan.control.b.type = 1;
						// no increment
						chan.control.b.dest = 2;
						break;
					case 3:
						met_abort("not implemented");
						break;
				}
			}
		}
	}

	void Dma::Check (uint8_t channum, uint8_t reason)
	{
		register Channel::Control cnt = m_chans[channum].control;

		if (cnt.b.enable &&
				cnt.b.start == reason)
			Process(channum);
	}

	void Dma::Process (uint8_t channum)
	{
		Channel& chan = m_chans[channum];

		int8_t s_inc, d_inc;
		s_inc = d_inc = 2; // increment or increment reload

		if (chan.control.b.src == 1) // decrement
			s_inc = -2;
		else if (chan.control.b.src == 2) // fixed
			s_inc = 0;
		else if (chan.control.b.src == 3)
			met_abort("prohibited");

		if (chan.control.b.dest == 1)
			d_inc = -2;
		else if (chan.control.b.dest == 2)
			d_inc = 0;
		//else if (chan.control.b.dest == 3)
		// same as 0, but we do something at the end

		if (chan.control.b.type) // 32 bits transfer
		{
			s_inc <<= 1;
			d_inc <<= 1;
		}

		if (chan.count == 0)
		{
			if (channum != 3)
				chan.count = 0x4000;
			// 0x10000 doesn't fill in 16 bits, we treat this case in the Copy() call
		}

		//printf("DMA%hhu from %08X to %08X of %hu %s\n", channum, chan.src, chan.dest, chan.count, chan.control.b.type ? "words":"halfwords");
		//if (i > debut)
		debug ("DMA" << IOS_NOR << (int)channum << ", from " << IOS_ADD << chan.src
				<< " to " << IOS_ADD << chan.dest
				<< " of " << IOS_NOR << (chan.count ? chan.count : 0x10000)
				<< (chan.control.b.type ? " words" : " halfwords"));
		if (traceenabled)
		{
			std::stringstream ss;
			ss << "DMA" << IOS_NOR << (int)channum << ", from " << IOS_ADD << chan.src
				<< " to " << IOS_ADD << chan.dest
				<< " of " << IOS_NOR << (chan.count ? chan.count : 0x10000)
				<< (chan.control.b.type ? " words" : " halfwords");
			trace_bizhawk(ss.str());
		}
#if 0
		if (channum == 3 && (chan.dest >> 24) == 0x0D || (chan.src >> 24) == 0x0D)
		{
			if (chan.control.b.type)
				met_abort("Word copy for EEPROM DMA3");
			if (d_inc != 2 || s_inc != 2)
				met_abort("Source or destination not incremeting in EEPROM DMA3");
			if ((chan.dest >> 24) == 0x0D)
				MEM.WriteEepromDma(chan.src, chan.count ? chan.count : 0x10000);
			else if ((chan.src >> 24) == 0x0D)
				MEM.ReadEepromDma(chan.dest, chan.count ? chan.count : 0x10000);
			chan.src += chan.count * 2;
			chan.dest += chan.count * 2;
		}
		else
#endif
		if (channum == 3 && (chan.dest >> 24) == 0x0D)
		{
			if (chan.control.b.type)
				met_abort("Word copy for EEPROM DMA3");
			if (d_inc != 2 || s_inc != 2)
				met_abort("Source or destination not incremeting in EEPROM DMA3");
			MEM.WriteEepromDma(chan.src, chan.count);
			chan.src += chan.count * 2;
			chan.dest += chan.count * 2;
		}
		else
			Copy(chan.src, chan.dest, s_inc, d_inc,
					chan.count ? chan.count : 0x10000, chan.control.b.type);

		if (chan.control.b.type)
		{
			CYCLES32NSeq(chan.src, chan.count);
			CYCLES32NSeq(chan.dest, chan.count);
			ICYCLES(2);
		}
		else
		{
			CYCLES16NSeq(chan.src, chan.count);
			CYCLES16NSeq(chan.dest, chan.count);
			ICYCLES(2);
		}
		uint32_t d = chan.dest >> 24;
		uint32_t s = chan.src >> 24;
		if (d >= 0x08 && d <= 0x0D && s >= 0x08 && s <= 0x0D)
			// if both source and destination are in GamePak
			ICYCLES(2);

		if (chan.control.b.irq)
			CPU.SendInterrupt(0x1 << (8 + channum));

		if (chan.control.b.dest == 3)
		{
			chan.dest = IO.DRead32(Io::DMA0DAD + channum * Io::DMA_CHANSIZE);
			if (channum == 3)
				chan.dest &= 0x0FFFFFFF;
			else
				chan.dest &= 0x07FFFFFF;
		}

		// if repeat but not sound dma
		//if (!((channum == 1 || channum == 2) && chan.control.b.start == Special) && chan.control.b.repeat)
		if (((channum != 1 && channum != 2) || chan.control.b.start != Special)
				&& chan.control.b.repeat)
		{
			chan.count = chan.reload;
		}

		if (!chan.control.b.repeat || chan.control.b.start == Immediately)
		{
			chan.control.b.enable = 0;
			IO.GetRef16(Io::DMA0CNT_H + channum * Io::DMA_CHANSIZE) &= 0x7FFF;
		}
	}

	void Dma::Copy (uint32_t& src, uint32_t& dest, int8_t s_inc, int8_t d_inc,
			uint32_t count, bool word)
	{
		uint32_t basedest = dest;

		if (word)
		{
			src &= 0xFFFFFFFC;
			dest &= 0xFFFFFFFC;
		}
		else
		{
			src &= 0xFFFFFFFE;
			dest &= 0xFFFFFFFE;
		}

		// sound
		if (dest == 0x040000A0)
		{
			SOUND.SendDigitalA((uint8_t*)MEM.GetRealAddress(src));
			src += 4*4;
			if (d_inc != 0)
				met_abort("dinc != 0 on dma sound, should not happen");
			return;
		}
		if (dest == 0x040000A4)
		{
			SOUND.SendDigitalB((uint8_t*)MEM.GetRealAddress(src));
			src += 4*4;
			if (d_inc != 0)
				met_abort("dinc != 0 on dma sound, should not happen");
			return;
		}

		if ((dest >> 24) >= 0x05 &&
				(dest >> 24) <= 0x07)
			m_graphic = true;

		if (word)
		{
			for (uint32_t cur = 0; cur < count; ++cur)
			{
				MEM.Write32(dest, MEM.Read32(src));
				src += s_inc;
				dest += d_inc;
			}
		}
		else
		{
			for (uint32_t cur = 0; cur < count; ++cur)
			{
				MEM.Write16(dest, MEM.Read16(src));
				src += s_inc;
				dest += d_inc;
			}
		}

		m_graphic = false;
		//if ((basedest >> 24) == 0x07)
		//	LCD.OamWrite(basedest, dest);
	}

	bool Dma::SaveState (std::ostream& stream)
	{
		SS_WRITE_ARRAY(m_chans);
		// no need to save or load m_graphic since we shouldn't save or load during
		// a dma

		return true;
	}

	bool Dma::LoadState (std::istream& stream)
	{
		SS_READ_ARRAY(m_chans);

		return true;
	}
}
