//--------------------------------------------------------------------------------------
// Copyright (C) 2008 http://shift.delblanco.es
//--------------------------------------------------------------------------------------
// SHIFT Videogame is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 3 of the License, or 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 General Public License for more details.
//--------------------------------------------------------------------------------------
// Main.cpp
// Fichero principal del proyecto
//--------------------------------------------------------------------------------------
#include "dxstdafx.h"
#include "Main.h"

#include "XMLIO.h"
#include "GUI.h"
#include "Menu.h"
#include "Settings.h"
#include "3DSoundMng.h"
#include "3DCamera.h"
#include "3DParticle.h"

//--------------------------------------------------------------------------------------
// Espacio de nombres
//--------------------------------------------------------------------------------------
using namespace std;

//--------------------------------------------------------------------------------------
// Variables Globales
//--------------------------------------------------------------------------------------
extern	CSettings              *g_Settings;   // Instancia externa para las opciones (Settings.cpp)
extern	C3DCamera              *g_Camera;     // Instancia externa para la cámara 3D (3DCamera.cpp)
extern	C3DSoundMng            *g_3DSoundMng; // Instancia externa para manejar sonidos 3D (3DSoundMng.cpp)
extern	CMenu                  *g_Menu;       // Instancia externa para el menú 3D (Menu.cpp)
extern	CGUI                   *g_GUI;        // Instancia externa para el GUI (GUI.cpp)
extern  bool				   g_bCamPer;     // Flag para indicar si la cámara es o no persecutoria (3DCamera.cpp)
extern	vector<C3DParticles*>  g_aParticles;  // Vector de sistemas de partículas (3DParticle.cpp)
extern  vector<C3DMesh*>	   g_aAlphaYes;   // Vector de geometrías transparentes siempre (3DMesh.cpp)
extern  vector<C3DMesh*>	   g_aAlphaCam;   // Vector de geometrías transparentes ante cámaras persecutorias (3DMesh.cpp)
bool	g_bCreated	= false;                  // Para saber si se creó el dispositivo
bool	g_bInGame	= false;                  // Para saber si estamos durante el juego
bool	g_bInMenu	= false;				  // Para saber si estamos durante el menú

//--------------------------------------------------------------------------------------
// Entrada del programa
//--------------------------------------------------------------------------------------
INT WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
    // Asociamos los eventos con las funciones manejadoras
    DXUTSetCallbackDeviceCreated( OnCreateDevice );
    DXUTSetCallbackDeviceDestroyed( OnDestroyDevice );
    DXUTSetCallbackDeviceReset( OnResetDevice );
    DXUTSetCallbackDeviceLost( OnLostDevice );
    DXUTSetCallbackFrameRender( OnFrameRender );
    DXUTSetCallbackFrameMove( OnFrameMove );
    DXUTSetCallbackKeyboard( KeyboardProc );

    // Mostrar el cursor y ocultarlo en pantalla completa
    DXUTSetCursorSettings( true, true );

    // -----------------------------------------------
    // Inicializamos los componentes de la aplicación
    if(!IniApp()) EndApp();
    // -----------------------------------------------

    // Inicializamos el dispositivo
    DXUTInit(true, true, true);
    DXUTCreateWindow(MAIN_WIN_TITLE);
    DXUTCreateDevice(D3DADAPTER_DEFAULT, true, g_Settings->getWidth(), g_Settings->getHeigth(), MIN_WIDTH, MIN_HEIGHT);

    // Comprobamos si se pudo inicializar el dispositivo
    if(!g_bCreated) EndApp();

    // Aplicamos la configuración
    g_Settings->Apply(0);

    // Ajustamos el aspecto de la cámara
    float Aspect = (float)DXUTGetPresentParameters().BackBufferWidth / (float)DXUTGetPresentParameters().BackBufferHeight;
    g_Camera->setAspect(Aspect);

    // Partimos del menú principal del GUI
    g_GUI->setTypeGUI(GUI_TYPE_MENU_INIT);

    // Controlamos los mensajes de windows
    DXUTSetCallbackMsgProc(MsgProc);

    // Comenzamos el bucle de trabajo
    DXUTMainLoop();

    // -----------------------------------------------
    // Liberamos los componentes de la aplicación
    EndApp();
    // -----------------------------------------------

    // Este código nunca se llegará a ejecutar
    return DXUTGetExitCode();
}

//--------------------------------------------------------------------------------------
// Inicializamos los componentes de la aplicación
//--------------------------------------------------------------------------------------
bool IniApp(void)
{
    // Creamos los elementos de la aplicación
    g_Settings   = new CSettings();   if(!g_Settings)   return false;
    g_Camera     = new C3DCamera();   if(!g_Camera)     return false;
    g_3DSoundMng = new C3DSoundMng(); if(!g_3DSoundMng) return false;
    g_Menu       = new CMenu();       if(!g_Menu)       return false;
    g_GUI        = new CGUI();        if(!g_GUI)        return false;

    return true;
}

//--------------------------------------------------------------------------------------
// Finalizamos los componentes de la aplicación
//--------------------------------------------------------------------------------------
void EndApp(void)
{
    // Tomamos el proceso como des-inicializado
    g_bCreated = false;

    // Desvinculamos el manejador de eventos del GUI
    DXUTSetCallbackMsgProc(NULL);

    // Liberamos los elementos de la aplicación
    SAFE_DELETE(g_GUI);
    SAFE_DELETE(g_Menu);
    SAFE_DELETE(g_3DSoundMng);
    SAFE_DELETE(g_Camera);
    SAFE_DELETE(g_Settings);

    // Salimos de la aplicación
    DXUTShutdown();
    exit(DXUTGetExitCode());
}

//--------------------------------------------------------------------------------------
// Cuando se crea el dispositivo
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnCreateDevice(IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc)
{
    // Inicializamos elementos de la aplicación
    if(!g_Camera->initialize())           return S_FALSE;
    if(!g_3DSoundMng->initialize())       return S_FALSE;
    if(!g_Menu->initialize())             return S_FALSE;
    if(!g_GUI->initialize())              return S_FALSE;

    // Tomamos el dispositivo como creado
    g_bCreated = true;
    return S_OK;
}

//--------------------------------------------------------------------------------------
// Cuando se destruye el dispositivo
//--------------------------------------------------------------------------------------
void CALLBACK OnDestroyDevice()
{
    // Liberamos los elementos de la aplicación
    SAFE_RELEASE(g_Camera);
    SAFE_RELEASE(g_Menu);
    SAFE_RELEASE(g_GUI);
}

//--------------------------------------------------------------------------------------
// Cuando se cambia entre windowed y fullscreen
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnResetDevice(IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc)
{
    // Si el dispositivo no está creado salimos
    if(!g_bCreated) return S_OK;

    // Adecuamos los elementos de la aplicación
    g_Camera->restore();
    g_Menu->restore();
    g_GUI->restore();

	// Adecuamos los sistemas de partículas
	for (vector<C3DParticles*>::iterator itr = g_aParticles.begin(); itr != g_aParticles.end() ; itr++)
		(*itr)->restore();

    return S_OK;
}

//--------------------------------------------------------------------------------------
// Cuando se cambia entre windowed y fullscreen
//--------------------------------------------------------------------------------------
void CALLBACK OnLostDevice()
{
    // Si el dispositivo no está creado salimos
    if(!g_bCreated) return;

    // Liberamos los elementos de la aplicación
    g_Camera->free();
    g_Menu->free();
    g_GUI->free();

	// Liberamos los sistemas de partículas
	for (vector<C3DParticles*>::iterator itr = g_aParticles.begin(); itr != g_aParticles.end() ; itr++)
		(*itr)->free();
}

//--------------------------------------------------------------------------------------
// Para actualizar el estado de la escena
//--------------------------------------------------------------------------------------
void CALLBACK OnFrameMove(IDirect3DDevice9* pd3dDevice, double fTime, float fElapsedTime)
{
    // Si el dispositivo no está creado salimos
    if(!g_bCreated) return;

    // Actualizamos los elementos de la aplicación
    g_Camera->update();
    g_Menu->update();
    g_GUI->update();

    // Actualizamos los cambios en los sonidos 3D
    g_3DSoundMng->update();
}

//--------------------------------------------------------------------------------------
// Para renderizar la escena
//--------------------------------------------------------------------------------------
void CALLBACK OnFrameRender(IDirect3DDevice9* pd3dDevice, double fTime, float fElapsedTime)
{
    // Si el dispositivo no está creado salimos
    if(!g_bCreated) return;

    // Borramos el contenido del backbuffer
    HRESULT hr;
    V(pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,0), 1.0f, 0));

    // Comenzamos el renderizado
    if(SUCCEEDED(pd3dDevice->BeginScene()))
    {
		// Renderizamos la cámara
        g_Camera->render();

		// Renderizamos la escena
        g_Menu->render();

		// Activamos alpha-blending
		pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
		pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);

		// Renderizamos los sistemas de partículas
		pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
		pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
		for (vector<C3DParticles*>::iterator itr = g_aParticles.begin(); itr != g_aParticles.end() ; itr++)
			(*itr)->render();

		// Renderizamos las geometrías trasnparentes siempre
		pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCCOLOR);
		pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
		for (vector<C3DMesh*>::iterator itr = g_aAlphaYes.begin(); itr != g_aAlphaYes.end() ; itr++)
			if((*itr)->getObject()->getEnabled()) (*itr)->render(true);

		// Renderizamos las geometrías trasnparentes ante cámaras persecutorias
		pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCCOLOR);
		pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
/*
		pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
		pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR);
*/
		if (g_bCamPer)
		for (vector<C3DMesh*>::iterator itr = g_aAlphaCam.begin(); itr != g_aAlphaCam.end() ; itr++)
			if((*itr)->getObject()->getEnabled()) (*itr)->render(true);

		// Desactivamos alpha-blending
		pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);
		pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);

		// Renderizamos el interfaz
        g_GUI->render();

        // Finalizamos el renderizado
        V(pd3dDevice->EndScene());
    }
}

//--------------------------------------------------------------------------------------
// Antes de manejar los mensajes de Windows
//--------------------------------------------------------------------------------------
LRESULT CALLBACK MsgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool* pbNoFurtherProcessing)
{
    // Pasamos los mensajes de windows al GUI
    *pbNoFurtherProcessing = g_GUI->getGUI()->MsgProc( hWnd, uMsg, wParam, lParam );
    return 0;
}

//--------------------------------------------------------------------------------------
// Manejador de las pulsaciones del teclado (excepto manejo en el juego)
//--------------------------------------------------------------------------------------
void CALLBACK KeyboardProc(UINT nChar, bool bKeyDown, bool bAltDown)
{
    // Si el dispositivo no está creado salimos
    if(!g_bCreated) return;

    // Manejamos poulsaciones de teclas
    if(bKeyDown)
    {
        switch(nChar)
        {
        case VK_ESCAPE:
            switch(g_GUI->getTypeGUI())
            {
            // Al pulsar escape en el modo juego y modo pausa al botón 'pausa'
            case GUI_TYPE_INTERFAZ_GAME:   g_GUI->throwEvent(GUI_BTN_GAME_PAUSE); break;
            // En otro caso, es como el botón 'cancelar'
            default:                       g_GUI->throwEvent(GUI_BTN_CANCEL); break;
            }
            break;
        case VK_RETURN:
            // Al pulsar intro es como que pulsásemos el botón de aceptar del GUI
            // (siempre que no estemos en el modo juego)
            if(g_GUI->getTypeGUI() != GUI_TYPE_INTERFAZ_GAME)
                g_GUI->throwEvent(GUI_BTN_ACCEPT);
            break;
        case VK_UP:
        case VK_LEFT:
            // Al pulsar izquierda o arriba es como que pulsásemos el botón de prior del GUI
            // (siempre que no estemos en el modo juego)
            if(g_GUI->getTypeGUI() != GUI_TYPE_INTERFAZ_GAME)
                g_GUI->throwEvent(GUI_BTN_SELECT_PRIOR);
            break;
        case VK_DOWN:
        case VK_RIGHT:
            // Al pulsar derecha o abajo es como que pulsásemos el botón de next del GUI
            // (siempre que no estemos en el modo juego)
            if(g_GUI->getTypeGUI() != GUI_TYPE_INTERFAZ_GAME)
                g_GUI->throwEvent(GUI_BTN_SELECT_NEXT);
            break;
        case 67:
            // Al pulsar 'c' cambiamos el tipo de cámara persecutoria
            // (siempre que estemos en el modo juego con un jugador)
			if( g_GUI->getTypeGUI() == GUI_TYPE_INTERFAZ_GAME &&
				g_Menu->getMGame()->getType() == GAME_1PLAYER )
				g_Camera->nextType();
            break;
        }
    }
}
