/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cnc.gcode.controller;

import cnc.gcode.controller.CNCCommand.Type;
import de.unikassel.ann.util.ColorHelper;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;

/**
 *
 * @author patrick
 */
public class PrintableLayers {

    private abstract class IElement {
        
        private final int index;

        public IElement(int index) 
        {
            this.index = index;
        }

        public abstract void paint(Graphics2D g2);

        public abstract boolean isInRange(Point2D p, double size);

        public Integer getIndex() 
        {
            return index;
        }
    }
    
    private class Point extends IElement 
    {

        double[] point;
        Type type;

        public Point(int index, double[] point, Type type) 
        {
            super(index);
            this.point  = point;
            this.type   = type;
        }

        @Override
        public void paint(Graphics2D g2) 
        {
            double ts = DatabaseV2.TOOLSIZE.getsaved();
            type.setupGraphicsOptions(g2, selected(getIndex()));
            g2.fill(new Ellipse2D.Double(point[0] - ts / 2,
                                        point[1] - ts / 2,
                                        ts,
                                        ts));
        }

        @Override
        public boolean isInRange(Point2D p, double size) {
            return Geometrics.pointInRectangle(p , new Rectangle2D.Double(point[0],
                                                                          point[1],
                                                                          0.0,
                                                                          0.0), size);
        }

    }

    private class Line extends IElement {

        Line2D.Double line;
        Type type;

        public Line(int index, double[] start, double[] end, Type type) 
        {
            super(index);
            line = new Line2D.Double(start[0], start[1], end[0], end[1]);
            this.type = type;
        }

        @Override
        public void paint(Graphics2D g2) 
        {
            type.setupGraphicsOptions(g2, selected(getIndex()));
            g2.draw(line);
        }

        @Override
        public boolean isInRange(Point2D p, double size) 
        {
            return line.ptSegDist(p)<size/2;
        }

    }

    private class XYZ extends IElement {
        Line2D.Double line;
        double z;

        public XYZ(int index, CNCCommand.Move m) 
        {
            super(index);
            if(m.isXyz()){
                z=(m.getStart()[2]+m.getEnd()[2])/2;
            }
            else
            {
                z=m.getA()/m.getDistanceXY();
            }
            line = new Line2D.Double(m.getStart()[0], m.getStart()[1], m.getEnd()[0],  m.getEnd()[1]);
        }

        @Override
        public void paint(Graphics2D g2) 
        {
            g2.setColor(Color.red);
            CNCCommand.Type.setupGraphicsOptions(g2, selected(getIndex()),ColorHelper.numberToColorPercentage(Tools.adjustDouble( (z-zmin)/(zmax-zmin)  , 0, 1)));
            g2.draw(line);
        }

        @Override
        public boolean isInRange(Point2D p, double size) 
        {
            return line.ptSegDist(p)<size/2;
        }

    }
    
    
    private HashMap<Double, LinkedList<IElement>> layers = new HashMap<>();
    private Double[] keys = new Double[0];
    private HashSet<Integer> sIndex = new HashSet<>();
    private int sMin =-1,sMax=-1;
    boolean isBlocked = false;
    private double zmin=Double.MAX_VALUE;
    private double zmax=-Double.MAX_VALUE;

    public void processMoves(int index, CNCCommand.Move[] moves) {
        for (CNCCommand.Move move : moves) 
        {
            //Calc Layer
            double sz = move.getStart()[2];
            double ez = move.getEnd()[2];
            if(Double.isNaN(sz) || Double.isNaN(ez))
            {
                return;
            }

            if(!Double.isNaN(move.getDistance()) && (move.isXyz() || (!Double.isNaN(move.getA()) && move.getA()!=0))){
                if(!layers.containsKey(Double.NaN)){
                    layers.put(Double.NaN, new LinkedList<>());
                }
                if(move.isXyz())
                {
                    //XYZ Move!
                    zmin=Math.min(Math.min(sz, ez),zmin);
                    zmax=Math.max(Math.max(sz, ez),zmax);
                    layers.get(Double.NaN).add(new XYZ(index, move));
                }
                else
                {
                    //A Move!
                    zmin=Math.min(move.getA()/move.getDistanceXY() ,zmin);
                    zmax=Math.max(move.getA()/move.getDistanceXY() ,zmax);
                    layers.get(Double.NaN).add(new XYZ(index, move));                    
                }
            }
            else{
                //Add layer if new
                if (!layers.containsKey(sz)) 
                {
                    layers.put(sz, new LinkedList<>());
                }
                if (!layers.containsKey(ez)) 
                {
                    layers.put(ez, new LinkedList<>());
                }
                if (sz == ez) 
                {
                    //Line
                    for(int i = 0;i < 2;i++)
                    {
                        if(Double.isNaN(move.getStart()[i]) || Double.isNaN(move.getEnd()[i]))
                        {
                            return;
                        }
                    }
                    layers.get(sz).add(new Line(index, Arrays.copyOfRange(move.getStart(), 0, 2), Arrays.copyOfRange(move.getEnd(), 0, 2), move.getType()));
                } else {
                    //Two Points
                    if(!Double.isNaN(move.getStart()[0]) && !Double.isNaN(move.getStart()[1]))
                    {
                        layers.get(sz).add(new Point(index, Arrays.copyOfRange(move.getStart(), 0, 2), move.getType()));
                    }
                    if(!Double.isNaN(move.getEnd()[0]) && !Double.isNaN(move.getEnd()[1]))
                    {
                        layers.get(ez).add(new Point(index, Arrays.copyOfRange(move.getEnd(), 0, 2), move.getType()));
                    }
                }
            }
        }
    }

    public void paint(Graphics2D g2, int index) {
        if (index < 0 || index >= keys.length) 
        {
            return;
        }

        layers.get(keys[index]).stream().forEach((p) -> {
            p.paint(g2);
        });
    }

    public String[] getLayers(double offset) {
        keys = layers.keySet().toArray(new Double[0]);
        Arrays.sort(keys);
        String[] slayers = new String[keys.length];
        for (int i = 0; i < keys.length; i++) 
        {
            if(Double.isNaN(keys[i])){
               slayers[i] = "XYZ/A Moves"; 
            }
            else{
                slayers[i] = Tools.dtostr(keys[i]+offset);
            }
        }
        return slayers;
    }

    public void blocknextIndexSet(){
        isBlocked = true;
    }
    
    public void setSelectedIndexs(int[] selectedIndices) {
        if(isBlocked)
        {
            isBlocked = false;
            return;
        }
        HashSet<Integer> hashmap = new HashSet<>(selectedIndices.length);
        for (int i : selectedIndices) 
        {
            hashmap.add(i);
        }
        this.sIndex = hashmap;
        
    }
    
    public void setSelectedRange(int start, int stop)
    {
        if(isBlocked)
        {
            isBlocked = false;
            return;
        }
        this.sIndex = null;
        this.sMin   = start;
        this.sMax   = stop;
    }

    private boolean selected(int i)
    {
        final HashSet<Integer> hs = this.sIndex;
        if(hs != null)
        {
            return hs.contains(i);
        }
        return i <= sMax && i >= sMin;
    }
    
    public int[] getIndexes(Point2D p, int index) {
        if (index < 0 || index >= keys.length) 
        {
            return new int[0];
        }
        LinkedList<Integer> list = new LinkedList<>();
        double size = DatabaseV2.TOOLSIZE.getsaved() * 2;
        layers.get(keys[index]).stream().filter((e) -> (e.isInRange(p, size))).forEach((e) -> {
            list.add(e.getIndex());
        });
                
        int[] r = new int[list.size()];
        int i = 0;
        for (int ri : list) 
        {
            r[i++] = ri;
        }
        return r;
    }
    
    
    public boolean paintlegend(Graphics2D g2, int index, int jpw, int jph){
        if (index < 0 || index >= keys.length || !Double.isNaN(keys[index])) 
        {
            return false;
        }
        
        jpw=jpw-100;
        Font font = new Font(Font.SANS_SERIF, Font.PLAIN, 16);
        g2.setFont(font);
        int zh      = (int)(font.getStringBounds(Tools.dtostr(100.0), g2.getFontRenderContext()).getHeight()) + 10;                    
        int elements= jph / zh;
        int dy      = (jph-elements * zh) / 2;
        for(int i = 0;i < elements && elements >= 2;i++)
        {
            double z = zmax - i * ((zmax-zmin) / (elements - 1));
            double relative = (z - zmin) / (zmax-zmin);
            relative = Tools.adjustDouble(relative, 0, 1);

            Color c = ColorHelper.numberToColorPercentage(relative);
            g2.setColor(c);
            g2.fillRect(jpw + 5,
                        dy + zh * i,
                        90,
                        zh - 4);
            g2.setColor(((299 * c.getRed() + 587 * c.getGreen() + 114 * c.getBlue())> 128000) ? Color.black:Color.white);
            g2.drawString(Tools.dtostr(z), jpw + 10, dy + zh * i + zh - 10);
            g2.setColor(Color.black);
            g2.drawRect(jpw + 5,
                        dy + zh * i,
                        90,
                        zh - 4);
        }    
        g2.setClip(0,0,jpw,jph);
        
        return true;
    }
}