package org.concord.energy2d.math;

import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.List;

/**
 * Mutatable implementation of polygon (GeneralPath is immutatable).
 * 
 * @author Charles Xie
 * 
 */
public class Polygon2D implements TransformableShape {

	private Point2D.Float[] vertex;
	private GeneralPath path;

	/** the coordinates of the vertices of this polygon. */
	public Polygon2D(float[] x, float[] y) {
		if (x.length != y.length)
			throw new IllegalArgumentException("the number of x coodinates must be equal to that of the y coordinates.");
		if (x.length < 3)
			throw new IllegalArgumentException("the number of vertices must be no less than 3.");
		vertex = new Point2D.Float[x.length];
		for (int i = 0; i < x.length; i++)
			setVertex(i, x[i], y[i]);
		path = new GeneralPath();
	}

	public Polygon2D duplicate() {
		int n = vertex.length;
		float[] x = new float[n];
		float[] y = new float[n];
		for (int i = 0; i < n; i++) {
			x[i] = vertex[i].x;
			y[i] = vertex[i].y;
		}
		return new Polygon2D(x, y);
	}

	public Polygon2D insertVertexBefore(int k) {
		int n = vertex.length;
		float[] x = new float[n + 1];
		float[] y = new float[n + 1];
		if (k > 0 && k < n) {
			for (int i = 0; i < k; i++) {
				x[i] = vertex[i].x;
				y[i] = vertex[i].y;
			}
			x[k] = 0.5f * (vertex[k].x + vertex[k - 1].x);
			y[k] = 0.5f * (vertex[k].y + vertex[k - 1].y);
			for (int i = k + 1; i < n + 1; i++) {
				x[i] = vertex[i - 1].x;
				y[i] = vertex[i - 1].y;
			}
		} else if (k == 0) {
			x[0] = 0.5f * (vertex[0].x + vertex[n - 1].x);
			y[0] = 0.5f * (vertex[0].y + vertex[n - 1].y);
			for (int i = 1; i < n + 1; i++) {
				x[i] = vertex[i - 1].x;
				y[i] = vertex[i - 1].y;
			}
		} else {
			return this;
		}
		return new Polygon2D(x, y);
	}

	public Polygon2D deleteVertexBefore(int k) {
		int n = vertex.length;
		if (n < 4)
			return this;
		float[] x = new float[n - 1];
		float[] y = new float[n - 1];
		if (k > 0 && k < n) {
			for (int i = 0; i < k; i++) {
				x[i] = vertex[i].x;
				y[i] = vertex[i].y;
			}
			for (int i = k + 1; i < n; i++) {
				x[i - 1] = vertex[i].x;
				y[i - 1] = vertex[i].y;
			}
		} else if (k == 0) {
			for (int i = 1; i < n; i++) {
				x[i - 1] = vertex[i - 1].x;
				y[i - 1] = vertex[i - 1].y;
			}
		} else {
			return this;
		}
		return new Polygon2D(x, y);
	}

	public boolean isClockwise() {
		float sum = 0;
		int n = vertex.length;
		for (int i = 0; i < n - 1; i++)
			sum += (vertex[i + 1].x - vertex[i].x) * (vertex[i + 1].y + vertex[i].y);
		sum += (vertex[0].x - vertex[n - 1].x) * (vertex[0].y + vertex[n - 1].y);
		return sum > 0;
	}

	private void update() {
		synchronized (path) {
			path.reset();
			path.moveTo(vertex[0].x, vertex[0].y);
			for (int i = 1; i < vertex.length; i++)
				path.lineTo(vertex[i].x, vertex[i].y);
			path.closePath();
		}
	}

	public void setVertices(List<Point2D.Float> points) {
		if (points.size() < 3)
			throw new IllegalArgumentException("the number of vertices must be no less than 3.");
		if (vertex == null || points.size() != vertex.length)
			path = new GeneralPath();
		vertex = new Point2D.Float[points.size()];
		for (int i = 0; i < vertex.length; i++) {
			Point2D.Float pi = points.get(i);
			setVertex(i, pi.x, pi.y);
		}
	}

	public void setVertex(int i, float x, float y) {
		if (i < 0 || i >= vertex.length)
			throw new IllegalArgumentException("index of vertex is out of bound.");
		if (vertex[i] == null)
			vertex[i] = new Point2D.Float(x, y);
		else
			vertex[i].setLocation(x, y);
	}

	public Point2D.Float getVertex(int i) {
		if (i < 0 || i >= vertex.length)
			throw new IllegalArgumentException("index of vertex is out of bound.");
		return vertex[i];
	}

	public int getVertexCount() {
		return vertex.length;
	}

	public void translateBy(float dx, float dy) {
		for (Point2D.Float p : vertex) {
			p.x += dx;
			p.y += dy;
		}
	}

	public void rotateBy(float degree) {
		Rectangle2D r = path.getBounds2D();
		double cx = r.getCenterX();
		double cy = r.getCenterY();
		double a = Math.toRadians(degree);
		double sin = Math.sin(a);
		double cos = Math.cos(a);
		double dx = 0;
		double dy = 0;
		for (Point2D.Float v : vertex) {
			dx = v.x - cx;
			dy = v.y - cy;
			v.x = (float) (dx * cos - dy * sin + cx);
			v.y = (float) (dx * sin + dy * cos + cy);
		}
	}

	public void scale(float scale) {
		Rectangle2D r = path.getBounds2D();
		double cx = r.getCenterX();
		double cy = r.getCenterY();
		for (Point2D.Float v : vertex) {
			v.x = (float) ((v.x - cx) * scale + cx);
			v.y = (float) ((v.y - cy) * scale + cy);
		}
	}

	public void scaleX(float scale) {
		Rectangle2D r = path.getBounds2D();
		double cx = r.getCenterX();
		for (Point2D.Float v : vertex) {
			v.x = (float) ((v.x - cx) * scale + cx);
		}
	}

	public void scaleY(float scale) {
		Rectangle2D r = path.getBounds2D();
		double cy = r.getCenterY();
		for (Point2D.Float v : vertex) {
			v.y = (float) ((v.y - cy) * scale + cy);
		}
	}

	public void shearX(float shear) {
		Rectangle2D r = path.getBounds2D();
		double cy = r.getCenterY();
		for (Point2D.Float v : vertex) {
			v.x += (float) (v.y - cy) * shear;
		}
	}

	public void shearY(float shear) {
		Rectangle2D r = path.getBounds2D();
		double cx = r.getCenterX();
		for (Point2D.Float v : vertex) {
			v.y += (float) (v.x - cx) * shear;
		}
	}

	public void flipX() {
		float cx = (float) path.getBounds2D().getCenterX();
		float dx = 0;
		for (Point2D.Float v : vertex) {
			dx = v.x - cx;
			v.x = cx - dx;
		}
	}

	public void flipY() {
		float cy = (float) path.getBounds2D().getCenterY();
		float dy = 0;
		for (Point2D.Float v : vertex) {
			dy = v.y - cy;
			v.y = cy - dy;
		}
	}

	public boolean contains(Point2D p) {
		return contains(p.getX(), p.getY());
	}

	public boolean intersects(Rectangle r) {
		update();
		return path.intersects(r);
	}

	public boolean contains(double x, double y) {
		update();
		return path.contains(x, y);
	}

	public Point2D.Float getBoundCenter() {
		Rectangle2D r = path.getBounds2D();
		return new Point2D.Float((float) r.getCenterX(), (float) r.getCenterY());
	}

	public void translateCenterTo(float x, float y) {
		Point2D.Float center = getCenter();
		translateBy(x - center.x, y - center.y);
	}

	public Point2D.Float getCenter() {
		float xc = 0;
		float yc = 0;
		for (Point2D.Float v : vertex) {
			xc += v.x;
			yc += v.y;
		}
		return new Point2D.Float(xc / vertex.length, yc / vertex.length);
	}

	public float getArea() {
		float area = 0;
		int n = vertex.length;
		Point2D.Float v1, v2;
		for (int i = 0; i < n - 1; i++) {
			v1 = vertex[i];
			v2 = vertex[i + 1];
			area += v1.getX() * v2.getY() - v2.getX() * v1.getY();
		}
		v1 = vertex[n - 1];
		v2 = vertex[0];
		area += v1.getX() * v2.getY() - v2.getX() * v1.getY();
		return area * 0.5f;
	}

	public Rectangle getBounds() {
		int xmin = Integer.MAX_VALUE;
		int ymin = xmin;
		int xmax = -xmin;
		int ymax = -xmin;
		for (Point2D.Float v : vertex) {
			if (xmin > v.x)
				xmin = Math.round(v.x);
			if (ymin > v.y)
				ymin = Math.round(v.y);
			if (xmax < v.x)
				xmax = Math.round(v.x);
			if (ymax < v.y)
				ymax = Math.round(v.y);
		}
		return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin);
	}

	public Rectangle2D getBounds2D() {
		float xmin = Float.MAX_VALUE;
		float ymin = xmin;
		float xmax = -xmin;
		float ymax = -xmin;
		for (Point2D.Float v : vertex) {
			if (xmin > v.x)
				xmin = v.x;
			if (ymin > v.y)
				ymin = v.y;
			if (xmax < v.x)
				xmax = v.x;
			if (ymax < v.y)
				ymax = v.y;
		}
		return new Rectangle2D.Float(xmin, ymin, xmax - xmin, ymax - ymin);
	}

	public boolean contains(Rectangle2D r) {
		update();
		return path.contains(r);
	}

	public boolean contains(double x, double y, double w, double h) {
		update();
		return path.contains(x, y, w, h);
	}

	public PathIterator getPathIterator(AffineTransform at) {
		update();
		return path.getPathIterator(at);
	}

	public PathIterator getPathIterator(AffineTransform at, double flatness) {
		update();
		return path.getPathIterator(at, flatness);
	}

	public boolean intersects(Rectangle2D r) {
		update();
		return path.intersects(r);
	}

	public boolean intersects(double x, double y, double w, double h) {
		update();
		return path.intersects(x, y, w, h);
	}

}