package cz.fidentis.analyst.symmetry;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.swing.JPanel;
import javax.vecmath.Point3d;


/**
 * Panel for graphing polyline profiles
 *
 * @author Dominik Racek
 */
public class PolylinePanel extends JPanel {
    private static final int PREF_W = 500;
    private static final int PREF_H = 500;
    private static final int BORDER_GAP = 15;
    private static final Color PRIMARY_COLOR = Color.green;
    private static final Color SECONDARY_COLOR = Color.blue;
    private static final Stroke GRAPH_STROKE = new BasicStroke(3f);
    private List<Point3d> primaryPoints;
    private List<Point3d> secondaryPoints;
    private List<Point3d> primaryMirrorPoints;
    private List<Point3d> secondaryMirrorPoints;
    private boolean alignProfiles = false;
    private double primaryOffsetZ = 0;
    private boolean mirrorCuts = false;

    private double scale = Double.POSITIVE_INFINITY;

    /**
     * Comparator for Point3d based on Y value
     *
     * @author Dominik Racek
     */
    public class CompareY implements Comparator<Point3d> {

        /**
         * Compare two Points3d objects
         */
        public int compare(final Point3d a, final Point3d b) {
            if (a.y < b.y) {
                return -1;
            } else if (a.y > b.y) {
                return 1;
            } else {
                return 0;
            }
        }
    }

    /**
     * Constructor for one face
     */
    public PolylinePanel(List<Point3d> values) {
        this.primaryPoints = values;
        Collections.sort(this.primaryPoints, new CompareY());
    }

    /**
     * Constructor for two faces
     */
    public PolylinePanel(List<Point3d> primaryPoints, List<Point3d> secondaryPoints) {
        this.primaryPoints = primaryPoints;
        this.secondaryPoints = secondaryPoints;
        Collections.sort(this.primaryPoints, new CompareY());
        Collections.sort(this.secondaryPoints, new CompareY());
    }

    protected void drawFace(Graphics2D g2, List<Point3d> values, Color faceColor, boolean isPrimary) {
        double minZ = Double.POSITIVE_INFINITY;
        double maxZ = Double.NEGATIVE_INFINITY;
        double minY = Double.POSITIVE_INFINITY;
        double maxY = Double.NEGATIVE_INFINITY;
        double offsetZ = 0;

        for (int i = 0; i < values.size(); i++) {
            if (values.get(i).z < minZ) {
                minZ = values.get(i).z;
            }

            if (values.get(i).z > maxZ) {
                maxZ = values.get(i).z;
            }

            if (values.get(i).y < minY) {
                minY = values.get(i).y;
            }

            if (values.get(i).y > maxY) {
                maxY = values.get(i).y;
            }
        }

        //only calculate scale for the first face and use it for the second as well
        if (scale == Double.POSITIVE_INFINITY) {
            scale = ((double) PREF_H - 2 * BORDER_GAP) / (maxY - minY);
        }

        //Calculate the offsets
        if (mirrorCuts) {
            if (secondaryPoints == null) {
                //Mirror cuts with single face - center the face
                this.primaryOffsetZ = (PREF_W * 0.7) - (maxZ * scale);
                offsetZ = this.primaryOffsetZ;
            } else {
                //Mirror cuts with two faces - separate primary and secondary
                offsetZ = isPrimary ? (PREF_W * 0.4) - (maxZ * scale) : (PREF_W * 0.9) - (maxZ * scale);
            }
        } else {
            //No mirror cuts - center all faces
            if (isPrimary) {
                this.primaryOffsetZ = (PREF_W * 0.7) - (maxZ * scale);
            }
           offsetZ = this.alignProfiles ? (PREF_W * 0.7) - (maxZ * scale) : this.primaryOffsetZ;
        }

        //Draw lines
        g2.setColor(faceColor);
        g2.setStroke(GRAPH_STROKE);
        for (int i = 0; i < values.size() - 1; i++) {
            double z1 = (values.get(i).z) * scale + BORDER_GAP + offsetZ;
            double y1 = (maxY - values.get(i).y) * scale + BORDER_GAP;
            double z2 = (values.get(i + 1).z) * scale + BORDER_GAP + offsetZ;
            double y2 = (maxY - values.get(i + 1).y) * scale + BORDER_GAP;

            g2.draw(new Line2D.Double(z1, y1, z2, y2));
        }
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D)g;


        if (mirrorCuts) {
            drawFace(g2, primaryPoints, PRIMARY_COLOR, true);
            drawFace(g2, primaryMirrorPoints, SECONDARY_COLOR, true);

            if (secondaryPoints != null) {
                drawFace(g2, secondaryPoints, PRIMARY_COLOR, false);
                drawFace(g2, secondaryMirrorPoints, SECONDARY_COLOR, false);
            }
        } else {
            drawFace(g2, primaryPoints, PRIMARY_COLOR, true);

            if (secondaryPoints != null) {
                drawFace(g2, secondaryPoints, SECONDARY_COLOR, false);
            }
        }
    }

    /**
     * Sets primary points and draws them in the panel
     *
     * @param points primary points
     */
    public void setPrimaryPoints(List<Point3d> points) {
        this.primaryPoints = points;
        Collections.sort(this.primaryPoints, new CompareY());
        repaint();
    }

    /**
     * Sets primary mirror points.
     * Only draws them if mirrorCuts is checked
     *
     * @param points primary mirror points
     */
    public void setPrimaryMirrorPoints(List<Point3d> points) {
        this.primaryMirrorPoints = points;
        Collections.sort(this.primaryMirrorPoints, new CompareY());
        repaint();
    }

    /**
     * Sets secondary points and draws them in the panel
     *
     * @param points secondary points
     */
    public void setSecondaryPoints(List<Point3d> points) {
        this.secondaryPoints = points;
        Collections.sort(this.secondaryPoints, new CompareY());
        repaint();
    }

    /**
     * Sets secondary mirror points.
     * Only draws them if mirrorCuts is checked
     *
     * @param points secondary mirror points
     */
    public void setSecondaryMirrorPoints(List<Point3d> points) {
        this.secondaryMirrorPoints = points;
        Collections.sort(this.secondaryMirrorPoints, new CompareY());
        repaint();
    }

    /**
     * Aligns the drawn faces in the panel
     *
     * @param align
     */
    public void setAlignProfiles(boolean align) {
        this.alignProfiles = align;
        repaint();
    }

    /**
     * Displays the mirror cuts
     *
     * @param mirror
     */
    public void setMirrorCuts(boolean mirror) {
        this.mirrorCuts = mirror;
        repaint();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(PREF_W, PREF_H);
    }

}