/*
* Copyright 2003-2010 Tufts University  Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 * 
 * http://www.osedu.org/licenses/ECL-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package tufts.vue.shape;

import java.awt.geom.RectangularShape;
import java.awt.geom.Rectangle2D;
import java.awt.geom.PathIterator;
import java.awt.geom.AffineTransform;

/**
 * This class implements a polygon shape fit into a specified rectanular region.
 * @author Scott Fraize
 */
public abstract class RectangularPoly2D extends RectangularShape
{
    public static final int CENTER = 0;
    public static final int NORTH      = 1;
    public static final int NORTH_EAST = 2;
    public static final int EAST       = 3;
    public static final int SOUTH_EAST = 4;
    public static final int SOUTH      = 5;
    public static final int SOUTH_WEST = 6;
    public static final int WEST       = 7;
    public static final int NORTH_WEST = 8;
        
    protected double x;
    protected double y;
    protected double width;
    protected double height;
    protected int sides;
    protected int layout;
    
    protected double[] xpoints;
    protected double[] ypoints;
    
    public RectangularPoly2D(int sides, double x, double y, double width, double height)
    {
        setSides(sides);
        setFrame(x, y, width, height);
    }

    public RectangularPoly2D(int sides)
    {
        setSides(sides);
    }

    /** For persistance */
    public RectangularPoly2D() {}

    public int getContentGravity() {
        return CENTER;
    }

    protected abstract void computeVertices();

    /** a point-up triangle */
    public static class Triangle extends RectangularPoly2D {
        public Triangle() { setSides(3); }
        public int getContentGravity() { return SOUTH; }
        protected void computeVertices()
        {
            xpoints[0] = x + width/2;
            ypoints[0] = y;
            
            xpoints[1] = x;
            ypoints[1] = y + height;
            
            xpoints[2] = x + width;
            ypoints[2] = y + height;
        }
        
    }
    /** a point-down triangle */
    public static class Shield extends RectangularPoly2D {
        public Shield() { setSides(3); }
        public int getContentGravity() { return NORTH; }
        protected void computeVertices()
        {
            xpoints[0] = x;
            ypoints[0] = y;
            
            xpoints[1] = x + width;
            ypoints[1] = y;

            xpoints[2] = x + width/2;
            ypoints[2] = y + height;
        }
        
    }
    /** a point to the right triangle */
    public static class Flag extends RectangularPoly2D {
        public Flag() { setSides(3); }
        public int getContentGravity() { return WEST; }
        protected void computeVertices()
        {
            xpoints[0] = x;
            ypoints[0] = y;
            
            xpoints[1] = x;
            ypoints[1] = y + height;
            
            xpoints[2] = x + width;
            ypoints[2] = y + height / 2;
        }
    }
    /** a point to the left triangle */
    public static class Flag2 extends RectangularPoly2D {
        public Flag2() { setSides(3); }
        public int getContentGravity() { return EAST; }
        protected void computeVertices()
        {
            xpoints[0] = x;
            ypoints[0] = y + height / 2;
            
            xpoints[1] = x + width;
            ypoints[1] = y;
            
            xpoints[2] = x + width;
            ypoints[2] = y + height;
            
        }
    }
    

    /** a 4 sided polygon */
    public static class Diamond extends RectangularPoly2D {
        public Diamond() { setSides(4); }
        protected void computeVertices()
        {
            xpoints[0] = x + width/2;
            ypoints[0] = y;
            
            xpoints[1] = x + width;
            ypoints[1] = y + height/2;
            
            xpoints[2] = x + width/2;
            ypoints[2] = y + height;
            
            xpoints[3] = x;
            ypoints[3] = y + height/2;
        }
    }
    
    /** a 4 sided polygon */
    public static class Rhombus extends RectangularPoly2D {
        public Rhombus() { setSides(4); }
        protected void computeVertices()
        {
        

            xpoints[0] = x + ((double)width-(double)width*0.8);
            ypoints[0] = y;
            
            xpoints[1] = x + width;
            ypoints[1] = y;
            
            xpoints[3] = x;
            ypoints[3] = y + height;
            
            xpoints[2] = x + ((double)width-(double)width*0.2);
            ypoints[2] = y + height;
        }
    }

    /** a 5 sided polygon */
    public static class Pentagon extends RectangularPoly2D {
        public Pentagon() { setSides(5); }
        protected void computeVertices()
        {
            xpoints[0] = x + width/2;
            ypoints[0] = y;
            
            xpoints[1] = x + width;
            ypoints[1] = y + height/2;
            
            xpoints[2] = x + width*3/4;
            ypoints[2] = y + height;
            
            xpoints[3] = x + width/4;
            ypoints[3] = y + height;
            
            xpoints[4] = x;
            ypoints[4] = y + height/2;
        }
    }

    /** a 6 sided polygon */
    public static class Hexagon extends RectangularPoly2D {
        public Hexagon() { setSides(6); }
        protected void computeVertices()
        {
            // tan(30) = inset/halfH
            // halfH*tan(30) = inset
            double halfH = height/2;
            //double inset = halfH * Math.tan(Math.PI/6);
            double inset = 0.2257085*width;
        
            //System.out.println("HEXAGON size=" + width + "x" + height);
            //System.out.println("HEXAGON HALFH=" + halfH);
            //System.out.println("HEXAGON INSET=" + inset);
            //System.out.println("HEXAGON SEGSIZE  TOP=" + (width-(inset*2)));
            //System.out.println("HEXAGON SEGSIZE LEFT=" + Math.sqrt(inset*inset+halfH*halfH));
            
            xpoints[0] = x + inset;
            ypoints[0] = y;

            xpoints[1] = x + (width - inset);
            ypoints[1] = y;

            xpoints[2] = x + width;
            ypoints[2] = y + halfH;

            xpoints[3] = x + (width - inset);
            ypoints[3] = y + height;

            xpoints[4] = x + inset;
            ypoints[4] = y + height;
        
            xpoints[5] = x;
            ypoints[5] = y + halfH;
        }
    }

    /** an 8 sided polygon */
    public static class Octagon extends RectangularPoly2D {
        public Octagon() { setSides(8); }
        protected void computeVertices()
        {
            double xInset = width / 3.4;
            double yInset = height / 3.4;
            
            xpoints[0] = x + xInset;
            ypoints[0] = y;
            
            xpoints[1] = x + (width - xInset);
            ypoints[1] = y;
            
            xpoints[2] = x + width;
            ypoints[2] = y + yInset;
            
            xpoints[3] = x + width;
            ypoints[3] = y + (height - yInset);
            
            xpoints[4] = x + (width - xInset);
            ypoints[4] = y + height;
            
            xpoints[5] = x + xInset;
            ypoints[5] = y + height;
            
            xpoints[6] = x;
            ypoints[6] = y + (height - yInset);
            
            xpoints[7] = x;
            ypoints[7] = y + yInset;
        }
    }
    /** a point sideways Chevron */  
    public static class Chevron extends RectangularPoly2D {  
    	  public Chevron() { setSides(6); }  
    	  public int getContentGravity() { return CENTER; }  
          protected void computeVertices()  
           {  
            
             xpoints[0] = x + ((double)width-(double)width*0.2);  
             ypoints[0] = y;         
             xpoints[1] = x + width;  
             ypoints[1] = y + (height/2);     
             xpoints[2] = x + ((double)width-(double)width*0.2);  
             ypoints[2] = y + height;   
             xpoints[3] = x;  
             ypoints[3] = y + height;  
             xpoints[4] = x + (double)width*0.2;  
             ypoints[4] = y + (height/2);  
             xpoints[5] = x;  
             ypoints[5] = y;             
           }      
     }  
    public void setSides(int sides)
    {
        if (sides < 3 || sides > 8)
            throw new IllegalArgumentException("RectangularPoly2D: sides not >=3 && <=8: "+sides);
        this.sides = sides;
        xpoints = new double[sides+1];
        ypoints = new double[sides+1];
        xpoints[sides] = 0;
        ypoints[sides] = 0;
    }

    public int getSides()
    {
        return this.sides;
    }

    public Rectangle2D getBounds2D() {
        return new Rectangle2D.Double(x, y, width, height);
    }
    
    public double getHeight() { return height; }
    public double getWidth() { return width; }
    public double getX() { return x; }
    public double getY() { return y; }
    
    public boolean isEmpty() {
        return width <= 0 || height <= 0;
    }
    
    /** does this polygon contain the given point? */
    public boolean contains(double px, double py)
    {
        // fast-reject: check if outside bounding box
        if (px < x || py < y || px > x + width || py > y + height)
            return false;

        // below adapted from java.awt.Polygon
        
	int hits = 0;

	double lastx = xpoints[sides - 1];
	double lasty = ypoints[sides - 1];
	double curx, cury;

	// Walk the edges of the polygon
	for (int i = 0; i < sides; lastx = curx, lasty = cury, i++)
        {
	    curx = xpoints[i];
	    cury = ypoints[i];

	    if (cury == lasty)
		continue;

	    double leftx;
	    if (curx < lastx) {
		if (x >= lastx) {
		    continue;
		}
		leftx = curx;
	    } else {
		if (px >= curx) {
		    continue;
		}
		leftx = lastx;
	    }

	    double test1, test2;
	    if (cury < lasty) {
		if (py < cury || py >= lasty) {
		    continue;
		}
		if (px < leftx) {
		    hits++;
		    continue;
		}
		test1 = px - curx;
		test2 = py - cury;
	    } else {
		if (py < lasty || py >= cury) {
		    continue;
		}
		if (px < leftx) {
		    hits++;
		    continue;
		}
		test1 = px - lastx;
		test2 = py - lasty;
	    }

	    if (test1 < (test2 / (lasty - cury) * (lastx - curx))) {
		hits++;
	    }
	}

	return ((hits & 1) != 0);
    }
    

    /** does shape entirely contain rectangle? */
    public boolean contains(double x, double y, double w, double h)
    {
        return contains(x, y) && contains(x+w, y+h) && contains(x+w, y) && contains(x,y+h);
        // todo: this will work to check the corners for entirely concave regular polygons
        // but will need to check crossings to support irregular polygons
        // (e.g. a star or cross)
    }
    
    /** does any part of shape intersect rectangle? */
    public boolean intersects(double x, double y, double w, double h)
    {
        // fast-reject: check if outside bounding box
        if (!(x + w > this.x &&
              y + h > this.y &&
              x < this.x + this.width &&
              y < this.y + this.height))
            return false;

        // fast-accept: that checks to see if any vertex is within the box
        for (int i = 0; i < sides; i++) {
            double xp = xpoints[i];
            double yp = ypoints[i];
            if (xp > x && xp < x + w &&
                yp > y && yp < y + h) {
                //System.out.println("fast accept vertex" + i + " " + this);
                return true;
            }
                
        }

	Crossings cross = getCrossings(x, y, x+w, y+h);
	return (cross == null || !cross.isEmpty());
    }
    
    private Crossings getCrossings(double xlo, double ylo,
				   double xhi, double yhi)
    {
	Crossings cross = new Crossings.EvenOdd(xlo, ylo, xhi, yhi);
	double lastx = xpoints[sides - 1];
	double lasty = ypoints[sides - 1];
	double curx, cury;

	// Walk the edges of the polygon
	for (int i = 0; i < sides; i++) {
	    curx = xpoints[i];
	    cury = ypoints[i];
	    if (cross.accumulateLine(lastx, lasty, curx, cury)) {
		return null;
	    }
	    lastx = curx;
	    lasty = cury;
	}

	return cross;
    }
    
    public void setFrame(double x, double y, double width, double height)
    {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        //System.out.println(this + " POLY setFrame " + x + "," + y + " " + width + "x" + height);
        //new Throwable().printStackTrace();
        computeVertices();
    }

    /*
    private void computeVertices()
    {
        if (sides == 3)
            computeVertices3();
        else if (sides == 4)
            computeVertices4();
        else if (sides == 5)
            computeVertices5();
        else if (sides == 6)
            computeVertices6();
        else if (sides == 8)
            computeVertices8();
    }
    */
    
    /*        
    private void computeVertices5()
        //final double horizontalOffset = 0.118033989; // verticalOffset * tan(18)
        final double horizontalOffset = verticalOffset*verticalOffset;
        // try taking into account verticalOffset before computing horizontal offset
        double dropDown = height * verticalOffset;
        double inset = width * horizontalOffset;
            
        vertices[0] = x + halfW;
        vertices[0] = y;

        vertices[1] = x + width;
        vertices[1] = y + dropDown;

        vertices[2] = x + width - inset;
        vertices[2] = y + height;

        vertices[3] = x + inset;
        vertices[3] = y + height;

        vertices[4] = x;
        vertices[4] = y + dropDown;
        }*/

    private void new_computeVertices6()
    {
        // equalateral polygons are inscribed
        // inside circles, not rectangles (all points
        // are on a containing circle) -- this
        // creates an equal hexagon inside a circle
        // of the given WIDTH -- height is ignored.
        // -- can't we just inscribe it in an ellipse?
        
        double hw = width/2;
        double hh = height/2;
        double qw = hw/2;
        double qh = Math.sqrt(hw*hw-qw*qw);
        double cx = x + hw;
        double cy = y + hh;
            
        xpoints[0] = cx + qw;
        ypoints[0] = cy - qh;

        xpoints[1] = x + width;
        ypoints[1] = y + hh;

        xpoints[2] = cx + qw;
        ypoints[2] = cy + qh;

        xpoints[3] = cx - qw;
        ypoints[3] = cy + qh;

        xpoints[4] = x;
        ypoints[4] = y + hh;
        
        xpoints[5] = cx - qw;
        ypoints[5] = cy - qh;
    }
    
    public PathIterator getPathIterator(AffineTransform affineTransform)
    {
        return new PolyIterator(affineTransform);
    }

    public Object clone()
    {
        RectangularPoly2D cloned = (RectangularPoly2D) super.clone();
        cloned.xpoints = new double[sides+1];
        cloned.ypoints = new double[sides+1];
        System.arraycopy(this.xpoints, 0, cloned.xpoints, 0, sides+1);
        System.arraycopy(this.ypoints, 0, cloned.ypoints, 0, sides+1);
        //cloned.computeVertices();
        return cloned;
    }

    public String toString()
    {
    	return getClass().getName() + "@" + Integer.toHexString(hashCode()) + "[sides=" + sides + " " + x + "," + y + " " + width + ", " + height + "]";
    }
    
    class PolyIterator implements PathIterator
    {
        int index = 0;
        AffineTransform affine;

        public PolyIterator(AffineTransform affine)
        {
            this.affine = affine;
        }
    
        public int currentSegment(double[] coords) { 
            coords[0] = xpoints[index];  
            coords[1] = ypoints[index];
            if (affine != null)
                affine.transform(coords, 0, coords, 0, 1);            
            //System.out.println("i"+index + " (double)coords=" +coords[0] + "," + coords[1]);
            if (index == 0)
                return SEG_MOVETO;
            else if (index == sides)
                return SEG_CLOSE;
            else
                return SEG_LINETO;
        }
    
        public int currentSegment(float[] coords){ 
            coords[0] = (float) xpoints[index];  
            coords[1] = (float) ypoints[index];
            if (affine != null)
                affine.transform(coords, 0, coords, 0, 1);            
            //System.out.println("i"+index + " (float)coords=" +coords[0] + "," + coords[1]);
            if (index == 0)
                return SEG_MOVETO;
            else if (index == sides)
                return SEG_CLOSE;
            else
                return SEG_LINETO;
        } 
			
        public int getWindingRule() { return PathIterator.WIND_NON_ZERO; }
        public boolean isDone() { return index > sides; } 
        public void next() { index++ ; } 
    }
    
    
}
