// A nice manager to wrap up all the SPS2 memory handling etc. into a nice easy to use
// class.
#include "sps2wrap.h"
#include "PS2Defines.h"
#include "dma.h"

// We have to create this to use the singleton. (it calls the constructor
// which sets up the ms_Singleton member).
CSPS2Manager SPS2Manager_SingletonInit;

static int iScreenClear = 0;

// Constructor
CSPS2Manager::CSPS2Manager()
{
	// Write the disk cache to the HDD for safety
	printf("Writing cache to HDD\n");
	system("sync");
	
	// The array to keep track of how much memory is allocated in each page
	m_iaFreeMemInPage = 0;
}

// Destructor
CSPS2Manager::~CSPS2Manager()
{
	sps2UScreenShutdown();
	sps2Release(m_iSPS2Desc);

	// Free up all of the memory that was allocated in Initialise
	if(m_iaFreeMemInPage)
	{
		delete [] m_iaFreeMemInPage;
		m_iaFreeMemInPage = 0;
	}
}

// Set up SPS2, and allocate all of the SPS2 memory that will be
// used
void CSPS2Manager::Initialise(const int iAllocPages)
{
	// Keep track of how many 4K pages will be allocated
	m_iNumPages = iAllocPages;

	// Initialise SPS2
	m_iSPS2Desc = sps2Init();

	if(m_iSPS2Desc < 0)
	{
		fprintf(stderr, "Failed to initialise SPS2 library." \
				"Please check that the module is loaded.\n");
		exit(-1);
	}

	// Open the vector units
	int iVPU0; iVPU0 = open("/dev/ps2vpu0", O_RDWR);
	int iVPU1; iVPU1 = open("/dev/ps2vpu1", O_RDWR);

	// Allocate the memory that we will be using, and get the cached and
	// cached pointers.
	m_pMemCached = sps2Allocate(iAllocPages * 4096,
			SPS2_MAP_BLOCK_4K | SPS2_MAP_CACHED, m_iSPS2Desc);
	m_pMemUncached = sps2Remap(m_pMemCached, SPS2_MAP_UNCACHED, m_iSPS2Desc);

	// Set up an array to keep track of how much memory is free in each page
	m_iaFreeMemInPage = new int[m_iNumPages];

	for(int iPage = 0; iPage < m_iNumPages; iPage++)
	{
		m_iaFreeMemInPage[iPage] = 256;	// 4K == 256 Quad words
	}

	// Set up the screen
	sps2UScreenInit(0);
	
	// Enable EI DI instructions (this is needed for the screen shot code)
	_sps2SetEIDIEnabled(1, m_iSPS2Desc);
}

// Allocate some of the memory that we created in Initialise.
void CSPS2Manager::Allocate(CDMAMem & Memory, int iQuadWords)
{
	assert(iQuadWords <= 256 && "A CDMAMem class can only handle a maximum of 256 quadwords");

	for(int iPage = 0; iPage < m_iNumPages; iPage++)
	{
		// Find the first page with enough free memory in it.
		if(m_iaFreeMemInPage[iPage] >= iQuadWords)
		{
			// Set up the CDMAMem structure.
			// Get the first free quad word in the page.
			int FirstQW = 256 - m_iaFreeMemInPage[iPage];
			m_iaFreeMemInPage[iPage] -= iQuadWords;
			// Get the pointers.
			void * pCached = &((char *)m_pMemCached->pvStart)[iPage * 4096 + (FirstQW * 16)];
			void * pUncached = &((char *)m_pMemUncached->pvStart)[iPage * 4096 + (FirstQW * 16)];
			// Set the CDMAMem's member variables
			Memory.Set(iQuadWords, pCached, pUncached, sps2GetPhysicalAddress(pCached, m_pMemCached));
			return;	// No need to search anymore
		}
	}

	assert(!"Not enough free memory!");
}

// Initialise the screen clear. With this class we clear the screen much faster than SPS2 would
// usually. This puts all of the screen clear DMA data into our static DMA buffer. This means it is
// only ever added once (very fast), and is then simply CALLed from the dynamic DMA buffer.
// Notice that instead of just using one big sprite to clear the screen we use 20 long tall strips.
// This is because the GS is optimised for polygons less than or equal to 32 pixels wide, and it
// "chokes" on ones that are larger.
void CSPS2Manager::InitScreenClear(int R, int G, int B)
{
	int x0 = (2048 - (sps2UScreenGetWidth() >> 1)) << 4;
	int y0 = (2048 - (sps2UScreenGetHeight() >> 1)) << 4;
	int x1 = (2048 + (sps2UScreenGetWidth() >> 1)) << 4;
	int y1 = (2048 + (sps2UScreenGetHeight() >> 1)) << 4;

	// Get the address of the screen clear packet so we can CALL it
	// from the dynamic packet.
	iScreenClear = VIFStaticDMA.GetPointer();

	// Start the VIF direct mode.
	VIFStaticDMA.StartDirect();

	VIFStaticDMA.Add128(GS_GIFTAG_BATCH(4 + (20 * 2), 1, 0, 0, 
						GIF_FLG_PACKED, GS_BATCH_1(GIF_REG_A_D)));

	VIFStaticDMA.Add64(TEST_SET(0, 0, 0, 0, 0, 0, 1, 1));
	VIFStaticDMA.Add64(TEST_1);

	VIFStaticDMA.Add64(PRIM_SET(0x6, 0, 0, 0, 0, 0, 0, 0, 0));
	VIFStaticDMA.Add64(PRIM);

	VIFStaticDMA.Add64(RGBAQ_SET(R, G, B, 0x80, 0x3f800000));
	VIFStaticDMA.Add64(RGBAQ);

	for(int i = 0; i < 20; i++)
	{
		VIFStaticDMA.Add64(XYZ2_SET(x0, y0, 0));
		VIFStaticDMA.Add64(XYZ2);

		VIFStaticDMA.Add64(XYZ2_SET(x0 + (32 << 4), y1, 0));
		VIFStaticDMA.Add64(XYZ2);

		x0 += (32 << 4);
	}

	VIFStaticDMA.Add64(TEST_SET(0, 0, 0, 0, 0, 0, 1, 3));
	VIFStaticDMA.Add64(TEST_1);

	VIFStaticDMA.EndDirect();

	VIFStaticDMA.DMARet();
}

// This just DMACall's the screen clear packet that was set up in InitScreenClear
// (I'm sure you can see why this is so much faster than the sps2 screen clear, as the
//  CPU hardly has to do any work at all here).
void CSPS2Manager::BeginScene()
{
	VIFDynamicDMA.DMACall(iScreenClear);
}


int CSPS2Manager::FileExists(const char * const FileName)
{
	FILE * f;
	f = fopen(FileName, "rb");	// Try to open the file
	if (f == NULL) return 0; 	// File doesn't exist
	fclose (f);
	return 1;					// File exists
}	

void CSPS2Manager::ScreenShot(const int ImageType)
{
	char name[256];
	unsigned char * data;
	unsigned char tmp;
	unsigned short Width, Height;
	int BytesPerPixel = 4;
	
	// Current Screen width and height
	Width = (unsigned short)GetScreenWidth();
	Height = (unsigned short)GetScreenHeight();
	
	// Get an unused filename
	do
	{
		sprintf (name, "sshot_%02d.tga", m_SS_Number++);
	} while (FileExists(name));
	
	// Print the filename to the console with some stats
	if(ImageType == IMAGE_RGB)
		printf("Dumping Screen to File %s. Dimensions %d x %d - RGB\n", name, Width, Height);
	else if(ImageType == IMAGE_RGBA)
		printf("Dumping Screen to File %s. Dimensions %d x %d - RGBA\n", name, Width, Height);
	
	
	// Open the file for writing to	
	FILE * f = fopen(name, "wb");

	// Create a targa header for a 640x480, 32bit picture
	unsigned char header[18] = {
		0,				// Field 1 - Number of Bytes in Field 6
		0, 				// Field 2 - Colour Map Type 0 = no colour map
		2, 				// Field 3 - Image Type = Uncompressed true colour image
		0, 0, 0, 0, 0,	// Field 4 - Colour map specification
						// Field 5 - The remaining 10 bytes
		0, 0, 			// X origin
		0, 0, 			// Y origin
		0x80, 2, 		// Width 640
		0xe0, 1, 		// Height 480
		32, 			// Pixel colour depth
		8				// Number of alpha channel bits
	};
	
	//for(int i =0; i< 18; i++)printf("%x ", header[i]);printf("\n");
		
	//Change the width and height in the header to the correct dimensions
	unsigned char * pDimension = (unsigned char *)(&Width);
	//printf("%x %x\n", pDimension[0], pDimension[1]);
	header[12] = pDimension[0];
	header[13] = pDimension[1];
	
	pDimension = (unsigned char *)(&Height);
	header[14] = pDimension[0];
	header[15] = pDimension[1];
	
	// Change header and bytes per pixel if RGB
	if(ImageType == IMAGE_RGB)
	{
		header[16] = 24;
		header[17] = 0;
		BytesPerPixel = 3;
	}
	
	
	//for(int i =0; i< 18; i++)printf("%x ", header[i]);printf("\n");
	
	// write the header to the file	
	fwrite(header, 18, 1, f);
	
	// Get two pages of memory to work with
	// one page for the DMA chain, 
	CDMAMem DMAChainMem = VIFDynamicDMA.getTempPage(0);
	// One page to hold the frame buffer data
	CDMAMem SShotMem = VIFDynamicDMA.getTempPage(1);
	
	// Now Download the Frame buffer, from the bottom up
	for(int y = 0; y < Height; y++)
	{
		DownloadVram(DMAChainMem, SShotMem, 0, 10, 0, (Height - 1 - y), Width, 1);
		
		// convert RGBA to BGRA
		data = (unsigned char *)SShotMem.GetUncached();
		for(int x = 0; x < Width; x++)
		{
			tmp = data[0];
			data[0] = data[2];
			data[2] = tmp;
			
			// Write pixel to file
			fwrite(data, BytesPerPixel, 1, f);
			
			data += 4;
		}
	}
	
	// Close the file
	fclose(f);
}

void CSPS2Manager::DownloadVram(CDMAMem & DMAChainMem, CDMAMem & SShotMem, 
								int addr, int bufw, int x, int y, int w, int h)
{
	// Pointer the memory for building the DMA chain
	uint32 * pDmaMem32 = (uint32 *)DMAChainMem.GetUncached();	

	uint32 uSrcPtr = addr;
	uint32 uQSize = (w*h*4)/16; // assuming 32bit framebuffer

	// Setup transfer texture back to memory
	pDmaMem32[0] = 0; //NOP;
	pDmaMem32[1] = 0x06008000; //MSKPATH3(0x8000);
	pDmaMem32[2] = 0x13000000; //FLUSHA;
	pDmaMem32[3] = 0x50000006; //DIRECT(6);

	// Add GifTag Data
	// Get pointer to GIFTag position
	uint128 * pGIFTag = (uint128 *)(pDmaMem32 + 4);
	
	// Store the GIFTag - The Giftag for A+D mode
	*pGIFTag = GS_GIFTAG_BATCH(	5,							// NLOOP
	 							1,							// EOP
	 							0,							// PRE
	 							0,							// PRIM				
	 							GIF_FLG_PACKED,				// FLG
	 							GS_BATCH_1(	GIF_REG_A_D));	// BATCH
	 
	// Get uint64 pointer to write the A_D data
	// [0] and [1] are the GIFTag									
	uint64 * pDmaMem64 = (uint64 *) (pGIFTag);

	pDmaMem64[2] = (BITBLTBUF_SET( uSrcPtr, bufw, 0, 0, 0, 0 ));
	pDmaMem64[3] = (GIF_A_D_REG_BITBLTBUF);

	pDmaMem64[4] = (TRXPOS_SET(x, y, 0, 0, 0)); // SSAX, SSAY, DSAX, DSAY, DIR
	pDmaMem64[5] = (GIF_A_D_REG_TRXPOS);

	pDmaMem64[6] = (TRXREG_SET(w, h)); // RRW, RRh
	pDmaMem64[7] = (GIF_A_D_REG_TRXREG);

	pDmaMem64[8] = (0);
	pDmaMem64[9] = (GIF_A_D_REG_FINISH);

	pDmaMem64[10] = (TRXDIR_SET(1)); // XDIR
	pDmaMem64[11] = (GIF_A_D_REG_TRXDIR);

	uint32 prev_chcr = *EE_D1_CHCR;
	
	// *_GS_IMR fetches the current setting of interrupt mask register (imr)
	// DPUT_GS_IMR() sets the current value of imr
	// Mark interrupts from the FINISH Event (GS Page 154)
	uint32 prev_imr = *_GS_IMR;
	DPUT_GS_IMR( prev_imr | 0x0200 );
	
	// vif1 must be available
	if ((*EE_D1_CHCR & 0x0100) != 0)
	{
		printf("VIF1 is not available for the Screen Shot\n");
		exit(-1);
	}	
	
	// Enable generation of the FINISH Event (GS Page 145)
	DPUT_GS_CSR( 2 ); 
	
	// Disable interrupts so we don't lose our finish event
	asm __volatile__ ("di");

	
	// Make sure all cache is writen to main memory
	FlushCache();
	
	// Setup and start DMA transfer to VIF1
	*EE_D1_QWC = 0x7;
	*EE_D1_MADR = DMAChainMem.GetPhysicalAddr();
	//             DIR      MOD       ASP     TTE       TIE      STR
	*EE_D1_CHCR = (1<<0) | (0<<2) | (0<<4) | (0<<6) | (0<<7) | (1<<8);

	// Wait till memory load is complete
	asm __volatile__( " sync.l " );
	
	// Wait till DMA is complete (STR=0)
	while ( *EE_D1_CHCR & 0x0100 );

	// Wait for the GS to finish loading the registers (FINISH EVENT)
	//while ( ((*GS_CSR_OFF(SPS2_GS_REGISTERS_START)) & 2) == 0 ); 
	while ( (*_GS_CSR & 2) == 0 );
	
	// Re-enable interrupts
	asm __volatile__ ("ei");
	
	
	// START! The actual download to memory 
	// wait for VIF1-FIFO to empty and become idle 
	while(*EE_VIF1_STAT & 0x1f000003); 
	
	// Change the VIF1 direction to VIF1->Memory
	*((volatile uint32 *) EE_VIF1_STAT) = (1<<23);
	
	// Change direction of VIF1 FIF0 Local->Host
	DPUT_GS_BUSDIR( ( uint64 ) 0x00000001 );
	
	// Setup and start DMA transfer from VIF1
	*EE_D1_QWC = uQSize;
	*EE_D1_MADR = SShotMem.GetPhysicalAddr();
	//          DIR    MOD       ASP     TTE       TIE      STR
	*EE_D1_CHCR = 0 | (0<<2) | (0<<4) | (0<<6) | (0<<7) | (1<<8);

	// Wait till memory load is complete
	asm __volatile__( " sync.l " );

	// check if DMA is complete (STR=0)
	while ( *EE_D1_CHCR & 0x0100 );
	
	// Restore CHCR and wait for it to complete
	*EE_D1_CHCR = prev_chcr;
	asm __volatile__( " sync.l " );
	
	// wait for VIF1-FIFO to empty and become idle 
	while ( *EE_VIF1_STAT & 0x1f000003 ); 

	
	// Change the VIF1 and VIF1 FIFO Direction back to normal
	*((volatile uint32 *) EE_VIF1_STAT) = 0;
	DPUT_GS_BUSDIR( ( uint64 ) 0 );
	
	
	// restore the setting of IMR
	DPUT_GS_IMR( prev_imr);
	// set the FINISH event to default
	//dput_gs_csr( GS_EE_CSR_FINISH_M );
	DPUT_GS_CSR( 2 ); 
	
	
	
	// Re-enable path3 transfers
	static uint32 enable_path3[4] ALIGN_QW = {
	0x06000000, //MSKPATH3(0),
	0,
	0,
	0};

	DPUT_EE_VIF1_FIFO( *( uint128 * ) enable_path3 );

	// Make sure data is written to main memory
	FlushCache();
}

