// ---------------------------------------------------------------------------
// 
//   Lily Engine alpha
// 
//   Copyright (c) 2010 Clifford Jolly
//     http://cliffjolly.com
//     cliff@cliffjolly.com
// 
// ---------------------------------------------------------------------------
// 
//   This program 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 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
//   General Public License for more details.
// 
//   You should have received a copy of the GNU 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
// -------------------------- END HEADER -------------------------------------

#include "../hacks/winhacks.h"

#include <iostream>
#include <vector>
#include <map>
#include <cmath>
#include <climits>
#include <sstream>
using namespace std;

#include "../console/console.h"
using namespace Console;

#include <SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>

#include "../soundmanager/soundmanager.h"
#include "../math/vector2d.h"
#include "../malfont/malfont.h"
#include "../malfont/fontmanager.h"
#include "../filesystem/filesystem.h"
#include "../texturemanager/texture.h"
#include "../texturemanager/texturemanager.h"

#include "polygon.h"
#include "boundingbox.h"
#include "world.h"
#include "entity.h"
#include "terrainblock.h"
#include "editorui.h"
#include "quadtree.h"

#include "renderer.h"
#include "input.h"
#include "engineui.h"
#include "config.h"

#include "../uijunk/widget.h"
#include "../uijunk/widgetcontainer.h"
#include "../uijunk/widgetbutton.h"
#include "../uijunk/widgetalignedbox.h"
#include "../uijunk/widgetpaddedbox.h"
#include "../uijunk/widgettext.h"

#define CHECK_GL_ERROR { assert(glGetError() == GL_NO_ERROR); }

// TODO: Find a better place to put this code.

struct JoyHeld {
    // up, down, left, right
    bool dirs[4];
    bool buttons[3];
};

void getJoystickEventsForUI(World *world, vector<WidgetEvent> &events) {

    static std::vector<JoyHeld> heldStuff;
    
    static bool initDone = false;
    if(!initDone) {
        initDone = true;

        int numJoys = inputGetNumJoysticks();

        for(int i = 0; i < numJoys; i++) {
            JoyHeld held = { {false, false, false, false }, {false, false, false} };
            heldStuff.push_back(held);
        }

    }

    for(int joyNum = 0; joyNum < inputGetNumJoysticks(); joyNum++) {

        SDL_Joystick *stick = (SDL_Joystick*)inputGetRawJoystick(joyNum);

        bool *joyDirectionHeld = heldStuff[joyNum].dirs;
        bool *buttonHeld = heldStuff[joyNum].buttons;

        if(!stick) continue;

        float axis0 = SDL_JoystickGetAxis(stick, 0);
        float axis1 = SDL_JoystickGetAxis(stick, 1);

        // TODO: Make these remappable.
        bool button[3];
        for(int i = 0; i < 3; i++) {
            button[i] = SDL_JoystickGetButton(stick, i);
        }

        float threshold = 16384;

        WidgetEvent nextTemplate;
        nextTemplate.type = WIDGETEVENT_FOCUSNEXT;

        WidgetEvent prevTemplate;
        prevTemplate.type = WIDGETEVENT_FOCUSPREV;

        // Confirmation just gets mapped to the enter key.
        WidgetEvent_Keyboard confirmTemplate;
        confirmTemplate.type = WIDGETEVENT_KEYDOWN;
        confirmTemplate.keyCode = WIDGET_KEY_RETURN;

        // We need something to do here. This should ideally turn into a
        // close/cancel thing.
        WidgetEvent cancelTemplate;
        cancelTemplate.type = WIDGETEVENT_IDUNNO;


        if(axis0 > threshold && !joyDirectionHeld[3]) {

            // Right pressed.
            joyDirectionHeld[3] = true;
            events.push_back(nextTemplate);

        } else if(axis0 < threshold && joyDirectionHeld[3]) {

            // Right released.
            joyDirectionHeld[3] = false;
        }

        if(axis0 < -threshold && !joyDirectionHeld[2]) {

            // Left pressed.
            joyDirectionHeld[2] = true;
            events.push_back(prevTemplate);

        } else if(axis0 > -threshold && joyDirectionHeld[2]) {

            // Left released.
            joyDirectionHeld[2] = false;
        }


        if(axis1 > threshold && !joyDirectionHeld[1]) {

            // Down pressed.
            joyDirectionHeld[1] = true;
            events.push_back(nextTemplate);

        } else if(axis1 < threshold && joyDirectionHeld[1]) {

            // Down released.
            joyDirectionHeld[1] = false;
        }

        if(axis1 < -threshold && !joyDirectionHeld[0]) {

            // Up pressed.
            joyDirectionHeld[0] = true;
            events.push_back(prevTemplate);

        } else if(axis1 > -threshold && joyDirectionHeld[0]) {

            // Up released.
            joyDirectionHeld[0] = false;
        }

        for(int i = 0; i < 3; i++) {
            if(button[i] && !buttonHeld[i]) {

                // Button was pressed.
                switch(i) {
                case 0:
                case 2:
                    events.push_back(*((WidgetEvent*)&confirmTemplate));
                    break;
                case 1:
                    events.push_back(cancelTemplate);
                    break;
                }

                buttonHeld[i] = true;

            } else if(!button[i]) {
                buttonHeld[i] = false;
            }
        }
    }
}


void gameMain(int argc, char *argv[]) {
	
	// Event and frame timing stuff.
	SDL_Event event;
	bool quit = false;
	unsigned int frameStartTime;

	// TODO: Move all this command line garbage to somewhere better.
	bool createEditor = true;
	bool debugMode = true;
	bool overrideVisibility = false;
	string gameName = "";
	
	bool disableShaders = false;
    bool disableLight = false;
    bool disableJoysticks = false;
    bool showFPS = false;

	for(int i = 1; i < argc; i++) {
		
		if(!strcmp(argv[i], "--disableshaders")) {
			disableShaders = true;
			continue;
		}
		
		/*
		if(!strcmp(argv[i], "--editor")) {
			createEditor = true;
			continue;
		}
		
		if(!strcmp(argv[i], "--debug")) {
			createEditor = true;
			debugMode = true;
			continue;
		}*/

		if(!strcmp(argv[i], "--override_editor_only_vis")) {
			overrideVisibility = true;
			continue;
		}
		
		if(!strcmp(argv[i], "--game")) {
			i++;
			if(i < argc) {
				gameName = argv[i];
			}
			continue;
		}

        if(!strcmp(argv[i], "--nolight")) {
            disableLight = true;
        }

        if(!strcmp(argv[i], "--nojoysticks")) {
            disableJoysticks = true;
        }

        if(!strcmp(argv[i], "--showfps")) {
            showFPS = true;
        }
	}

    // Open joysticks and stuff.
    inputInit(disableJoysticks);

	// Create the game world and editor.
	World *gameWorld = new World(gameName);
	
	if(debugMode) {
		gameWorld->setSaveName("debug");
	}
    
    // Make the editor.
	EditorUI *editorUI = NULL;
	if(createEditor) {
		editorUI = new EditorUI(gameWorld, debugMode);
		if(!debugMode) {
			editorUI->setVisible(true);
		}
		if(overrideVisibility) {
			editorUI->overrideEditorOnlyVisibility = true;
		}
	}

    if(!gameName.size()) {

        // No game specified. Start something based on the list of
        // games if there's only one or show a UI box to select one.

        popupGameSelectScreen(gameWorld);

    } else {

        loadConfig(gameName, gameWorld);

    }

    // Do lighting / shaders command line stuff. AFTER loading the
    // config if the game was specified on the command line.

    if(disableLight) {
        gameWorld->setLighting(!disableLight);
    }
	
    TextureManager *texManager = TextureManager::get();
	if(disableShaders) {
		texManager->setShadersEnabled(false);
	}
    
    // Pre scan all textures to get size information.

    texManager->scanTextureSizes("games");

    // Main loop.
	
	unsigned int frame = 0;

    int lagTime = 0; // How much time we have to make up for by not
                     // drawing.

	unsigned int lastFrameTime = 1; // Time spent on the last frame,
                                    // total.

	while(!quit) {

		gameWorld->getRenderer()->beginScene();
		
		frameStartTime = SDL_GetTicks();
		frame++;
		
		if(editorUI) {
			if(editorUI->getVisible()) {
				gameWorld->getRenderer()->clearScreen(1, 0, 1);
			} else {
				gameWorld->getRenderer()->clearScreen(0, 0, 0);
			}
		} else {
			gameWorld->getRenderer()->clearScreen(0, 0, 0);
		}
		
        if(lagTime <= 0) {

            // Give the GPU something to do while we do game events.
            gameWorld->draw();

            // Draw editor UI.
            if(editorUI) {
                editorUI->draw();
            }

            // Draw main game UI.
            Vector2D screenSize = gameWorld->getRenderer()->getScreenSize();
            widgetPushTransforms(screenSize.x, screenSize.y);
			
            glClearStencil(0);
            glClear(GL_STENCIL_BUFFER_BIT);
            glMatrixMode(GL_MODELVIEW);
            glEnable(GL_STENCIL_TEST);

            // Only display the game UI if the editor isn't up.
            if(widgetRoot && !(editorUI && editorUI->getVisible())) {
                widgetRoot->setWidth(screenSize.x);
                widgetRoot->setHeight(screenSize.y);
                widgetRoot->draw();
            }
        
            // Engine UI goes on top of the editor and game UI
            engineUIDraw(screenSize.x, screenSize.y);

            glDisable(GL_STENCIL_TEST);
			
            widgetPopTransforms();
			
            // Some stuff the UI might leave dirty...
            glEnable(GL_TEXTURE_2D);
            glColor4f(1, 1, 1, 1);

            // Quick and dirty hack to draw the framerate.
            if(showFPS) {

                ostringstream fpsStr;
                fpsStr << "FPS: " << 1000.0 / float(lastFrameTime) << endl;

                FontManager *fontManager = gameWorld->getFontManager();
                MalFont *font = fontManager->getFont("editordata/font1.tga");
                glPushMatrix();
                glScalef(1, -1, 1);
                font->drawText(screenSize.x - 128, -screenSize.y + 16, fpsStr.str().c_str());
                glPopMatrix();
            }

            gameWorld->getRenderer()->endScene();
        }

		// Handle game logic.
        if(engineUIOpen()) {

            engineUIUpdate();
            gameWorld->minimumUpdate();

        } else {

            if(editorUI) {
                editorUI->update();
                if(!editorUI->getVisible() && debugMode) {
                    inputPumpState();
                    gameWorld->update();
                }
            } else {
                inputPumpState();
                gameWorld->update();
            }
        }

		// Check for quit events and stuff.
		while(SDL_PollEvent(&event)) {
			
			// Only do something here if the editor didn't already eat it.
			bool uiHandledIt = false;
			
			// Give the game UI a chance too.
            WidgetEvent convertedEvent;
            Vector2D screenSize = gameWorld->getRenderer()->getScreenSize();
            widgetPushTransforms(screenSize.x, screenSize.y);
            widgetConvertSDLEvent(&event, &convertedEvent);
	
            if(convertedEvent.type != WIDGETEVENT_IDUNNO) {

                uiHandledIt = engineUIHandleEvent(&convertedEvent, gameWorld);
                quit |= engineUIGotQuit();

                if(widgetRoot && !uiHandledIt && !editorUI->getVisible()) {
                    uiHandledIt = widgetRoot->handleEvent(&convertedEvent);
                }

            }
				
            widgetPopTransforms();
				
			if(editorUI && !uiHandledIt) {
				uiHandledIt = editorUI->handleEvent(&event);
			}
			
            widgetCleanupDeletedWidgets();

			// FIXME: Tab key events are making it through this?

			if(!uiHandledIt) {
			
				// TODO: Make this a switch()?
				
				if(event.type == SDL_KEYDOWN) {
					if(event.key.keysym.sym == SDLK_ESCAPE) {
						quit = true;
					}
					if(editorUI) {
						//if(event.key.keysym.sym == SDLK_e && (SDL_GetModState() & KMOD_CTRL)) {
						if(event.key.keysym.sym == SDLK_BACKQUOTE) {
							editorUI->setVisible(!editorUI->getVisible());
							gameWorld->resetListColors();
						}
					}
                }
				
                // Mouse junk.
                if( event.type == SDL_MOUSEMOTION ||
                    event.type == SDL_MOUSEBUTTONDOWN ||
                    event.type == SDL_MOUSEBUTTONUP) {

                    gameWorld->handleMouseEvent(&event);
                }

				if(event.type == SDL_QUIT) quit = true;
				
				// FIXME: Lots of subsybsystem-specific junk. Clean it up.
				if(event.type == SDL_VIDEORESIZE) {
					
					// SDL_RESIZABLE is only set in GL mode anyway.
					SDL_FreeSurface(gameWorld->getRenderer()->screen);
					
					gameWorld->getRenderer()->initGL(event.resize.w, event.resize.h, SDL_OPENGL | SDL_RESIZABLE);
					
					// For Windows, we need to reload all the textures.
					// Doesn't seem to be an issue in Linux. Untested
					// in Mac.
					//#ifdef WIN32
						TextureManager::get()->clearAllTextures();
						widgetInvalidateDisplayLists();
                        gameWorld->getRenderer()->purgeLists();
						gameWorld->reloadTileLists();
                        gameWorld->killLayerBuffers();
                    //#endif
					
					gameWorld->screenSize.x = event.resize.w;
					gameWorld->screenSize.y = event.resize.h;
					
					glMatrixMode(GL_PROJECTION);
						glLoadIdentity();
						gluOrtho2D(0, event.resize.w, 0, event.resize.h);
					glMatrixMode(GL_MODELVIEW);

					glViewport(0, 0, event.resize.w, event.resize.h);
				}
			}
		}

        // Handle joystick events for the UI.
        if(widgetRoot) {
            vector<WidgetEvent> joystickEvents;
            getJoystickEventsForUI(gameWorld, joystickEvents);
            for(unsigned int i = 0; i < joystickEvents.size(); i++) {
                widgetRoot->handleEvent(&joystickEvents[i]);
            }
        }

		gameWorld->getRenderer()->processExtraEvents();

        if(lagTime <= 0) {
            gameWorld->getRenderer()->flipScreen();
		}

		// Delay the rest of the frame - save some CPU.
        int timeForThisFrame = (30 - lagTime);
        int timeTaken = (SDL_GetTicks() - frameStartTime);
		int delayTime = timeForThisFrame - timeTaken;
        bool skippedDrawThisFrame = lagTime;

		if(delayTime > 0) {
            SDL_Delay(delayTime);
        }

        lagTime -= 30 - timeTaken;

        if(lagTime < 0) {
            lagTime = 0;
        }

        if(lagTime > 90) {
            // More than three frames behind? Draw anyway.
            lagTime = 0;
        }

        // FPS counter shouldn't bother looking at frames where we had
        // to catch up by skipping drawing.
        if(!skippedDrawThisFrame) {
            lastFrameTime = SDL_GetTicks() - frameStartTime;
        }
	}

    // Save config and shutdown.
    
    saveConfig(gameWorld->getGameName(), gameWorld);

    inputShutdown();

	if(editorUI) delete editorUI;
    widgetCleanupDeletedWidgets();
	if(gameWorld) delete gameWorld;
}

