// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.pdfimport.pdfbox;

import static org.openstreetmap.josm.tools.I18n.tr;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.openstreetmap.josm.gui.progress.ProgressMonitor;
import org.openstreetmap.josm.plugins.pdfimport.LayerInfo;
import org.openstreetmap.josm.plugins.pdfimport.PathOptimizer;
import org.openstreetmap.josm.plugins.pdfimport.PdfPath;

public class GraphicsProcessor {

    public PathOptimizer target;
    private Shape clipShape;
    private List<PdfPath> clipPath;
    private final LayerInfo info = new LayerInfo();
    int pathNo = 0;
    private boolean complexClipShape;
    private boolean clipAreaDrawn;

    private final AffineTransform transform;
    private final ProgressMonitor monitor;
    private final int maxPaths;

    public GraphicsProcessor(PathOptimizer target, int rotation, int maxPaths, ProgressMonitor monitor) {
        this.maxPaths = maxPaths;
        this.target = target;
        this.transform = new AffineTransform();
        this.transform.rotate(-Math.toRadians(rotation));
        this.info.stroke = Color.BLACK;
        this.info.fill = Color.BLACK;
        this.monitor = monitor;
    }

    private void addPath(Shape s, boolean closed) {
        pathNo++;

        if (pathNo % 100 == 0) {
            this.monitor.setCustomText(tr(" {0} objects so far", pathNo));
        }

        if (pathNo >= maxPaths) {
            return;
        }

        List<PdfPath> paths = this.parsePath(s, closed);

        for (PdfPath p: paths) {
            p.nr = pathNo;
        }


        if (paths.size() > 1) {
            this.target.addMultiPath(this.info, paths);
            this.parsePath(s, closed);
        } else if (paths.size() == 1) {
            this.target.addPath(this.info, paths.get(0));
        }
    }

    private List<PdfPath> parsePath(Shape s, boolean closed) {
        List<PdfPath> result = new ArrayList<>(2);
        List<Point2D> points = new ArrayList<>(2);

        PathIterator iter = s.getPathIterator(null);
        double[] coords = new double[6];

        while (!iter.isDone()) {
            int type = iter.currentSegment(coords);

            if (type == PathIterator.SEG_CLOSE) {
                //close polygon
                this.addPoint(points, points.get(0));
                if (points.size() > 1) {
                    result.add(new PdfPath(points));
                }
                points = new ArrayList<>(2);
            } else if (type == PathIterator.SEG_CUBICTO) {
                //cubic curve
                this.addPoint(points, this.parsePoint(coords, 4));
            } else if (type == PathIterator.SEG_LINETO) {
                this.addPoint(points, this.parsePoint(coords, 0));
            } else if (type == PathIterator.SEG_MOVETO) {
                //new path
                if (points.size() > 1) {
                    result.add(new PdfPath(points));
                }
                points = new ArrayList<>(2);
                this.addPoint(points, this.parsePoint(coords, 0));
            } else if (type == PathIterator.SEG_QUADTO) {
                //quadratic curve
                this.addPoint(points, this.parsePoint(coords, 2));
            } else if (type == PathIterator.WIND_EVEN_ODD) {
                //fill even odd
            } else if (type == PathIterator.WIND_NON_ZERO) {
                //fill all
            }

            iter.next();
        }

        if (points.size() > 1) {
            if (closed) {
                this.addPoint(points, points.get(0));
            }

            result.add(new PdfPath(points));
        }

        return result;
    }

    private void addPoint(List<Point2D> points, Point2D point) {
        if (points.size() > 0) {
            Point2D prevPoint = points.get(points.size() - 1);

            if (prevPoint.getX() == point.getX() && prevPoint.getY() == point.getY()) {
                return;
            }
        }

        points.add(point);
    }

    private Point2D parsePoint(double[] buffer, int offset) {
        //invert Y.
        Point2D point = new Point2D.Double(buffer[offset], buffer[offset + 1]);
        Point2D dest = new Point2D.Double();
        this.transform.transform(point, dest);
        return this.target.getUniquePoint(dest);
    }

    public void drawPath(Shape path, Color stroke, Color fill,
            int windingRule) {

        if (complexClipShape) {
            if (!this.clipAreaDrawn) {
                this.info.stroke = null;
                this.info.fill = Color.CYAN;
                this.addPath(this.clipShape, true);
                this.clipAreaDrawn = true;
            }
        }

        if (!complexClipShape || fill != null) {
            this.info.stroke = stroke;
            this.info.fill = fill;
            this.addPath(path, fill != null);
        }
    }

    public void drawImage() {

        if (!this.clipAreaDrawn) {
            this.info.stroke = null;
            this.info.fill = Color.CYAN;
            this.addPath(this.clipShape, true);
            this.clipAreaDrawn = true;
        }
    }

    public void setClip(Shape clip) {
        if (this.shapesEqual(this.clipShape, clip))
            return;

        this.clipPath = this.parsePath(clip, true);

        boolean complexClipPath = false;

        if (clipPath.size() > 1) {
            complexClipPath = true;
        } else if (clipPath.size() == 1 && clipPath.get(0).points.size() > 5) {
            //more than 4 points.
            complexClipPath = true;
        }

        this.complexClipShape = complexClipPath;
        this.clipAreaDrawn = false;
        this.clipShape = clip;
    }

    private boolean shapesEqual(Shape shape1, Shape shape2) {

        if (shape1 == null || shape2 == null) {
            return false;
        }

        return shape1.getBounds2D().equals(shape2.getBounds2D());
    }

    public void setStroke(Stroke s) {
        BasicStroke stroke = (BasicStroke) s;
        this.info.width = stroke.getLineWidth();
        this.info.dash = 0;

        if (stroke.getDashArray() != null) {
            this.info.dash = Arrays.hashCode(stroke.getDashArray());
        }
    }

    public void drawString(float x, float y, String character, Color color) {
        // TODO Auto-generated method stub
    }
}
