package cz.fidentis.analyst.scene;

import com.jogamp.opengl.GL2;
import cz.fidentis.analyst.mesh.core.MeshFacet;
import cz.fidentis.analyst.mesh.core.MeshModel;
import java.awt.Color;
import java.util.List;
import java.util.Map;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

/**
 * Heatmap rendering.
 * 
 * @author Daniel Sokol
 * @author Daniel Schramm
 */
public class HeatmapRenderer {
    
    private Color minColor = Color.RED;
    private Color maxColor = Color.BLUE;

    public void setMinColor(Color color) {
        minColor = color;
    }
    
    public void setMaxColor(Color color) {
        maxColor = color;
    }
    
    /**
     * Maps distances of mesh model vertices to colors and renders the taken heatmap.
     * @param gl OpenGL context
     * @param model Mesh model to be rendered
     * @param distances Distances in mesh model vertices
     * @param saturation Saturation of colors at mesh model vertices
     */
    public void drawMeshModel(GL2 gl, MeshModel model,
            Map<MeshFacet, List<Double>> distances, Map<MeshFacet, List<Double>> saturation) {
        double minDistance = Double.POSITIVE_INFINITY;
        double maxDistance = Double.NEGATIVE_INFINITY;
        
        for (MeshFacet f: model.getFacets()) {
            List<Double> distanceList = distances.get(f);
            if (distanceList != null) {
                minDistance = Math.min(
                        minDistance,
                        distanceList.parallelStream()
                                .mapToDouble(Double::doubleValue)
                                .min()
                                .getAsDouble()
                );
                maxDistance = Math.max(
                        maxDistance,
                        distanceList.parallelStream()
                                .mapToDouble(Double::doubleValue)
                                .max()
                                .getAsDouble()
                );
            }
        }
        
        for (MeshFacet f: model.getFacets()) {
            if (distances.containsKey(f)) {
                renderMeshFacet(gl, f, distances.get(f), saturation.get(f), minDistance, maxDistance);
            }
        }
    }
    
    /**
     * Maps distances of mesh facet to colors and renders the taken heatmap.
     * @param gl OpenGL context
     * @param facet Mesh facet
     * @param distancesList Distances in the mesh facet vertices
     * @param saturationList Saturation of colors at mesh facet vertices. May be {@code null}.
     * @param minDistance Minimal distance threshold (smaller distances are cut-off)
     * @param maxDistance Maxim distance threshold (bigger distances are cut-off)
     */
    public void renderMeshFacet(GL2 gl, MeshFacet facet, List<Double> distancesList,
            List<Double> saturationList, double minDistance, double maxDistance) {
        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++) {
            int vertexIndex = facet.getCornerTable().getRow(v).getVertexIndex();
            
            // render the normals
            Vector3d norm = facet.getVertices().get(vertexIndex).getNormal();
            if (norm != null) {
                gl.glNormal3d(norm.x, norm.y, norm.z);
            }
            
            // render the vertices
            Point3d vert = facet.getVertices().get(vertexIndex).getPosition();
            
            //get color of vertex
            double currentDistance = distancesList.get(vertexIndex);
            double currentSaturation = saturationList == null ? 1d : saturationList.get(vertexIndex);
            Color c = getColor(currentDistance, currentSaturation, minDistance, maxDistance);
            gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_DIFFUSE, c.getComponents(null), 0);

            gl.glVertex3d(vert.x, vert.y, vert.z);
        }
        gl.glEnd();
        gl.glPopAttrib();
    }
    
    private Color getColor(double currentDistance, double currentSaturation, double minDistance, double maxDistance) {
        double currentParameter = ((currentDistance - minDistance) / (maxDistance - minDistance));

        float[] hsb1 = Color.RGBtoHSB(minColor.getRed(), minColor.getGreen(), minColor.getBlue(), null);
        float h1 = hsb1[0];
        float s1 = hsb1[1];
        float b1 = hsb1[2];

        float[] hsb2 = Color.RGBtoHSB(maxColor.getRed(), maxColor.getGreen(), maxColor.getBlue(), null);
        float h2 = hsb2[0];
        float s2 = hsb2[1];
        float b2 = hsb2[2];

        // determine clockwise and counter-clockwise distance between hues
        float distCCW;
        float distCW;

        if (h1 >= h2) {
            distCCW = h1 - h2;
            distCW = 1 + h2 - h1;
        } else {
            distCCW = 1 + h1 - h2;
            distCW = h2 - h1;
        }

        float hue;

        if (distCW >= distCCW) {
            hue = (float) (h1 + (distCW * currentParameter));
        } else {
            hue = (float) (h1 - (distCCW * currentParameter));
        }

        if (hue < 0) {
            hue = 1 + hue;
        }
        if (hue > 1) {
            hue = hue - 1;
        }

        float saturation = (float) (((1 - currentParameter) * s1 + currentParameter * s2) * currentSaturation);
        float brightness = (float) ((1 - currentParameter) * b1 + currentParameter * b2);

        return Color.getHSBColor(hue, saturation, brightness);
    }
}
