package ei.engine;

import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;

import ei.engine.renderer.Camera;
import ei.engine.state.GameStateManager;
import ei.engine.util.FpsTimer;
import ei.engine.util.MultiPrintStream;

public class LWJGLGameWindow {
	// Exit app
	private static boolean exit = false;
	// The size of the window
	private static int width;
	private static int height;
	// The camera
	private static Camera cam;
	// The framerate
	private int fps;
	// If fullscreen
	private boolean fullscreen;
	// The title
	private String title;
	// The fps calculator
	private FpsTimer timer;


	/**
	 * Creates a default window whit 800x600
	 * no fullscreen
	 * 
	 * @param title The title of the window
	 */
	public LWJGLGameWindow(String title){
		this(800, 600, 60, false, title);
	}
	
	/**
	 * Creates a custom window
	 * @param width The width of the window
	 * @param height The height of the window
	 * @param fps The frame rate
	 * @param fullscreen If fullscreen
	 * @param title The title
	 */
	public LWJGLGameWindow(int width, int height, int fps, boolean fullscreen, String title){
		LWJGLGameWindow.width = width;
		LWJGLGameWindow.height = height;
		this.fps = fps;
		this.fullscreen = fullscreen;
		this.title = title;
		
		MultiPrintStream.makeInstance(new MultiPrintStream("log.txt"));
		
	    try {
	    	initDisplay();
	    	run();
	    } catch (Exception e) {
	    	e.printStackTrace(System.err);
	    	Sys.alert(title, "An error occured and the game will exit.");
	    } finally {
	    	cleanup();
	    }
	    System.exit(0);		
	}

	 
	/**
	 * Initialise the game
	 * @throws Exception if init fails
	 */
	private void initDisplay() throws Exception {		
		DisplayMode dm = new DisplayMode(width, height);
		Display.setDisplayMode(dm); 
		
		Display.setTitle(title);
		Display.setFullscreen(fullscreen);

		// grab the mouse, dont want that hideous cursor when we're playing!
		Mouse.setGrabbed(false);

		// Create the display window
		Display.create();

		// enable textures since we're going to use these for our sprites
		GL11.glEnable(GL11.GL_TEXTURE_2D);
        // Enable Smooth Shading
		GL11.glShadeModel(GL11.GL_SMOOTH);        
		// disable the OpenGL depth test since we're rendering 2D graphics
		GL11.glDisable(GL11.GL_DEPTH_TEST);
		//GL11.glEnable(GL11.GL_DEPTH_TEST); // Enables Depth Testing
		//GL11.glDepthFunc(GL11.GL_LEQUAL); // The Type Of Depth Testing To Do
		// disable lights (we do not nead them)
		GL11.glDisable(GL11.GL_LIGHTING);   
        // Black Background
		GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);  
        // Depth Buffer Setup
		GL11.glClearDepth(1.0f);
		
		
		//Select The Projection Matrix
        GL11.glMatrixMode(GL11.GL_PROJECTION);
        //Reset The Projection Matrix
        GL11.glLoadIdentity();
        //turn on 2D mode
        GL11.glOrtho(0.0f, width, height, 0, -1, 1);
        
        // Select The Modelview Matrix (controls model orientation)
        GL11.glMatrixMode(GL11.GL_MODELVIEW);


        //Enable Blending
        GL11.glEnable(GL11.GL_BLEND);
        //GL11.glBlendFunc(GL11.GL_SRC_ALPHA,GL11.GL_ONE);
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

		// Enable vsync if we can (due to how OpenGL works, it cannot be guarenteed to always work)
		Display.setVSyncEnabled(true);

		
		// The last steps
		timer = new FpsTimer();
		cam = new Camera();
		
		init();
		

	}
	
	/**
	 * Runs the game (the "main loop")
	 */
	private void run() {
		int frame = 0;
		while (!exit) {
			timer.startTimer();
			// Always call Window.update(), all the time - it does some behind the
			// scenes work, and also displays the rendered output
			Display.update();

			// Check for close requests
			if (Display.isCloseRequested()) {
				exit = true;
			} 

			// The window is in the foreground, so we should play the game
			else if (Display.isActive()) {
				GameStateManager.getInstance().update();
				mainUpdate();
				mainRender();
				Display.sync(fps);
			} 

			// The window is not in the foreground, so we can allow other stuff to run and
			// infrequently update
			else {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {}
				GameStateManager.getInstance().update();
				mainUpdate();

				// Only bother rendering if the window is visible or dirty
				if (Display.isVisible() || Display.isDirty()) {
					mainRender();
				}
			}
			
			//calculate fps and print it on title
			if(frame >= fps/2){
				timer.stopTimer();
				Display.setTitle(title+"     ["+timer.getFps()+"]");
				frame = 0;
			}
			frame++;
		}
	}

	/**
	 * Render the current frame
	 */
	private void mainRender() {
		// clear the screen
		GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
		GL11.glMatrixMode(GL11.GL_MODELVIEW);
		GL11.glLoadIdentity();
		
		cam.setWorldTranslationGL();
		GL11.glPushMatrix();
			//let subsystem paint
			GameStateManager.getInstance().render();
			render();
		GL11.glPopMatrix();
		
		//update window contents
		Display.update();
	}
	
	/**
	 * Update the engine
	 */
	private void mainUpdate() {
		cam.update();
		update();
	}
	
	/**
	 * this method shid be overriden
	 */
	protected void render(){}
	
	/**
	 * this method shid be overriden
	 */
	protected void init(){}
	
	/**
	 * Do any game-specific cleanup
	 * this method shid be overriden
	 */
	protected void cleanup() {
		// Close the window
		Display.destroy();
	}

	/**
	 * Do all calculations, handle input, etc.
	 * this method shid be overriden
	 */
	protected void update() {
		// Example input handler: we'll check for the ESC key and finish the game instantly when it's pressed
		if (Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {
			exit = true;
		}
	}
	
	public static void exit(){
		exit = true;
	}
	
	public static int getHeight(){
		return height;
	}
	
	public static int getWidth(){
		return width;
	}
	
	public static void setCamera(Camera c){
		cam = c;
	}
	
	public static Camera getCamera(){
		return cam;
	}
	
}
