package cz.fidentis.analyst.gui;

import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL2;
import com.jogamp.opengl.GLAutoDrawable;
import cz.fidentis.analyst.face.HumanFace;
import cz.fidentis.analyst.mesh.core.MeshFacet;
import cz.fidentis.analyst.mesh.core.MeshModel;

import javax.vecmath.Vector3d;
import java.awt.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.jogamp.opengl.GL.GL_FRONT_AND_BACK;
import static com.jogamp.opengl.GL.GL_VIEWPORT;
import static com.jogamp.opengl.GL2GL3.GL_FILL;
import static com.jogamp.opengl.GL2GL3.GL_LINE;
import static com.jogamp.opengl.fixedfunc.GLMatrixFunc.GL_MODELVIEW_MATRIX;
import static com.jogamp.opengl.fixedfunc.GLMatrixFunc.GL_PROJECTION_MATRIX;
import cz.fidentis.analyst.visitors.mesh.Curvature;
import cz.fidentis.analyst.visitors.mesh.HausdorffDistance;

/**
 * 
 * @author radek oslejsek
 */
public class ComparisonGLEventListener extends GeneralGLEventListener {
    private Map<MeshFacet, List<Double>> distances = new HashMap<>();
    private HumanFace comparedFace;
    boolean renderHeatmap;
    Color minColor;
    Color maxColor;



    public ComparisonGLEventListener(Canvas canvas) {
        super(canvas);
    }

    public void setComparedFace(HumanFace inputComparedFace) {
        if (inputComparedFace != null) {
            comparedFace = inputComparedFace;
        }
        renderHeatmap = false;
    }

    public void compare(Color minColor, Color maxColor) {
        //HausdorffDistance hVisitor = new HausdorffDistance(getModel(), HausdorffDistance.Strategy.POINT_TO_POINT, false, false);
        //comparedFace.getMeshModel().compute(hVisitor);
        //distances = hVisitor.getDistances();
        Curvature visitor = new Curvature();
        comparedFace.getMeshModel().compute(visitor);
        distances = visitor.getGaussianCurvatures();
        renderHeatmap = true;
        this.minColor = minColor;
        this.maxColor = maxColor;
    }

    @Override
    public void display(GLAutoDrawable glad) {
        wireModel = glCanvas.getDrawWired(); // is wire-frame or not

        if (whiteBackround) {
            gl.glClearColor(0.9f, 0.9f, 0.9f, 0);
        } else {
            gl.glClearColor(0.25f, 0.25f, 0.25f, 0);
        }
        // background for GLCanvas
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        gl.glLoadIdentity();

        // sets model to proper position
        glu.gluLookAt(xCameraPosition, yCameraPosition, zCameraPosition, xCenter, yCenter, zCenter, xUpPosition, yUpPosition, zUpPosition);

        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 there is any model, draw
        if (comparedFace != null) {
            if (renderHeatmap) {
                drawHeatmap(comparedFace.getMeshModel());
            } else {
                drawWithoutTextures(comparedFace.getMeshModel());
            }
        } else if (getModel() != null) {
            if (wireModel) {
                gl.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); //drawn as wire-frame
            } else {
                gl.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // drawn as full traingles
            }

            drawWithoutTextures(getModel());
        }

        gl.glFlush();
    }

    public void drawHeatmap(MeshModel model) {
        for (int i = 0; i < model.getFacets().size(); i++) {
            List<Double> distanceList = distances.get(model.getFacets().get(i));
            Double minDistance = distanceList.stream().mapToDouble(Double::doubleValue).min().getAsDouble();
            Double maxDistance = distanceList.stream().mapToDouble(Double::doubleValue).max().getAsDouble();
            minDistance = -0.05; // !!!
            maxDistance = 0.01; // !!!
            //minDistance = -0.2; // !!! min
            //maxDistance = 0.05; // !!! min
            //System.out.println("AAA" + minDistance + " " + maxDistance);
            renderFaceWithHeatmap(model.getFacets().get(i), distances.get(model.getFacets().get(i)), minDistance, maxDistance);
        }
    }

    public void renderFaceWithHeatmap(MeshFacet facet, List<Double> distancesList, 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++) {
            // 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();
            //get color of vertex
            Color c = getColor(distancesList.get(facet.getCornerTable().getRow(v).getVertexIndex()), 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();
    }

    Color getColor(Double currentDistance, Double minDistance, Double maxDistance) {
        double currentParameter = ((currentDistance - minDistance) / (maxDistance - minDistance));
        //return new Color((float)currentParameter, 0.5f, 0.5f);
        
        if (currentDistance > maxDistance || currentDistance < minDistance) { // !!!
            currentParameter = 1.0;
        }
        //currentParameter *= 10;
        //if (currentParameter > maxDistance) {
        //    currentParameter = 1.0;
        //}
        //System.out.println("AAA " + currentParameter + " " + minDistance + " " + maxDistance);
        
        float[] hsb1 = Color.RGBtoHSB(minColor.getRed(), minColor.getGreen(), minColor.getBlue(), null);
        double h1 = hsb1[0];
        double s1 = hsb1[1];
        double b1 = hsb1[2];

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

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

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

        double hue;

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

        if (hue < 0) {
            hue = 1 + hue;
        }
        if (hue > 1) {
            hue = hue - 1;
        }
        
        double saturation = (1 - currentParameter) * s1 + currentParameter * s2;
        double brightness = (1 - currentParameter) * b1 + currentParameter * b2;

        //System.out.println("AAA " + hue);

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