/** SGX

The   sss    gggg x   x   
     s      g      x x    Engine
      sss   g  gg   x
         s  g   g  x x          Copyright 1995-2009.
     ssss    gggg x   x         Steven Goodwin. All Rights Reserved.
    
This version has been released under the GNU Public License v2.0.

Please see the license files for details.

File: sgx/src/sgx/graphics/graphics.cpp

*/

#include "sgx/core/core.hpp"
#include "sgx/graphics/graphics.hpp"
#include "domains/graphics/frustum/frustum.hpp"


CSGXGraphicsEngine *CSGXGraphicsEngine::m_pSingleton = NULL;

CSGXGraphicsEngine *CSGXGraphicsEngine::create()
{
	m_pSingleton = new CSGXGraphicsEngine();
	if (m_pSingleton) {
		m_pSingleton->initialize();
	}
	return m_pSingleton;
}

CSGXGraphicsEngine *CSGXGraphicsEngine::get()
{
	if (!m_pSingleton) {
		return create();
	}
	return m_pSingleton;
}

CSGXGraphicsEngine::CSGXGraphicsEngine()
:m_BackgroundColor(sgxColorRGB(0.5f,0.5f,0.5f)), 
 m_ClearBufferColor(sgxColorRGB(0.0f,0.0f,0.0f)),
 m_pHandler(NULL),
 m_bProjectViewDirty(FALSE)
{
	tUINT32 size = 64*1024;
	tBYTE *ptr = new tBYTE[size];
	m_pAlphaFaces = new CFaceBuffer(1024);
	m_pFrameBlock = new CFrameBlock(ptr, size, 4);

	m_pFrustum = new CViewFrustum();
}

// NOTE: This is a virtual function, and so can use OpenGL drivers,
// for example. This is were the bulk of the initialzation should be
// done since static members have no 'this', and constructors have
// no vtable.
void 
CSGXGraphicsEngine::initialize()
{
	CSGXEngine::initialize();

	//
	// Prepare viewport areas for multi-screen rendering
	//

	// Full screen
	m_ViewportData[0] = sgxViewportSettings(0,0, 1.0f, 1.0f);
	// 2 Vertical
	m_ViewportData[1] = sgxViewportSettings(0,0, 0.5f, 1.0f);
	m_ViewportData[2] = sgxViewportSettings(0.5f,0, 0.5f,1.0f);
	// 2 Horizontal
	m_ViewportData[3] = sgxViewportSettings(0,0, 1.0f, 0.5f);
	m_ViewportData[4] = sgxViewportSettings(0,0.5f, 1.0f,0.5f);
	// 3 vertical
	m_ViewportData[5] = sgxViewportSettings(0,0,				1.0f/3.0f, 1.0f);
	m_ViewportData[6] = sgxViewportSettings(1.0f/3.0f, 0,	1.0f/3.0f, 1.0f);
	m_ViewportData[7] = sgxViewportSettings(2.0f/3.0f, 0,	1.0f/3.0f, 1.0f);
	// 3 horizontal
	m_ViewportData[8] = sgxViewportSettings(0,0,				1.0f,1.0f/3.0f);
	m_ViewportData[9] = sgxViewportSettings(0,1.0f/3.0f,		1.0f,1.0f/3.0f);
	m_ViewportData[10] = sgxViewportSettings(0,2.0f/3.0f,	1.0f,1.0f/3.0f);
	// 4 quads
	m_ViewportData[11] = sgxViewportSettings(0,0, 0.5f,0.5f);
	m_ViewportData[12] = sgxViewportSettings(0.5f,0, 0.5f,0.5f);
	m_ViewportData[13] = sgxViewportSettings(0,0.5f, 0.5f,0.5f);
	m_ViewportData[14] = sgxViewportSettings(0.5f,0.5f, 0.5f,0.5f);


// TODO: Config.Engine(GetAlphaSize)
	setMultiScene(1);
	updateViewports();
	clearClipRect();

	m_ViewCamera.clear();
	for(tUINT32 cams=0;cams<4;++cams) {
		m_ViewCamera.push_back((CSGXCamera()));
	}

	for(tMEM_SIZE i=0;i<getMaxLights();++i) {
		m_Lights.push_back(CreateLight());
	}

	m_PrimitiveMode.setState(eSolid);
	m_Fogging.setState(sgxFogging());
}

void CSGXGraphicsEngine::setCallbackHandler(sgxGraphicsEngineCallbackHandler *pHandler) {
	m_pHandler = pHandler;
}

void CSGXGraphicsEngine::showSystemCursor(const tBOOL show) {
	SGX_UNUSED(show);
}

void CSGXGraphicsEngine::hideSystemCursor() {
}


tBOOL sgxStateRenderMode::doApply()
{ 
	if (state == SGX_RENDERMODE_3D) {
		CSGXGraphicsEngine::get()->set3DMode();
	} else {
		CSGXGraphicsEngine::get()->set2DMode();
	}
	return bDirty; 
}

void 
CSGXGraphicsEngine::set2DMode()
{
}

void 
CSGXGraphicsEngine::set3DMode()
{
}

void 
CSGXGraphicsEngine::drawSkyBox(const CSGXTexture **pTextures,const sgxMatrix43 &transform, const sgxVector3 &minpos, const sgxVector3 &maxpos)
{
	SGX_UNUSED(pTextures);
	SGX_UNUSED(transform);
	SGX_UNUSED(minpos);
	SGX_UNUSED(maxpos);
}

// TODO: setCullMode
// TODO: RegisterLight

void 
CSGXGraphicsEngine::setMultiScene(tUINT32 split, CSGXGraphicsEngine::tMultiSceneSplit orientation)
{
	m_SplitScreenWindows = split;
	m_SplitScreenOrientation = orientation;
	updateViewports();
}

tUINT32 
CSGXGraphicsEngine::getMultiSceneWindows()
{
	return m_SplitScreenWindows;
}

void 
CSGXGraphicsEngine::updateViewports()
{
static tUINT32 start_idx[] = {0/*UNUSED*/, 0,1,5,11};

	// TODO: does reserve erase, or is there a better function here?
	//. TODO: Is it clear or erase

	m_Viewport.clear();
	m_Viewport.reserve(m_SplitScreenWindows);

	tUINT32 sw = getScreenWidth();
	tUINT32 sh = getScreenHeight();
	tUINT32 idx;

	idx = start_idx[m_SplitScreenWindows];
	if ((m_SplitScreenWindows == 2 || (m_SplitScreenWindows == 3))
		&& m_SplitScreenOrientation == eSceneSplitVertical) {
		idx += m_SplitScreenWindows;
	}

	for(tUINT32 i=0;i<m_SplitScreenWindows;++i,++idx) {
		m_Viewport.push_back(sgxViewport(m_ViewportData[idx], sw, sh));
	}

}

CSGXTexture *
CSGXGraphicsEngine::grabFrame(const tBOOL resizeToSquarePowerOf2)
{
	SGX_UNUSED(resizeToSquarePowerOf2);
	return NULL;
}

void *
CSGXGraphicsEngine::frameAlloc(tMEM_SIZE size)
{
	return m_pFrameBlock->Alloc(size);
}

void
CSGXGraphicsEngine::drawAlphaFaces()
{
	m_RenderMode.apply(SGX_RENDERMODE_3D);
	m_pAlphaFaces->draw();
}


void
CSGXGraphicsEngine::updateViewTransform(const sgxMatrix44 &view)
{
	m_ViewTransform = view;
	m_bProjectViewDirty = TRUE;
}

void
CSGXGraphicsEngine::updateProjectionTransform(const sgxMatrix44 &projection)
{
	m_ProjectionTransform = projection;
	m_bProjectViewDirty = TRUE;
}

void
CSGXGraphicsEngine::updateViewProjectionTransform()
{
	if (m_bProjectViewDirty) {
		m_ProjectViewTransform = m_ViewTransform * m_ProjectionTransform;
		m_bProjectViewDirty = FALSE;
	}
}

tBOOL 
CSGXGraphicsEngine::transformToScreen(sgxVector2 &screen, const sgxVector3 &world)
{
	const sgxViewport &viewport = m_Viewport[getCurrentScene()].getState();
	sgxVector4 scrpos(world);

	scrpos.transform(m_ProjectViewTransform);

	if (scrpos.w == 0) {
		return FALSE;
	}

	// TODO: Check order of transform
	scrpos.reduce();
	screen.x = viewport.origin_x + (1.0f+scrpos.x) * viewport.width/2;
	screen.y = viewport.origin_y + (1.0f+scrpos.y) * viewport.height/2;

	return TRUE;
}


void
CSGXGraphicsEngine::refresh() 
{
}

void
CSGXGraphicsEngine::release() 
{
	SGX_ENGINE_STATS("gfxengine_release");

	CSGXEngine::release();
}

//
//
//
void CSGXGraphicsEngine::clearTransformStack() {
	m_MatrixStack.clear();
}

void CSGXGraphicsEngine::setTransform(const sgxMatrix43 &m) {
	if (m_MatrixStack.size() == 0) {
		m_MatrixStack.push_back(m);
	} else {
		m_MatrixStack[m_MatrixStack.size()-1] = m;
	}
}

void CSGXGraphicsEngine::getTransform(sgxMatrix43 &m) const {
	if (m_MatrixStack.size() == 0) {
		m.identity();
	} else {
		m = m_MatrixStack[m_MatrixStack.size()-1];
	}
}

void CSGXGraphicsEngine::pushTransform(const sgxMatrix43 &m) {
	m_MatrixStack.push_back(m);
}

void CSGXGraphicsEngine::popTransform() {
	m_MatrixStack.pop_back();
}



void 
CSGXGraphicsEngine::clearClipRect() {
	setClipRect(0, 0, getScreenWidth(), getScreenHeight());
}

void 
CSGXGraphicsEngine::setClipRect(const tPOINT x1, const tPOINT y1, const tPOINT x2, const tPOINT y2) {
	m_CurrentClipArea.left = x1;
	m_CurrentClipArea.top = y1;
	m_CurrentClipArea.right = x2;
	m_CurrentClipArea.bottom = y2;
}

void 
CSGXGraphicsEngine::setClipRect(const sgxRect2i &rc) {
	setClipRect(rc.left,rc.top,rc.right,rc.bottom);
}

void CSGXGraphicsEngine::getClipRect(sgxRect2i &rc) {
	rc = m_CurrentClipArea;
}

//
//
//
tBOOL	
sgxGraphicsEngineCallbackHandler::onGraphicsEngineClosed() {
	return TRUE;
}
