/*
    Copyright 2017 Will Winder

    This file is part of Universal Gcode Sender (UGS).

    UGS is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    UGS is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with UGS.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.willwinder.ugs.platform.surfacescanner;

import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.Iterables;
import com.jogamp.opengl.GL2;
import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.util.gl2.GLUT;
import com.willwinder.ugs.nbm.visualizer.options.VisualizerOptions;
import com.willwinder.ugs.nbm.visualizer.shared.Renderable;
import com.willwinder.universalgcodesender.model.Position;
import com.willwinder.universalgcodesender.model.UnitUtils;
import com.willwinder.universalgcodesender.model.UnitUtils.Units;
import javax.vecmath.Point3d;

/**
 *
 * @author wwinder
 */
public class AutoLevelPreview extends Renderable {
    private final GLUT glut;

    private ImmutableCollection<Position> positions;
    private Point3d[][] grid = null;

    // The maximum distance of a probe used for coloring.
    private double maxZ, minZ;

    private float high[] = {0, 255, 0}; // green
    private float low[] = {255, 0, 0}; // red

    public AutoLevelPreview(String title) {
        super(10, title);

        glut = new GLUT();

        reloadPreferences(new VisualizerOptions());
    }

    @Override
    public boolean rotate() {
        return true;
    }

    @Override
    public boolean center() {
        return true;
    }

    @Override
    public void init(GLAutoDrawable drawable) {
    }

    @Override
    public final void reloadPreferences(VisualizerOptions vo) {
        high = VisualizerOptions.colorToFloatArray(vo.getOptionForKey(VisualizerOptions.VISUALIZER_OPTION_LOW).value);
        low = VisualizerOptions.colorToFloatArray(vo.getOptionForKey(VisualizerOptions.VISUALIZER_OPTION_HIGH).value);
    }

    public void updateSettings(
            ImmutableCollection<Position> positions,
            Units gridUnits,
            final Point3d[][] grid,
            Point3d max,
            Point3d min) {
        if (positions != null && !positions.isEmpty()) {
            this.positions = positions;
            this.grid = grid;
            if (max != null) {
                this.maxZ = max.z;
            }
            if (min != null) {
                this.minZ = min.z;
            }
        }
    }

    @Override
    public void draw(GLAutoDrawable drawable, boolean idle, Point3d workCoord, Point3d objectMin, Point3d objectMax, double scaleFactor, Point3d mouseWorldCoordinates, Point3d rotation) {

        // Don't draw something invalid.
        if (positions == null || positions.isEmpty()) {
            return;
        }

        Position first = Iterables.getFirst(positions, null);
        Units unit = first.getUnits();

        double objectX = objectMax.x - objectMin.x;
        double objectY = objectMax.y - objectMin.y;
        double diameter = Math.max(objectX*0.005, objectY*0.005);
                
        double minx, miny, minz;
        double maxx, maxy, maxz;

        minx = maxx = first.x;
        miny = maxy = first.y;
        minz = maxz = first.z;
        
        GL2 gl = drawable.getGL().getGL2();
        gl.glPushMatrix();
            // Scale inch to mm if needed
            double scale = UnitUtils.scaleUnits(unit, Units.MM);
            if (unit != Units.MM) {
                gl.glScaled(scale, scale, scale);
            }

            // Balls indicating the probe start locations.
            gl.glColor4fv(new float[]{0.1f, 0.1f, 0.1f, 1.0f}, 0);
            for (Position p : positions) {
                gl.glPushMatrix();
                    gl.glTranslated(p.x, p.y, p.z);
                    glut.glutSolidSphere(diameter/scale, 7, 7);

                    // update min/max
                    minx = Math.min(minx, p.x);
                    maxx = Math.max(maxx, p.x);
                    miny = Math.min(miny, p.y);
                    maxz = Math.max(maxz, p.z);
                    minz = Math.min(minz, p.z);
                    maxy = Math.max(maxy, p.y);
                gl.glPopMatrix();
            }

            // Outline of probe area
            gl.glPushMatrix();
                gl.glTranslated(
                        (minx+maxx)/2,
                        (miny+maxy)/2,
                        (minz+maxz)/2);
                gl.glScaled(maxx-minx, maxy-miny, maxz-minz);
                gl.glColor4fv(new float[]{0.3f, 0, 0, 0.1f}, 0);
                glut.glutWireCube((float) 1.);
            gl.glPopMatrix();

            drawProbedSurface(gl);
        gl.glPopMatrix();
    }

    private void setColorForZ(GL2 gl, double zPos) {
        float ratio = (float) ((zPos - minZ) / (maxZ - minZ));
        float r = ratio * high[0] + (1-ratio) * low[0];
        float g = ratio * high[1] + (1-ratio) * low[1];
        float b = ratio * high[2] + (1-ratio) * low[2];
        float a = ratio * high[3] + (1-ratio) * low[3];

        gl.glColor4f(r, g, b, a);
    }

    private void drawProbedSurface(GL2 gl) {
        if (this.grid == null) {
            return;
        }

        /*
    0,5 ?   ?   ?   ?   ? 5,5

        *   ?   ?   ?   ?
          \
        *   *   ?   ?   ?
          x   \
        *   *   *   ?   ?
          x   x   \
    0,0 *   *   *   *   ? 5,0

        */

        gl.glBegin(GL2.GL_TRIANGLES); 
        int xLen = this.grid.length;
        for(int x = 0; x < xLen - 1; x++) {
            if (this.grid[x] == null || this.grid[x+1] == null) {
                continue;
            }

            for (int y = 0; y < this.grid[x].length - 1; y++) {
                Point3d pos1 = this.grid[x][y];
                Point3d pos2 = this.grid[x+1][y];
                Point3d pos3 = this.grid[x][y+1];
                Point3d pos4 = this.grid[x+1][y+1];

                // Bottom left of quad
                if (pos1 != null && pos2 != null && pos3 != null) {
                    setColorForZ(gl, pos1.z);
                    gl.glVertex3d( pos1.x, pos1.y, pos1.z ); // Left Of Triangle (Front)

                    setColorForZ(gl, pos3.z);
                    gl.glVertex3d( pos3.x, pos3.y, pos3.z ); // Top Of Triangle (Front)

                    setColorForZ(gl, pos2.z);
                    gl.glVertex3d( pos2.x, pos2.y, pos2.z ); // Right Of Triangle (Front)
                }
                // Top right of quad
                if (pos2 != null && pos3 != null && pos4 != null) {
                    setColorForZ(gl, pos4.z);
                    gl.glVertex3d( pos4.x, pos4.y, pos4.z ); // Right Of Triangle (Front)
                    
                    setColorForZ(gl, pos3.z);
                    gl.glVertex3d( pos3.x, pos3.y, pos3.z ); // Top Of Triangle (Front)
                    
                    setColorForZ(gl, pos2.z);
                    gl.glVertex3d( pos2.x, pos2.y, pos2.z ); // Left Of Triangle (Front)
                }
            }
        }

        gl.glEnd();
    }
}
