package com.gamepatriot.framework2d.implementation;

import java.util.ArrayList;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;

import com.gamepatriot.androidframework.framework.AndroidBasicShape;
import com.gamepatriot.androidframework.framework.AndroidScreen;
import com.gamepatriot.androidframework.framework.AndroidShape;

/** 
 * The shape class contains individual shape references in the form of {@link BasicShape}s. These basic shapes are stored in a display list so that they may 
 * be displayed to a screen by a {@link Renderer}.
 * 
 * @see AndroidShape
 * @author Pete Schmitz, May 9, 2013
 *
 */
public class Shape implements AndroidShape {
	
	/** 
	 * The ShapeType enum provides flags for acceptable shapes during a shape's draw call ({@link Shape#draw(Canvas)}).
	 * 
	 * @author Pete Schmitz, May 9, 2013
	 *
	 */
	public static enum ShapeType{
		LINE, CIRCLE, RECTANGLE, POLY, TEXT;
	}
	
	
	//Containers
	
	/** (Read-only) The position of this shape. Children basic shapes are offset by this number. Call {@link #setPosition(int, int)} to modify. **/
	public Point position;
	
	/** (Read-only) The display list for this shape. Us thee various add/remove/clear calls in the {@link Shape} class to manipulate the display contents and order. **/
	public ArrayList<BasicShape> basicShapes;
	
	/** The default paint of this shape **/
	private Paint paint;
	
	
	//Flags
	
	/** Parent reference that this shape is a child of; null if no parent. **/
	private Screen parent;
	
	/** Whether or not shadows are enabled for basic shapes. **/
	private boolean shadowsEnabled;
	
	/** The amount of radius to apply on basic shapes' shadows. **/
	private float shadowRadius;
	
	/** The X-offset of the shadow being applied. **/
	private float shadowOffsetX;
	
	/** The Y-offset of the shadow being applied. **/
	private float shadowOffsetY;
	
	/** The color of the shadow being applied. **/
	private int shadowColor;
	
	
	public Shape(){
		init();
		setDefaultPaint();
	}
	
	public Shape(Paint $paint){
		init();
		paint = $paint;
	}
	
	public void init(){
		parent = null;
		shadowsEnabled = false;
		
		position = new Point();
		basicShapes = new ArrayList<BasicShape>(0);
	}
	
	@Override
	public void setPosition(int $x, int $y) {
		position.x = $x;
		position.y = $y;
		
		for (BasicShape $basicShape : basicShapes){
			if ($basicShape.path != null){
				$basicShape.buildPath($x, $y);
			}
		}
	}

	@Override
	public void setDefaultPaint() {
		paint = new Paint();
		paint.setColor(Color.WHITE);
		paint.setStyle(Style.STROKE);
		paint.setAlpha((int) (255 * .9));
		paint.setAntiAlias(Main.ALLOW_ANTI_ALIASING);
		paint.setDither(Main.ALLOW_DITHER);
		paint.setFilterBitmap(Main.ALLOW_ANTI_ALIASING || Main.ALLOW_DITHER);
	}
	
	@Override
	public void enableShadows(float $radius, float $offsetX, float $offsetY, int $color) {
		shadowsEnabled = true;
		
		shadowRadius = $radius;
		shadowOffsetX = $offsetX;
		shadowOffsetY = $offsetY;
		shadowColor = $color;
		
		for (BasicShape $bs : basicShapes) checkShadow($bs.paint);
	}
	
	@Override
	public void disableShadows(){
		shadowsEnabled = false;
		
		for (BasicShape $bs : basicShapes) checkShadow($bs.paint);
	}

	@Override
	public void draw(Canvas $canvas) {
		
		for (BasicShape $shape : basicShapes){
			switch ($shape.type){
			case CIRCLE:
				$canvas.drawCircle($shape.points[0] + position.x, $shape.points[1] + position.y, $shape.radius, $shape.paint == null ? paint : $shape.paint);
				break;
				
			case LINE:
				$canvas.drawLine($shape.points[0] + position.x, $shape.points[1] + position.y, $shape.points[2] + position.x, $shape.points[3] + position.y, $shape.paint == null ? paint : $shape.paint);
				break;
				
			case RECTANGLE:
				$canvas.drawRect($shape.points[0] + position.x, $shape.points[1] + position.y, $shape.points[2] + position.x, $shape.points[3] + position.y, $shape.paint == null ? paint : $shape.paint);
				break;
				
			case TEXT:
				if ($shape.paint == null) paint.setTextSize($shape.radius);
				else $shape.paint.setTextSize($shape.radius);
				$canvas.drawText($shape.text, $shape.points[0] + position.x, $shape.points[1] + position.y, $shape.paint == null ? paint : $shape.paint);
				break;
				
			case POLY:
				$canvas.drawPath($shape.path, $shape.paint == null ? paint : $shape.paint);
				break;
			}
		}
	}

	@Override
	public void clear() {
		basicShapes.clear();
	}

	@Override
	public AndroidBasicShape addCircle(Point $p, float $radius) {
		return addCircle($p.x, $p.y, $radius, null);
	}

	@Override
	public AndroidBasicShape addCircle(PointF $p, float $radius) {
		return addCircle((int) $p.x, (int) $p.y, $radius, null);
	}

	@Override
	public AndroidBasicShape addCircle(int $x, int $y, float $radius) {
		return addCircle($x, $y, $radius, null);
	}
	
	@Override
	public AndroidBasicShape addCircle(int $x, int $y, float $radius, Paint $paint) {
		BasicShape $bShape = new BasicShape();
		checkShadow($paint);
		$bShape.setAsCircle($x, $y, $radius, $paint);
		basicShapes.add($bShape);
		
		return $bShape;
	}
	
	@Override
	public AndroidBasicShape addCircle(Point $p, float $radius, Paint $paint) {
		return addCircle($p.x, $p.y, $radius, $paint);
	}
	
	@Override
	public AndroidBasicShape addCircle(PointF $p, float $radius, Paint $paint) {
		return addCircle((int) $p.x, (int) $p.y, $radius, $paint);
	}

	@Override
	public AndroidBasicShape addRect(Rect $r) {
		return addRect($r.left, $r.top, $r.width(), $r.height(), null);
	}

	@Override
	public AndroidBasicShape addRect(RectF $r) {
		return addRect((int) $r.left, (int) $r.top, (int) $r.width(), (int) $r.height(), null);
	}

	@Override
	public AndroidBasicShape addRect(int $x, int $y, int $width, int $height) {
		return addRect($x, $y, $width, $height, null);
	}
	
	@Override
	public AndroidBasicShape addRect(int $x, int $y, int $width, int $height, Paint $paint) {
		BasicShape $bShape = new BasicShape();
		checkShadow($paint);
		$bShape.setAsRectangle($x, $y, $x + $width, $y + $height, $paint);
		basicShapes.add($bShape);
		
		return $bShape;
	}
	
	@Override
	public AndroidBasicShape addRect(Rect $r, Paint $paint) {
		return addRect($r.left, $r.top, $r.width(), $r.height(), $paint);
	}
	
	@Override
	public AndroidBasicShape addRect(RectF $r, Paint $paint) {
		return addRect((int) $r.left, (int) $r.top, (int) $r.width(), (int) $r.height(), $paint);
	}

	@Override
	public AndroidBasicShape addLine(Point $p1, Point $p2) {
		return addLine($p1.x, $p1.y, $p2.x, $p2.y, null);
	}

	@Override
	public AndroidBasicShape addLine(PointF $p1, PointF $p2) {
		return addLine((int) $p1.x, (int) $p1.y, (int) $p2.x, (int) $p2.y, null);
	}

	@Override
	public AndroidBasicShape addLine(int $x1, int $y1, int $x2, int $y2) {
		return addLine($x1, $y1, $x2, $y2, null);
	}
	
	@Override
	public AndroidBasicShape addLine(int $x1, int $y1, int $x2, int $y2, Paint $paint) {
		BasicShape $bShape = new BasicShape();
		checkShadow($paint);
		$bShape.setAsLine($x1, $y1, $x2, $y2, $paint);
		basicShapes.add($bShape);
		
		return $bShape;
	}
	
	@Override
	public AndroidBasicShape addLine(Point $p1, Point $p2, Paint $paint) {
		return addLine($p1.x, $p1.y, $p2.x, $p2.y, $paint);
	}
	
	@Override
	public AndroidBasicShape addLine(PointF $p1, PointF $p2, Paint $paint) {
		return addLine((int) $p1.x, (int) $p1.y, (int) $p2.x, (int) $p2.y, $paint);
	}

	@Override
	public AndroidBasicShape addText(String $text, float $size, Point $p) {
		return addText($text, $size, $p.x, $p.y, null);
	}

	@Override
	public AndroidBasicShape addText(String $text, float $size, PointF $p) {
		return addText($text, $size, (int) $p.x, (int) $p.y, null);
	}

	@Override
	public AndroidBasicShape addText(String $text, float $size, int $x, int $y) {
		return addText($text, $size, $x, $y, null);
	}
	
	@Override
	public AndroidBasicShape addText(String $text, float $size, int $x, int $y, Paint $paint) {
		BasicShape $bShape = new BasicShape();
		checkShadow($paint);
		$bShape.setAsText($text, $size, $x, $y, $paint);
		basicShapes.add($bShape);
		
		return $bShape;
	}
	
	@Override
	public AndroidBasicShape addText(String $text, float $size, Point $p, Paint $paint) {
		return addText($text, $size, $p.x, $p.y, $paint);
	}
	
	@Override
	public AndroidBasicShape addText(String $text, float $size, PointF $p, Paint $paint) {
		return addText($text, $size, (int) $p.x, (int) $p.y, $paint);
	}
	
	@Override
	public AndroidBasicShape addPoly(int $x, int $y, int $numberPoints, float $degreeAngle,
			float $radius) {
		return addPoly($x, $y, $numberPoints, $degreeAngle, $radius, null);
	}
	
	@Override
	public AndroidBasicShape addPoly(float[] $points) {
		return addPoly($points, null);
	}
	
	@Override
	public AndroidBasicShape addPoly(float[] $points, Paint $paint) {
		BasicShape $bShape = new BasicShape();
		checkShadow($paint);
		$bShape.setAsPoly($points, $paint);
		basicShapes.add($bShape);
		
		return $bShape;
	}
	
	@Override
	public AndroidBasicShape addPoly(int $x, int $y, int $numberPoints, float $degreeAngle,
			float $radius, Paint $paint) {
		float[] $points = new float[$numberPoints * 2];
		int $i;
		float $x1;
		float $y1;
		float $angle;
		
		for ($i = 0; $i < $numberPoints;  $i++){
			
			$angle = (float)(Math.PI * 2 *  ((float) $i/ (float) $numberPoints));
			$angle += $degreeAngle;
			$x1 = (float) ($x + Math.cos($angle) * $radius);
			$y1 = (float) ($y + Math.sin($angle) * $radius);
			
			$points[$i * 2] = $x1;
			$points[$i * 2 + 1] = $y1;
		}
		
		return addPoly($points, $paint);
	}

	@Override
	public boolean removeBasicShape(AndroidBasicShape $bShape) {
		return basicShapes.remove($bShape);
	}

	@Override
	public AndroidScreen getParent() {
		return parent;
	}

	@Override
	public void removeFromParent() {
		if (parent == null) return;
		
		parent.removeShape(this);
	}

	@Override
	public boolean hasParent() {
		return !(parent == null);
	}

	@Override
	public void setParent(AndroidScreen $screen) {
		parent = (Screen) $screen;
	}
	
	/** Apply or disable shadows for the supplied paint. **/
	private void checkShadow(Paint $paint){
		if ($paint == null) return;
		
		if (!shadowsEnabled) $paint.clearShadowLayer();
		else $paint.setShadowLayer(shadowRadius, shadowOffsetX, shadowOffsetY, shadowColor);
	}
	
	
	
}
