package cz.fidentis.analyst.scene;

import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL2;
import com.jogamp.opengl.glu.GLU;
import cz.fidentis.analyst.mesh.core.MeshFacet;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

/**
 * Handles {@link DrawableMesh}s - objects to be drawn, and (re-)renders them on demand. 
 * 
 * @author Natalia Bebjakova
 * @author Radek Oslejsek
 * @author Richard Pajersky
 */
public class SceneRenderer {

    public static final Color BRIGHT_BACKGROUND = new Color(0.9f, 0.9f, 0.9f);
    public static final Color DARK_BACKGROUND = new Color(0.25f, 0.25f, 0.25f);
    
    private final GLU glu = new GLU();
    private GL2 gl;
    
    private boolean brightBackground = false;
    
    /**
     * Constructor.
     * 
     * @param gl OpenGL context of drawable objects
     * @throws IllegalArgumentException if the {@code gl} is null
     */
    public void initGLContext(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_LIGHT1);
        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.GL_DEPTH_TEST); 

        gl.glEnable(GL2.GL_NORMALIZE);
        gl.glDisable(GL2.GL_CULL_FACE);
        
        gl.glEnable(GL2.GL_BLEND);    // enable transparency
        gl.glBlendFunc(GL2.GL_SRC_ALPHA, GL2.GL_ONE_MINUS_SRC_ALPHA);
    }
    
    /**
     * 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) {
        //OutputWindow out = OutputWindow.measureTime();
        
        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(50, width / (float) height, 5.0f, 1500.0f);
        
        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glLoadIdentity();
        gl.glTranslatef(0.0f, 0.0f, -40.0f);
        
        //out.printDuration("GL viewport set to " + width + "x" + height);
    }
    
    /**
     * Renders drawable objects.
     */
    public void renderScene(Camera camera, Collection<Drawable> drawables) {
        //OutputWindow out = OutputWindow.measureTime();
        clearScene();
        
        // light backface
        gl.glLightfv(GL2.GL_LIGHT1, GL2.GL_POSITION, new float[] {0f, 0f, -1f, 0f}, 0);
        gl.glLightfv(GL2.GL_LIGHT1, GL2.GL_DIFFUSE, Color.white.getComponents(null), 0);
        
        setPosition(camera);
        List<DrawableFace> faces = getOrderedFaces(drawables);
        
        for (Drawable obj: drawables) {
            if (!(obj instanceof DrawableFace) && obj.isShown()) {
                obj.render(gl);
            }
        }
        
        for (DrawableFace obj: faces) {
            if (obj.isShown()) {
                //showBackface(obj);
                obj.render(gl);
            }
        }
        
        gl.glFlush();
        
        //out.printDuration("Scene rendering");
    }
    
    /**
     * Sets model to proper position
     * 
     * @param camera Camera
     */
    private void setPosition(Camera camera) {
        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);
    }
    
    /**
     * Sets up transparent objects to render later in order to render properly
     * Now works only for two meshes
     * 
     * @param drawables List of meshes
     */
    private List<DrawableFace> getOrderedFaces(Collection<Drawable> drawables) {
        List<DrawableFace> faces = new ArrayList<>();
        for (Drawable obj: drawables) {
            if (obj instanceof DrawableFace) {
                faces.add((DrawableFace) obj);
            }
        }
        if (faces.get(0).getTransparency() != 1) {
            Collections.reverse(faces);
        }
        return faces;
    }
    
    /**
     * Clears the scene and prepares it for the re-drawing.
     */
    protected void clearScene() { // join with the renderScene() ???
        if (brightBackground) {
            gl.glClearColor(
                    BRIGHT_BACKGROUND.getRed()   / 255f, 
                    BRIGHT_BACKGROUND.getGreen() / 255f, 
                    BRIGHT_BACKGROUND.getBlue()  / 255f, 
                    0.0f);
        } else {
            gl.glClearColor(
                    DARK_BACKGROUND.getRed()   / 255f, 
                    DARK_BACKGROUND.getGreen() / 255f, 
                    DARK_BACKGROUND.getBlue()  / 255f, 
                    0.0f);
        }
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        gl.glLoadIdentity();
    }
    
    /**
     * Loops through the facet and render all the vertices as they are stored in corner table
     * 
     * @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
            Point3d vert = facet.getVertices().get(facet.getCornerTable().getRow(v).getVertexIndex()).getPosition();
            gl.glVertex3d(vert.x, vert.y, vert.z);
        }
        
        gl.glEnd();
    }
}
