package com.gamepatriot.framework2d.implementation;

import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;

import com.gamepatriot.androidframework.framework.AndroidAnimationData;
import com.gamepatriot.androidframework.framework.AndroidImage;
import com.gamepatriot.androidframework.framework.AndroidScreen;

/**
 * The Image class provides references to loaded application images so they can be displayed on a {@link Screen}. In addition, Image classes may consist of individual
 * {@link AnimationData} references to properly display and cycle through animations.
 * 
 * @see AndroidImage
 * @author Pete Schmitz, May 8, 2013
 *
 */
public class Image implements AndroidImage {
	
	/**
	 * Preset positions to anchor an Image at. See: {@link Image#setPivot(PivotPosition)}.
	 * 
	 * @author Pete Schmitz, May 8, 2013
	 *
	 */
	public static enum PivotPosition {
		LEFT_TOP(0.0f, 0.0f), 		CENTER_TOP(0.5f, 0.0f), 	RIGHT_TOP(1.0f, 0.0f),
		LEFT_MIDDLE(0.0f, 0.5f), 	CENTER_MIDDLE(0.5f, 0.5f), 	RIGHT_MIDDLE(1.0f, 0.5f),
		LEFT_BOTTOM(0.0f, 1.0f), 	CENTER_BOTTOM(0.5f, 1.0f), 	RIGHT_BOTTOM(1.0f, 1.0f);
		
		private float x;
		private float y;
		
		private PivotPosition(float $x, float $y){
			x = $x;
			y = $y;
		}
		
		public float getX(){
			return x;
		}
		
		public float getY(){
			return y;
		}
	}
	
	/** Screen that this image has been added to. **/
	private Screen parent;
	
	/** (Read-only) The decoded bitmap source this Image is using for its draw source. Generally a reference from {@link Atlas}. **/
	public Bitmap source;
	
	/** (Read-only) The rectangle area that is being drawn from the Image's source. **/
	public Rect srcRect;
	
	/** (Read-only) The requested destination area this Image should be drawn to. **/
	public RectF dstRect;
	
	/** (Read-only) Matrix that provides the destination draw area for more complex manipulations like scaling and rotation of the Image. **/
	public Matrix matrix;
	
	/** (Read-only) The pivot point this Image's position and rotation anchor around. Call {@link #setPivot(float, float)} to adjust. **/
	public PointF pivot;
	
	/** (Read-only) The current position of where this Image will be drawn at. Call {@link #setPosition(float, float)} to adjust. **/
	public PointF position;
	
	/** (Read-only) The X and Y axes scaling amounts for this Image. Must have allowed matrix. Call {@link #setScale(double)} to adjust. **/
	public PointF scale;
	
	/** (Read-only The alpha this image will be drawn at. Call {@link #setAlpha(float)} to adjust. **/
	public int alpha;
	
	/** (Read-only) The width of the source frame that is being drawn. **/
	public int frameWidth;
	
	/** (Read-only) The height of the source frame that is being drawn. **/
	public int frameHeight;
	
	/** Whether or not this Image should continue its animation automatically on a {@link Renderer}'s update pass. **/
	public boolean animate;
	
	/** (Read-only) Whether or not this Image should allow draw calls that involve scaling and rotation. Call {@link #setMatrix(boolean)} to adjust. **/
	public boolean allowMatrix;
	
	/** (Read-only) Matrix that contains the calculations for this Image's rotation, position, and scaling. **/
	public Bitmap matrixFrame;
	
	/** (Read-only) Rotation, in degrees, that this image should display at. Must have allowed matrix. Call {@link #setRotationDegrees(float)} to adjust. **/
	public float rotationDegrees = 0;
	
	/** (Read-only) Rotation, in radians, that this image should display at. Must have allowed matrix. Call {@link #setRotationRadians(float)} to adjust. **/
	public float rotationRadians = 0;
	
	/** Animation data associated with this Image object. Note: AnimationDatas should be unique per Image (never referenced). **/
	private AnimationData animation;
	
	/** (Read-only) Animation frame the image is currently on. If no {@link AnimationData} exists the value will default to 0. **/
	public int currentFrame;
	
	/** (Read-only) The total amount of animation frames associated with this Image. If no {@link AnimationData} exists the default value will be 1. **/
	public int totalFrames;
	
	public Image(){
		init();
	}
	
	public Image(Bitmap $source, boolean $allowMatrix){
		init();
		set($source, 0, 0, $source.getWidth(), $source.getHeight(), null, $allowMatrix);
	}
	
	public void init(){
		source = null;
		parent = null;
		animation = null;
		srcRect = new Rect();
		dstRect = new RectF();
		pivot = new PointF();
		position = new PointF();
		scale = new PointF();
	}

	@Override
	public void set(Bitmap $source, int $sourceX, int $sourceY,
			int $frameWidth, int $frameHeight, AndroidAnimationData $animation,
			boolean $allowMatrix) {
		allowMatrix = $allowMatrix;
		currentFrame = 0;
		animate = true;
		source = $source;
		frameWidth = $frameWidth;
		frameHeight = $frameHeight;
		animation = (AnimationData) $animation;
		totalFrames = (animation == null) ? 1 : animation.totalFrames;
		scale.x = 1;
		scale.y = 1;
		srcRect.set($sourceX, $sourceY, $sourceX + $frameWidth, $sourceY + $frameHeight);
		
		setMatrix($allowMatrix);
		setPivot(PivotPosition.LEFT_TOP);
		setPosition(0, 0);
		setRotationDegrees(0);
		setAlpha(1.0f);
	}

	@Override
	public void setPosition(float $x, float $y) {
		position.x = $x;
		position.y = $y;
		$x -= pivot.x;
		$y -= pivot.y;
		
		dstRect.left = $x;
		dstRect.top = $y;
		dstRect.right = $x + frameWidth;
		dstRect.bottom = $y + frameHeight;
		
		calculateMatrix();
	}

	@Override
	public void setSource(int $x, int $y) {
		srcRect.left = $x;
		srcRect.right = $x + frameWidth;
		srcRect.top = $y;
		srcRect.bottom = $y + frameHeight;
	}

	@Override
	public void setScaleX(float $x) {
		setScale($x, scale.y);
	}

	@Override
	public void setScaleY(float $y) {
		setScale(scale.x, $y);
	}

	@Override
	public void setScale(float $x, float $y) {
		if ($x < 0) $x = 0;
		if ($y < 0) $y = 0;
		
		scale.x = $x;
		scale.y = $y;
		
		calculateMatrix();
	}

	@Override
	public void setScale(float $amount) {
		setScale($amount, $amount);
	}
	
	@Override
	public void setScale(double $amount) {
		setScale((float) $amount);
	}

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

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

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

	@Override
	public void setParent(AndroidScreen $screen) {
		parent = (Screen) $screen;
	}

	@Override
	public boolean nextFrame() {
		if (animation == null || animation.totalFrames <= 1) return false;
		
		boolean $belowTotal = true;
		
		if (currentFrame >= animation.totalFrames - 1){
			currentFrame = 0;
			$belowTotal = false;
		} else {
			currentFrame++;
		}
		
		setSource(animation.frameX[currentFrame], animation.frameY[currentFrame]);
		
		updateMatrixFrame();
		
		return $belowTotal;
	}
	
	@Override
	public void frame(int $frame) {
		
		// Return if requesting the frame this image is already set to
		if ($frame == currentFrame) return;
		
		// Set global frame
		if ($frame < 0 || $frame > animation.totalFrames - 1) currentFrame = 0;
		else currentFrame = $frame;
		
		setSource(animation.frameX[currentFrame], animation.frameY[currentFrame]);
		updateMatrixFrame();
	}

	@Override
	public void setPivot(float $x, float $y) {
		pivot.x = $x;
		pivot.y = $y;
		
		setPosition(position.x, position.y);
	}
	
	/**
	 * Set the pivot point this image will anchor its display and rotation at.
	 * @param $position		The {@link PivotPosition} to anchor this Image at.
	 */
	public void setPivot(PivotPosition $position) {
		if (source == null){
			setPivot(0, 0);
			return;
		}
		
		setPivot($position.getX() * frameWidth, $position.getY() * frameHeight);
		
//		float $width = srcRect.width();
//		float $height = srcRect.height();
//		
//		
//		switch ($position){
//		case LEFT_TOP:
//			setPivot(0, 0);
//			break;
//			
//		case CENTER_TOP:
//			setPivot($width/2, 0);
//			break;
//			
//		case RIGHT_TOP:
//			setPivot($width, 0);
//			break;
//			
//		case LEFT_MIDDLE:
//			setPivot(0, $height/2);
//			break;
//			
//		case CENTER_MIDDLE:
//			setPivot($width/2, $height/2);
//			break;
//			
//		case RIGHT_MIDDLE:
//			setPivot($width, $height/2);
//			break;
//			
//		case LEFT_BOTTOM:
//			setPivot(0, $height);
//			break;
//			
//		case CENTER_BOTTOM:
//			setPivot($width/2, $height);
//			break;
//			
//		case RIGHT_BOTTOM:
//			setPivot($width, $height);
//			break;
//		}
	}

	@Override
	public void setRotationDegrees(float $degrees) {
		if (!allowMatrix) return;
		
		while($degrees > 360) $degrees -= 360;
		while ($degrees < -360) $degrees += 360;
		
		rotationDegrees = $degrees;
		rotationRadians = (float) ($degrees * (Math.PI/180));
		
		calculateMatrix();
	}

	@Override
	public void setRotationRadians(float $radians) {
		if (!allowMatrix) return;
		
		double $circ = Math.PI * 2;
		while ($radians > $circ) $radians -= $circ;
		while ($radians < -$circ) $radians += $circ;
		
		rotationDegrees = (float) ($radians * (180/Math.PI));
		rotationRadians = $radians;
		
		calculateMatrix();
	}
	
	@Override
	public void setAlpha(float $amount){
		alpha = (int) ($amount * 255);
	}

	@Override
	public void setMatrix(boolean $allowMatrix) {
		allowMatrix = $allowMatrix;
		
		if (matrixFrame != null){
			matrixFrame.recycle();
			matrixFrame = null;
		}
		if ($allowMatrix){
			if (source == null) throw new RuntimeException("Source is not set on image!");
			if (srcRect.left != 0 || srcRect.top != 0 || frameWidth != source.getWidth() || frameHeight != source.getHeight()){
				matrixFrame = Bitmap.createBitmap(frameWidth, frameHeight, Main.CONFIG);
			} else {
				matrixFrame = source;
			}
			
			if (matrix == null){
				matrix = new Matrix();
			}
			
			updateMatrixFrame();
			calculateMatrix();
		}
	}
	
	/** Apply the scale, translation, and rotation contained in this Image to a matrix so the matrix can be used to display the Image's transformation. **/
	private void calculateMatrix(){
		if (!allowMatrix) return;
		
		matrix.reset();
		
		matrix.setScale(scale.x, scale.y);
		matrix.postTranslate(position.x - (pivot.x * scale.x), position.y - (pivot.y * scale.y));
		matrix.postRotate(rotationDegrees, position.x, position.y);
		
	}
	
	/** Assign the matrix frame to the appropriate location. (Call this after any change to the source, source rectangle, or frame dimensions) **/
	private void updateMatrixFrame(){
		if (!allowMatrix || matrixFrame == null || matrixFrame == source) return;
		
		int[] $pixels = new int[frameWidth * frameHeight];
		source.getPixels($pixels, 0, frameWidth, srcRect.left, srcRect.top, frameWidth, frameHeight);
		matrixFrame.setPixels($pixels, 0, frameWidth, 0, 0, frameWidth, frameHeight);
	}
	
	
	
}
