//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

#include <stdio.h>
#include "scratchpad3d.h"
#include "tier0/dbg.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#ifndef POSIX
// NOTE - linux doesn't need any of this code!

extern "C"
{
	extern void __stdcall Sleep( unsigned long ms );
};


class CFileRead
{
public:
				CFileRead( IFileSystem* pFileSystem, FileHandle_t fp )
				{
					m_pFileSystem = pFileSystem;
					m_fp = fp;
					m_Pos = 0;
				}

	bool		Read( void *pDest, int len )
	{
		int count = m_pFileSystem->Read( pDest, len, m_fp );
		m_Pos += count;
		return count == len;
	}

	IFileSystem*	m_pFileSystem;
	FileHandle_t	m_fp;
	int				m_Pos;
};


// ------------------------------------------------------------------------ //
// CCommand_Point.
// ------------------------------------------------------------------------ //

void CScratchPad3D::CCommand_Point::Read( CFileRead *pFile )
{
	pFile->Read( &m_flPointSize, sizeof(m_flPointSize) );
	pFile->Read( &m_Vert, sizeof(m_Vert) );
}

void CScratchPad3D::CCommand_Point::Write( IFileSystem* pFileSystem, FileHandle_t fp )
{
	pFileSystem->Write( &m_flPointSize, sizeof(m_flPointSize), fp );
	pFileSystem->Write( &m_Vert, sizeof(m_Vert), fp );
}


// ------------------------------------------------------------------------ //
// CCommand_Line.
// ------------------------------------------------------------------------ //

void CScratchPad3D::CCommand_Line::Read( CFileRead *pFile )
{
	pFile->Read( m_Verts, sizeof(m_Verts) );
}

void CScratchPad3D::CCommand_Line::Write( IFileSystem* pFileSystem, FileHandle_t fp )
{
	pFileSystem->Write( m_Verts, sizeof(m_Verts), fp );
}


// ------------------------------------------------------------------------ //
// CCommand_Polygon.
// ------------------------------------------------------------------------ //

void CScratchPad3D::CCommand_Polygon::Read( CFileRead *pFile )
{
	int count;
	pFile->Read( &count, sizeof(count) );
	m_Verts.RemoveAll();
	m_Verts.AddMultipleToTail( count );
	
	if( count )
		pFile->Read( &m_Verts[0], sizeof(CSPVert)*count );
}

void CScratchPad3D::CCommand_Polygon::Write( IFileSystem* pFileSystem, FileHandle_t fp )
{
	int count = m_Verts.Size();
	pFileSystem->Write( &count, sizeof(count), fp );
	
	if( count )
		pFileSystem->Write( &m_Verts[0], sizeof(CSPVert)*count, fp );
}


// ------------------------------------------------------------------------ //
// CCommand_Matrix.
// ------------------------------------------------------------------------ //

void CScratchPad3D::CCommand_Matrix::Read( CFileRead *pFile )
{
	pFile->Read( &m_mMatrix, sizeof(m_mMatrix) );
}

void CScratchPad3D::CCommand_Matrix::Write( IFileSystem* pFileSystem, FileHandle_t fp )
{
	pFileSystem->Write( &m_mMatrix, sizeof(m_mMatrix), fp );
}


// ------------------------------------------------------------------------ //
// CCommand_RenderState.
// ------------------------------------------------------------------------ //

void CScratchPad3D::CCommand_RenderState::Read( CFileRead *pFile )
{
	pFile->Read( &m_State, sizeof(m_State) );
	pFile->Read( &m_Val, sizeof(m_Val) );
}

void CScratchPad3D::CCommand_RenderState::Write( IFileSystem* pFileSystem, FileHandle_t fp )
{
	pFileSystem->Write( &m_State, sizeof(m_State), fp );
	pFileSystem->Write( &m_Val, sizeof(m_Val), fp );
}


// ------------------------------------------------------------------------ //
// CCommand_Text.
// ------------------------------------------------------------------------ //

void CScratchPad3D::CCommand_Text::Read( CFileRead *pFile )
{
	int strLen;
	pFile->Read( &strLen, sizeof( strLen ) );
	m_String.SetSize( strLen );
	pFile->Read( m_String.Base(), strLen );

	pFile->Read( &m_TextParams, sizeof( m_TextParams ) );
}


void CScratchPad3D::CCommand_Text::Write( IFileSystem* pFileSystem, FileHandle_t fp )
{
	int strLen = m_String.Count();
	pFileSystem->Write( &strLen, sizeof( strLen ), fp );
	pFileSystem->Write( m_String.Base(), strLen, fp );

	pFileSystem->Write( &m_TextParams, sizeof( m_TextParams ), fp );
}


// ------------------------------------------------------------------------ //
// CScratchPad3D internals.
// ------------------------------------------------------------------------ //

CScratchPad3D::CScratchPad3D( char const *pFilename, IFileSystem* pFileSystem, bool bAutoClear )
{
	m_pFileSystem = pFileSystem;
	m_pFilename = pFilename;
	m_bAutoFlush = true;

	if( bAutoClear )
		Clear();	// Clear whatever is in the file..
}

void CScratchPad3D::AutoFlush()
{
	if( m_bAutoFlush )
		Flush();
}

void CScratchPad3D::DrawRectGeneric( int iPlane, int otherDim1, int otherDim2, float planeDist, const Vector2D &vMin, const Vector2D &vMax, const CSPColor &vColor )
{
	Vector verts[4];

	verts[0][iPlane] = verts[1][iPlane] = verts[2][iPlane] = verts[3][iPlane] = planeDist;

	verts[0][otherDim1] = vMin.x;
	verts[0][otherDim2] = vMin.y;

	verts[1][otherDim1] = vMin.x;
	verts[1][otherDim2] = vMax.y;

	verts[2][otherDim1] = vMax.x;
	verts[2][otherDim2] = vMax.y;

	verts[3][otherDim1] = vMax.x;
	verts[3][otherDim2] = vMin.y;

	DrawPolygon( CSPVertList(verts, 4, vColor) );
}

void CScratchPad3D::DeleteCommands()
{
	for( int i=0; i < m_Commands.Size(); i++ )
		delete m_Commands[i];

	m_Commands.RemoveAll();
}

bool CScratchPad3D::LoadCommandsFromFile( )
{
	DeleteCommands();

	FileHandle_t fp = m_pFileSystem->Open( m_pFilename, "rb" );
	if( !fp )
		return false;

	long fileEndPos = m_pFileSystem->Size( fp );

	CFileRead fileRead( m_pFileSystem, fp );
	while( fileRead.m_Pos != fileEndPos )
	{
		unsigned char iCommand;
		fileRead.Read( &iCommand, sizeof(iCommand) );
		
		CBaseCommand *pCmd = NULL;
		if( iCommand == COMMAND_POINT )
			pCmd = new CCommand_Point;
		else if( iCommand == COMMAND_LINE )
			pCmd = new CCommand_Line;
		else if( iCommand == COMMAND_POLYGON )
			pCmd = new CCommand_Polygon;
		else if( iCommand == COMMAND_MATRIX )
			pCmd = new CCommand_Matrix;
		else if( iCommand == COMMAND_RENDERSTATE )
			pCmd = new CCommand_RenderState;
		else if ( iCommand == COMMAND_TEXT )
			pCmd = new CCommand_Text;

		if( !pCmd )
		{
			Assert( !"LoadCommandsFromFile: invalid file" );
			m_pFileSystem->Close( fp );
			return false;
		}

		pCmd->Read( &fileRead );
		m_Commands.AddToTail( pCmd );
	}	

	m_pFileSystem->Close( fp );
	return true;
}


// ------------------------------------------------------------------------ //
// CScratchPad3D's IScratchPad3D implementation.
// ------------------------------------------------------------------------ //

void CScratchPad3D::Release()
{
	Flush();
	delete this;
}

void CScratchPad3D::SetMapping( 
	const Vector &vInputMin, 
	const Vector &vInputMax,
	const Vector &vOutputMin,
	const Vector &vOutputMax )
{
	CCommand_Matrix *cmd = new CCommand_Matrix;
	m_Commands.AddToTail( cmd );
	
	Vector vDivisor(1,1,1);
	for( int i=0; i < 3; i++ )
		vDivisor[i] = fabs(vInputMax[i] - vInputMin[i]) < 0.0001f ? 0.001f : (vInputMax[i] - vInputMin[i]);

	Vector vScale = (vOutputMax - vOutputMin) / vDivisor; 
	Vector vShift = -vInputMin * vScale + vOutputMin;

	cmd->m_mMatrix.Init( 
		vScale.x,		0,			0,			vShift.x,
		0,				vScale.y,	0,			vShift.y,
		0,				0,			vScale.z,	vShift.z,
		0,				0,			0,			1 );

	
	AutoFlush();
}

bool CScratchPad3D::GetAutoFlush()
{
	return m_bAutoFlush;
}

void CScratchPad3D::SetAutoFlush( bool bAutoFlush )
{
	m_bAutoFlush = bAutoFlush;
	if( m_bAutoFlush )
		Flush();
}

void CScratchPad3D::DrawPoint( CSPVert const &v, float flPointSize )
{
	CCommand_Point *cmd = new CCommand_Point;
	m_Commands.AddToTail( cmd );

	cmd->m_Vert = v;
	cmd->m_flPointSize = flPointSize;

	AutoFlush();
}

void CScratchPad3D::DrawLine( CSPVert const &v1, CSPVert const &v2 )
{
	CCommand_Line *cmd = new CCommand_Line;
	m_Commands.AddToTail( cmd );

	cmd->m_Verts[0] = v1;
	cmd->m_Verts[1] = v2;

	AutoFlush();
}

void CScratchPad3D::DrawPolygon( CSPVertList const &verts )
{
	CCommand_Polygon *cmd = new CCommand_Polygon;
	m_Commands.AddToTail( cmd );

	cmd->m_Verts.AddVectorToTail( verts.m_Verts );
	
	AutoFlush();
}

void CScratchPad3D::DrawRectYZ( float xPos, const Vector2D &vMin, const Vector2D &vMax, const CSPColor &vColor )
{
	DrawRectGeneric( 0, 1, 2, xPos, vMin, vMax, vColor );
}

void CScratchPad3D::DrawRectXZ( float yPos, const Vector2D &vMin, const Vector2D &vMax, const CSPColor &vColor )
{
	DrawRectGeneric( 1, 0, 2, yPos, vMin, vMax, vColor );
}

void CScratchPad3D::DrawRectXY( float zPos, const Vector2D &vMin, const Vector2D &vMax, const CSPColor &vColor )
{
	DrawRectGeneric( 2, 0, 1, zPos, vMin, vMax, vColor );
}

void CScratchPad3D::SetRenderState( RenderState state, unsigned long val )
{
	CCommand_RenderState *cmd = new CCommand_RenderState;
	m_Commands.AddToTail( cmd );

	cmd->m_State = (unsigned long)state;
	cmd->m_Val = val;
}

void CScratchPad3D::DrawWireframeBox( const Vector &vMin, const Vector &vMax, const Vector &vColor )
{
	// Bottom 4.
	DrawLine( 
		CSPVert(Vector(vMin.x, vMin.y, vMin.z), vColor), 
		CSPVert(Vector(vMax.x, vMin.y, vMin.z), vColor) );

	DrawLine( 
		CSPVert(Vector(vMin.x, vMin.y, vMin.z), vColor), 
		CSPVert(Vector(vMin.x, vMax.y, vMin.z), vColor) );

	DrawLine( 
		CSPVert(Vector(vMax.x, vMin.y, vMin.z), vColor), 
		CSPVert(Vector(vMax.x, vMax.y, vMin.z), vColor) );

	DrawLine( 
		CSPVert(Vector(vMax.x, vMax.y, vMin.z), vColor), 
		CSPVert(Vector(vMin.x, vMax.y, vMin.z), vColor) );

	// Top 4.
	DrawLine( 
		CSPVert(Vector(vMin.x, vMin.y, vMax.z), vColor), 
		CSPVert(Vector(vMax.x, vMin.y, vMax.z), vColor) );

	DrawLine( 
		CSPVert(Vector(vMin.x, vMin.y, vMax.z), vColor), 
		CSPVert(Vector(vMin.x, vMax.y, vMax.z), vColor) );

	DrawLine( 
		CSPVert(Vector(vMax.x, vMin.y, vMax.z), vColor), 
		CSPVert(Vector(vMax.x, vMax.y, vMax.z), vColor) );

	DrawLine( 
		CSPVert(Vector(vMax.x, vMax.y, vMax.z), vColor), 
		CSPVert(Vector(vMin.x, vMax.y, vMax.z), vColor) );

	// Connecting 4.
	DrawLine( 
		CSPVert(Vector(vMin.x, vMin.y, vMin.z), vColor), 
		CSPVert(Vector(vMin.x, vMin.y, vMax.z), vColor) );

	DrawLine( 
		CSPVert(Vector(vMin.x, vMax.y, vMin.z), vColor), 
		CSPVert(Vector(vMin.x, vMax.y, vMax.z), vColor) );

	DrawLine( 
		CSPVert(Vector(vMax.x, vMax.y, vMin.z), vColor), 
		CSPVert(Vector(vMax.x, vMax.y, vMax.z), vColor) );

	DrawLine( 
		CSPVert(Vector(vMax.x, vMin.y, vMin.z), vColor), 
		CSPVert(Vector(vMax.x, vMin.y, vMax.z), vColor) );
}


void CScratchPad3D::DrawText( const char *pStr, const CTextParams &params )
{
	CCommand_Text *cmd = new CCommand_Text;
	m_Commands.AddToTail( cmd );

	cmd->m_String.CopyArray( pStr, strlen( pStr ) + 1 );
	cmd->m_TextParams = params;

	AutoFlush();
}


void CScratchPad3D::Clear()
{
	FileHandle_t fp;
	
	while( ( fp = m_pFileSystem->Open(m_pFilename, "wb") ) == NULL )
	{
#ifdef _WIN32
		Sleep( 5 );
#elif POSIX
		usleep( 5 );
#endif
	}

	m_pFileSystem->Close( fp );

	DeleteCommands();
}


void CScratchPad3D::Flush()
{
	FileHandle_t fp;

	while( ( fp = m_pFileSystem->Open(m_pFilename, "ab+") ) == NULL )
	{
#ifdef _WIN32
		Sleep( 5 );
#elif POSIX
		usleep( 5 );
#endif
	}
	
	// Append the new commands to the file.
	for( int i=0; i < m_Commands.Size(); i++ )
	{
		m_pFileSystem->Write( &m_Commands[i]->m_iCommand, sizeof(m_Commands[i]->m_iCommand), fp );
		m_Commands[i]->Write( m_pFileSystem, fp );
	}
	
	m_pFileSystem->Close( fp );

	DeleteCommands();
}

void CScratchPad3D::DrawImageBW(
		unsigned char const *pData, 
		int width, 
		int height, 
		int pitchInBytes, 
		bool bOutlinePixels, 
		bool bOutlineImage,
		Vector *vCorners )
{
	SPRGBA *pRGBA = new SPRGBA[width*height];
	for( int y=0; y < height; y++ )
	{
		SPRGBA *pDest = &pRGBA[ y * width ];
		unsigned char const *pSrc = &pData[ y * pitchInBytes ];
		for( int x=0; x < width; x++ )
		{
			pDest->r = pDest->g = pDest->b = *pSrc;
			++pSrc;
			++pDest;
		}
	}

	DrawImageRGBA( pRGBA, width, height, width*sizeof(SPRGBA), bOutlinePixels, bOutlineImage, vCorners );
	delete [] pRGBA;
}


void CScratchPad3D::DrawPolygonsForPixels(
		SPRGBA *pData, 
		int width, 
		int height, 
		int pitchInBytes, 
		Vector *vCorners )
{
	// Scan top-down.
	Vector vCurLeft = vCorners[1];
	Vector vCurRight = vCorners[2];
	
	Vector vLeftInc = (vCorners[0] - vCorners[1]) / height;
	Vector vRightInc = (vCorners[3] - vCorners[2]) / height;
	
	Vector vNextLeft = vCurLeft + vLeftInc;
	Vector vNextRight = vCurRight + vRightInc;

	Vector vPolyBox[4];
	Vector &vTopLeft = vPolyBox[0];
	Vector &vTopRight = vPolyBox[1];
	Vector &vBottomRight = vPolyBox[2];
	Vector &vBottomLeft = vPolyBox[3];

	for( int y=0; y < height; y++ )
	{
		vTopLeft = vCurLeft;
		vBottomLeft = vNextLeft;
		
		Vector vTopXInc = (vCurRight - vCurLeft) / width;
		Vector vBottomXInc = (vNextRight - vNextLeft) / width;

		vTopRight = vTopLeft + vTopXInc;
		vBottomRight = vBottomLeft + vBottomXInc;

		SPRGBA *pSrc = &pData[ y * (pitchInBytes/sizeof(SPRGBA)) ];
		for( int x=0; x < width; x++ )
		{
			if ( pData )
				DrawPolygon( CSPVertList( vPolyBox, 4, Vector(pSrc->r/255.1f, pSrc->g/255.1f, pSrc->b/255.1f) ) );
			else
				DrawPolygon( CSPVertList( vPolyBox, 4, Vector(1,1,1) ) );
			
			++pSrc;
			vTopLeft += vTopXInc;
			vTopRight += vTopXInc;
			vBottomLeft += vBottomXInc;
			vBottomRight += vBottomXInc;
		}

		vCurLeft += vLeftInc;
		vNextLeft += vLeftInc;
		vCurRight += vRightInc;
		vNextRight += vRightInc;
	}
}


void CScratchPad3D::DrawImageRGBA(
		SPRGBA *pData, 
		int width, 
		int height, 
		int pitchInBytes, 
		bool bOutlinePixels,
		bool bOutlineImage,
		Vector *vCorners )
{
	Assert( pitchInBytes % sizeof(SPRGBA) == 0 );

	Vector vDefaultCorners[4];
	if ( !vCorners )
	{
		vCorners = vDefaultCorners;
		vDefaultCorners[0].Init( -100, -100 );
		vDefaultCorners[1].Init( -100, 100 );
		vDefaultCorners[2].Init( 100,  100 );
		vDefaultCorners[3].Init( 100, -100 );
	}

	// Don't auto-flush while drawing all these primitives.
	bool bOldAutoFlush = m_bAutoFlush;
	m_bAutoFlush = false;

	// Draw solids.
	SetRenderState( IScratchPad3D::RS_FillMode, IScratchPad3D::FillMode_Solid );
	DrawPolygonsForPixels( pData, width, height, pitchInBytes, vCorners );

	// Draw wireframe.
	if ( bOutlinePixels )
	{
		SetRenderState( IScratchPad3D::RS_FillMode, IScratchPad3D::FillMode_Wireframe );
		DrawPolygonsForPixels( NULL, width, height, pitchInBytes, vCorners );
	}

	// Draw an outline around the whole image.
	if ( bOutlineImage )
	{
		SetRenderState( IScratchPad3D::RS_FillMode, IScratchPad3D::FillMode_Wireframe );
		DrawPolygon( CSPVertList( vCorners, 4 ) );
	}

	// Restore the old auto-flush state.
	m_bAutoFlush = bOldAutoFlush;
	AutoFlush();
}


// ------------------------------------------------------------------------ //
// Global functions.
// ------------------------------------------------------------------------ //
IFileSystem* ScratchPad3D_SetupFileSystem()
{
	// Get a filesystem interface.
	CSysModule *pModule = Sys_LoadModule( "filesystem_stdio" );
	if( !pModule )
		return NULL;

	CreateInterfaceFn fn = Sys_GetFactory( pModule );
	IFileSystem *pFileSystem;
	if( !fn || (pFileSystem = (IFileSystem *)fn( FILESYSTEM_INTERFACE_VERSION, NULL )) == NULL )
	{
		Sys_UnloadModule( pModule );
		return NULL;
	}

	return pFileSystem;
}

IScratchPad3D* ScratchPad3D_Create( char const *pFilename )
{
	IFileSystem *pFileSystem = ScratchPad3D_SetupFileSystem();
	if( !pFileSystem )
		return NULL;

	CScratchPad3D *pRet = new CScratchPad3D( pFilename, pFileSystem, true );
	return pRet;
}
#endif // POSIX