/**
 *  Path.java
 *
Copyright (c) 2018, Innovatics Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and / or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.pdfjet;

import java.util.*;


/**
 *  Used to create path objects.
 *  The path objects may consist of lines, splines or both.
 *
 *  Please see Example_02.
 */
public class Path implements Drawable {

    private int color = Color.black;
    private float width = 0.3f;
    private String pattern = "[] 0";
    private boolean fill_shape = false;
    private boolean close_path = false;

    private List<Point> points = null;

    private float box_x;
    private float box_y;

    private int lineCapStyle = 0;
    private int lineJoinStyle = 0;


    /**
     *  The default constructor.
     *
     *
     */
    public Path() {
        points = new ArrayList<Point>();
    }


    /**
     *  Adds a point to this path.
     *
     *  @param point the point to add.
     */
    public void add(Point point) {
        points.add(point);
    }


    /**
     *  Sets the line dash pattern for this path.
     *
     *  The line dash pattern controls the pattern of dashes and gaps used to stroke paths.
     *  It is specified by a dash array and a dash phase.
     *  The elements of the dash array are positive numbers that specify the lengths of
     *  alternating dashes and gaps.
     *  The dash phase specifies the distance into the dash pattern at which to start the dash.
     *  The elements of both the dash array and the dash phase are expressed in user space units.
     *  <pre>
     *  Examples of line dash patterns:
     *
     *      "[Array] Phase"     Appearance          Description
     *      _______________     _________________   ____________________________________
     *
     *      "[] 0"              -----------------   Solid line
     *      "[3] 0"             ---   ---   ---     3 units on, 3 units off, ...
     *      "[2] 1"             -  --  --  --  --   1 on, 2 off, 2 on, 2 off, ...
     *      "[2 1] 0"           -- -- -- -- -- --   2 on, 1 off, 2 on, 1 off, ...
     *      "[3 5] 6"             ---     ---       2 off, 3 on, 5 off, 3 on, 5 off, ...
     *      "[2 3] 11"          -   --   --   --    1 on, 3 off, 2 on, 3 off, 2 on, ...
     *  </pre>
     *
     *  @param pattern the line dash pattern.
     */
    public void setPattern(String pattern) {
        this.pattern = pattern;
    }


    /**
     *  Sets the pen width that will be used to draw the lines and splines that are part of this path.
     *
     *  @param width the pen width.
     */
    public void setWidth(double width) {
        this.width = (float) width;
    }


    /**
     *  Sets the pen width that will be used to draw the lines and splines that are part of this path.
     *
     *  @param width the pen width.
     */
    public void setWidth(float width) {
        this.width = width;
    }


    /**
     *  Sets the pen color that will be used to draw this path.
     *
     *  @param color the color is specified as an integer.
     */
    public void setColor(int color) {
        this.color = color;
    }


    /**
     *  Sets the close_path variable.
     *
     *  @param close_path if close_path is true a line will be draw between the first and last point of this path.
     */
    public void setClosePath(boolean close_path) {
        this.close_path = close_path;
    }


    /**
     *  Sets the fill_shape private variable. If fill_shape is true - the shape of the path will be filled with the current brush color.
     *
     *  @param fill_shape the fill_shape flag.
     */
    public void setFillShape(boolean fill_shape) {
        this.fill_shape = fill_shape;
    }


    /**
     *  Sets the line cap style.
     *
     *  @param style the cap style of this path. Supported values: Cap.BUTT, Cap.ROUND and Cap.PROJECTING_SQUARE
     */
    public void setLineCapStyle(int style) {
        this.lineCapStyle = style;
    }


    /**
     *  Returns the line cap style for this path.
     *
     *  @return the line cap style for this path.
     */
    public int getLineCapStyle() {
        return this.lineCapStyle;
    }


    /**
     *  Sets the line join style.
     *
     *  @param style the line join style code. Supported values: Join.MITER, Join.ROUND and Join.BEVEL
     */
    public void setLineJoinStyle(int style) {
        this.lineJoinStyle = style;
    }


    /**
     *  Returns the line join style.
     *
     *  @return the line join style.
     */
    public int getLineJoinStyle() {
        return this.lineJoinStyle;
    }


    /**
     *  Places this path in the specified box at position (0.0, 0.0).
     *
     *  @param box the specified box.
     */
    public void placeIn(Box box) throws Exception {
        placeIn(box, 0.0f, 0.0f);
    }


    /**
     *  Places the path inside the spacified box at coordinates (x_offset, y_offset) of the top left corner.
     *
     *  @param box the specified box.
     *  @param x_offset the x_offset.
     *  @param y_offset the y_offset.
     */
    public void placeIn(
            Box box,
            double x_offset,
            double y_offset) throws Exception {
    	placeIn(box, (float) x_offset, (float) y_offset);
    }


    /**
     *  Places the path inside the spacified box at coordinates (x_offset, y_offset) of the top left corner.
     *
     *  @param box the specified box.
     *  @param x_offset the x_offset.
     *  @param y_offset the y_offset.
     */
    public void placeIn(
            Box box,
            float x_offset,
            float y_offset) throws Exception {
        box_x = box.x + x_offset;
        box_y = box.y + y_offset;
    }


    public void setLocation(float x, float y) {
        box_x += x;
        box_y += y;
    }


    /**
     *  Scales the path using the specified factor.
     *
     *  @param factor the specified factor.
     */
    public void scaleBy(double factor) throws Exception {
        scaleBy((float) factor);
    }


    /**
     *  Scales the path using the specified factor.
     *
     *  @param factor the specified factor.
     */
    public void scaleBy(float factor) throws Exception {
        for (int i = 0; i < points.size(); i++) {
            Point point = points.get(i);
            point.x *= factor;
            point.y *= factor;
        }
    }


    /**
     * Returns a list containing the start point, first control point, second control point and the end point of elliptical curve segment.
     * Please see Example_18.
     *
     * @param x the x coordinate of the center of the ellipse.
     * @param y the y coordinate of the center of the ellipse.
     * @param r1 the horizontal radius of the ellipse.
     * @param r2 the vertical radius of the ellipse.
     * @param segment the segment to draw - please see the Segment class.
     * @return a list of the curve points.
     * @throws Exception
     */
    public static List<Point> getCurvePoints(
            float x,
            float y,
            float r1,
            float r2,
            int segment) throws Exception {
        // The best 4-spline magic number
        float m4 = 0.551784f;
        List<Point> list = new ArrayList<Point>();

        if (segment == 0) {
            list.add(new Point(x, y - r2));
            list.add(new Point(x + m4*r1, y - r2, Point.CONTROL_POINT));
            list.add(new Point(x + r1, y - m4*r2, Point.CONTROL_POINT));
            list.add(new Point(x + r1, y));
        }
        else if (segment == 1) {
            list.add(new Point(x + r1, y));
            list.add(new Point(x + r1, y + m4*r2, Point.CONTROL_POINT));
            list.add(new Point(x + m4*r1, y + r2, Point.CONTROL_POINT));
            list.add(new Point(x, y + r2));
        }
        else if (segment == 2) {
            list.add(new Point(x, y + r2));
            list.add(new Point(x - m4*r1, y + r2, Point.CONTROL_POINT));
            list.add(new Point(x - r1, y + m4*r2, Point.CONTROL_POINT));
            list.add(new Point(x - r1, y));
        }
        else if (segment == 3) {
            list.add(new Point(x - r1, y));
            list.add(new Point(x - r1, y - m4*r2, Point.CONTROL_POINT));
            list.add(new Point(x - m4*r1, y - r2, Point.CONTROL_POINT));
            list.add(new Point(x, y - r2));
        }

        return list;
    }


    /**
     *  Draws this path on the page using the current selected color, pen width, line pattern and line join style.
     *
     *  @param page the page to draw this path on.
     *  @return x and y coordinates of the bottom right corner of this component.
     *  @throws Exception
     */
    public float[] drawOn(Page page) throws Exception {
        if (fill_shape) {
            page.setBrushColor(color);
        }
        else {
            page.setPenColor(color);
        }
        page.setPenWidth(width);
        page.setLinePattern(pattern);
        page.setLineCapStyle(lineCapStyle);
        page.setLineJoinStyle(lineJoinStyle);

        for (int i = 0; i < points.size(); i++) {
            Point point = points.get(i);
            point.x += box_x;
            point.y += box_y;
        }

        if (fill_shape) {
            page.drawPath(points, 'f');
        }
        else {
            if (close_path) {
                page.drawPath(points, 's');
            }
            else {
                page.drawPath(points, 'S');
            }
        }

        float x_max = 0f;
        float y_max = 0f;
        for (int i = 0; i < points.size(); i++) {
            Point point = points.get(i);
            if (point.x > x_max) { x_max = point.x; }
            if (point.y > y_max) { y_max = point.y; }
            point.x -= box_x;
            point.y -= box_y;
        }

        return new float[] {x_max, y_max};
    }

}   // End of Path.java
