package cz.fidentis.analyst.newgui;

import com.jogamp.opengl.GL;
import static com.jogamp.opengl.GL.GL_DEPTH_TEST;
import static com.jogamp.opengl.GL.GL_FRONT_AND_BACK;
import com.jogamp.opengl.GL2;
import static com.jogamp.opengl.GL2GL3.GL_FILL;
import static com.jogamp.opengl.GL2GL3.GL_LINE;
import com.jogamp.opengl.glu.GLU;
import cz.fidentis.analyst.mesh.core.MeshFacet;
import cz.fidentis.analyst.mesh.core.MeshModel;
import java.util.ArrayList;
import java.util.List;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector4f;

/**
 * An object responsible for handling {@link Drawable}s included in the scene 
 * (in the OpenGL context of the OpenGL canvas) and their rendering.
 * 
 * @author Natalia Bebjakova
 * @author Radek Oslejsek
 */
public class Scene {

    private static final Vector4f BRIGHT_BACKGROUND = new Vector4f(0.9f, 0.9f, 0.9f, 0);
    private static final Vector4f DARK_BACKGROUND = new Vector4f(0.25f, 0.25f, 0.25f, 0);
    
    private final GLU glu = new GLU();
    private GL2 gl;
    
    private boolean brightBackground = false;
    private boolean wireframe = false;
    
    private List<Drawable> drawables = new ArrayList<>();
    
    private final Camera camera = new Camera();
    
    public void addDrawale(Drawable dr) {
        if (dr != null) {
            drawables.add(dr);
        }
    }
    
    /**
     * Constructor.
     * 
     * @param gl OpenGL context of drawable objects
     * @throws IllegalArgumentException if the {@code gl} is null
     */
    public void initSceneGLContext(GL2 gl) {
        if (gl == null) {
            throw new IllegalArgumentException("gl is null");
        }
        this.gl = gl;
        
        gl.setSwapInterval(1);
        gl.glEnable(GL2.GL_LIGHTING);
        gl.glEnable(GL2.GL_LIGHT0);
        gl.glEnable(GL2.GL_DEPTH_TEST);
        gl.glClearColor(0,0,0,0);     // background for GLCanvas
        gl.glClear(GL2.GL_COLOR_BUFFER_BIT);
        
        gl.glShadeModel(GL2.GL_SMOOTH);    // use smooth shading

        gl.glDepthFunc(GL2.GL_LESS);
        gl.glDepthRange(0.0, 1.0);
        gl.glEnable(GL_DEPTH_TEST); 

        gl.glEnable(GL2.GL_NORMALIZE);
        gl.glDisable(GL2.GL_CULL_FACE);
    }
    
    /**
     * Sets the scene background to bright.
     */
    public void setBrightBackground() {
        this.brightBackground = true;
    }
    
    /**
     * Sets the scene background to dark.
     */
    public void setDarkBackground() {
        this.brightBackground = false;
    }
    
    /**
     * Sets the view-port.
     * 
     * @param x X corner
     * @param y Y corner
     * @param width Width 
     * @param height Height
     */
    public void setViewport(int x, int y, int width, int height) {

        if (height == 0) {
            height = 1;    // to avoid division by 0 in aspect ratio below
        }
        gl.glViewport(x, y, width, height);  // size of drawing area

        float h = (float) height / (float) width;

        gl.glMatrixMode(GL2.GL_PROJECTION);
        gl.glLoadIdentity();

        glu.gluPerspective(65, width / (float) height, 5.0f, 1500.0f);
        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glLoadIdentity();

        gl.glTranslatef(0.0f, 0.0f, -40.0f);
    }
    
    /**
     * Renders drawable objects.
     */
    public void renderScene() {
        clearScene();
        
        // sets model to proper position
        Vector3d currentPosition = camera.getPosition();
        Vector3d center = camera.getCenter();
        Vector3d upDirection = camera.getUpDirection();
        glu.gluLookAt(currentPosition.x, currentPosition.y, currentPosition.z,  
                center.x, center.y, center.z,
                upDirection.x, upDirection.y, upDirection.z);

        gl.glShadeModel(GL2.GL_SMOOTH);
        //gl.glGetIntegerv(GL_VIEWPORT, viewport, 0);
        //gl.glGetFloatv(GL_MODELVIEW_MATRIX, modelViewMatrix, 0);
        //gl.glGetFloatv(GL_PROJECTION_MATRIX, projectionMatrix, 0);
        
        if (this.wireframe) {
            gl.glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
        } else {
            gl.glPolygonMode( GL_FRONT_AND_BACK, GL_FILL);
        }
        
        for (Drawable obj: drawables) {
            for (MeshFacet facet: obj.getFacets()) {
                renderFacet(facet);
            }
        }
        
         gl.glFlush();
    }
    
    public Camera getCamera() {
        return camera;
    }
    
    /**
     * Clears the scene and prepares it for the re-drawing.
     */
    protected void clearScene() { // join with the renderScene() ???
        if (brightBackground) {
            gl.glClearColor(BRIGHT_BACKGROUND.x, BRIGHT_BACKGROUND.y, BRIGHT_BACKGROUND.z, BRIGHT_BACKGROUND.w);
        } else {
            gl.glClearColor(DARK_BACKGROUND.x, DARK_BACKGROUND.y, DARK_BACKGROUND.z, DARK_BACKGROUND.w);
        }
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        gl.glLoadIdentity();
    }
    
    /**
     * Sets the wire-frame rendering mode
     */
    protected void setWireframeMode() {
        this.wireframe = true;
    }
    
    /**
     * Sets the "shaded" rendering mode (filled triangles)
     */
    protected void setFillMode() {
        this.wireframe = false;
    }
    
    /**
     * Renders the whole mesh model.
     * 
     * @param model Mesh model to be rendered
     */
    protected void renderMeshModel(MeshModel model) {
        for (int i = 0; i < model.getFacets().size(); i++) {
            renderFacet(model.getFacets().get(i));
        }
    }
    
    /**
     * Loops through the facet and render all the vertices as they are stored in corner table
     * 
     * @param gl OpenGL context
     * @param facet facet of model
     */
    protected void renderFacet(MeshFacet facet) {
        gl.glBegin(GL2.GL_TRIANGLES); //vertices are rendered as triangles
        
         // get the normal and tex coords indicies for face i  
        for (int v = 0; v < facet.getCornerTable().getSize(); v++) { 
            // render the normals
            Vector3d norm = facet.getVertices().get(facet.getCornerTable().getRow(v).getVertexIndex()).getNormal(); 
            if(norm != null) {
                gl.glNormal3d(norm.x, norm.y, norm.z);
            }
            // render the vertices
            Vector3d vert = facet.getVertices().get(facet.getCornerTable().getRow(v).getVertexIndex()).getPosition(); 
            gl.glVertex3d(vert.x, vert.y, vert.z);
        }
        
        gl.glEnd();
    }
    
}
