/*
 * This source file is part of KEOS (Free 3D Engine)
 * For the latest info, see http://www.keosengine.org/
 * E-mails : thierry.vouriot@keosengine.org, yeri@keosengine.org
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA, or go to
 * http://www.gnu.org/copyleft/lesser.txt.
 *
 */

#include "Main.h"

const String CCgShaders::ms_Shaders[] =  {"negative", "scale", "grey", "simple", "none"};

//-----------------------------------------------------------------------
void CCgShaders::Init(void) 
{
	m_CameraPos.Set(0, 0, -50);
	m_LightDir.Set(1.0f, 0.0f, 0.5f);
	m_LightDir.Normalize();
    
    // Init 3D transformation matrix
    CMatrix4 MatView, MatProj, MatRotY, MatRotZ;
    MatRotY.SetRotationY(-3.14159f / 2, TVector3F(0, 0, 0));
    MatRotZ.SetRotationZ(-3.14159f / 2, TVector3F(0, 0, 0));
    m_MatView3D.LookAt(m_CameraPos, m_CameraPos + TVector3F(0.0f, 0.0f, 1.0f));
    m_MatView3D = MatRotY * MatRotZ * m_MatView3D;
    m_MatProj3D.PerspectiveFOV(3.14159f / 2, (Real)GetRenderTarget()->GetWidth()/(Real)GetRenderTarget()->GetHeight(), 1.0f, 300.0f);

    // Init 2D transformation matrix
    m_MatView2D.Identity();
    m_MatProj2D.OrthoOffCenter(0, 0, GetRenderTarget()->GetWidth(), GetRenderTarget()->GetHeight());

    // Change background color
	GetRenderSystem()->SetClearColor(CColor(150,150,150));

	// Search path
    CMediaManager::Instance().AddSearchPath("Textures");
    CMediaManager::Instance().AddSearchPath("Fonts");
    CMediaManager::Instance().AddSearchPath("Models");
    CMediaManager::Instance().AddSearchPath("Shaders");
    
	// Load first shaders
    m_nShaderIndex = -1;
    NextShaders();

    // Load font
    CFontManager::Instance().LoadFont("Atarian.ttf","Font",14);

    // Create graphic string
	m_pFPS = new CGraphicString(TVector2I(0, 0), "\n\n\n\n\n\n", CColor::Black, "Font");
	m_pFPS->Align(ALIGN_LEFT | ALIGN_BOTTOM, CRectangle(10, 10, GetRenderTarget()->GetWidth()-20, GetRenderTarget()->GetHeight()-20)); 
		
	m_strModel = "tekkblade.md2";
	m_strTexture = "tekkblade.pcx";
	m_pGSModel = new CGraphicString(TVector2I(0, 0), "", CColor::Black, "Font");
	m_pGSModel->Align(ALIGN_RIGHT | ALIGN_BOTTOM, CRectangle(10, 10, GetRenderTarget()->GetWidth()-20, GetRenderTarget()->GetHeight()-20)); 
		
    // Add a "look" to the console
    CConsole::Instance().ChangeLook(new Console::CLookDefault("Font", TVector2F(GetRenderTarget()->GetWidth(), (Real)GetRenderTarget()->GetHeight() / 3.0), "ConsoleBG.jpg"));	
    
	// Register console command
	CConsole::Instance().RegisterCommand("load", Console::Bind(Load, *this), "load <modelfile> <texturefile>");
    CConsole::Instance().RegisterCommand("loadmodel", Console::Bind(LoadModel, *this), "loadmodel <modelfile>");
    CConsole::Instance().RegisterCommand("loadtexture", Console::Bind(LoadTexture, *this), "loadtexture <texturefile>");
	CConsole::Instance().RegisterCommand("nextshaders", Console::Bind(NextShaders, *this), "nextshaders");
	CConsole::Instance().RegisterCommand("next", Console::Bind(NextShaders, *this), "next");

    // Load model with texture
	Load(m_strModel,m_strTexture);
}

//-----------------------------------------------------------------------
//! Load a model and a texture
void CCgShaders::Load(const String& Filename, const String& Filename2)
{
	m_Model = CMediaManager::Instance().LoadMediaFromFile<CModel>(Filename);
	m_strModel = Filename;
    m_Model->SetTexture(Filename2);
	m_strTexture = Filename2;
}

//-----------------------------------------------------------------------
//! Load a model
void CCgShaders::LoadModel(const String& Filename)
{
    m_Model = CMediaManager::Instance().LoadMediaFromFile<CModel>(Filename);
	m_strModel = Filename;
	m_strTexture = "(default)";
}

//-----------------------------------------------------------------------
//! Load a texture
void CCgShaders::LoadTexture(const String& Filename)
{
	m_Model->SetTexture(Filename);
	m_strTexture = Filename;
}

//-----------------------------------------------------------------------
//! Change current shaders
String CCgShaders::NextShaders()
{
    m_nShaderIndex = (m_nShaderIndex + 1) % (sizeof(ms_Shaders) / sizeof(*ms_Shaders));

    if (ms_Shaders[m_nShaderIndex] == "none")
    {
        // Unload shaders
        m_VertexShader.Unload();
        m_PixelShader.Unload();
    }
    else
    {
        // Load shaders
        m_VertexShader.LoadFromFile(ms_Shaders[m_nShaderIndex] + ".vcg");
        m_PixelShader.LoadFromFile(ms_Shaders[m_nShaderIndex] + ".pcg");
    }

	return "Shaders: " + ms_Shaders[m_nShaderIndex];
}

//-----------------------------------------------------------------------
//! Update the parameters of current shaders
void CCgShaders::UpdateShadersParams()
{
    // Get matrix
    CMatrix4 ModelViewProj;
    CMatrix4 ModelView;
	GetRenderSystem()->GetMatrix(MAT_MODELVIEW, ModelView);
	ModelViewProj = ModelView * m_MatProj3D;

    if (ms_Shaders[m_nShaderIndex] == "scale")
    {
        m_VertexShader.SetParameter("ModelViewProj", ModelViewProj);
        m_VertexShader.SetParameter("ModelView",     ModelView);
        m_VertexShader.SetParameter("LightDir",      m_LightDir);
		m_VertexShader.SetParameter("Scale",         1.1);
    }
    else if (ms_Shaders[m_nShaderIndex] == "simple")
    {
        m_VertexShader.SetParameter("ModelViewProj", ModelViewProj);
    }
    else if (ms_Shaders[m_nShaderIndex] == "negative")
    {
        m_VertexShader.SetParameter("ModelViewProj", ModelViewProj);
        m_PixelShader.SetParameter("Factor", cosf(GetTickCount() / 1000.0f) * 0.5f + 0.5f);
    }
    else if (ms_Shaders[m_nShaderIndex] == "grey")
    {
        m_VertexShader.SetParameter("ModelViewProj", ModelViewProj);
    }
}

//-----------------------------------------------------------------------
void CCgShaders::Render(void)
{
    // Switch to 3D mode
    GetRenderSystem()->LoadMatrix(MAT_MODELVIEW,  m_MatView3D);
    GetRenderSystem()->LoadMatrix(MAT_PROJECTION, m_MatProj3D);

    // Save current view matrix
    GetRenderSystem()->PushMatrix(MAT_MODELVIEW);

    // Rotation and translation
	m_Translation.SetTranslation(0, -25, 0);
    GetRenderSystem()->LoadMatrixMult(MAT_MODELVIEW, m_Rotation * m_Translation);
    
    // Update shaders parameters
    // Must be called when the modelview matrix is correct (rotation, translation)
    UpdateShadersParams();

    // Enable shaders
    GetRenderSystem()->SetVertexShader(m_VertexShader.GetShader());
    GetRenderSystem()->SetPixelShader(m_PixelShader.GetShader());

	// Display model (right)
    m_Model->Render();

    // Disable shaders
    GetRenderSystem()->SetVertexShader(NULL);
    GetRenderSystem()->SetPixelShader(NULL);

    // Get the last view matrix
	GetRenderSystem()->PopMatrix(MAT_MODELVIEW);

    // Save current view matrix
	GetRenderSystem()->PushMatrix(MAT_MODELVIEW);

    // Rotation and translation
	m_Translation.SetTranslation(0, 25, 0);
	GetRenderSystem()->LoadMatrixMult(MAT_MODELVIEW, m_Rotation2 * m_Translation);

    // Display model (left)
	m_Model->Render();

    // Get the last view matrix
    GetRenderSystem()->PopMatrix(MAT_MODELVIEW);

    // Switch to 2D mode
    GetRenderSystem()->LoadMatrix(MAT_MODELVIEW,  m_MatView2D);
    GetRenderSystem()->LoadMatrix(MAT_PROJECTION, m_MatProj2D);
    
    // Draw text
    m_pFPS->Draw();
	m_pGSModel->Draw();

	// Draw console
    CConsole::Instance().Draw();
}

//-----------------------------------------------------------------------
void CCgShaders::Update(void)
{
	uint nCurrentTime = GetTime();

    // Change the rotation matrix
    m_Rotation.SetRotationZ(nCurrentTime / 1500.f);
	m_Rotation2.SetRotationZ(-(nCurrentTime / 1500.f));
    
    // Update the FPS text
    m_pFPS->Text = "Current FPS: " + CStringConverter::ToString(GetRenderTarget()->GetLastFPS()) + 
                    "\nAverage FPS: " + CStringConverter::ToString(GetRenderTarget()->GetAverageFPS()) +
                    "\nBest FPS: " + CStringConverter::ToString(GetRenderTarget()->GetBestFPS()) +
                    "\nWorst FPS: " + CStringConverter::ToString(GetRenderTarget()->GetWorstFPS()) +
					"\n\nf1 / f2 to show / hide the console, escape to exit";

    // Update the "Model" text
	m_pGSModel->Text = "Model: " + m_strModel + "\nTexture: " 
						+ m_strTexture + "\nShaders: " + ms_Shaders[m_nShaderIndex];
	m_pGSModel->Align(ALIGN_RIGHT | ALIGN_BOTTOM, CRectangle(10, 10, GetRenderTarget()->GetWidth()-20, GetRenderTarget()->GetHeight()-20)); 

    // Console update
    CConsole::Instance().Update();
}

//-----------------------------------------------------------------------
void CCgShaders::Release(void)
{
	KEOS_DELETE(m_pFPS);
	KEOS_DELETE(m_pGSModel);
}

//-----------------------------------------------------------------------
int main(int argc, char* argv[])
{ 
	KEOS_START();
    try
    { 
        CCgShaders CgShadersSample;

		// Show the configuration dialog and initialise the system
        // You can skip this and use KeosRoot.RestoreConfig() to load configuration
        // settings if you were sure there are valid ones saved in config.xml
        if (KeosRoot.ShowConfigDialog())
		{
			KeosRoot.Initialise(true, "KEOS Sample: CgShaders");
			KeosRoot.GetRenderSystem()->SetRenderApp(&CgShadersSample);
			KeosRoot.StartRendering();
		}
    }
    catch (const CException& E)
    {
        MessageBox(NULL, E.what(), "Keos Exception", MB_ICONWARNING);
    }

    KEOS_SHUTDOWN();
	return EXIT_SUCCESS;
}