// 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/graphics/bglayer.hpp"
#include "ameteor/graphics/screen.hpp"
#include "../debug.hpp"

namespace AMeteor
{
	namespace Graphics
	{
		BgLayer::BgLayer (int8_t num, Memory& memory, Io& io, uint16_t* pPalette) :
			m_memory(memory),
			m_io(io),
			m_num(num),
			m_priority(0),
			m_cnt(0),
			m_xoff(0),
			m_yoff(0),
			m_tWidth(32),
			m_tHeight(32),
			m_rWidth(16),
			m_rHeight(16),
			m_mapAdd(0x06000000),
			m_charAdd(0x06000000),
			m_pPalette(pPalette)
		{
		}

		BgLayer::~BgLayer ()
		{
		}

		void BgLayer::DrawLine0 (uint8_t line, uint16_t* ptr)
		{
			uint8_t mosH;
			if (m_cnt & (0x1 << 6))
			{
				mosH = m_io.DRead8(Io::MOSAIC);
				uint8_t mosV = mosH >> 4;
				mosH &= 0x0F;

				++mosV;
				if (mosH) // let it be 0 if it's 0 (=> no mosaic)
					++mosH;

				line /= mosV;
				line *= mosV;
			}
			else
				mosH = 0;

			uint16_t* pMap = (uint16_t*)m_memory.GetRealAddress(m_mapAdd), *pTile;
			uint8_t* pChar = m_memory.GetRealAddress(m_charAdd), *tpChar;
			uint8_t i = 0;

			// theses are the coordinates on the layer from which we will draw a line
			uint16_t xoff = m_xoff % (m_tWidth * 8);
			uint16_t yoff = (m_yoff + line) % (m_tHeight * 8);

			// theses are the coordinates on the tile of the pixels we are drawing
			// NOTE : if the tile is horizontally flipped tileX = 8 - tileX, thus
			// when we draw it is ALWAYS incremented no matter how the tile is
			// flipped
			// NOTE : tileY is NOT redefined if the tile is flipped vertically
			int8_t tileX = xoff % 8, tileY;
			bool flipH;

			// sets pTile to the tile we must draw first
			// +---+---+
			// | 1 | 2 |
			// +---+---+
			// | 3 | 4 |
			// +---+---+
			if (yoff >= 256)
				if (m_tWidth == 64)
					if (xoff >= 256)
						// zone 4
						pTile = pMap + 32*32 + 32*32 + 32*32
							+ 32 * ((yoff-256)/8) + (xoff-256)/8;
					else
						// zone 3 (large map)
						pTile = pMap + 32*32 + 32*32 + 32 * ((yoff-256)/8) + xoff/8;
				else
					// zone 3 (no large map)
					pTile = pMap + 32*32 + 32 * ((yoff-256)/8) + xoff/8;
			else
				if (xoff >= 256)
					// zone 2
					pTile = pMap + 32*32 + 32 * (yoff/8) + (xoff-256)/8;
				else
					// zone 1
					pTile = pMap + 32 * (yoff/8) + xoff/8;

			if (m_hicolor)
			{
				while (i < 240)
				{
					flipH = (bool)(*pTile & (0x1 << 10));

					// if the tile is vertically fliped
					if (*pTile & (0x1 << 11))
						tileY = 7 - (yoff % 8);
					else
						tileY = yoff % 8;

					if (flipH)
						tpChar = pChar + (*pTile & 0x3FF)*8*8 + tileY*8 + 7 - tileX;
					else
						tpChar = pChar + (*pTile & 0x3FF)*8*8 + tileY*8 + tileX;

					while (tileX < 8)
					{
						if (mosH && i % mosH)
							*ptr = ptr[-1];
						else
						{
							if (*tpChar)
								*ptr = m_pPalette[*tpChar] | 0x8000;
							else
								*ptr = 0x0;
						}
						if (flipH)
							--tpChar;
						else
							++tpChar;

						++ptr;
						++tileX;
						++i;

						if (i == 240)
							return;
					}
					tileX = 0;
					++pTile;

					// support for big maps and wrap around
					if (!((pTile - pMap) % 32))
						if (m_tWidth == 32)
							pTile -= 32;
						else
							if (yoff >= 256 && pTile - pMap > 32*32 * 3
									|| yoff < 256 && pTile - pMap > 32*32)
								pTile -= 32*33;
							else
								pTile += 32*31;
				}
			}
			else
			{
				uint16_t* pPalette;
				uint8_t colorInd;

				// for each pixel of the line we are drawing
				while (i < 240)
				{
					pPalette = m_pPalette + ((*pTile >> 12) & 0xF) * 16;
					flipH = (bool)(*pTile & (0x1 << 10));

					// if the tile is vertically fliped
					if (*pTile & (0x1 << 11))
						tileY = 7 - (yoff % 8);
					else
						tileY = yoff % 8;

					if (flipH)
						tpChar = pChar + ((*pTile & 0x3FF)*8*8 + tileY*8 + 7 - tileX)/2;
					else
						tpChar = pChar + ((*pTile & 0x3FF)*8*8 + tileY*8 + tileX)/2;

					// we draw until the end of the tile or the line
					while (tileX < 8)
					{
						if (flipH)
							if (tileX % 2)
							{
								colorInd = *tpChar & 0xF;
								--tpChar;
							}
							else
								colorInd = *tpChar >> 4;
						else
							if (tileX % 2)
							{
								colorInd = *tpChar >> 4;
								++tpChar;
							}
							else
								colorInd = *tpChar & 0xF;

						if (mosH && i % mosH)
							*ptr = ptr[-1];
						else
						{
							if (colorInd)
								*ptr = pPalette[colorInd] | 0x8000;
							else
								*ptr = 0x0;
						}

						++ptr;
						++tileX;
						++i;

						// if this was the last pixel of the line
						if (i == 240)
							return;
					}

					// we have finished drawing a tile, so we go to the next tile
					tileX = 0;
					++pTile;

					// support for big maps and wrap around
					if (!((pTile - pMap) % 32))
						if (m_tWidth == 32)
							pTile -= 32;
						else
							if (yoff >= 256 && pTile - pMap > 32*32 * 3
									|| yoff < 256 && pTile - pMap > 32*32)
								pTile -= 32*33;
							else
								pTile += 32*31;
				}
			}
		}

		void BgLayer::DrawLine2 (uint16_t* ptr,
				int32_t curX, int32_t curY,
				int16_t dx, int16_t dy)
		{
			if (!m_hicolor)
			{
				//FIXME is this possible ??
				//this seems to be impossible, but we should not crash since some games
				//do it
				//puts("rotated layer with 16 colors");
				//abort();
				// XXX
				//it seems now that we should not care about this flag, we draw in 256
				//colors, that's all
				//return;
			}
			int32_t intX, intY;

			uint16_t colorInd;

			uint8_t* pMap = (uint8_t*)m_memory.GetRealAddress(m_mapAdd);
			uint8_t* pChar = m_memory.GetRealAddress(m_charAdd);

			for (uint8_t x = 0; x < 240; ++x, ++ptr, curX += dx, curY += dy)
			{
				intX = curX >> 8;
				intY = curY >> 8;

				// if we are off layer
				if (intX < 0 || intX >= m_rWidth*8)
					if (m_cnt & (0x1 << 13))
					{
						// NOTE : in C++, the modulus can be negative, this is because in
						// x86, the IDIV instruction gives a remainder of the same sign of
						// the dividend
						curX %= m_rWidth*8 << 8;
						if (curX < 0)
							curX += m_rWidth*8 << 8;
						intX = curX >> 8;
					}
					else
						continue;
				if (intY < 0 || intY >= m_rHeight*8)
					if (m_cnt & (0x1 << 13))
					{
						curY %= m_rHeight*8 << 8;
						if (curY < 0)
							curY += m_rHeight*8 << 8;
						intY = curY >> 8;
					}
					else
						continue;

				colorInd = pChar[pMap[intY / 8 * m_rWidth + intX / 8] * 8 * 8
					+ (intY % 8) * 8 + intX % 8];

				if (colorInd)
					*ptr = m_pPalette[colorInd] | 0x8000;
				else
					*ptr = 0x0;
			}
		}

		void BgLayer::DrawLine3 (uint16_t* ptr,
				int32_t curX, int32_t curY,
				int16_t dx, int16_t dy)
		{
			int32_t intX, intY;

			uint16_t* pChar = (uint16_t*) m_memory.GetRealAddress(0x06000000);

			for (uint8_t x = 0; x < 240; ++x, ++ptr, curX += dx, curY += dy)
			{
				intX = curX >> 8;
				intY = curY >> 8;

				// if we are off layer
				if (intX < 0 || intX >= 240)
					/*
					if (m_cnt & (0x1 << 13))
					{
						// NOTE : in C++, the modulus can be negative
						intX %= 240;
						if (intX < 0)
							intX += 240;
					}
					else*/
						continue;
				if (intY < 0 || intY >= 160)
					/*
					if (m_cnt & (0x1 << 13))
					{
						intY %= 160;
						if (intY < 0)
							intY += 160;
					}
					else*/
						continue;

				*ptr = pChar[intY * 240 + intX] | 0x8000;
			}
		}

		void BgLayer::DrawLine4 (uint8_t line, uint16_t* ptr,
				int32_t curX, int32_t curY,
				int16_t dx, int16_t dmx, int16_t dy, int16_t dmy, bool frame1)
		{
			uint8_t mosH;
			if (m_cnt & (0x1 << 6))
			{
				// TODO horizontal mosaic not implemented
				mosH = m_io.DRead8(Io::MOSAIC);
				uint8_t mosV = mosH >> 4;
				mosH &= 0x0F;

				++mosV;
				if (mosH) // let it be 0 if it's 0 (=> no mosaic)
					++mosH;

				uint8_t back = line % mosV;

				curX -= back*dmx;
				curY -= back*dmy;
			}
			else
				mosH = 0;

			int32_t intX, intY;

			uint16_t colorInd;

			uint8_t* pChar =
				m_memory.GetRealAddress(frame1 ? 0x0600A000 : 0x06000000);

			for (uint8_t x = 0; x < 240; ++x, ++ptr, curX += dx, curY += dy)
			{
				if (mosH && x % mosH)
				{
					*ptr = ptr[-1];
					continue;
				}

				intX = curX >> 8;
				intY = curY >> 8;

				// if we are off layer
				if (intX < 0 || intX >= 240)
					if (m_cnt & (0x1 << 13))
					{
						// NOTE : in C++, the modulus can be negative
						intX %= 240;
						if (intX < 0)
							intX += 240;
					}
					else
						continue;
				if (intY < 0 || intY >= 160)
					if (m_cnt & (0x1 << 13))
					{
						intY %= 160;
						if (intY < 0)
							intY += 160;
					}
					else
						continue;

				colorInd = pChar[intY * 240 + intX];

				if (colorInd)
					*ptr = m_pPalette[colorInd] | 0x8000;
				else
					*ptr = 0x0;
			}
		}

		void BgLayer::DrawLine5 (uint16_t* ptr,
				int32_t curX, int32_t curY,
				int16_t dx, int16_t dy, bool frame1)
		{
			int32_t intX, intY;

			uint16_t* pChar = (uint16_t*) m_memory.GetRealAddress(frame1 ? 0x0600A000 : 0x06000000);

			for (uint8_t x = 0; x < 240; ++x, ++ptr, curX += dx, curY += dy)
			{
				intX = curX >> 8;
				intY = curY >> 8;

				// if we are off layer
				if (intX < 0 || intX >= 160)
						continue;
				if (intY < 0 || intY >= 128)
						continue;

				*ptr = pChar[intY * 160 + intX] | 0x8000;
			}
		}

		void BgLayer::UpdateCnt (uint16_t cnt)
		{
			if (m_cnt == cnt)
				return;

			switch (cnt >> 14)
			{
				case 0:
					m_tWidth = m_tHeight = 32;
					m_rWidth = m_rHeight = 16;
					break;
				case 1:
					m_tWidth = 64;
					m_tHeight = 32;
					m_rWidth = m_rHeight = 32;
					break;
				case 2:
					m_tWidth = 32;
					m_tHeight = 64;
					m_rWidth = m_rHeight = 64;
					break;
				case 3:
					m_tWidth = m_tHeight = 64;
					m_rWidth = m_rHeight = 128;
					break;
			}

			m_priority = (cnt & 0x3);
			m_charAdd = 0x06000000 + ((cnt >> 2) & 0x3) * 0x4000;
			m_hicolor = cnt & (0x1 << 7);
			m_mapAdd = 0x06000000 + ((cnt >> 8) & 0x1F) * 0x0800;

			m_cnt = cnt;
		}
	}
}
