/*
 * 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 "KeosRoot.h"
#include "KeosDynLibManager.h"
#include "KeosResourceManager.h"
#include "KeosMediaManager.h"
#include "KeosRenderSystem.h"
#include "KeosLogger.h"
#include "KeosPlatformManager.h"
#include "KeosDynLib.h"
#include "KeosImagesLoader.h"
#include "KeosXMLConfigFile.h"
#include "KeosFontManager.h"
#include "KeosConsole.h"
#include "KeosConfigDialog.h"
#include "KeosErrorDialog.h"
#include "KeosMD2Loader.h"
#include "KeosMDLQ1Loader.h"
#include "KeosShadersLoader.h"

namespace Keos
{
    //=======================================================================
    // CRoot implementation
    //=======================================================================
    
    //-----------------------------------------------------------------------
    // Termination handler
    extern "C" KEOS_EXPORT void handleTerminate(void)
    {
        IErrorDialog* pDlg = CPlatformManager::Instance().CreateErrorDialog();
        CException* pE = CException::GetLastException();

        if (pE)
            pDlg->Display(pE->what());
        else
            pDlg->Display("Unknown");

        CRoot::Instance().Shutdown();

        // Abort
        exit(-1);
    }

    //-----------------------------------------------------------------------
    void CRoot::_TermHandler()
    {
        handleTerminate();
    }

    //-----------------------------------------------------------------------
    CRoot::CRoot()
    { 
		CRoot::_SetInstance(this);

		// Exception handler
        SET_TERM_HANDLER;

        // Init default logger
        ILogger::SetLogger(new CLoggerFile(KEOS_LOG_FILE));

        // Managers creation
        CDynLibManager::Create();
        CResourceManager::Create();
        CMediaManager::Create();
        CPlatformManager::Create();
        CFontManager::Create();

        // Timer
        m_pTimer = CPlatformManager::Instance().CreateTimer();

		// Console
		CConsole::Create();

        // Register loaders
        CMediaManager& MediaManager = CMediaManager::Instance();
        MediaManager.RegisterLoader(new CImagesLoader, "bmp, dds, jpg, pcx, png, pnm, raw, sgi, tga, tif");
        MediaManager.RegisterLoader(new CMD2Loader, "md2");
        MediaManager.RegisterLoader(new CMDLQ1Loader, "mdl");
        MediaManager.RegisterLoader(new CShadersLoader(SHADER_VERTEX), "vcg");
        MediaManager.RegisterLoader(new CShadersLoader(SHADER_PIXEL),  "pcg");
    
        m_pActiveRenderSystem = NULL;

		m_strVersion = CStringConverter::ToString(KEOS_VERSION_MAJOR) + "." + 
            CStringConverter::ToString(KEOS_VERSION_MINOR) + "." + 
            CStringConverter::ToString(KEOS_VERSION_PATCH) + " " + 
            "(" + KEOS_VERSION_NAME + ")";

		LoadPlugin(KEOS_D3D9_LIB);
		LoadPlugin(KEOS_OGL_LIB);
    }

    //-----------------------------------------------------------------------
    CRoot::~CRoot()
    {    
        
    }

    //-----------------------------------------------------------------------
    IRenderWindow* CRoot::Initialise(bool bAutoCreateWindow, const String& windowTitle)
    {
        Assert(m_pActiveRenderSystem != 0);

		ILogger::Log("--> Initialise Root");

        return m_pActiveRenderSystem->Initialise(bAutoCreateWindow, windowTitle);
    }
    
    //-----------------------------------------------------------------------
	void CRoot::LoadPlugin(const String& pluginName)
	{
        CDynLib* lib = CDynLibManager::Instance().Load(pluginName);

		// Store for later unload
		m_PluginLibs.push_back(lib);

		// Call startup function
		DLL_START_PLUGIN pFunc = (DLL_START_PLUGIN)lib->GetSymbol("dllStartPlugin");
		
        if( !pFunc )
            DYNLIB_EXCEPT(pluginName, "Cannot find symbol dllStartPlugin", "CRoot::LoadPlugin");

        pFunc();
	}

    //-----------------------------------------------------------------------
    void CRoot::UnloadPlugins(void)
    {
        std::vector<CDynLib*>::reverse_iterator i;

        // NB Unload plugins in reverse order to enforce dependencies
        for (i = m_PluginLibs.rbegin(); i != m_PluginLibs.rend(); ++i)
        {
            // Call plugin shutdown
            DLL_STOP_PLUGIN pFunc = (DLL_STOP_PLUGIN)(*i)->GetSymbol("dllStopPlugin");
            pFunc();
            // Unload library & destroy
            CDynLibManager::Instance().Unload(*i);
        }
        m_PluginLibs.clear();
    }

    //-----------------------------------------------------------------------
    void CRoot::StopRendering(void)
    {
	    m_bStopRendering = true;
    }	

    //-----------------------------------------------------------------------
    void CRoot::StartRendering(void)
    {
        Assert(m_pActiveRenderSystem != 0);

        m_pActiveRenderSystem->InitAllRenderTargets();
        

        // Infinite loop, until broken out of by frame listeners
		// or break out by calling queueEndRendering()
        ulong cpt = 0;
		m_bStopRendering = false;
		while( !m_bStopRendering)
		{
#if KEOS_PLATFORM == KEOS_PLATFORM_WIN32
            // Pump events on Win32
    		MSG  msg;
			while( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
			{
				TranslateMessage( &msg );
				DispatchMessage( &msg );
			}
#endif

            if (!RenderOneFrame())
                break;

            cpt++;
		}
    }

    //-----------------------------------------------------------------------
    bool CRoot::RenderOneFrame(void)
    {
        m_pActiveRenderSystem->UpdateAllRenderTargets();
        return true;
    }
    
    //-----------------------------------------------------------------------
    CTimer* CRoot::GetTimer(void)
    {
        return m_pTimer;
    }
    
    //-----------------------------------------------------------------------
    void CRoot::Shutdown(void)
    {
		// Console destruction
		CConsole::Destroy();
	
		// Managers destruction
		CFontManager::Destroy();
		CResourceManager::Destroy();
		CMediaManager::Destroy();
		CPlatformManager::Instance().DestroyTimer(m_pTimer);
		CPlatformManager::Destroy();
		UnloadPlugins();
		CDynLibManager::Destroy();
	    
		Keos::CRoot::Destroy();
		ILogger::Log("<-- Shutdown Root");
		ILogger::Destroy();    
    }

    //-----------------------------------------------------------------------
    bool CRoot::RestoreConfig(void)
    {
		// Restores configuration from saved state
        // Returns true if a valid saved configuration is
        //   available, and false if no saved config is
        //   stored, or if there has been a problem
        CXMLConfigFile configFile;

        if (!configFile.Load(KEOS_CONFIG_FILE))
			return false;

		ILogger::Log("Load of the config file (%s) :", KEOS_CONFIG_FILE);
        ILogger::Log("--------------------------------------");
       
		String strRS;
        RenderSystemList::iterator pRend;

        strRS = configFile.GetSetting("RenderSystem");
        if(strRS.empty())
        {
            // No render system entry - error
            return false;
        }
		
        pRend = GetAvailableRenderers()->begin();
        while (pRend != GetAvailableRenderers()->end())
        {
            String strName = (*pRend)->GetName();
            if (strName == strRS)
                break;
            pRend++;
        }

        if (pRend == GetAvailableRenderers()->end())
        {
            // Unrecognised render system
            return false;
        }

        SetRenderSystem(*pRend);

        for( SettingsMap::iterator it = configFile.GetSettingsMap().begin(); 
            it != configFile.GetSettingsMap().end(); ++it )
        {
            String strKey = it->first;
            String strValue = it->second;
            
			if (strKey != "RenderSystem")
				m_pActiveRenderSystem->SetConfigOption(strKey, strValue);

            ILogger::Log("* %s = %s", strKey.c_str(), strValue.c_str());
        }  

		return true;
    }

    //-----------------------------------------------------------------------
    void CRoot::SaveConfig(void)
    {
        CXMLConfigFile configFile;
		ConfigOptionMap Options = m_pActiveRenderSystem->GetConfigOptions();

		ConfigOption optDevice;
		optDevice.name = "RenderSystem";
		optDevice.currentValue = m_pActiveRenderSystem->GetName();
		
		Options["RenderSystem"] = optDevice;
        if (!configFile.Save(KEOS_CONFIG_FILE, Options))
        {
			ILogger::Log("Cannot save the config file (%s) !", KEOS_CONFIG_FILE);         
        }

		ILogger::Log("Config file saved : %s", KEOS_CONFIG_FILE);
    }

    //-----------------------------------------------------------------------
    bool CRoot::ShowConfigDialog(void)
    {
        // Displays the standard config dialog
        IConfigDialog* pDlg;
        bool bIsOk;

        pDlg = CPlatformManager::Instance().CreateConfigDialog();

        bIsOk = pDlg->Display();

        CPlatformManager::Instance().DestroyConfigDialog(pDlg);

        return bIsOk;
    }

	//-----------------------------------------------------------------------
    void CRoot::SetRenderSystem(IRenderSystem* pSystem)
    {
        // Sets the active rendering system
        // Can be called direct or will be called by
        //   standard config dialog

        // Is there already an active renderer?
        // If so, disable it and init the new one
        if( m_pActiveRenderSystem && m_pActiveRenderSystem != pSystem )
        {
            m_pActiveRenderSystem->Shutdown();
        }

        m_pActiveRenderSystem = pSystem;
    }

	//-----------------------------------------------------------------------
    void CRoot::AddRenderSystem(IRenderSystem *pNewRend)
    {
        m_Renderers.push_back(pNewRend);
    }

} // namespace Keos