package de.rwth_aachen.graphics;

import com.sun.corba.se.impl.protocol.SharedCDRClientRequestDispatcherImpl;
import de.rwth_aachen.graphics.Primitives;
import de.rwth_aachen.graphics.TextureImporter;
import de.rwth_aachen.graphics.ObjImporter;
import de.rwth_aachen.graphics.Camera;
import de.rwth_aachen.graphics.CatmullRom;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ByteBuffer;

import java.util.Vector;
import java.util.ArrayList;

import java.lang.*;
import java.util.Collections;
import java.util.Comparator;

import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.ARBShaderObjects;
import org.lwjgl.opengl.ARBVertexShader;
import org.lwjgl.opengl.ARBFragmentShader;
import org.lwjgl.opengl.ARBVertexBufferObject;
import org.lwjgl.opengl.EXTFramebufferObject;
import org.lwjgl.util.glu.GLU;
import org.lwjgl.util.vector.Matrix3f;
import org.lwjgl.util.vector.Vector2f;
import org.lwjgl.util.vector.Vector3f;
import org.lwjgl.util.vector.Vector4f;
import org.lwjgl.input.Mouse;
import org.lwjgl.input.Keyboard;

//import com.obj.TextureLoader;


/**
 * This is the main class. Here you will find the following tasks:
 * <br>
 * {@link #init() Task 1.b Upload rusty metal textures.}<br>
 * {@link #loop() Task 1.c Setting rusty metal uniforms and textures.}<br>
 * {@link #init() Task 2.b Initialize the render target.}<br>
 * {@link #loop() Task 2.c/d Render to the screen.}<br>
 * <br>
 * <i>General notes:</i> As in the previous exercises we already 
 * provide some interactivity which provides changing the current shader 
 * by pressing "s" and changing the current geometry by pressing "g". The
 * "space" key will switch the camera mode. You can also switch the lights
 * on and off by using the keys 1, 2 and 3. <br>
 * <br>
 */
public class Main
{
    private float GL_VERSION;

    private final int MAXZ = 21; //1..21
    private Camera camera = new Camera(
        640,
        480,
        60.0f,
        0.9f,
        MAXZ+0.1f,
        new Vector4f(5.0f, 5.0f, 5.0f, 1.0f),
        new Vector4f(0.0f, 0.0f, 0.0f, 1.0f));

    //3 for position, 2 for texture coordinates
    private final int SIZE_OF_DATA = 5;
    private final int SIZE_OF_FLOAT = 4;
    
    private final float mouseSpeed = 0.5f;
    private final float moveSpeed = 10.0f;
    private final float cameraSpeed = 0.8f;
    private final float targetSpeed = 0.8f;
    private final float second = 1000.0f;
    
    private boolean leftMouseButtonDown = false;
    private boolean rightMouseButtonDown = false;
    private Vector2f prevMousePos = new Vector2f(0.0f, 0.0f);
    
    private final int MOVE_MODE_FIRST_PERSON = 0;
    private final int MOVE_MODE_THIRD_PERSON = 1;
    private final int MOVE_MODE_SPLINE_PATH = 2;
    private final int MOVE_MODE_MAX = 3;
    private int currentMoveMode = MOVE_MODE_FIRST_PERSON;
    
    private final int GEOMETRY_DUMMY = 0;
    private final int GEOMETRY_BUNNY = 1;
    private final int GEOMETRY_MAX = 2;
    private int currentGeometry = GEOMETRY_DUMMY;
    private int[] numberOfTriangles = new int[GEOMETRY_MAX];
    private IntBuffer vboid = IntBuffer.allocate(GEOMETRY_MAX);
    
    private final int SHADER_PROGRAM_DISABLED = -1;
    private final int SHADER_PROGRAM_SIRD = 0;
    private final int SHADER_PROGRAM_MAX = 1;
    private int currentShaderProgram = SHADER_PROGRAM_SIRD;
    private int[] shaderProgram = new int[SHADER_PROGRAM_MAX];
    
    private final int LIGHT_RED = 0;
    private final int LIGHT_GREEN = 1;
    private final int LIGHT_BLUE = 2;
    private final int LIGHT_MAX = 3;
    private boolean[] lightSwitchedOn = new boolean[LIGHT_MAX];
    private Vector4f[] lightPosition = new Vector4f[LIGHT_MAX];
    private Vector4f[] lightColor = new Vector4f[LIGHT_MAX];
    
    private final int TEXTURE_RANDOM = 0;
    private final int TEXTURE_RENDER_DEPTH_TARGET = 1; //depth-texture the depth of the scene is rendered into
    private final int TEXTURE_RENDER_SIRD = 2; //color-texture the SIRD calculated from the depth is rendered into
    private final int TEXTURE_RENDER_SIRD_TEMP = 3; //color-texture which is temporary used to render parts of the SIRD
    private final int TEXTURE_FLOATERS = 4; //texture contains additional pictures drawn over the SIRD
    private final int TEXTURE_MAX = 5;
    private IntBuffer textureid = IntBuffer.allocate(TEXTURE_MAX);

    private final int RENDERBUFFER_DEPTH_TARGET = 0; //renderbuffer used in the same framebuffer as the textures with the same name
    private final int RENDERBUFFER_SIRD = 1;
    private final int RENDERBUFFER_SIRD_TEMP = 2;
    private final int RENDERBUFFER_MAX = 3;
    private IntBuffer renderbufferid = IntBuffer.allocate(RENDERBUFFER_MAX);

    private final int FRAMEBUFFER_DISABLED = -1;
    private final int FRAMEBUFFER_DEPTH_TARGET = 0; //framebuffer used when the the scene is rendered (everything except the depth is discarded)
    private final int FRAMEBUFFER_SIRD = 1; //framebuffer where the final SIRD (and every final SIRD strip is rendered into)
    private final int FRAMEBUFFER_SIRD_TEMP = 2;//framebuffer where a partial SIRD strip is rendered into
    private final int FRAMEBUFFER_MAX = 3;
    private IntBuffer fboid = IntBuffer.allocate(FRAMEBUFFER_MAX);
    
    private CatmullRom cameraPath = new CatmullRom("res/camera.path");
    private CatmullRom targetPath = new CatmullRom("res/target.path");

    int loadTexture(int target, String name){
        TextureImporter ti=new TextureImporter("res/textures/"+name);
        GL11.glTexImage2D(target, 0, ti.hasAlpha()?GL11.GL_RGBA8:GL11.GL_RGB8, 
        		ti.getWidth(), ti.getHeight(), 0, ti.hasAlpha()?GL11.GL_RGBA:GL11.GL_RGB, 
        		GL11.GL_UNSIGNED_BYTE,  ti.getData());
        return ti.getWidth();
    }
    int loadBindTexture(int id, String name){
        GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureid.get(id));
        GL11.glTexParameterf( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR );
        GL11.glTexParameterf( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR );
        GL11.glTexParameterf( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT );
        GL11.glTexParameterf( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T , GL11.GL_REPEAT );
        return loadTexture(GL11.GL_TEXTURE_2D,name);
    }
    void allocateRenderBuffer(int id, boolean textureWasDepthOnly){
        EXTFramebufferObject.glBindRenderbufferEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, renderbufferid.get(id));
        if (textureWasDepthOnly)
            EXTFramebufferObject.glRenderbufferStorageEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, GL11.GL_RGBA, depthBufferSize, depthBufferSize);
        else
            EXTFramebufferObject.glRenderbufferStorageEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, GL11.GL_DEPTH_COMPONENT, depthBufferSize, depthBufferSize);
        EXTFramebufferObject.glBindRenderbufferEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, 0);
    }
    void allocateRenderTargetTexture(int id, boolean depthOnly){
        GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureid.get(id));
        GL11.glTexParameterf( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR );
        GL11.glTexParameterf( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR );
        GL11.glTexParameterf( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT );
        GL11.glTexParameterf( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T , GL11.GL_REPEAT );
        if (depthOnly)
            GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL14.GL_DEPTH_COMPONENT24, depthBufferSize, depthBufferSize, 0, GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_BYTE, (ByteBuffer)null);
        else
            GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, depthBufferSize, depthBufferSize, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer)null);            
    }
    void bindFrameBuffer(int id){
        int rid=0;
        if (id!=FRAMEBUFFER_DISABLED) rid=fboid.get(id);
        EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, rid);
    }
    void combineFrameBuffer(int id, int texture, int renderBuffer, boolean textureIsDepth){
        bindFrameBuffer(id);
        if (textureIsDepth){
            EXTFramebufferObject.glFramebufferRenderbufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT, EXTFramebufferObject.GL_RENDERBUFFER_EXT, renderbufferid.get(renderBuffer));
            EXTFramebufferObject.glFramebufferTexture2DEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT, GL11.GL_TEXTURE_2D, textureid.get(texture),0);
        } else {
            EXTFramebufferObject.glFramebufferRenderbufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT, EXTFramebufferObject.GL_RENDERBUFFER_EXT, renderbufferid.get(renderBuffer));
            EXTFramebufferObject.glFramebufferTexture2DEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT, GL11.GL_TEXTURE_2D, textureid.get(texture),0);
        }
    }
    void useProgram(int id){
        int rid=id==SHADER_PROGRAM_DISABLED?0:shaderProgram[id];
        if(GL_VERSION >= 2.0)
            GL20.glUseProgram(rid);
        else
            ARBShaderObjects.glUseProgramObjectARB(rid);
    }
    int getUniformLocation(int id, String location){
        int rid=shaderProgram[id];

        int result;
        if (GL_VERSION >= 2.0)
            result=GL20.glGetUniformLocation(rid, getByteBufferFromString(location));
        else
            result=ARBShaderObjects.glGetUniformLocationARB(rid, getByteBufferFromString(location));
        if (result==-1) throw new IllegalArgumentException("Couldn't get uniform location "+location);
        return result;
    }

    private int eyeDistance=80; //size of a sird strip in pixels on the created texture, proportional to eye distance
    private int sisImageSize; //size of the image used as base texture

    //uniform parameter of SIRD Shader
    int uniformDepthTexture;
    int uniformRenderTexture;
    int uniformEyeDistance;
    int uniformDepthScaling;

    //Size of depth buffer texture
    private int depthBufferSize = 512;
    //Actually used size
    private int depthBufferSizeUsedW = 512;
    private int depthBufferSizeUsedH = 480;

    //User configurable properties of the SIRD rendering
    //Displaymode
    private final static int DISPLAY_SIRDS=0;
    private final static int DISPLAY_DEPTH=1;
    private final static int DISPLAY_NORMAL=2;
    private final static int DISPLAY_DEPTH_NORMAL_BLENDED=3;
    private int displayMode = DISPLAY_SIRDS;

    //How the SIRD should flicker
    private final static int FLICKER_OFF=0;
    private final static int FLICKER_TEXTURE_OFFSET=1;
    private final static int FLICKER_CLEAR=2;
    private final static int FLICKER_DYNAMIC=3;
    private final static int FLICKER_MAX=4;
    private int flickerMode = FLICKER_DYNAMIC;

    private long sceneChangedAt = 0;

    //If the SIRD is drawn sharp (not scaled afterwards)
    private final static int SCALING_LINEAR=0;
    private final static int SCALING_SHARP=1;
    private final static int SCALING_NEAREST_SHARP=2;
    private final static int SCALING_MAX=3;
    private int drawSharpSIRD = SCALING_LINEAR;
    //If it is drawn inverted (cross eye instead of parallel)
    private boolean drawInvertedSIRD = false;

    //Which texture is used to draw the sis
    private final String[] availableTextures = new String[] {/*"test.png",*/"random.png","grayrandom.png","bwrandom.png","squares.png","leopard.png","mud.png","cubes.png","cggroup.png"};
    private int curTexture=0;

    private ArrayList<FloaterGL> floaters=new ArrayList<FloaterGL>();
    /* ***************************************************************************
     * End of additional code
     ****************************************************************************/
    
    /**
     * Entry point for the java program.
     */
    public static void main(String[] args) 
    {
        new Main().execute();
        System.exit(0);
    }

    /**
     * Execution of the initialization and the render loop. The display
     * will be destroyed when the close button is hit and the loop stops.
     */
    private void execute() 
    {
        try 
        {
            init();
        } 
        catch (LWJGLException oLWJGLException) 
        {
            oLWJGLException.printStackTrace();
            System.out.println("Failed to initialize Application.");
            return;
        }

        loop();

        destroy();
    }
    
    /**
     * This is the initialization of the open gl window. Here we set up
     * the display with its viewport and the projection matrix.<br>
     * <br>
     * <b><font color = "#ff0000">Task 1.b:</font></b><br>
     * Load and upload the different textures for the rusty metal shader.
     * Also locate your needed uniforms of the shader.<br>
     * <br>
     * <i>Hint:</i> The shader context is shaderProgram[SHADER_PROGRAM_TEXTURE]
     * for this task. Use the contexts {@link #textureid textureid} for the
     * textures. These contexts will be deleted in the {@link #destroy destroy()} 
     * method. Please access the context buffer of the textures by using the global constants
     * {@link #TEXTURE_METAL_DIFFUSE TEXTURE_METAL_DIFFUSE} (for the diffuse 
     * texture channel "metal.jpg"), {@link #TEXTURE_METAL_SPECULAR TEXTURE_METAL_SPECULAR} 
     * (for the specular texture channel "metalspec.jpg") and {@link #TEXTURE_CUBE_MAP TEXTURE_CUBE_MAP} 
     * (for the reflection of an environment "stpeters_cross_{r|l|t|b|n|f}.jpg" 
     * where the letters stand for right, left, top, bottom, near and far respectively). 
     * You can use the {@link de.rwth_aachen.graphics.TextureImporter TextureImporter}
     * to load the image files. You will find the mentioned files in the "res/textures"
     * directory.<br>
     * <br>
     * <b><font color = "#ff0000">Task 2.a:</font></b><br>
     * Create a frame buffer and a render buffer that can be used to render the 
     * scene into a texture.<br>
     * <br>
     * <i>Hint:</i> Use the texture context textureid.get(TEXTURE_RENDER_TARGET)
     * as the render target and the frame buffer context fboid.get(0) for the color
     * component. Use the render buffer context renderbufferid.get(0) for the
     * depth component since the depth can be discarded after rendering and does
     * not need to be stored in a texture. These contexts will be deleted in the 
     * {@link #destroy destroy()} method.<br>
     */
    private void init() throws LWJGLException 
    {
        Display.setLocation(
            (Display.getDisplayMode().getWidth() - camera.getWidth()) / 2,
            (Display.getDisplayMode().getHeight() - camera.getHeight()) / 2);
        Display.setDisplayMode(new DisplayMode(camera.getWidth(), camera.getHeight()));
        Display.setTitle("Computer Graphics");
        Display.create();
                
        GL_VERSION = Float.valueOf(GL11.glGetString(GL11.GL_VERSION).substring(0, 3)).floatValue();
        System.out.println("OpenGL Version: " + GL_VERSION);
        
        //Setup OpenGL
        for(int i = 0; i < LIGHT_MAX; i++)
        {
            lightPosition[i] = new Vector4f();
            lightSwitchedOn[i] = true;
        }
        lightColor[LIGHT_RED] = new Vector4f(1.0f, 0.0f, 0.0f, 1.0f);
        lightColor[LIGHT_GREEN] = new Vector4f(0.0f, 1.0f, 0.0f, 1.0f);
        lightColor[LIGHT_BLUE] = new Vector4f(0.0f, 0.0f, 1.0f, 1.0f);
                
        //We don't change the material over time anymore
        //so set it fixed to white
        GL11.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, FloatBuffer.wrap(new float[] {0.2f, 0.2f, 0.2f,  1.0f}));
        GL11.glMaterial(GL11.GL_FRONT, GL11.GL_DIFFUSE, FloatBuffer.wrap(new float[] {0.8f, 0.8f, 0.8f,  1.0f}));
        GL11.glMaterial(GL11.GL_FRONT, GL11.GL_SPECULAR, FloatBuffer.wrap(new float[] {1.0f, 1.0f, 1.0f,  1.0f}));
        GL11.glMaterial(GL11.GL_FRONT, GL11.GL_EMISSION, FloatBuffer.wrap(new float[] {0.0f, 0.0f, 0.0f,  1.0f}));
        GL11.glMaterialf(GL11.GL_FRONT, GL11.GL_SHININESS, 64.0f);
        
        GL11.glEnable(GL11.GL_CULL_FACE);
        GL11.glEnable(GL11.GL_DEPTH_TEST);
        GL11.glEnable(GL11.GL_NORMALIZE);
       
        //Generate the VBO buffer
        if(GL_VERSION >= 1.5)
            GL15.glGenBuffers(vboid);
        else
            ARBVertexBufferObject.glGenBuffersARB(vboid);
        
        //Load some files from obj...
        ObjImporter objImporter = null;
        
        objImporter = new ObjImporter("res/geometry/Dummy.obj");
        numberOfTriangles[GEOMETRY_DUMMY] = objImporter.getTriangleCount();
        if(GL_VERSION >= 1.5)
        {
            GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboid.get(GEOMETRY_DUMMY));
            GL15.glBufferData(GL15.GL_ARRAY_BUFFER, createData(objImporter), GL15.GL_STATIC_DRAW);
        }
        else
        {
            ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, vboid.get(GEOMETRY_DUMMY));
            ARBVertexBufferObject.glBufferDataARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, createData(objImporter), ARBVertexBufferObject.GL_STATIC_DRAW_ARB);
        }

        numberOfTriangles[GEOMETRY_BUNNY] = 0; //load it only if g is pressed

        //no light possible with sirds
        GL11.glEnable(GL11.GL_LIGHTING);
        
        //Load shader from vert and frag files
        shaderProgram[SHADER_PROGRAM_SIRD] = loadShaders("res/shaders/phong.vert", "res/shaders/phong.frag");

        useProgram(SHADER_PROGRAM_SIRD);
        uniformDepthTexture = getUniformLocation(SHADER_PROGRAM_SIRD, "depthTexture");
        uniformRenderTexture = getUniformLocation(SHADER_PROGRAM_SIRD, "previousImage");
        uniformEyeDistance = getUniformLocation(SHADER_PROGRAM_SIRD, "eyeDistance");
        uniformDepthScaling = getUniformLocation(SHADER_PROGRAM_SIRD, "depthFactor");
        useProgram(SHADER_PROGRAM_DISABLED);

        //Create the needed texture contexts
        GL11.glGenTextures(textureid);
        sisImageSize=loadBindTexture(TEXTURE_RANDOM, availableTextures[curTexture]);
        loadBindTexture(TEXTURE_FLOATERS, "floaters.png");

        //Create the frame and render buffer
        if(GL_VERSION >= 3.0)
        {
            GL30.glGenFramebuffers(fboid);
            GL30.glGenRenderbuffers(renderbufferid);
        }
        else 
        {
            EXTFramebufferObject.glGenFramebuffersEXT(fboid);
            EXTFramebufferObject.glGenRenderbuffersEXT(renderbufferid);
        }

        allocateRenderBuffer(RENDERBUFFER_DEPTH_TARGET, true);
        allocateRenderBuffer(RENDERBUFFER_SIRD, false);
        allocateRenderBuffer(RENDERBUFFER_SIRD_TEMP, false);

        allocateRenderTargetTexture(TEXTURE_RENDER_DEPTH_TARGET, true);
        allocateRenderTargetTexture(TEXTURE_RENDER_SIRD, false);
        allocateRenderTargetTexture(TEXTURE_RENDER_SIRD_TEMP, false);

        combineFrameBuffer(FRAMEBUFFER_DEPTH_TARGET, TEXTURE_RENDER_DEPTH_TARGET, RENDERBUFFER_DEPTH_TARGET, true);
        combineFrameBuffer(FRAMEBUFFER_SIRD, TEXTURE_RENDER_SIRD, RENDERBUFFER_SIRD, false);
        combineFrameBuffer(FRAMEBUFFER_SIRD_TEMP, TEXTURE_RENDER_SIRD_TEMP, RENDERBUFFER_SIRD_TEMP, false);

        bindFrameBuffer(FRAMEBUFFER_DISABLED);



        FloaterGL floater=new FloaterGL();
        floater.visible=true;
        floater.textureId=textureid.get(TEXTURE_FLOATERS);
        floater.z=MAXZ / 2;
        floater.w=20.0f/camera.getWidth();
        floater.h=20.0f/camera.getHeight();
        floater.s=0; floater.sSize=20.0f/64;
        floater.t=1-20.0f/64; floater.tSize=20.0f/64;
        floaters.add(floater);

        System.out.println("Usage:");
        System.out.println("G = Change Geometry; Space = Change Movement; Mouse = Move/Scale Scene; Arrows = Move Camera");
        System.out.println("T = Change SIS Texture; I = Draw Inverted; M = Change Display Mode");
        System.out.println("-/+ (numpad): Change Eye Distance; lctrl + -/+ (num.): Change Width, rctrl + -/+: Change Height ");
        System.out.println("F = Change Flicker Mode, S = Change Sharpness, C = Change Floater Cursor, Z = Show temporary render buffer (no visible change)");
    }
    
    /**
     * This method converts a String to a ByteBuffer.
     * @param string The string that has to be converted.
     * @return A zero terminated ByteBuffer of the string in UTF-8.
     */
    private ByteBuffer getByteBufferFromString(String string)
    {
        string += "\0";
        try
        {
            return ByteBuffer.wrap(string.getBytes("UTF-8"));
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }
    
    /**
     * This method to load vertex and fragment shaders. <br>
     * @param vertFile The file name of the vertex shader.
     * @param fragFile The file name of the fragment shader.
     * @return Program context of the shader.
     */
    private int loadShaders(String vertFile, String fragFile)
    {
        ByteBuffer vertContent = getFileContent(vertFile);
        ByteBuffer fragContent = getFileContent(fragFile);
        
        int shaderProgram = -1;
        
        if(GL_VERSION >= 2.0)
        {
            int vertShader = GL20.glCreateShader(GL20.GL_VERTEX_SHADER);
            int fragShader = GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER);
            
            GL20.glShaderSource(vertShader, vertContent);
            GL20.glShaderSource(fragShader, fragContent);
            
            GL20.glCompileShader(vertShader);
            GL20.glCompileShader(fragShader);
            
            shaderProgram = GL20.glCreateProgram();
            
            GL20.glAttachShader(shaderProgram, vertShader);
            GL20.glAttachShader(shaderProgram, fragShader);
            
            GL20.glLinkProgram(shaderProgram);
            
            GL20.glDeleteShader(vertShader);
            GL20.glDeleteShader(fragShader);
        }
        else
        {
            int vertShader = ARBShaderObjects.glCreateShaderObjectARB(ARBVertexShader.GL_VERTEX_SHADER_ARB);
            int fragShader = ARBShaderObjects.glCreateShaderObjectARB(ARBFragmentShader.GL_FRAGMENT_SHADER_ARB);
            
            ARBShaderObjects.glShaderSourceARB(vertShader, vertContent);
            ARBShaderObjects.glShaderSourceARB(fragShader, fragContent);
            
            ARBShaderObjects.glCompileShaderARB(vertShader);
            ARBShaderObjects.glCompileShaderARB(fragShader);
            
            shaderProgram = ARBShaderObjects.glCreateProgramObjectARB();
            
            ARBShaderObjects.glAttachObjectARB(shaderProgram, vertShader);
            ARBShaderObjects.glAttachObjectARB(shaderProgram, fragShader);
            
            ARBShaderObjects.glLinkProgramARB(shaderProgram);
            
            ARBShaderObjects.glDeleteObjectARB(vertShader);
            ARBShaderObjects.glDeleteObjectARB(fragShader);
        }
        return shaderProgram;
    }
    
    /**
     * This returns the contents of a file as a ByteBuffer.
     * @param fileName The file name of which the content should be loaded.
     * @return The content of the file as ByteBuffer or null if the loading
     * failed.
     */
    private ByteBuffer getFileContent(String fileName)
    {
        File file = new File(fileName);
        try
        {
            FileInputStream input = new FileInputStream(file);
            byte[] data = new byte[(int) file.length()];
            input.read(data);
            input.close();
            return ByteBuffer.wrap(data);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        return null;
    }
    
    private FloatBuffer createData(ObjImporter objImporter){
        return createData(objImporter.getTriangleCount(), objImporter.getDataSize(),objImporter.getTexCoordPosition(),
                          objImporter.getVertexBuffer(), objImporter.getTexCoordBuffer(), objImporter.getDataIndexBuffer());
    }

    /**
     * This method creates the VBO data of a loaded obj file.
     * @param objImporter The obj file that should be used for the creation
     * of the data buffer.
     * @return A buffer with vertex positions, normals and texture coordinates
     * that can be used for a VBO.
     */
    private FloatBuffer createData(int triangles, int dataSize, int texCoordPosition, FloatBuffer vertexBuffer, FloatBuffer texCoordBuffer, IntBuffer dataIndexBuffer )
    {
        FloatBuffer arrayBuffer = FloatBuffer.allocate(SIZE_OF_DATA * 3 * triangles);
        
        int index = 0;
        for(int i = 0; i < 3 * triangles; i++)
        {
            index = 3 * dataIndexBuffer.get(dataSize * i);
            arrayBuffer.put(SIZE_OF_DATA * i, vertexBuffer.get(index));
            arrayBuffer.put(SIZE_OF_DATA * i + 1, vertexBuffer.get(index + 1));
            arrayBuffer.put(SIZE_OF_DATA * i + 2, vertexBuffer.get(index + 2));
            if(texCoordPosition < 0)
            {
                arrayBuffer.put(SIZE_OF_DATA * i + 3, 0.0f);
                arrayBuffer.put(SIZE_OF_DATA * i + 4, 0.0f);
            }
            else
            {
                index = 3 * dataIndexBuffer.get(dataSize * i + texCoordPosition);
                arrayBuffer.put(SIZE_OF_DATA * i + 3, texCoordBuffer.get(index));
                arrayBuffer.put(SIZE_OF_DATA * i + 4, texCoordBuffer.get(index + 1));
            }
        }
        return arrayBuffer;
    }

    /**
     * This is the render loop. It will stop, when the close button of the
     * Display is hit.<br>
     * <br>
     * <b><font color = "#ff0000">Task 1.c:</font></b><br>
     * Bind any textures and set any uniform variables that you need for
     * the rusty metal shader.<br>
     * <br>
     * <i>Hint:</i> The method {@link de.rwth_aachen.graphics.Camera#getInverseViewMatrix() getInverseViewMatrix}
     * of the camera might be helpful for the reflection.<br>
     * <br>
     * <b><font color = "#ff0000">Task 2.b/c:</font></b><br>
     * Currently the object is rendered twice. Once as hidden line (as seen
     * in exercise 2) and once as phong or rusty metal. Since we want to have
     * the hidden line version on the screen in the scene, you have
     * to use the frame buffer and render buffer of task 2.a as render target.<br>
     * <br>
     * <i>Hint:</i> Please note, that we already set different projections
     * for the two renderings by using two cameras ({@link #camera camera} and
     * {@link #cameraFBO cameraFBO}). We also display the camera for the render
     * target in the scene as white box. It uses the defined spline path to
     * move around the object.<br>
     */
    private void loop() 
    {
        long checkTime = 1000;
        long currentMillis = System.currentTimeMillis();
        long startMillis = currentMillis;
        long fpsCheck = currentMillis + checkTime;
        long nextTextureFlicker = 0;
        float randomTextureOffsetX = 0;
        float randomTextureOffsetY = 0;
       // long textureFlickerInterval = 60*60*1000;
        long fps = 0;

        while (!Display.isCloseRequested()) 
        {
            //Overall is the time since the start of the application
            float overall = (float)(System.currentTimeMillis() - startMillis);
            //Step is the time since the last frame
            float step = (float)(System.currentTimeMillis() - currentMillis);

            if (flickerMode==FLICKER_CLEAR) {
                step+=20;
                fps++;
            }

            //Count the frames per second
            currentMillis = System.currentTimeMillis();
            if(fpsCheck > currentMillis)
            {
                fps++;
            }
            else
            {
                float timeUsed = (float)(fpsCheck - currentMillis + checkTime);
                fpsCheck = currentMillis + checkTime;
                Display.setTitle("Computer Graphics (" + ((float)fps / (timeUsed / second)) + " fps)");
                fps = 0;
            }
            
            //Handle all input events.
            handleEvents();
            
            //Handle time related camera movements
            switch(currentMoveMode)
            {
                case MOVE_MODE_FIRST_PERSON:
                {
                    Vector4f move = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
                    if(Keyboard.isKeyDown(Keyboard.KEY_UP)) 
                    {
                        move.x += camera.getForwardDirection().x * moveSpeed * step / second;
                        move.y += camera.getForwardDirection().y * moveSpeed * step / second;
                        move.z += camera.getForwardDirection().z * moveSpeed * step / second;
                        sceneChangedAt = currentMillis;
                    }
                    if(Keyboard.isKeyDown(Keyboard.KEY_DOWN)) 
                    {
                        move.x -= camera.getForwardDirection().x * moveSpeed * step / second;
                        move.y -= camera.getForwardDirection().y * moveSpeed * step / second;
                        move.z -= camera.getForwardDirection().z * moveSpeed * step / second;
                        sceneChangedAt = currentMillis;
                    }
                    if(Keyboard.isKeyDown(Keyboard.KEY_RIGHT))
                    {
                        move.x += camera.getRightDirection().x * moveSpeed * step / second;
                        move.y += camera.getRightDirection().y * moveSpeed * step / second;
                        move.z += camera.getRightDirection().z * moveSpeed * step / second;
                        sceneChangedAt = currentMillis;
                    }
                    if(Keyboard.isKeyDown(Keyboard.KEY_LEFT))
                    {
                        move.x -= camera.getRightDirection().x * moveSpeed * step / second;
                        move.y -= camera.getRightDirection().y * moveSpeed * step / second;
                        move.z -= camera.getRightDirection().z * moveSpeed * step / second;
                        sceneChangedAt = currentMillis;
                    }
                    camera.setPosition(Vector4f.add(camera.getPosition(), move, null), false);
                } break;
                case MOVE_MODE_THIRD_PERSON:
                {
                    Vector4f move = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
                    Vector3f fowardInXZ = new Vector3f(camera.getForwardDirection().x, 0.0f, camera.getForwardDirection().z).normalise(null);
                    Vector3f rightInXZ = new Vector3f(camera.getRightDirection().x, 0.0f, camera.getRightDirection().z).normalise(null);
                    if(Keyboard.isKeyDown(Keyboard.KEY_UP)) 
                    {
                        move.x += fowardInXZ.x * moveSpeed * step / second;
                        move.y += fowardInXZ.y * moveSpeed * step / second;
                        move.z += fowardInXZ.z * moveSpeed * step / second;
                        sceneChangedAt = currentMillis;
                    }
                    if(Keyboard.isKeyDown(Keyboard.KEY_DOWN)) 
                    {
                        move.x -= fowardInXZ.x * moveSpeed * step / second;
                        move.y -= fowardInXZ.y * moveSpeed * step / second;
                        move.z -= fowardInXZ.z * moveSpeed * step / second;
                        sceneChangedAt = currentMillis;
                    }
                    if(Keyboard.isKeyDown(Keyboard.KEY_RIGHT)) 
                    {
                        move.x += rightInXZ.x * moveSpeed * step / second;
                        move.y += rightInXZ.y * moveSpeed * step / second;
                        move.z += rightInXZ.z * moveSpeed * step / second;
                        sceneChangedAt = currentMillis;
                    }
                    if(Keyboard.isKeyDown(Keyboard.KEY_LEFT)) 
                    {
                        move.x -= rightInXZ.x * moveSpeed * step / second;
                        move.y -= rightInXZ.y * moveSpeed * step / second;
                        move.z -= rightInXZ.z * moveSpeed * step / second;
                        sceneChangedAt = currentMillis;
                    }
                    camera.setTarget(Vector4f.add(camera.getTarget(), move, null), true);
                } break;
                case MOVE_MODE_SPLINE_PATH:
                {
                    Vector3f t = targetPath.getPoint(targetSpeed * overall / second);
                    camera.setTarget(new Vector4f(t.x, t.y, t.z, 1.0f), false);

                    Vector3f c = cameraPath.getPoint(cameraSpeed * overall / second);
                    camera.setPosition(new Vector4f(c.x, c.y, c.z, 1.0f), true);
                } break;
            }
   
            //Setting the lights before all renderings
            float lightRadius = 4.0f;
            lightPosition[LIGHT_RED].set((float)Math.sin(0.001f * overall) * lightRadius, (float)Math.cos(0.001f * overall) * lightRadius, 0.0f, 1.0f);
            lightPosition[LIGHT_GREEN].set((float)Math.sin(0.002f * overall) * lightRadius, 0.0f, (float)Math.cos(0.002f * overall) * lightRadius, 1.0f);
            lightPosition[LIGHT_BLUE].set(0.0f, (float)Math.sin(0.004f * overall) * lightRadius, (float)Math.cos(0.004f * overall) * lightRadius, 1.0f);

            float depthBufferSizeUsedRelativeW=depthBufferSizeUsedW*1.0f / depthBufferSize;
            float depthBufferSizeUsedRelativeH=depthBufferSizeUsedH*1.0f / depthBufferSize;
            int minDisplacement = eyeDistance;
            int maxDisplacement = eyeDistance+MAXZ;


            if (flickerMode==FLICKER_CLEAR) {
                GL11.glClearColor(0.5f, 0.5f, 0.5f, 0);
                GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
                Display.update();
                try{
                Thread.sleep(20);
                }catch(InterruptedException e){}
                currentMillis = System.currentTimeMillis();
            } else {
                GL11.glClearColor(0f, 0f, 0f, 0);
                GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
            }
            
            //Draw scene depth-only to frame buffer
            if (displayMode != DISPLAY_NORMAL) {

                //Enable Framebuffer
                bindFrameBuffer(FRAMEBUFFER_DEPTH_TARGET);

                //Draw
                GL11.glViewport(maxDisplacement, 0, depthBufferSizeUsedW-maxDisplacement, depthBufferSizeUsedH );
                drawDefaultScene();

                //Disable Framebuffer
                bindFrameBuffer(FRAMEBUFFER_DISABLED);
            }

            //restrict viewport to the size where the SIRD would be drawn
            if (displayMode != DISPLAY_SIRDS) {
                if (drawSharpSIRD!=SCALING_LINEAR) GL11.glViewport(maxDisplacement+(camera.getWidth()-depthBufferSizeUsedW)/2, (camera.getHeight()-depthBufferSizeUsedH)/2, depthBufferSizeUsedW-maxDisplacement, depthBufferSizeUsedH);
                else GL11.glViewport(maxDisplacement, 0, camera.getWidth()-maxDisplacement, camera.getHeight());
            }

            //Draw normal scene (if needed)
            if (displayMode == DISPLAY_NORMAL || displayMode == DISPLAY_DEPTH_NORMAL_BLENDED){
                drawDefaultScene();
            }

            //Draw remaining in ortho mode
            if (displayMode != DISPLAY_NORMAL) {
                GL11.glMatrixMode(GL11.GL_PROJECTION);
                GL11.glLoadIdentity();
                GLU.gluOrtho2D(0, 1, 0, 1);

                GL11.glMatrixMode(GL11.GL_MODELVIEW);
                GL11.glLoadIdentity();

                GL11.glDisable(GL11.GL_DEPTH_TEST);
                GL11.glEnable(GL11.GL_TEXTURE_2D);

                if (displayMode!=DISPLAY_SIRDS){
                    if (displayMode==DISPLAY_DEPTH_NORMAL_BLENDED) {
                        GL11.glColor4f(1.0f, 1f,1.0f,0.5f);
                        GL11.glEnable(GL11.GL_BLEND);
                        GL14.glBlendColor(0.5f,0.5f,0.5f,0.5f);
                        GL11.glBlendFunc(GL11.GL_CONSTANT_ALPHA,GL11.GL_ONE_MINUS_CONSTANT_ALPHA);
                    }

                    GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureid.get(TEXTURE_RENDER_DEPTH_TARGET));
                    float relDisplacement=maxDisplacement*1.0f/depthBufferSize;
                    Primitives.drawQuadStrip(1, 0, 1, 0, 1, relDisplacement, depthBufferSizeUsedRelativeW-relDisplacement, 0, depthBufferSizeUsedRelativeH);
                } else { 
                    //-------------------Draw SIRD--------------------
                    float eyeDistancef;
                    float depthFactorf;
                    
                    if (!drawInvertedSIRD){
                        eyeDistancef = eyeDistance * 1.0f / depthBufferSize;
                        depthFactorf = 1.0f / depthBufferSize;
                    } else {
                        eyeDistancef = (eyeDistance+MAXZ) * 1.0f / depthBufferSize;
                        depthFactorf = -1.0f / depthBufferSize;
                    }

                    //stripCount = stripCount + (stripCount & 1); //=> even => last rendered buffer is FRAMEBUFFER_SIRD
                    float stripStart = maxDisplacement*1.0f/depthBufferSize;
                    float stripWidth = minDisplacement*1.0f/depthBufferSize;
                    float stripHeight = depthBufferSizeUsedRelativeH;
                    int stripCount = (int)Math.ceil((depthBufferSizeUsedRelativeW - stripStart) / stripWidth);
//System.out.println("count:"+stripCount);

                    GL11.glViewport(0, 0, depthBufferSize, depthBufferSize);
                    GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureid.get(TEXTURE_RANDOM));

                    //Init base level image
                    int quadCount = (depthBufferSizeUsedH+sisImageSize-1)/sisImageSize;
                    float imageHeight = quadCount*sisImageSize/(1.0f*depthBufferSize);
                    float imageHeightT = quadCount;
                    float imageWidth = maxDisplacement/(1.0f*depthBufferSize);
                    float imageWidthT = maxDisplacement/(1.0f*sisImageSize);
                    float imageLeftT=0;
                    float imageTopT=0;
                    if (flickerMode==FLICKER_TEXTURE_OFFSET){
                        imageLeftT=(float)Math.random();
                        imageTopT=(float)Math.random();
                    } else if (flickerMode==FLICKER_DYNAMIC){
                        if (((currentMillis>nextTextureFlicker || (sceneChangedAt+50>currentMillis))
                                && (sceneChangedAt+1000>currentMillis))){
                            randomTextureOffsetX=(float)Math.random();
                            randomTextureOffsetY=(float)Math.random();
                            nextTextureFlicker = currentMillis + (int)(200*(1-Math.exp((sceneChangedAt-currentMillis)/1000.0f)));
                        }
                        imageLeftT = randomTextureOffsetX;
                        imageTopT = randomTextureOffsetY;
                    }
                    if (drawSharpSIRD!=SCALING_LINEAR){
                        //prevent interpolation between the pixels of the base image
                        imageLeftT=Math.round(sisImageSize*imageLeftT)*1.0f/sisImageSize;
                        imageTopT=Math.round(sisImageSize*imageTopT)*1.0f/sisImageSize;
                    }
                    //draw the first column of the SIRD
                    //= repeation of the base image
                    bindFrameBuffer(FRAMEBUFFER_SIRD);
                    //GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
                    Primitives.drawQuadStrip(1, 0, imageWidth, 0, imageHeight, imageLeftT, imageWidthT, imageTopT, imageHeightT);
                    bindFrameBuffer(FRAMEBUFFER_SIRD_TEMP);
                    //GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
                    Primitives.drawQuadStrip(1, 0, imageWidth, 0, imageHeight, imageLeftT, imageWidthT, imageTopT, imageHeightT);

  //                  Primitives.drawQuadStrip(quadCount, 0, (eyeDistance+MAXZ)/(1.0f*depthBufferSize), 0, sisImageSize/(1.0f*depthBufferSize),
//                                                                                0, (eyeDistance+MAXZ)/(1.0f*sisImageSize), 0, 1);
                    //Primitives.drawQuadStrip(depthBufferSize/sisImageSize, 0, eyeDistance/(1.0f*depthBufferSize), 0, sisImageSize/(1.0f*depthBufferSize),
                    //                                                       0, eyeDistance/(1.0f*sisImageSize), 0, 1);
                    //                    Primitives.drawQuadStrip(camera.getHeight()/sisImageSize, 0, eyeDistance/(1.0f*camera.getWidth()), 0, sisImageSize/(1.0f*camera.getHeight()),
                    //                                                                         0, eyeDistance/(1.0f*sisImageSize), 0, 1);

//System.out.println("xc:"+stripCount+" w:"+stripWidth);

                    useProgram(SHADER_PROGRAM_SIRD);

                    if (GL_VERSION >= 2.0) {
                        GL20.glUniform1i(uniformDepthTexture, 0);
                        GL20.glUniform1i(uniformRenderTexture, 1);
                        GL20.glUniform1f(uniformEyeDistance, eyeDistancef);
                        GL20.glUniform1f(uniformDepthScaling, -MAXZ*depthFactorf);
                    } else {
                        ARBShaderObjects.glUniform1iARB(uniformDepthTexture, 0);
                        ARBShaderObjects.glUniform1iARB(uniformRenderTexture, 1);
                        ARBShaderObjects.glUniform1fARB(uniformEyeDistance, eyeDistancef);
                        ARBShaderObjects.glUniform1fARB(uniformDepthScaling, -MAXZ*depthFactorf);
                    }
                    GL13.glActiveTexture(GL13.GL_TEXTURE0);
                    GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureid.get(TEXTURE_RENDER_DEPTH_TARGET));


                    GL13.glActiveTexture(GL13.GL_TEXTURE1);//stripCount=3;
                    bindFrameBuffer(FRAMEBUFFER_SIRD);
                    GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureid.get(TEXTURE_RENDER_SIRD));
                    Primitives.drawQuadStrip(1, stripStart, stripWidth, 0, stripHeight,
                                                stripStart, stripWidth, 0, stripHeight);
                    for (int strip=0; strip<stripCount; strip++){
                        bindFrameBuffer(FRAMEBUFFER_SIRD_TEMP);
                        GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureid.get(TEXTURE_RENDER_SIRD_TEMP));
                        Primitives.drawQuadStrip(1, stripStart+2*stripWidth*strip, 2*stripWidth, 0, stripHeight,
                                                    stripStart+2*stripWidth*strip, 2*stripWidth, 0, stripHeight);

                        bindFrameBuffer(FRAMEBUFFER_SIRD);
                        GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureid.get(TEXTURE_RENDER_SIRD));
                        Primitives.drawQuadStrip(1, stripStart+stripWidth+2*stripWidth*strip, 2*stripWidth, 0, stripHeight,
                                                    stripStart+stripWidth+2*stripWidth*strip, 2*stripWidth, 0, stripHeight);
                    }
                    bindFrameBuffer(FRAMEBUFFER_DISABLED);
                    useProgram(SHADER_PROGRAM_DISABLED);
                    GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
                    GL13.glActiveTexture(GL13.GL_TEXTURE0);

                    if (drawSharpSIRD!=SCALING_LINEAR) GL11.glViewport((camera.getWidth()-depthBufferSizeUsedW)/2, (camera.getHeight()-depthBufferSizeUsedH)/2, depthBufferSizeUsedW, depthBufferSizeUsedH);
                    else GL11.glViewport(0, 0, camera.getWidth(), camera.getHeight());
                    
                    if (Keyboard.isKeyDown(Keyboard.KEY_Z)) GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureid.get(TEXTURE_RENDER_SIRD_TEMP));
                    else GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureid.get(TEXTURE_RENDER_SIRD));
                    Primitives.drawQuadStrip(1, 0, 1, 0, 1, 0, (1.0f*depthBufferSizeUsedW)/depthBufferSize, 0, (1.0f*depthBufferSizeUsedH)/depthBufferSize);


                    //--------------------Draw Floater on SIRD-----------------
                    Collections.sort(floaters, new Comparator<FloaterGL>(){
                        public int compare(FloaterGL obj1, FloaterGL obj2){
                            if (obj1.textureId==obj2.textureId) return 0;
                            else if (obj1.textureId<obj2.textureId) return -1;
                            else return 1;
                        }
                    });

                    GL11.glViewport(0, 0, camera.getWidth(), camera.getHeight());

                    GL11.glEnable(GL11.GL_BLEND);
                    GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
                    int curTexture=-1;
                    for (FloaterGL floater: floaters){
                        if (!floater.visible) continue;
                        if (floater.textureId!=curTexture) {
                            curTexture=floater.textureId;
                            GL11.glBindTexture(GL11.GL_TEXTURE_2D, curTexture);
                        }
                       // System.out.println(curTexture+" "+floater.tSize+" "+floater.sSize);
                        float floaterDistance = eyeDistancef + depthFactorf*floater.z; //distance fractions of the width of the render buffers
                        if (drawSharpSIRD!=SCALING_LINEAR)
                            floaterDistance = floaterDistance*depthBufferSize/camera.getWidth();
                        
                        //Render floater at floater.x + k*floaterDistance \in [0,1]
                        for (int i=(int)Math.floor((-floater.w-floater.x)/floaterDistance); i<=(int)Math.ceil((1-floater.x)/floaterDistance);i++)
                            Primitives.drawQuad(floater.x+i*floaterDistance, floater.w, floater.y, floater.h, floater.s, floater.sSize, floater.t,  floater.tSize);
                    }
                }

                GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);


                GL11.glDisable(GL11.GL_BLEND);
                GL11.glDisable(GL11.GL_TEXTURE_2D);
                GL11.glEnable(GL11.GL_DEPTH_TEST);
            }

            //Swap the buffers
            Display.update();
            if (flickerMode==FLICKER_CLEAR) {
                try{
                    long sleepTime=20-(System.currentTimeMillis()-currentMillis);
                    if (sleepTime>0) Thread.sleep(sleepTime);
                }catch(InterruptedException e){}
            }
        }
    }

    /**
     * Draw the scene with the cubes, grid and bunny, to the currently active
     * rendertarget (framebuffer/screen)
     */
    private void drawDefaultScene(){
            //Setup Projection and Viewport
            GL11.glMatrixMode(GL11.GL_PROJECTION);
            //Load the projection matrix from the camera class
            GL11.glLoadMatrix(camera.getProjectionMatrix());
            GL11.glMatrixMode(GL11.GL_MODELVIEW);

            //Load the view matrix from the camera class
            GL11.glLoadMatrix(camera.getViewMatrix());

            GL11.glClearColor(0,0,0,1);//0.6f, 0.6f, 0.6f, 1.0f);

            //Clear to the background color
            GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);

            //no light...
            GL11.glDisable(GL11.GL_LIGHTING);

            //Draw a grid for better orientation in the scene
            GL11.glColor3f(0.8f, 0.8f, 0.8f);
            GL11.glLineWidth(2.0f);
            Primitives.drawGrid(20, 1.0f);

            //Draw the target as a yellow cube
            GL11.glColor3f(0.0f, 1.0f, 1.0f);
            GL11.glPushMatrix();
                GL11.glTranslatef(camera.getTarget().x, camera.getTarget().y, camera.getTarget().z);
                GL11.glScalef(0.1f, 0.1f, 0.1f);
                Primitives.drawCube();
            GL11.glPopMatrix();

            //Draw the 3 lights in red, green and blue
            for(int i = 0; i < LIGHT_MAX; i++)
            {
                if(lightSwitchedOn[i])
                {
                    GL11.glColor3f(lightColor[i].x, lightColor[i].y, lightColor[i].z);
                    GL11.glPushMatrix();
                        GL11.glTranslatef(lightPosition[i].x, lightPosition[i].y, lightPosition[i].z);
                        GL11.glScalef(0.1f, 0.1f, 0.1f);
                        Primitives.drawCube();
                    GL11.glPopMatrix();
                }
            }

            GL11.glColor3f(1, 1, 1);

            //Draw the VBO
            if(GL_VERSION >= 1.5)
                GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboid.get(currentGeometry));
            else
                ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, vboid.get(currentGeometry));

            GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
            GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);

            GL11.glVertexPointer(3, GL11.GL_FLOAT, SIZE_OF_DATA * SIZE_OF_FLOAT, 0);

            GL13.glClientActiveTexture(GL13.GL_TEXTURE0);
            GL11.glTexCoordPointer(2, GL11.GL_FLOAT, SIZE_OF_DATA * SIZE_OF_FLOAT, 3 * SIZE_OF_FLOAT);

            GL11.glPushMatrix();
                if(currentGeometry == GEOMETRY_BUNNY)
                    GL11.glScalef(2.0f, 2.0f, 2.0f);
                GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, 3 * numberOfTriangles[currentGeometry]);
            GL11.glPopMatrix();

            GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY);
            GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);

            if(GL_VERSION >= 1.5)
                GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
            else
                ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, 0);

    }

    /**
     * Handle any event from the mouse or the keyboard.
     */
    private void handleEvents() 
    {
        Vector2f currMousePos = new Vector2f(Mouse.getX(), Mouse.getY());

        if(currMousePos.x != prevMousePos.x || currMousePos.y != prevMousePos.y) 
        {
            if(currentMoveMode == MOVE_MODE_FIRST_PERSON)
            {
                if(leftMouseButtonDown)
                {
                    float dx = (currMousePos.x - prevMousePos.x) * mouseSpeed;
                    float dy = (currMousePos.y - prevMousePos.y) * mouseSpeed;
                    camera.setYaw(camera.getYaw() - dx);
                    camera.setPitch(camera.getPitch() + dy);
                    prevMousePos = currMousePos;
                    sceneChangedAt = System.currentTimeMillis();
                }
            }
            else if(currentMoveMode == MOVE_MODE_THIRD_PERSON)
            {
                if(leftMouseButtonDown)
                {
                    float dx = (currMousePos.x - prevMousePos.x) * mouseSpeed;
                    float dy = (currMousePos.y - prevMousePos.y) * mouseSpeed;
                    camera.setYawAroundTarget(camera.getYaw() - dx);
                    camera.setPitchAroundTarget(camera.getPitch() + dy);
                    prevMousePos = currMousePos;
                    sceneChangedAt = System.currentTimeMillis();
                }
                else if(rightMouseButtonDown)
                {
                    float dy = (currMousePos.y - prevMousePos.y) * mouseSpeed;
                    Vector4f move = new Vector4f(
                        camera.getForwardDirection().x * dy,
                        camera.getForwardDirection().y * dy,
                        camera.getForwardDirection().z * dy,
                        0.0f);
                    float distanceAfterMove = 
                        (float)Math.sqrt(Vector4f.sub(camera.getTarget(), camera.getPosition(), null).lengthSquared()) - 
                        (float)Math.sqrt(move.lengthSquared());
                    if(distanceAfterMove < camera.getNear() && dy > 0)
                    {
                        move.set(
                            camera.getForwardDirection().x * -camera.getNear(),
                            camera.getForwardDirection().y * -camera.getNear(),
                            camera.getForwardDirection().z * -camera.getNear());
                        camera.setPosition(Vector4f.add(camera.getTarget(), move,null), false);
                    }
                    else
                    {
                        camera.setPosition(Vector4f.add(camera.getPosition(), move,null), false);
                    }
                    prevMousePos = currMousePos;
                    sceneChangedAt = System.currentTimeMillis();
                }
            }
        }
        while(Mouse.next()) 
        {
            if(Mouse.getEventButton() == 0 && Mouse.getEventButtonState() == true) 
            {
                leftMouseButtonDown = true;
                prevMousePos.x = Mouse.getX();
                prevMousePos.y = Mouse.getY();
            }
            else if(Mouse.getEventButton() == 0 && Mouse.getEventButtonState() == false) 
            {
                leftMouseButtonDown = false;
            }
            else if(Mouse.getEventButton() == 1 && Mouse.getEventButtonState() == true) 
            {
                rightMouseButtonDown = true;
                prevMousePos.x = Mouse.getX();
                prevMousePos.y = Mouse.getY();
            }
            else if(Mouse.getEventButton() == 1 && Mouse.getEventButtonState() == false) 
            {
                rightMouseButtonDown = false;
            }
        }
        while(Keyboard.next()) 
        {
            if(Keyboard.isKeyDown(Keyboard.KEY_SPACE)) 
            {
                currentMoveMode = (currentMoveMode + 1) % MOVE_MODE_MAX;
                switch(currentMoveMode)
                {
                case MOVE_MODE_FIRST_PERSON:
                    System.out.println("Movement is now: First Person!");
                    break;
                case MOVE_MODE_THIRD_PERSON:
                    System.out.println("Movement is now: Third Person!");
                    camera.setPosition(camera.getPosition(), true);
                    break;
                case MOVE_MODE_SPLINE_PATH:
                    System.out.println("Movement is now: Spline Path!");
                    break;
                }
            }
            else if(Keyboard.isKeyDown(Keyboard.KEY_G)) 
            {
                currentGeometry = (currentGeometry + 1) % GEOMETRY_MAX;
                switch(currentGeometry)
                {
                case GEOMETRY_DUMMY:
                    System.out.println("Geometry is now: Dummy!");
                    break;
                case GEOMETRY_BUNNY:
                    if (0==numberOfTriangles[GEOMETRY_BUNNY]){
                        ObjImporter objImporter = new ObjImporter("res/geometry/Bunny.obj");
                        numberOfTriangles[GEOMETRY_BUNNY] = objImporter.getTriangleCount();
                        if(GL_VERSION >= 1.5)
                        {
                            GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboid.get(GEOMETRY_BUNNY));
                            GL15.glBufferData(GL15.GL_ARRAY_BUFFER, createData(objImporter), GL15.GL_STATIC_DRAW);
                        }
                        else
                        {
                            ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, vboid.get(GEOMETRY_BUNNY));
                            ARBVertexBufferObject.glBufferDataARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, createData(objImporter), ARBVertexBufferObject.GL_STATIC_DRAW_ARB);
                        }//*/
                    }
                    System.out.println("Geometry is now: Bunny!");
                    break;
                }
            }
            else if(Keyboard.isKeyDown(Keyboard.KEY_S)) 
            {
                drawSharpSIRD++;
                if (drawSharpSIRD>SCALING_MAX) drawSharpSIRD=0;
                switch (drawSharpSIRD) {
                    case SCALING_LINEAR:
                        System.out.println("Draw SIRD normal/blurred (scaled)");
                        GL11.glEnable(GL11.GL_TEXTURE_2D);
                        GL11.glBindTexture(GL11.GL_TEXTURE_2D, TEXTURE_RENDER_SIRD);
                        GL11.glTexParameterf( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR );
                        GL11.glTexParameterf( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR );
                        GL11.glBindTexture(GL11.GL_TEXTURE_2D, TEXTURE_RENDER_SIRD_TEMP);
                        GL11.glTexParameterf( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR );
                        GL11.glTexParameterf( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR );
                        GL11.glDisable(GL11.GL_TEXTURE_2D);
                        break;
                    case SCALING_SHARP:
                        System.out.println("Draw SIRD sharp (no scaling)");
                        break;
                    case SCALING_NEAREST_SHARP:
                        System.out.println("Draw SIRD super sharp (no scaling, nearest interpolation)");
                        GL11.glEnable(GL11.GL_TEXTURE_2D);
                        GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureid.get(TEXTURE_RENDER_SIRD));
                        GL11.glTexParameterf( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST );
                        GL11.glTexParameterf( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST );
                        GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureid.get(TEXTURE_RENDER_SIRD_TEMP));
                        GL11.glTexParameterf( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST );
                        GL11.glTexParameterf( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST );
                        GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
                        GL11.glDisable(GL11.GL_TEXTURE_2D);
                        break;
                }
            } else if (Keyboard.isKeyDown(Keyboard.KEY_T)){
                curTexture++;
                if (curTexture>=availableTextures.length) curTexture=0;
                sisImageSize=loadBindTexture(TEXTURE_RANDOM, availableTextures[curTexture]);
                System.out.println("Texture is now: "+availableTextures[curTexture]);
                GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
            }else if(Keyboard.isKeyDown(Keyboard.KEY_I))
            {
                drawInvertedSIRD=!drawInvertedSIRD;
                if (drawInvertedSIRD) System.out.println("Draw SIRD inverted => Cross eyes");
                else System.out.println("Draw SIRD normal => Parallel eyes");
            }
            else if(Keyboard.isKeyDown(Keyboard.KEY_M))
            {
                displayMode++;
                if (displayMode>4) displayMode=0;
                switch (displayMode){
                        case DISPLAY_SIRDS:
                            System.out.println("Display: SIRD");
                            break;
                        case DISPLAY_DEPTH:
                            System.out.println("Display: depth");
                            break;
                        case DISPLAY_NORMAL:
                            System.out.println("Display: normal");
                            break;
                        case DISPLAY_DEPTH_NORMAL_BLENDED:
                            System.out.println("Display: normal + depth blended");
                            break;
                }
            }
            else if(Keyboard.isKeyDown(Keyboard.KEY_C))
            {
                floaters.get(0).visible  = !floaters.get(0).visible;
                if (floaters.get(0).visible) System.out.println("Floatercursor is visible");
                else System.out.println("Floater Cursor is hidden");
            }
            else if(Keyboard.isKeyDown(Keyboard.KEY_F))
            {
                flickerMode++;
                if (flickerMode>=FLICKER_MAX) flickerMode=0;

                switch (flickerMode){
                        case FLICKER_OFF:
                            System.out.println("Flicker: off");
                            break;
                        case FLICKER_TEXTURE_OFFSET:
                            System.out.println("Flicker: texture offset");
                            break;
                        case FLICKER_CLEAR:
                            System.out.println("Flicker: clear/render (fix framerate)");
                            break;
                        case FLICKER_DYNAMIC:
                            System.out.println("Flicker: dynamic texture offset");
                            break;
                }
            }
        }
        if(Keyboard.isKeyDown(Keyboard.KEY_POWER)||Keyboard.isKeyDown(Keyboard.KEY_ADD)){
            if (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)) depthBufferSizeUsedW=Math.min(depthBufferSize,depthBufferSizeUsedW+1);
            else if (Keyboard.isKeyDown(Keyboard.KEY_RCONTROL)) depthBufferSizeUsedH=Math.min(depthBufferSize,depthBufferSizeUsedH+1);
            else eyeDistance=Math.min(depthBufferSizeUsedW/2,eyeDistance+1);
            System.out.println("Depth buffer size  is: "+depthBufferSizeUsedW+"x"+depthBufferSizeUsedH+" with eye distance "+eyeDistance);
        }
        else if(Keyboard.isKeyDown(Keyboard.KEY_MINUS)||Keyboard.isKeyDown(Keyboard.KEY_SUBTRACT)){
            if (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)) depthBufferSizeUsedW=Math.max(16,depthBufferSizeUsedW-1);
            else if (Keyboard.isKeyDown(Keyboard.KEY_RCONTROL)) depthBufferSizeUsedH=Math.max(16,depthBufferSizeUsedH-1);
            else eyeDistance=Math.max(MAXZ,eyeDistance-1);
            System.out.println("Depth buffer size  is: "+depthBufferSizeUsedW+"x"+depthBufferSizeUsedH+" with eye distance "+eyeDistance);
        }

        //Draw Custom Mouse pointer
        float mouseX=currMousePos.x / camera.getWidth();
        float mouseY=currMousePos.y / camera.getHeight();
        floaters.get(0).x=mouseX;
        floaters.get(0).y=mouseY-floaters.get(0).h;
    }
    
    /**
     * Destroy the display. This is a simple method right now, but later we can
     * clean up other things within this function.
     */
    private void destroy() 
    {
        //Clean FBO stuff
        if(GL_VERSION >= 3.0)
        {    
            GL30.glDeleteFramebuffers(fboid);
            GL30.glDeleteRenderbuffers(renderbufferid);
        }
        else
        {
            EXTFramebufferObject.glDeleteFramebuffersEXT(fboid);
            EXTFramebufferObject.glDeleteRenderbuffersEXT(renderbufferid);
        }
        //Clean Textures
        GL11.glDeleteTextures(textureid);
        
        //Clean the shaders
        if(GL_VERSION >= 2.0)
            for(int i = 0; i < SHADER_PROGRAM_MAX; i++)
                GL20.glDeleteProgram(shaderProgram[i]);
        else
            for(int i = 0; i < SHADER_PROGRAM_MAX; i++)
                ARBShaderObjects.glDeleteObjectARB(shaderProgram[i]);
        
        //Clean up the VBO buffer
        if(GL_VERSION >= 1.5)
            GL15.glDeleteBuffers(vboid);
        else
            ARBVertexBufferObject.glDeleteBuffersARB(vboid);

        Display.destroy();
    }
}
