#if !defined SPUUTIL_RAWSPU_H__
#include "rawspu.h"
#endif

#if !defined IMAGEINFO_ELF_H__
#include "imageinfo/elf.h"
#endif

#include <stdio.h>
#include <string.h>

namespace spuutil
{

	RawSPU::RawSPU() :	m_refCount( 1 ),
					  	m_spuID( 0xffffffff ),//TODO:  Is there a library defined value for this?
					  	m_baseAddr( 0 ),
					  	m_buffer( 0 ),
					  	m_dmaBuffer( 0 ),
						m_dmaBufferSize( 0 ),
						m_dmaTransferSize( 0 ),
						m_state( unallocated )

	{
	}
	
	RawSPU::~RawSPU()
	{
		this->Reset();

		if(  m_buffer  )
		{
			delete[] m_buffer;
			
			m_buffer 			= 0;
			m_dmaBuffer			= 0;
			m_dmaBufferSize		= 0;
			m_dmaTransferSize	= 0;
		}
	}


	uint32_t RawSPU::Release()
	{
		const uint32_t result = --m_refCount;

		if(  result == 0  )
		{
			delete this;
		}

		return result;
	}


	bool RawSPU::Initialize()
	{
		bool			result	= false;
		raw_spu_t 		spuID;
		raw_spu_attr_t*	attr 	= 0;

		if(  m_state == unallocated  )
		{
			if(  raw_spu_create( &spuID, attr ) == SUCCEEDED  )
			{
				m_spuID 	= spuID;
				m_baseAddr	= ((RAW_SPU_OFFSET * spuID) + RAW_SPU_BASE_ADDR);
				m_state		= ready;

				result = true;
			}
		}

		return result;
	}

	bool RawSPU::Initialize( raw_spu_t spuID )
	{
		m_spuID 	= spuID;
		m_baseAddr	= ((RAW_SPU_OFFSET * spuID) + RAW_SPU_BASE_ADDR);
		m_state		= unknown;

		return true;
	}



	bool RawSPU::SetELF( const char* elfFileName )
	{
		bool result = false;

		FILE* elfFile = fopen( elfFileName, "rb" );

		fseek( elfFile, 0, SEEK_END );
		int imageSize = ftell( elfFile );
		fseek( elfFile, 0, SEEK_SET );

		if(  imageSize > m_dmaBufferSize  )
		{
			delete[] m_buffer;

			m_dmaBufferSize = ((imageSize + 0x7f) & ~0x7f);																	//align the transferSize

			m_buffer = new unsigned char[m_dmaBufferSize + 0x7f];

			m_dmaBuffer = reinterpret_cast< uint8_t* >( (reinterpret_cast< uint64_t >( (m_buffer + 0x7f) ) & ~0x7f) );	//align the DMA start address
		}


		if(  m_dmaBuffer  )
		{
			uint32_t alignedImageSize = ((imageSize + 0x7f) & ~0x7f);

			m_dmaTransferSize = (alignedImageSize < m_dmaBufferSize) ? alignedImageSize :m_dmaBufferSize;

			fread( m_dmaBuffer, 1, imageSize, elfFile );

			result = true;
		}

		return result;
	}

	bool RawSPU::Deploy( uint32_t unused )
	{
		bool result = false;

		//TODO:  This code expects the first segment address to be dma'able and first program header to be the loadable segment containing the entry point.  I should
		//eventually write a more robust loader.

		if(  (m_dmaTransferSize != 0) & (m_state == ready)  )
		{
			imageinfo::ELF32Header* elfHeader = (imageinfo::ELF32Header*)(&m_dmaBuffer[0]);

			imageinfo::ELF32Phdr* pHeader0 = (imageinfo::ELF32Phdr*)(&m_dmaBuffer[0] + elfHeader->phoff);

			//get some values from the elf headers.
			uint32_t 	elfEntry 			= elfHeader->entry;
			uint32_t	elfVAddr			= pHeader0->vaddr;
			uint32_t 	elfCodeOffset 		= pHeader0->offset;
			uint32_t	elfCodeTransferSize = ((m_dmaTransferSize - elfCodeOffset) + 0xf) & ~0xf;

			this->IssueDMACommand( 0x40, elfVAddr, reinterpret_cast< uint8_t* >( &m_dmaBuffer[elfCodeOffset] ), elfCodeTransferSize, 0 );
			this->WaitForDMATagGroup( 0x1 );

			//Set the NPC(next program counter) value for our elf file
			this->WriteProblemStateWord( SPU_NPC, elfEntry );
			asm volatile( "eieio" );

			m_state |= deployed;

			result = true;
		}

		return result;
	}

	bool RawSPU::Start()
	{
		bool result = false;

		if(  (m_state & deployed) != 0  )
		{
			//Set the run bit on the SPU.
			this->WriteProblemStateWord( SPU_RunCntl, 1 );

			m_state |= started;

			result = true;
		}
		
		return result;
	}

	bool RawSPU::Stop()
	{
		bool result = false;

		if(  (m_state & started) != 0  )
		{
			//Clear the run bit.
			this->WriteProblemStateWord( SPU_RunCntl, 0 );

			m_state &= ~started;

			result = true;
		}

		return result;
	}

	bool RawSPU::Reset()
	{
		bool result = false;

		if(  m_spuID != 0xffffffff  )
		{
			this->Stop();

			raw_spu_destroy( m_spuID );

			m_spuID = 0xffffffff;
			m_state	= unallocated;
		}
		
		return result;
	}

	bool RawSPU::IsDeployed() const
	{
		return ((m_state & deployed) != 0);
	}
	
	raw_spu_t RawSPU::SPUID() const
	{
		return m_spuID;
	}

	bool RawSPU::IssueDMACommand( uint32_t command, uint32_t lsAddress, uint8_t* ea, uint32_t size, uint32_t tag )
	{
		this->WriteProblemStateWord( DMA_LSA, lsAddress );
		this->WriteProblemStateWord( DMA_EAH, static_cast< uint32_t >( reinterpret_cast< uint64_t >( ea ) >> 0x20 ) );
		this->WriteProblemStateWord( DMA_EAL, static_cast< uint32_t >( reinterpret_cast< uint64_t >( ea ) & 0xffffffffUL ) );
		this->WriteProblemStateWord( DMA_Size, ((size << 0x10) | tag) );

		do
		{
			this->WriteProblemStateWord( DMA_CMD, command );

		}while(  this->ReadProblemStateWord( DMA_CMDStatus ) != 0  );

		return true;
	}

	bool RawSPU::WaitForDMATagGroup( uint32_t tagGroup )
	{
		this->WriteProblemStateWord( Prxy_QueryMask, tagGroup );

		uint32_t tagGroupStatus = 0;

		do
		{
			asm volatile( "eieio" );

			tagGroupStatus |= ReadProblemStateWord( Prxy_TagStatus );

		}while(  tagGroupStatus != tagGroup  );


		return true;
	}

	void RawSPU::WriteProblemStateWord( PROBLEMSTATEOFFSETTYPE offset, uint32_t value )
	{
		rawspu_WriteProblemStateWord( m_baseAddr, offset, value );
	}

	uint32_t RawSPU::ReadProblemStateWord( PROBLEMSTATEOFFSETTYPE offset )
	{
		return rawspu_ReadProblemStateWord( m_baseAddr, offset );
	}

}
