/**
 * Copyright 2004-2008 Ricard Marxer  <email@ricardmarxer.com>
 *
    This file is part of Geomerative.
 *
 * Geomerative 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.
 *
 * Geomerative 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
 * Geomerative. If not, see <http://www.gnu.org/licenses/>.
 */
package geomerative;

import processing.core.*;

/**
 * RPolygon is a reduced interface for creating, holding and drawing complex
 * polygons. Polygons are groups of one or more contours (RContour). This
 * interface allows us to perform binary operations (difference, xor, union and
 * intersection) on polygons.
 *
 * @eexample RPolygon
 * @usage Geometry
 * @related RContour
 * @related createCircle ( )
 * @related createRing ( )
 * @related createStar ( )
 * @related diff ( )
 * @related xor ( )
 * @related union ( )
 * @related intersection ( )
 * @extended
 */
public class RPolygon extends RGeomElem {

    /**
     * @invisible
     */
    public int type = RGeomElem.POLYGON;
    public static int defaultDetail = 50;

    /**
     * Array of RContour objects holding the contours of the polygon.
     *
     * @eexample contours
     * @related RContour
     * @related countContours ( )
     * @related addContour ( )
     */
    public RContour[] contours;
    int currentContour = 0;

    // ----------------------
    // --- Public Methods ---
    // ----------------------
    /**
     * Make a copy of the given polygon.
     *
     * @eexample createPolygon
     * @param p the object of which to make a copy
     */
    public RPolygon(RPolygon p) {
        if (p == null) {
            return;
        }

        for (int i = 0; i < p.countContours(); i++) {
            this.append(new RContour(p.contours[i]));
        }
        type = RGeomElem.POLYGON;

        setStyle(p);
    }

    /**
     * Create a new polygon given an array of points.
     *
     * @eexample createPolygon
     * @param points the points for the new polygon.
     */
    public RPolygon(RPoint[] points) {
        this(new RContour(points));
    }

    /**
     * Create a new polygon given a contour.
     *
     * @param newcontour the contour for the new polygon.
     */
    public RPolygon(RContour newcontour) {
        this.append(newcontour);
        type = RGeomElem.POLYGON;
    }

    /**
     * Create an empty polygon.
     */
    public RPolygon() {
        contours = null;
        type = RGeomElem.POLYGON;
    }

    /**
     * Use this method to create a new circle polygon.
     *
     * @param x
     * @param y
     * @eexample createCircle
     * @param radius the radius of the circle
     * @param detail the number of vertices of the polygon
     * @return RPolygon, the circular polygon newly created
     */
    static public RPolygon createCircle(float x, float y, float radius, int detail) {
        RPoint[] points = new RPoint[detail];
        double radiansPerStep = 2 * Math.PI / detail;
        for (int i = 0; i < detail; i++) {
            points[i] = new RPoint(
                    radius * Math.cos(i * radiansPerStep) + x,
                    radius * Math.sin(i * radiansPerStep) + y
            );
        }
        return new RPolygon(points);
    }

    static public RPolygon createCircle(float radius, int detail) {
        return createCircle(0, 0, radius, detail);
    }

    static public RPolygon createCircle(float x, float y, float radius) {
        return createCircle(x, y, radius, defaultDetail);
    }

    static public RPolygon createCircle(float radius) {
        return createCircle(0, 0, radius, defaultDetail);
    }

    /**
     * Use this method to create a new rectangle polygon.
     *
     * @eexample createRectangle
     * @param x the upper-left corner x coordinate
     * @param y the upper-left corner y coordinate
     * @param w the width
     * @param h the height
     * @return RPolygon, the circular polygon newly created
     */
    static public RPolygon createRectangle(float x, float y, float w, float h) {
        RPolygon rectangle = new RPolygon();
        rectangle.addPoint(x, y);
        rectangle.addPoint(x + w, y);
        rectangle.addPoint(x + w, y + h);
        rectangle.addPoint(x, y + h);
        rectangle.addPoint(x, y);
        return rectangle;
    }

    static public RPolygon createRectangle(float w, float h) {
        return createRectangle(0, 0, w, h);
    }

    /**
     * Use this method to create a new starform polygon.
     *
     * @param x
     * @param y
     * @eexample createStar
     * @param radiusBig the outter radius of the star polygon
     * @param radiusSmall the inner radius of the star polygon
     * @param spikes the amount of spikes on the star polygon
     * @return RPolygon, the starform polygon newly created
     */
    static public RPolygon createStar(float x, float y, float radiusBig, float radiusSmall, int spikes) {
        int numPoints = spikes * 2;
        RPoint[] points = new RPoint[numPoints];
        double radiansPerStep = Math.PI / spikes;
        for (int i = 0; i < numPoints; i += 2) {
            points[i] = new RPoint(
                    radiusBig * Math.cos(i * radiansPerStep) + x,
                    radiusBig * Math.sin(i * radiansPerStep) + y
            );
            points[i + 1] = new RPoint(
                    radiusSmall * Math.cos(i * radiansPerStep) + x,
                    radiusSmall * Math.sin(i * radiansPerStep) + y
            );
        }
        return new RPolygon(points);
    }

    static public RPolygon createStar(float radiusBig, float radiusSmall, int spikes) {
        return createStar(0, 0, radiusBig, radiusSmall, spikes);
    }

    /**
     * Use this method to create a new ring polygon.
     *
     * @param x
     * @param y
     * @eexample createRing
     * @param radiusBig the outter radius of the ring polygon
     * @param radiusSmall the inner radius of the ring polygon
     * @param detail the number of vertices on each contour of the ring
     * @return RPolygon, the ring polygon newly created
     */
    static public RPolygon createRing(float x, float y, float radiusBig, float radiusSmall, int detail) {
        RPoint[] inner = new RPoint[detail];
        RPoint[] outer = new RPoint[detail];
        double radiansPerStep = 2 * Math.PI / detail;
        for (int i = 0; i < detail; i++) {
            inner[i] = new RPoint(
                    radiusSmall * Math.cos(i * radiansPerStep) + x,
                    radiusSmall * Math.sin(i * radiansPerStep) + y
            );
            outer[i] = new RPoint(
                    radiusBig * Math.cos(i * radiansPerStep) + x,
                    radiusBig * Math.sin(i * radiansPerStep) + y
            );
        }
        RPolygon ring = new RPolygon();
        ring.addContour(outer);
        ring.addContour(inner);
        return ring;
    }

    static public RPolygon createRing(float radiusBig, float radiusSmall, int detail) {
        return createRing(0, 0, radiusBig, radiusSmall, detail);
    }

    static public RPolygon createRing(float x, float y, float radiusBig, float radiusSmall) {
        return createRing(x, y, radiusBig, radiusSmall, defaultDetail);
    }

    static public RPolygon createRing(float radiusBig, float radiusSmall) {
        return createRing(0, 0, radiusBig, radiusSmall, defaultDetail);
    }

    /**
     * Use this method to get the centroid of the element.
     *
     * @eexample RGroup_getCentroid
     * @return RPo the centroid point of the element
     * @related getBounds ( )
     * @related getCenter ( )
     */
    @Override
    public RPoint getCentroid() {
        RPoint bestCentroid = new RPoint();
        float bestArea = Float.NEGATIVE_INFINITY;
        if (contours != null) {
            for (RContour contour : contours) {
                float area = Math.abs(contour.getArea());
                if (area > bestArea) {
                    bestArea = area;
                    bestCentroid = contour.getCentroid();
                }
            }
            return bestCentroid;
        }
        return null;
    }

    /**
     * Use this method to count the number of contours in the polygon.
     *
     * @eexample countContours
     * @return int the number contours in the polygon
     * @related addContour ( )
     */
    public int countContours() {
        if (this.contours == null) {
            return 0;
        }

        return this.contours.length;
    }

    /**
     * Add a new contour to the polygon.
     *
     * @eexample addContour
     * @param c the contour to be added
     * @related addPoint ( )
     */
    public void addContour(RContour c) {
        this.append(c);
    }

    /**
     * Add an empty contour to the polygon.
     *
     * @eexample addContour
     * @related addPoint ( )
     */
    public void addContour() {
        this.append(new RContour());
    }

    /**
     * Add a new contour to the polygon given an array of points.
     *
     * @eexample addContour
     * @param points the points of the new contour to be added
     * @related addPoint ( )
     */
    public void addContour(RPoint[] points) {
        this.append(new RContour(points));
    }

    /**
     * Use this method to set the current contour to which append points.
     *
     * @param indContour
     * @eexample addContour
     * @related addPoint ( )
     */
    public void setContour(int indContour) {
        this.currentContour = indContour;
    }

    /**
     * Add a new point to the current contour.
     *
     * @eexample addPoint
     * @param p the point to be added
     * @related addContour ( )
     * @related setCurrent ( )
     */
    public void addPoint(RPoint p) {
        if (contours == null) {
            this.append(new RContour());
        }
        this.contours[currentContour].append(p);
    }

    /**
     * Add a new point to the current contour.
     *
     * @eexample addPoint
     * @param x the x coordinate of the point to be added
     * @param y the y coordinate of the point to be added
     * @related addContour ( )
     * @related setCurrent ( )
     */
    public void addPoint(float x, float y) {
        if (contours == null) {
            this.append(new RContour());
        }
        this.contours[currentContour].append(new RPoint(x, y));
    }

    /**
     * Add a new point to the selected contour.
     *
     * @eexample addPoint
     * @param indContour the index of the contour to which the point will be
     * added
     * @param p the point to be added
     * @related addContour ( )
     * @related setCurrent ( )
     */
    public void addPoint(int indContour, RPoint p) {
        if (contours == null) {
            this.append(new RContour());
        }
        this.contours[indContour].append(p);
    }

    /**
     * Add a new point to the selected contour.
     *
     * @eexample addPoint
     * @param indContour the index of the contour to which the point will be
     * added
     * @param x the x coordinate of the point to be added
     * @param y the y coordinate of the point to be added
     * @related addContour ( )
     * @related setCurrent ( )
     */
    public void addPoint(int indContour, float x, float y) {
        if (contours == null) {
            this.append(new RContour());
        }
        this.contours[indContour].append(new RPoint(x, y));
    }

    public void addClose() {
        if (contours == null) {
            return;
        }

        contours[contours.length - 1].addClose();
    }

    /**
     * Use this method to create a new mesh from a given polygon.
     *
     * @eexample toMesh
     * @return RMesh, the mesh made of tristrips resulting of a tesselation of
     * the polygon
     * @related draw ( )
     */
    @Override
    public RMesh toMesh() {
        if (contours == null) {
            return new RMesh();
        }

        RMesh mesh = RClip.polygonToMesh(this);
        if (mesh == null) {
            return null;
        }

        mesh.setStyle(this);
        return mesh;
    }

    @Override
    public void print() {
        System.out.println("polygon: ");
        for (int i = 0; i < countContours(); i++) {
            System.out.println("---  contour " + i + " ---");
            contours[i].print();
            System.out.println("---------------");
        }
    }

    /**
     * Removes contours with less than 3 points. These are contours that are
     * open. Since close polygons have points[0] == points[-1] and two more
     * points to form a triangle at least. This is useful to avoid the clipping
     * algorithm from breaking.
     *
     * @return 
     * @invisible
     */
    protected RPolygon removeOpenContours() {
        RPolygon clean = new RPolygon();
        for (int i = 0; i < countContours(); i++) {
            if (contours[i].countPoints() > 3) {
                clean.addContour(contours[i]);
            }
        }
        clean.setStyle(this);
        return clean;
    }

    /**
     * @return 
     * @invisible
     */
    @Override
    public RPolygon toPolygon() {
        return new RPolygon(this);
    }

    /**
     * @return 
     * @invisible
     */
    @Override
    public RShape toShape() {
        int numContours = countContours();

        RShape result = new RShape();
        for (int i = 0; i < numContours; i++) {
            RPoint[] newpoints = this.contours[i].getHandles();

            if (newpoints != null) {
                result.addMoveTo(newpoints[0]);

                for (int j = 1; j < newpoints.length; j++) {
                    result.addLineTo(newpoints[j]);
                }

                if (contours[i].closed) {
                    result.addClose();
                }

                result.paths[i].setStyle(contours[i]);
            }
        }

        result.setStyle(this);
        return result;
    }

    /**
     * Use this to return the points of the polygon. It returns the points in
     * the way of an array of RPoint.
     *
     * @eexample RPolygon_getHandles
     * @return RPoint[], the points returned in an array.
   *
     */
    @Override
    public RPoint[] getHandles() {
        int numContours = countContours();
        if (numContours == 0) {
            return null;
        }

        RPoint[] result = null;
        RPoint[] newresult = null;
        for (int i = 0; i < numContours; i++) {
            RPoint[] newPoints = contours[i].getHandles();
            if (newPoints != null) {
                if (result == null) {
                    result = new RPoint[newPoints.length];
                    System.arraycopy(newPoints, 0, result, 0, newPoints.length);
                } else {
                    newresult = new RPoint[result.length + newPoints.length];
                    System.arraycopy(result, 0, newresult, 0, result.length);
                    System.arraycopy(newPoints, 0, newresult, result.length, newPoints.length);
                    result = newresult;
                }
            }
        }
        return result;
    }

    /**
     * Use this to return the points of the polygon. It returns the points in
     * the way of an array of RPoint.
     *
     * @eexample RPolygon_getPoints
     * @return RPoint[], the points returned in an array.
   *
     */
    @Override
    public RPoint[] getPoints() {
        int numContours = countContours();
        if (numContours == 0) {
            return null;
        }

        RPoint[] result = null;
        RPoint[] newresult = null;
        for (int i = 0; i < numContours; i++) {
            RPoint[] newPoints = contours[i].getPoints();
            if (newPoints != null) {
                if (result == null) {
                    result = new RPoint[newPoints.length];
                    System.arraycopy(newPoints, 0, result, 0, newPoints.length);
                } else {
                    newresult = new RPoint[result.length + newPoints.length];
                    System.arraycopy(result, 0, newresult, 0, result.length);
                    System.arraycopy(newPoints, 0, newresult, result.length, newPoints.length);
                    result = newresult;
                }
            }
        }
        return result;
    }

    /**
     * Use this method to get the type of element this is.
     *
     * @eexample RPolygon_getType
     * @return int, will allways return RGeomElem.POLYGON
     */
    public int getType() {
        return type;
    }

    /**
     * Use this method to get the area covered by the polygon.
     *
     * @eexample getArea
     * @return float, the area covered by the polygon
     * @related draw ( )
     */
    @Override
    public float getArea() {
        if (getNumPoints() < 3) {
            return 0.0F;
        }
        float ax = getX(0);
        float ay = getY(0);
        float area = 0.0F;
        for (int i = 1; i < (getNumPoints() - 1); i++) {
            float bx = getX(i);
            float by = getY(i);
            float cx = getX(i + 1);
            float cy = getY(i + 1);
            float tarea = ((cx - bx) * (ay - by)) - ((ax - bx) * (cy - by));
            area += tarea;
        }
        area = 0.5F * Math.abs(area);
        return area;
    }

    /**
     * Use this method to draw the polygon.
     *
     * @eexample drawPolygon
     * @param g PGraphics, the graphics object on which to draw the polygon
     * @related draw ( )
     */
    @Override
    public void draw(PGraphics g) {
        int numContours = countContours();
        if (numContours != 0) {
            if (isIn(g)) {
                if (!RG.ignoreStyles) {
                    saveContext(g);
                    setContext(g);
                }

                // Check whether to draw the fill or not
                if (g.fill) {
                    // Since we are drawing the different tristrips we must turn off the stroke or make it the same color as the fill
                    // NOTE: there's currently no way of drawing the outline of a mesh, since no information is kept about what vertices are at the edge

                    // Save the information about the current stroke color and turn off
                    boolean stroking = g.stroke;
                    g.noStroke();

                    // Save smoothing state and turn off
                    int smoothing = g.smooth;
                    try {
                        if (smoothing > 0) {
                            g.noSmooth();
                        }
                    } catch (Exception e) {
                    }

                    RMesh tempMesh = this.toMesh();
                    tempMesh.draw(g);

                    // Restore the old stroke color
                    if (stroking) {
                        g.stroke(g.strokeColor);
                    }

                    // Restore the old smoothing state
                    try {
                        if (smoothing > 0) {
                            g.smooth();
                        }
                    } catch (Exception e) {
                    }
                }

                // Check whether to draw the stroke or not
                if (g.stroke) {
                    for (int i = 0; i < numContours; i++) {
                        contours[i].draw(g);
                    }
                }

                if (!RG.ignoreStyles) {
                    restoreContext(g);
                }
            }
        }
    }

    @Override
    public void draw(PApplet g) {
        int numContours = countContours();
        if (numContours != 0) {
            if (isIn(g)) {
                if (!RG.ignoreStyles) {
                    saveContext(g);
                    setContext(g);
                }

                // Check whether to draw the fill or not
                if (g.g.fill) {
                    // Since we are drawing the different tristrips we must turn off the stroke or make it the same color as the fill
                    // NOTE: there's currently no way of drawing the outline of a mesh, since no information is kept about what vertices are at the edge

                    // Save the information about the current stroke color and turn off
                    boolean stroking = g.g.stroke;
                    g.noStroke();

                    // Save smoothing state and turn off
                    int smoothing = g.g.smooth;
                    try {
                        if (smoothing > 0) {
                            g.noSmooth();
                        }
                    } catch (Exception e) {
                    }

                    RMesh tempMesh = this.toMesh();
                    if (tempMesh != null) {
                        tempMesh.draw(g);
                    }

                    // Restore the old stroke color
                    if (stroking) {
                        g.stroke(g.g.strokeColor);
                    }

                    // Restore the old smoothing state
                    try {
                        if (smoothing > 0) {
                            g.smooth();
                        }
                    } catch (Exception e) {
                    }
                }

                // Check whether to draws the stroke or not
                if (g.g.stroke) {
                    for (int i = 0; i < numContours; i++) {
                        contours[i].draw(g);
                    }
                }

                if (!RG.ignoreStyles) {
                    restoreContext(g);
                }
            }
        }
    }

    /**
     * Use this method to get the intersection of this polygon with the polygon
     * passed in as a parameter.
     *
     * @eexample intersection
     * @param p RPolygon, the polygon with which to perform the intersection
     * @return RPolygon, the intersection of the two polygons
     * @related union ( )
     * @related xor ( )
     * @related diff ( )
     */
    public RPolygon intersection(RPolygon p) {
        RPolygon res = RClip.intersection(p, this);
        res.setStyle(this.getStyle());
        return res;
    }

    /**
     * Use this method to get the union of this polygon with the polygon passed
     * in as a parameter.
     *
     * @eexample union
     * @param p RPolygon, the polygon with which to perform the union
     * @return RPolygon, the union of the two polygons
     * @related intersection ( )
     * @related xor ( )
     * @related diff ( )
     */
    public RPolygon union(RPolygon p) {
        RPolygon res = RClip.union(p, this);
        res.setStyle(this.getStyle());
        return res;
    }

    /**
     * Use this method to get the xor of this polygon with the polygon passed in
     * as a parameter.
     *
     * @eexample xor
     * @param p RPolygon, the polygon with which to perform the xor
     * @return RPolygon, the xor of the two polygons
     * @related union ( )
     * @related intersection ( )
     * @related diff ( )
     */
    public RPolygon xor(RPolygon p) {
        RPolygon res = RClip.xor(p, this);
        res.setStyle(this.getStyle());
        return res;
    }

    /**
     * Use this method to get the difference between this polygon and the
     * polygon passed in as a parameter.
     *
     * @eexample diff
     * @param p RPolygon, the polygon with which to perform the difference
     * @return RPolygon, the difference of the two polygons
     * @related union ( )
     * @related xor ( )
     * @related intersection ( )
     */
    public RPolygon diff(RPolygon p) {
        RPolygon res = RClip.diff(this, p);
        res.setStyle(this.getStyle());
        return res;
    }

    /**
     * Use this method to get a rebuilt version of a given polygon by removing
     * extra points and solving intersecting contours or holes.
     *
     * @eexample RPolygon_update
     * @return RPolygon, the updated polygon
     * @related diff ( )
     * @related union ( )
     * @related xor ( )
     * @related intersection ( )
     */
    public RPolygon update() {
        return RClip.update(this);
    }

    @Override
    public RPoint getPoint(float t) {
        PApplet.println("Feature not yet implemented for this class.");
        return null;
    }

    @Override
    public RPoint getTangent(float t) {
        PApplet.println("Feature not yet implemented for this class.");
        return null;
    }

    /**
     *
     * @return
     */
    @Override
    public RPoint[] getTangents() {
        PApplet.println("Feature not yet implemented for this class.");
        return null;
    }

    @Override
    public RPoint[][] getPointsInPaths() {
        PApplet.println("Feature not yet implemented for this class.");
        return null;
    }

    @Override
    public RPoint[][] getHandlesInPaths() {
        PApplet.println("Feature not yet implemented for this class.");
        return null;
    }

    @Override
    public RPoint[][] getTangentsInPaths() {
        PApplet.println("Feature not yet implemented for this class.");
        return null;
    }

    @Override
    public boolean contains(RPoint p) {
        PApplet.println("Feature not yet implemented for this class.");
        return false;
    }

    /**
     * Use this method to transform the polygon.
     *
     * @eexample RPolygon_transform
     * @param m RMatrix, the matrix of the affine transformation to apply to the
     * polygon
     */
    /*
    public void transform(RMatrix m){
    int numContours = countContours();
    if(numContours!=0){
    for(int i=0;i<numContours;i++){
    contours[i].transform(m);
    }
    }
    }
     */
    // ----------------------
    // --- Private Methods ---
    // ----------------------
    /**
     * Remove all of the points. Creates an empty polygon.
     */
    protected void clear() {
        this.contours = null;
    }

    /**
     * Add a point to the first inner polygon.
     * @param x
     * @param y
     */
    protected void add(float x, float y) {
        if (contours == null) {
            this.append(new RContour());
        }
        this.contours[0].append(new RPoint(x, y));
    }

    /**
     * Add a point to the first inner polygon.
     * @param p
     */
    protected void add(RPoint p) {
        if (contours == null) {
            this.append(new RContour());
        }
        this.contours[0].append(p);
    }

    /**
     * Add an inner polygon to this polygon - assumes that adding polygon does
     * not have any inner polygons.
     * @param p
     */
    protected void add(RPolygon p) {
        /*if (this.contours.length > 0 && this.isHole){
      throw new IllegalStateException("Cannot add polys to something designated as a hole.");
      }*/
        RContour c = new RContour();
        for (int i = 0; i < p.getNumPoints(); i++) {
            c.addPoint(p.getX(i), p.getY(i));
        }
        this.append(c);
    }

    /**
     * Add an inner polygon to this polygon - assumes that adding polygon does
     * not have any inner polygons.
     * @param c
     */
    protected void add(RContour c) {
        /*if (this.contours.length > 0 && this.isHole){
      throw new IllegalStateException("Cannot add polys to something designated as a hole.");
      }*/
        this.append(c);
    }

    /**
     * Return true if the polygon is empty
     * @return 
     */
    protected boolean isEmpty() {
        return (this.contours == null);
    }

    /**
     * Returns the bounding box of the polygon.
     * @return 
     */
    protected RRectangle getBBox() {
        if (this.contours == null) {
            return new RRectangle();
        } else if (this.contours.length == 1) {

            float xmin = Float.MAX_VALUE;
            float ymin = Float.MAX_VALUE;
            float xmax = -Float.MAX_VALUE;
            float ymax = -Float.MAX_VALUE;

            if (this.contours[0].points == null) {
                return new RRectangle();
            }

            for (RPoint point : this.contours[0].points) {
                float x = point.getX();
                float y = point.getY();
                if (x < xmin) {
                    xmin = x;
                }
                if (x > xmax) {
                    xmax = x;
                }
                if (y < ymin) {
                    ymin = y;
                }
                if (y > ymax) {
                    ymax = y;
                }
            }

            return new RRectangle(xmin, ymin, (xmax - xmin), (ymax - ymin));
        } else {
            throw new UnsupportedOperationException("getBounds not supported on complex poly.");
        }
    }

    /**
     * Returns the polygon at this index.
     * @param polyIndex
     * @return 
     */
    protected RPolygon getInnerPoly(int polyIndex) {
        return new RPolygon(this.contours[polyIndex]);
    }

    /**
     * Returns the number of inner polygons - inner polygons are assumed to
     * return one here.
     * @return 
     */
    protected int getNumInnerPoly() {
        if (this.contours == null) {
            return 0;
        }
        return this.contours.length;
    }

    /**
     * Return the number points of the first inner polygon
     * @return 
     */
    protected int getNumPoints() {
        if (this.contours == null) {
            return 0;
        }
        if (this.contours[0].points == null) {
            return 0;
        }
        return this.contours[0].points.length;
    }

    /**
     * Return the X value of the point at the index in the first inner polygon
     * @param index
     * @return 
     */
    protected float getX(int index) {
        if (this.contours == null) {
            return 0;
        }
        return this.contours[0].points[index].x;
    }

    /**
     * Return the Y value of the point at the index in the first inner polygon
     * @param index
     * @return 
     */
    protected float getY(int index) {
        if (this.contours == null) {
            return 0;
        }
        return this.contours[0].points[index].y;
    }

    /**
     * Return true if this polygon is a hole. Holes are assumed to be inner
     * polygons of a more complex polygon.
     *
     * @return 
     * @throws IllegalStateException if called on a complex polygon.
     */
    public boolean isHole() {
        if (this.contours == null || this.contours.length > 1) {
            throw new IllegalStateException("Cannot call on a poly made up of more than one poly.");
        }
        return this.contours[0].isHole;
    }

    /**
     * Set whether or not this polygon is a hole. Cannot be called on a complex
     * polygon.
     *
     * @param isHole
     * @throws IllegalStateException if called on a complex polygon.
     */
    protected void setIsHole(boolean isHole) {
        if (this.contours == null || this.contours.length > 1) {
            throw new IllegalStateException("Cannot call on a poly made up of more than one poly.");
        }
        this.contours[0].isHole = isHole;
    }

    /**
     * Return true if the given inner polygon is contributing to the set
     * operation. This method should NOT be used outside the Clip algorithm.
     * @param polyIndex
     * @return 
     */
    protected boolean isContributing(int polyIndex) {
        return this.contours[polyIndex].isContributing;
    }

    /**
     * Set whether or not this inner polygon is constributing to the set
     * operation. This method should NOT be used outside the Clip algorithm.
     * @param polyIndex
     * @param contributes
     */
    protected void setContributing(int polyIndex, boolean contributes) {
        /*
    if( this.contours.length != 1 )
      {
        throw new IllegalStateException( "Only applies to polys of size 1" );
      }
         */
        this.contours[polyIndex].isContributing = contributes;
    }

    private void append(RContour nextcontour) {
        RContour[] newcontours;
        if (contours == null) {
            newcontours = new RContour[1];
            newcontours[0] = nextcontour;
            currentContour = 0;
        } else {
            newcontours = new RContour[this.contours.length + 1];
            System.arraycopy(this.contours, 0, newcontours, 0, this.contours.length);
            newcontours[this.contours.length] = nextcontour;
            currentContour++;
        }
        this.contours = newcontours;
    }
}