package org.concord.energy2d.model;

import java.awt.Color;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import org.concord.energy2d.util.XmlCharacterEncoder;

/**
 * Clouds (three circles intersected within a rectangle) are expensive to calculate. So we use additional variables (x, y) for setting locations and avoiding recalculation of shapes when moving them.
 * 
 * @author Charles Xie
 * 
 */
public class Cloud extends Manipulable {

	private float x; // the x coordinate of the upper-left corner
	private float y; // the y coordinate of the upper-left corner
	private float speed; // clouds only move in the horizontal direction
	private Color color = Color.WHITE;
	private Rectangle2D.Float boundingBox;

	/** Construct a cloud based on the specified bounding box, which must start from (0, 0). If not, the (x, y) will be ignored. */
	public Cloud(Shape bb) {
		super(bb);
		if (!(bb instanceof Rectangle2D.Float))
			throw new IllegalArgumentException("Shape must be a Rectangle2D.Float");
		Rectangle2D.Float r = (Rectangle2D.Float) bb;
		setDimension(r.width, r.height);
	}

	// the size and shape of a cloud are determined by its bounding box that cuts three circles
	public static Area getShape(Rectangle2D.Float r) {
		// the positions and sizes of the circles must ensure that r is the bounding box
		float max = Math.max(r.width, r.height);
		Area a = new Area(new Ellipse2D.Float(r.x + r.width / 6, r.y, max / 2, max / 2));
		a.add(new Area(new Ellipse2D.Float(r.x, r.y + r.height / 2, max / 3, max / 3)));
		a.add(new Area(new Ellipse2D.Float(r.x + r.width / 3, r.y + r.height / 3, max / 2, max / 2)));
		a.add(new Area(new Ellipse2D.Float(r.x + 2 * r.width / 3, r.y + 2 * r.height / 3, r.width / 3, r.width / 3)));
		a.intersect(new Area(r));
		return a;
	}

	public void setColor(Color color) {
		this.color = color;
	}

	public Color getColor() {
		return color;
	}

	public void move(float timeStep, float lx) {
		if (speed == 0)
			return;
		x += speed * timeStep;
		// apply periodic boundary condition
		if (x > lx)
			x -= lx + boundingBox.width;
		else if (x < -boundingBox.width)
			x += lx + boundingBox.width;
	}

	@Override
	public void translateBy(float dx, float dy) {
		x += dx;
		y += dy;
	}

	public void setLocation(float x, float y) {
		this.x = x;
		this.y = y;
	}

	@Override
	public Point2D.Float getCenter() {
		Rectangle2D bound = getShape().getBounds2D();
		return new Point2D.Float((float) bound.getCenterX() + x, (float) bound.getCenterY() + y);
	}

	@Override
	public boolean contains(float rx, float ry) {
		return getShape().contains(rx - x, ry - y);
	}

	public void setX(float x) {
		this.x = x;
	}

	public float getX() {
		return x;
	}

	public void setY(float y) {
		this.y = y;
	}

	public float getY() {
		return y;
	}

	public void setDimension(float w, float h) {
		boundingBox = new Rectangle2D.Float(0, 0, w, h);
		setShape(getShape(boundingBox));
	}

	public float getWidth() {
		return boundingBox.width;
	}

	public float getHeight() {
		return boundingBox.height;
	}

	public void setSpeed(float speed) {
		this.speed = speed;
	}

	public float getSpeed() {
		return speed;
	}

	@Override
	public Cloud duplicate(float newX, float newY) {
		Cloud c = new Cloud(new Rectangle2D.Float(0, 0, boundingBox.width, boundingBox.height));
		c.speed = speed;
		c.setLabel(getLabel());
		c.color = color;
		c.setX(newX - boundingBox.width / 2); // offset to the center, since this method is called to paste.
		c.setY(newY - boundingBox.height / 2);
		return c;
	}

	@Override
	public Cloud duplicate() {
		Cloud c = new Cloud(new Rectangle2D.Float(0, 0, boundingBox.width, boundingBox.height));
		c.speed = speed;
		c.setLabel(getLabel());
		c.color = color;
		c.x = x;
		c.y = y;
		return c;
	}

	public String toXml() {
		XmlCharacterEncoder xce = new XmlCharacterEncoder();
		String xml = "<cloud";
		String uid = getUid();
		if (uid != null && !uid.trim().equals(""))
			xml += " uid=\"" + xce.encode(uid) + "\"";
		String label = getLabel();
		if (label != null && !label.trim().equals(""))
			xml += " label=\"" + xce.encode(label) + "\"";
		if (!Color.WHITE.equals(color))
			xml += " color=\"" + Integer.toHexString(0x00ffffff & color.getRGB()) + "\"";
		xml += " x=\"" + x + "\"";
		xml += " y=\"" + y + "\"";
		xml += " width=\"" + boundingBox.width + "\"";
		xml += " height=\"" + boundingBox.height + "\"";
		xml += " speed=\"" + speed + "\"/>";
		return xml;
	}

}