/* 
 *	Copyright (C) 2007-2009 Gabest
 *	http://www.gabest.org
 *
 *  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, 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 GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#include "stdafx.h"
#include "GPUState.h"

GPUState::GPUState(const CSize& scale)
	: m_mem(scale)
	, s_n(0)
{
	memset(m_status, 0, sizeof(m_status));

	for(int i = 0; i < countof(m_fpGPUStatusCommandHandlers); i++)
	{
		m_fpGPUStatusCommandHandlers[i] = &GPUState::SCH_Null;
	}

	m_fpGPUStatusCommandHandlers[0x00] = &GPUState::SCH_ResetGPU;
	m_fpGPUStatusCommandHandlers[0x01] = &GPUState::SCH_ResetCommandBuffer;
	m_fpGPUStatusCommandHandlers[0x02] = &GPUState::SCH_ResetIRQ;
	m_fpGPUStatusCommandHandlers[0x03] = &GPUState::SCH_DisplayEnable;
	m_fpGPUStatusCommandHandlers[0x04] = &GPUState::SCH_DMASetup;
	m_fpGPUStatusCommandHandlers[0x05] = &GPUState::SCH_StartOfDisplayArea;
	m_fpGPUStatusCommandHandlers[0x06] = &GPUState::SCH_HorizontalDisplayRange;	
	m_fpGPUStatusCommandHandlers[0x07] = &GPUState::SCH_VerticalDisplayRange;
	m_fpGPUStatusCommandHandlers[0x08] = &GPUState::SCH_DisplayMode;
	m_fpGPUStatusCommandHandlers[0x10] = &GPUState::SCH_GPUInfo;

	m_fpGPUPacketHandler[0] = &GPUState::PH_Command;
	m_fpGPUPacketHandler[1] = &GPUState::PH_Polygon;
	m_fpGPUPacketHandler[2] = &GPUState::PH_Line;
	m_fpGPUPacketHandler[3] = &GPUState::PH_Sprite;
	m_fpGPUPacketHandler[4] = &GPUState::PH_Move;
	m_fpGPUPacketHandler[5] = &GPUState::PH_Write;
	m_fpGPUPacketHandler[6] = &GPUState::PH_Read;
	m_fpGPUPacketHandler[7] = &GPUState::PH_Environment;

	Reset();
}

GPUState::~GPUState()
{
}

void GPUState::Reset()
{
	m_env.Reset();

	m_mem.Invalidate(CRect(0, 0, 1024, 512));

	memset(&m_v, 0, sizeof(m_v));
}

void GPUState::Flush()
{
	FlushPrim();
}

void GPUState::SetPrim(GPUReg* r)
{
	if(m_env.PRIM.TYPE != r->PRIM.TYPE)
	{
		ResetPrim();
	}

	GPURegPRIM PRIM = r->PRIM;
	
	PRIM.VTX = 0;

	switch(r->PRIM.TYPE)
	{
	case GPU_POLYGON: 
		PRIM.ai32 = (r->PRIM.ai32 & 0xF7000000) | 3; // TYPE IIP TME ABE TGE
		break;
	case GPU_LINE: 
		PRIM.ai32 = (r->PRIM.ai32 & 0xF2000000) | 2; // TYPE IIP ABE
		PRIM.TGE = 1; // ?
		break; 
	case GPU_SPRITE: 
		PRIM.ai32 = (r->PRIM.ai32 & 0xE7000000) | 2; // TYPE TME ABE TGE
		break; 
	}

	if(m_env.PRIM.ai32 != PRIM.ai32)
	{
		Flush();

		m_env.PRIM = PRIM;
	}
}

void GPUState::SetCLUT(GPUReg* r)
{
	UINT32 mask = 0xFFFF0000; // X Y

	UINT32 value = (m_env.CLUT.ai32 & ~mask) | (r->ai32 & mask);

	if(m_env.CLUT.ai32 != value)
	{
		Flush();

		m_env.CLUT.ai32 = value;
	}
}

void GPUState::SetTPAGE(GPUReg* r)
{
	UINT32 mask = 0x000001FF; // TP ABR TY TX

	UINT32 value = (m_env.STATUS.ai32 & ~mask) | ((r->ai32 >> 16) & mask);

	if(m_env.STATUS.ai32 != value)
	{
		Flush();

		m_env.STATUS.ai32 = value;
	}
}

void GPUState::Invalidate(const CRect& r)
{
	m_mem.Invalidate(r);
}

void GPUState::WriteData(const BYTE* mem, UINT32 size)
{
	GSPerfMonAutoTimer pmat(m_perfmon);

	size <<= 2;

	m_write.Append(mem, size);

	int i = 0;

	while(i < m_write.bytes)
	{
		GPUReg* r = (GPUReg*)&m_write.buff[i];

		int ret = (this->*m_fpGPUPacketHandler[r->PACKET.TYPE])(r, (m_write.bytes - i) >> 2);

		if(ret == 0) return; // need more data

		i += ret << 2;
	}

	m_write.Remove(i);
}

void GPUState::ReadData(BYTE* mem, UINT32 size)
{
	GSPerfMonAutoTimer pmat(m_perfmon);

	int remaining = m_read.bytes - m_read.cur;

	int bytes = (int)size << 2;

	if(bytes > remaining)
	{
		// ASSERT(0);

		TRACE(_T("WARNING: ReadData\n"));

		// memset(&mem[remaining], 0, bytes - remaining);

		bytes = remaining;
	}

	memcpy(mem, &m_read.buff[m_read.cur], bytes);

	m_read.cur += bytes;

	if(m_read.cur >= m_read.bytes)
	{
		m_env.STATUS.IMG = 0;
	}
}

void GPUState::WriteStatus(UINT32 status)
{
	GSPerfMonAutoTimer pmat(m_perfmon);

	UINT32 b = status >> 24;

	m_status[b] = status;

	(this->*m_fpGPUStatusCommandHandlers[b])((GPUReg*)&status);	
}

UINT32 GPUState::ReadStatus()
{
	GSPerfMonAutoTimer pmat(m_perfmon);

	m_env.STATUS.LCF = ~m_env.STATUS.LCF; // ?

	return m_env.STATUS.ai32;
}

void GPUState::Freeze(GPUFreezeData* data)
{
	data->status = m_env.STATUS.ai32;
	memcpy(data->control, m_status, 256 * 4);
	m_mem.ReadRect(CRect(0, 0, 1024, 512), data->vram);
}

void GPUState::Defrost(const GPUFreezeData* data)
{
	m_env.STATUS.ai32 = data->status;
	memcpy(m_status, data->control, 256 * 4);
	m_mem.WriteRect(CRect(0, 0, 1024, 512), data->vram);

	for(int i = 0; i <= 8; i++)
	{
		WriteStatus(m_status[i]);
	}
}

void GPUState::SCH_Null(GPUReg* r)
{
	ASSERT(0);
}

void GPUState::SCH_ResetGPU(GPUReg* r)
{
	Reset();
}

void GPUState::SCH_ResetCommandBuffer(GPUReg* r)
{
	// ?
}

void GPUState::SCH_ResetIRQ(GPUReg* r)
{
	// ?
}

void GPUState::SCH_DisplayEnable(GPUReg* r)
{
	m_env.STATUS.DEN = r->DEN.DEN;
}

void GPUState::SCH_DMASetup(GPUReg* r)
{
	m_env.STATUS.DMA = r->DMA.DMA;
}

void GPUState::SCH_StartOfDisplayArea(GPUReg* r)
{
	m_env.DAREA = r->DAREA;
}

void GPUState::SCH_HorizontalDisplayRange(GPUReg* r)
{
	m_env.DHRANGE = r->DHRANGE;
}

void GPUState::SCH_VerticalDisplayRange(GPUReg* r)
{
	m_env.DVRANGE = r->DVRANGE;
}

void GPUState::SCH_DisplayMode(GPUReg* r)
{
	m_env.STATUS.WIDTH0 = r->DMODE.WIDTH0;
	m_env.STATUS.HEIGHT = r->DMODE.HEIGHT;
	m_env.STATUS.ISPAL = r->DMODE.ISPAL;
	m_env.STATUS.ISRGB24 = r->DMODE.ISRGB24;
	m_env.STATUS.ISINTER = r->DMODE.ISINTER;
	m_env.STATUS.WIDTH1 = r->DMODE.WIDTH1;
}

void GPUState::SCH_GPUInfo(GPUReg* r)
{
	UINT32 value = 0;

	switch(r->GPUINFO.PARAM)
	{
	case 0x2: 
		value = m_env.TWIN.ai32; 
		break;
	case 0x0:
	case 0x1:
	case 0x3: 
		value = m_env.DRAREATL.ai32; 
		break;
	case 0x4: 
		value = m_env.DRAREABR.ai32; 
		break;
	case 0x5: 
	case 0x6: 
		value = m_env.DROFF.ai32; 
		break;
	case 0x7: 
		value = 2; 
		break;
	case 0x8: 
	case 0xf: 
		value = 0xBFC03720; // ?
		break;
	default: 
		ASSERT(0); 
		break;
	}

	m_read.RemoveAll();
	m_read.Append((BYTE*)&value, 4);
	m_read.cur = 0;
}

int GPUState::PH_Command(GPUReg* r, int size)
{
	switch(r->PACKET.OPTION)
	{
	case 0: // ???

		return 1;

	case 1: // clear cache

		return 1;

	case 2: // fillrect
	
		if(size < 3) return 0;

		Flush();

		CRect r2;

		r2.left = r[1].XY.X;
		r2.top = r[1].XY.Y;
		r2.right = r2.left + r[2].XY.X;
		r2.bottom = r2.top + r[2].XY.Y;

		WORD c = (WORD)(((r[0].RGB.R >> 3) << 10) | ((r[0].RGB.R >> 3) << 5) | (r[0].RGB.R >> 3));

		m_mem.FillRect(r2, c);

		Invalidate(r2);

		Dump(_T("f"));

		return 3;
	}

	ASSERT(0);

	return 1;
}

int GPUState::PH_Polygon(GPUReg* r, int size)
{
	int required = 1;

	int vertices = r[0].POLYGON.VTX ? 4 : 3;

	required += vertices;

	if(r[0].POLYGON.TME) required += vertices;

	if(r[0].POLYGON.IIP) required += vertices - 1;	

	if(size < required) return 0;

	//

	SetPrim(r);

	if(r[0].POLYGON.TME)
	{
		SetCLUT(&r[2]);

		SetTPAGE(&r[r[0].POLYGON.IIP ? 5 : 4]);
	}

	//

	GPUVertex v[4];

	for(int i = 0, j = 0; j < vertices; j++)
	{
		v[j].RGB = r[r[0].POLYGON.IIP ? i : 0].RGB;

		if(j == 0 || r[0].POLYGON.IIP) i++;

		v[j].XY = r[i++].XY;
		
		if(r[0].POLYGON.TME)
		{
			v[j].UV.X = r[i].UV.U;
			v[j].UV.Y = r[i].UV.V;

			i++;
		}
	}

	for(int i = 0; i <= vertices - 3; i++)
	{
		for(int j = 0; j < 3; j++)
		{
			m_v = v[i + j];

			VertexKick();
		}
	}

	//

	return required;
}

int GPUState::PH_Line(GPUReg* r, int size)
{
	int required = 1;

	int vertices = 0;

	if(r->LINE.PLL)
	{
		required++;

		for(int i = 1; i < size; i++)
		{
			if(r[i].ai32 == 0x55555555)
			{
				vertices = i - 1;
			}
		}

		if(vertices < 2)
		{
			return 0;
		}
	}
	else
	{
		vertices = 2;
	}

	required += vertices;

	if(r->LINE.IIP) required += vertices - 1;

	//

	SetPrim(r);

	//

	for(int i = 0, j = 0; j < vertices; j++)
	{
		if(j >= 2) VertexKick();

		m_v.RGB = r[r[0].LINE.IIP ? i : 0].RGB;

		if(j == 0 || r[0].LINE.IIP) i++;

		m_v.XY = r[i++].XY;

		VertexKick();
	}

	//

	return required;
}

int GPUState::PH_Sprite(GPUReg* r, int size)
{
	int required = 2;

	if(r[0].SPRITE.TME) required++;
	if(r[0].SPRITE.SIZE == 0) required++;

	if(size < required) return 0;

	//

	SetPrim(r);

	if(r[0].SPRITE.TME)
	{
		SetCLUT(&r[2]);
	}

	//

	int i = 0;

	m_v.RGB = r[i++].RGB;

	m_v.XY = r[i++].XY;
	
	if(r[0].SPRITE.TME)
	{
		m_v.UV.X = r[i].UV.U;
		m_v.UV.Y = r[i].UV.V;

		i++;
	}

	VertexKick();

	int w = 0;
	int h = 0;

	switch(r[0].SPRITE.SIZE)
	{
	case 0: w = r[i].XY.X; h = r[i].XY.Y; i++; break;
	case 1: w = h = 1; break;
	case 2: w = h = 8; break;
	case 3: w = h = 16; break;
	default: __assume(0);
	}

	m_v.XY.X += w;
	m_v.XY.Y += h;

	if(r[0].SPRITE.TME)
	{
		m_v.UV.X += w;
		m_v.UV.Y += h;
	}

	VertexKick();

	//

	return required;
}

int GPUState::PH_Move(GPUReg* r, int size)
{
	if(size < 4) return 0;

	Flush();

	CPoint src, dst;

	src.x = r[1].XY.X;
	src.y = r[1].XY.Y;

	dst.x = r[2].XY.X;
	dst.y = r[2].XY.Y;

	int w = r[3].XY.X;
	int h = r[3].XY.Y;

	m_mem.MoveRect(src, dst, w, h);

	Invalidate(CRect(dst, CSize(w, h)));

	// Dump(_T("m"));

	return 4;
}

int GPUState::PH_Write(GPUReg* r, int size)
{
	if(size < 3) return 0;

	int w = r[2].XY.X;
	int h = r[2].XY.Y;

	int required = 3 + ((w * h + 1) >> 1);

	if(size < required) return 0;

	Flush();

	CRect r2;

	r2.left = r[1].XY.X;
	r2.top = r[1].XY.Y;
	r2.right = r2.left + w;
	r2.bottom = r2.top + h;

	m_mem.WriteRect(r2, (const WORD*)&r[3]);

	Invalidate(r2);

	Dump(_T("w"));

	m_perfmon.Put(GSPerfMon::Swizzle, w * h * 2);

	return required;
}

int GPUState::PH_Read(GPUReg* r, int size)
{
	if(size < 3) return 0;

	Flush();

	int w = r[2].XY.X;
	int h = r[2].XY.Y;

	CRect r2;

	r2.left = r[1].XY.X;
	r2.top = r[1].XY.Y;
	r2.right = r2.left + w;
	r2.bottom = r2.top + h;

	m_read.bytes = ((w * h + 1) & ~1) * 2;
	m_read.cur = 0;
	m_read.Reserve(m_read.bytes);

	m_mem.ReadRect(r2, (WORD*)m_read.buff);

	Dump(_T("r"));

	m_env.STATUS.IMG = 1;

	return 3;
}

int GPUState::PH_Environment(GPUReg* r, int size)
{
	Flush(); // TODO: only call when something really changes

	switch(r->PACKET.OPTION)
	{
	case 1: // draw mode setting

		m_env.STATUS.TX = r->MODE.TX;
		m_env.STATUS.TY = r->MODE.TY;
		m_env.STATUS.ABR = r->MODE.ABR;
		m_env.STATUS.TP = r->MODE.TP;
		m_env.STATUS.DTD = r->MODE.DTD;
		m_env.STATUS.DFE = r->MODE.DFE;

		return 1;

	case 2: // texture window setting 

		m_env.TWIN = r->TWIN;

		return 1;

	case 3: // set drawing area top left

		m_env.DRAREATL = r->DRAREA;

		return 1;

	case 4: // set drawing area bottom right

		m_env.DRAREABR = r->DRAREA;

		return 1;

	case 5: // drawing offset

		m_env.DROFF = r->DROFF;

		return 1;

	case 6: // mask setting

		m_env.STATUS.MD = r->MASK.MD;
		m_env.STATUS.ME = r->MASK.ME;

		return 1;
	}

	ASSERT(0);

	return 1;
}

//

GPUState::Buffer::Buffer()
{
	bytes = 0;
	maxbytes = 4096;
	buff = (BYTE*)_aligned_malloc(maxbytes, 16);
	cur = 0;
}

GPUState::Buffer::~Buffer()
{
	_aligned_free(buff);
}

void GPUState::Buffer::Reserve(int size)
{
	if(size > maxbytes)
	{
		maxbytes = (maxbytes + size + 1023) & ~1023;

		buff = (BYTE*)_aligned_realloc(buff, maxbytes, 16);
	}
}

void GPUState::Buffer::Append(const BYTE* src, int size)
{
	Reserve(bytes + (int)size);

	memcpy(&buff[bytes], src, size);

	bytes += size;
}

void GPUState::Buffer::Remove(int size)
{
	ASSERT(size <= bytes);

	if(size < bytes)
	{
		memmove(&buff[0], &buff[size], bytes - size);

		bytes -= size;
	}
	else
	{
		bytes = 0;
	}

	#ifdef DEBUG
	memset(&buff[bytes], 0xff, maxbytes - bytes);
	#endif
}

void GPUState::Buffer::RemoveAll()
{
	bytes = 0;
}
