#include "dma.h"
#include "texture.h"

// Returns the base 2 logarithm of the texture size
// (also a handy way to check for illegal texture sizes)
uint32 TextureLog2(uint32 n)
{
	switch(n)
	{
	case 256:
		return 8;
	case 128:
		return 7;
	case 64:
		return 6;
	case 32:
		return 5;
	default:
		{
			printf("Unsupported texture size\n");
			exit(-1);
		}
	}
}

uint32 TSMFromColourDepth(uint32 depth)
{
	switch(depth)
	{
	case 32:
		return PSMCT32;
	case 8:
		return PSMT8;
	default:
		printf("Unsupported colour depth.\n");
		exit(-1);
	}
}

// Set the valid flag to false until texture loaded
CTexture::CTexture()
{
	m_bValid = false;
}

CTexture::~CTexture()
{
}

// This method will load 24 or 8 bit windows bmp files
bool CTexture::LoadBitmap(const char * strFilename, bool bBlackTransparent, bool bRedAsAlpha)
{
	// Open the file
	FILE * fp = NULL;
	fp = fopen(strFilename, "rb");

	if(fp == NULL)
	{
		printf("Failed to load %s\n", strFilename);
		return false;
	}

	// This struct contains the headers that we will read in.
	BitmapFile bmp;

	// Read in the bitmap header
	fread(&bmp.bitmapfileheader,sizeof(BitmapFileHeader),1,fp);

	// Check that this is a bitmap file and someone isn't trying to read
	// some weird file in.
	if(bmp.bitmapfileheader.bfType != BITMAP_ID)
	{
		printf("%s is not a valid bitmap file\n",strFilename);
		
		fclose(fp);
		return false;
	}

	// Read in the position of the bitmap header, then set the file
	// pointer to it
	fread(&bmp.bitmapinfoheader,sizeof(BitmapInfoHeader),1,fp);

	m_iWidth = bmp.bitmapinfoheader.biWidth;
	m_iHeight = bmp.bitmapinfoheader.biHeight;

	// Make sure that the user isn't trying to load an unsupported colour
	// depth
	assert(bmp.bitmapinfoheader.biBitCount == 8 ||
		   bmp.bitmapinfoheader.biBitCount == 24);

	// We will use PSMT8 for 8 bit images, and PSMCT32 for 24 bit images
	m_iImageDepth = (bmp.bitmapinfoheader.biBitCount == 8) ? 8 : 32;

	// The log2 function has an assert in it that will be fired off if
	// the texture is not a power of 2 or is an unsupported size
	m_l2Width = TextureLog2(m_iWidth);
	m_l2Height = TextureLog2(m_iHeight);

	// The image size in bytes
	int iImageSize;

	// Some image utilities don't set this field, so check first
	if(bmp.bitmapinfoheader.biSizeImage!=0)
		iImageSize = bmp.bitmapinfoheader.biSizeImage;
	else
		iImageSize = bmp.bitmapfileheader.bfSize - bmp.bitmapfileheader.bfOffBits;

	// Next, if we are loading an 8 bit texture we must load
	// a CLUT (Colour look-up table). 
	int clut[256], swizzledclut[256];

	if(bmp.bitmapinfoheader.biBitCount == 8)
	{
		fread(&clut, sizeof(int), 256, fp);

		// Loop through each CLUT entry and convert it from
		// BGR to RGB, and "swizzle" it
		for(int p=0; p<256; p++)
		{	
			if(bRedAsAlpha)
			{
				uint8 * pPtr = (uint8*)&clut[p];
				pPtr[3]= pPtr[2] / 2;
				pPtr[0] = 0xFF;
				pPtr[1] = 0xFF;
				pPtr[2] = 0xFF;
			}
			else
			{
				unsigned char * pPtr = (unsigned char *)&clut[p];
				unsigned char red = pPtr[2];
				pPtr[2] = pPtr[0];
				pPtr[0] = red;

				// Set the alpha to 0 if the colour is black, 0x80 otherwise
				if(clut[p] == 0 && bBlackTransparent)
					pPtr[3] = 0x00;
				else
					pPtr[3] = 0x80;
			}

			// If you look at page 32 of GSUSER_E.pdf you will see
			// a table called "Arrangement of CLUT in IDTEX8" if you
			// look closely you will see that the CLUT is not laid out
			// contiguously. Therefore we must "swizzle" the position so that
			// the clut is placed into memory correctly.
			// (basically what this does is swap bits 3 & 4, see the tutorial
			// doc for more info)
			((uint32 *) swizzledclut)[(p & 231) + ((p & 8) << 1) + ((p & 16) >> 1)] = clut[p];
		}
	}

	// The texture data buffer
	m_iTexAddr = VIFStaticDMA.GetPointer();
	VIFStaticDMA.StartDirect();

	// Get the total size of all of the pixel data
	m_iDataSize = m_iWidth * m_iHeight * (m_iImageDepth / 8);

	assert((m_iDataSize & 0xF) == 0);	// Make sure the texture data size is a quadword multiple.

	// Start on the last line and work our way up the image as
	// bitmaps are stored upside down
	unsigned int uLine = m_iHeight - 1;
	int iColumn = 0;
	int i = 0;

	// Seek the file to the image data
	fseek(fp,(int)(bmp.bitmapfileheader.bfOffBits) + (uLine * m_iWidth * (bmp.bitmapinfoheader.biBitCount / 8)) ,SEEK_SET);

	for(i = 0; i < (m_iWidth * m_iHeight); i++, iColumn++)
	{
		// We have finished a whole row move up one line
		// (we know we have finished a row if i is a multiple
		// of the image width
		if(i > 0 && i % m_iWidth == 0)
		{
			--uLine;
			iColumn = 0;

			fseek(fp,(int)(bmp.bitmapfileheader.bfOffBits) + (uLine * m_iWidth * (bmp.bitmapinfoheader.biBitCount / 8)) ,SEEK_SET);
		}

		if(bmp.bitmapinfoheader.biBitCount == 8)
		{
			// We have to wait until we have 4 pixels to add them to the DMA chain, because 32 bits are the smallest size
			// the DMA class accepts. (we only accept images that have widths that are a multiple of 4 so we don't need to
			// check that)
			if( i % 4 == 0 )
			{
				// Read the pixel in
				int pixel = 0;
				fread(&pixel,4,1,fp);

				VIFStaticDMA.Add32(pixel);
			}
		}
		else	// Must be 24 bit colour
		{
			// Read the pixel in
			uint32 pixel = 0;
			fread(&pixel,1,3,fp);

			if(bRedAsAlpha)
			{
				uint8 * pPtr = (uint8*)&pixel;
				pPtr[3]= pPtr[2] / 2;
				pPtr[0] = 0xFF;
				pPtr[1] = 0xFF;
				pPtr[2] = 0xFF;
			}
			else
			{
				// Swap the red and blue channels
				uint8 * pPtr = (uint8*)&pixel;
				uint8 red = pPtr[2];
				pPtr[2] = pPtr[0];
				pPtr[0] = red;

				// Set the alpha to 0 if the colour is black
				if(pixel == 0 && bBlackTransparent)
					pPtr[3] = 0;
				else
					pPtr[3] = 0x80;
			}

			VIFStaticDMA.Add32(pixel);
		}
	}

	// End the texture data buffer
	VIFStaticDMA.EndDirect();
	VIFStaticDMA.DMARet();

	// And if we have a clut...
	if( m_iImageDepth == 8 )
	{
		// Start a clut buffer
		m_iClutAddr = VIFStaticDMA.GetPointer();

		VIFStaticDMA.StartDirect();

		for(int i = 0; i < 256; i++)
		{
			VIFStaticDMA.Add32(swizzledclut[i]);
		}

		VIFStaticDMA.EndDirect();

		VIFStaticDMA.DMARet();
	}

	// Close the file
	fclose(fp);
	
	// Flag the texture as valid
	m_bValid = true;
	
	return true;
}

// Select the texture for use - it needs to be uploaded first
void CTexture::Select(uint64 Tex0 = 0)
{
	assert(m_bValid);
	
	// Wait for idle state
	VIFDynamicDMA.Add32(FLUSH);
		
	VIFDynamicDMA.StartDirect();
	VIFDynamicDMA.StartAD();
	VIFDynamicDMA.AddAD(0, TEXFLUSH);
	VIFDynamicDMA.AddAD(ALPHA_SET(0, 1, 0, 1, 128), ALPHA_1);
	VIFDynamicDMA.AddAD(TEX1_SET(1, 0, 1, 1, 0, 0, 0), TEX1_1);

	// If the user passed their own TEX0 value, use it.
	// Otherwise just use the default.
	if(Tex0 != 0)
		VIFDynamicDMA.AddAD(Tex0, TEX0_1);
	else
		VIFDynamicDMA.AddAD(m_iTex0, TEX0_1);

	VIFDynamicDMA.EndAD();
	VIFDynamicDMA.EndDirect();
}

uint64 CTexture::Upload(uint32 uGSPage)
{
	assert(m_bValid);
	
	VIFDynamicDMA.Add32(FLUSH);

	uint32 iDstPtr = (uGSPage << 5);
	uint32 iClutPtr = ((uGSPage + 15) << 5);
	uint32 uQSizeClut = (256 >> 2);

	if( m_iImageDepth == 8 )
	{
		// Add the clut
		VIFDynamicDMA.StartDirect();

		VIFDynamicDMA.Add128(GS_GIFTAG_BATCH(4, 0, 0, 0, 
							 GIF_FLG_PACKED, GS_BATCH_1(GIF_REG_A_D)));

		VIFDynamicDMA.Add64(BITBLTBUF_SET(0, 0, 0, iClutPtr, 1, 0));
		VIFDynamicDMA.Add64(BITBLTBUF);

		VIFDynamicDMA.Add64(TRXPOS_SET(0, 0, 0, 0, 0));
		VIFDynamicDMA.Add64(TRXPOS);

		VIFDynamicDMA.Add64(TRXREG_SET(16, 16));
		VIFDynamicDMA.Add64(TRXREG);

		VIFDynamicDMA.Add64(TRXDIR_SET(0));
		VIFDynamicDMA.Add64(TRXDIR);

		VIFDynamicDMA.Add128(GS_GIFTAG_BATCH(uQSizeClut, 0, 0, 0, 
							 GIF_FLG_IMAGE, 0));

		VIFDynamicDMA.EndDirect();

		VIFDynamicDMA.DMACall(m_iClutAddr);
	}

	// Add the texture data itself.
	VIFDynamicDMA.StartDirect();
	VIFDynamicDMA.Add128(GS_GIFTAG_BATCH(4, 0, 0, 0,
						 GIF_FLG_PACKED, GS_BATCH_1(GIF_REG_A_D)));

	// TODO - This bit doesn't seem to work so just set TBW to 4 (256 wide) for now.
	// Problems arise with the texture displayed when in 8 bit mode with a texture width less than 256?
	// Can anybody tell me why? (i'd love to know!)
	int TBW = 4;
	//int TBW = m_iWidth / 64;

	VIFDynamicDMA.Add64(BITBLTBUF_SET(0, 0, 0, iDstPtr, (TBW == 0) ? 1 : TBW, m_iImageDepth == 8 ? PSMT8 : PSMCT32));
	VIFDynamicDMA.Add64(BITBLTBUF);

	VIFDynamicDMA.Add64(TRXPOS_SET(0, 0, 0, 0, 0));
	VIFDynamicDMA.Add64(TRXPOS);

	VIFDynamicDMA.Add64(TRXREG_SET(m_iWidth, m_iHeight));
	VIFDynamicDMA.Add64(TRXREG);

	VIFDynamicDMA.Add64(TRXDIR_SET(0));
	VIFDynamicDMA.Add64(TRXDIR);

	uint32 iQWC = (m_iDataSize) >> 4;

	VIFDynamicDMA.Add128(GS_GIFTAG_BATCH(iQWC, 1, 0, 0, 
						 GIF_FLG_IMAGE, 0));

	VIFDynamicDMA.EndDirect();

	VIFDynamicDMA.DMACall(m_iTexAddr);

	// Generate a default TEX0.
	m_iTex0 = TEX0_SET(iDstPtr, (TBW == 0) ? 1 : TBW, m_iImageDepth == 8 ? PSMT8 : PSMCT32, m_l2Width, m_l2Height, 1, 0, iClutPtr, 0, 0, 0, 1);

	return m_iTex0;
}