/*
 * PanoramaGL library
 * Version 0.2 beta
 * Copyright (c) 2010 Javier Baez <[email protected]>
 *
 * Licensed under the Apache 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.apache.org/licenses/LICENSE-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 com.panoramagl;

import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import com.panoramagl.ios.structs.CGRect;
import com.panoramagl.ios.structs.CGSize;
import com.panoramagl.utils.PLUtils;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;

public class PLImage implements PLIImage
{
	/**member variables*/
	
	private Bitmap mBitmap;
	private int mWidth, mHeight;
	private boolean mIsRecycled, mIsLoaded;
	
	/**init methods*/
	
	public PLImage()
	{
		super();
		mBitmap = null;
		mWidth = mHeight = 0;
		mIsRecycled = mIsLoaded = false;
	}
	
	public PLImage(Bitmap bitmap)
	{
		this(bitmap, true);
	}
	
	public PLImage(Bitmap bitmap, boolean copy)
	{
		super();
		this.createWithBitmap(bitmap, copy);
	}
	
	public PLImage(CGSize size)
	{
		this(size.width, size.height);
	}
	
	public PLImage(int width, int height)
	{
		super();
		this.createWithSize(width, height);	
	}
	
	public PLImage(String path)
	{
		super();
		this.createWithPath(path, Bitmap.Config.ARGB_8888);
	}
	
	public PLImage(String path, Bitmap.Config config)
	{
		super();
		this.createWithPath(path, config);
	}
	
	public PLImage(byte[] buffer)
	{
		super();
		this.createWithBuffer(buffer);
	}
	
	/**create methods*/
	
	protected void createWithPath(String path, Bitmap.Config config)
	{
		BitmapFactory.Options options = new BitmapFactory.Options();
		options.inDither = true;
		options.inPreferredConfig = config;
		mBitmap = BitmapFactory.decodeFile(path, options);
		mWidth = mBitmap.getWidth();
		mHeight = mBitmap.getHeight();
		mIsRecycled = false;
		mIsLoaded = true;
	}
	
	protected void createWithBitmap(Bitmap bitmap, boolean copy)
	{
		mWidth = bitmap.getWidth();
		mHeight = bitmap.getHeight();
		mBitmap = (copy ? Bitmap.createBitmap(bitmap) : bitmap);
		mIsRecycled = false;
		mIsLoaded = true;
	}
	
	protected void createWithSize(int width, int height)
	{
		this.deleteImage();
		this.createWithBitmap(Bitmap.createBitmap(null, 0, 0, width, height), false);
	}
	
	protected void createWithBuffer(byte[] buffer)
	{
		if(buffer != null)
		{
			mBitmap = BitmapFactory.decodeByteArray(buffer, 0, buffer.length);
			mWidth = mBitmap.getWidth();
			mHeight = mBitmap.getHeight();
			mIsRecycled = false;
			mIsLoaded = true;
		}
	}
	
	/**property methods*/
	
	@Override
	public int getWidth()
	{
		return mWidth;
	}
	
	@Override
	public int getHeight()
	{
		return mHeight;
	}
	
	@Override
	public CGSize getSize()
	{
		return CGSize.CGSizeMake(mWidth, mHeight);
	}
	
	@Override
	public CGRect getRect()
	{
		return CGRect.CGRectMake(0, 0, mWidth, mHeight);
	}
	
	@Override
	public int getCount()
	{
		return (mWidth * mHeight * 4);
	}
	
	@Override
	public Bitmap getBitmap()
	{
		return mBitmap;
	}
	
	@Override
	public ByteBuffer getBits()
	{
		if(this.isValid())
			return null;
		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
		mBitmap.compress(Bitmap.CompressFormat.PNG, 100, buffer);
		ByteBuffer byteBuffer = ByteBuffer.wrap(buffer.toByteArray());
		byteBuffer.order(ByteOrder.BIG_ENDIAN);
		return byteBuffer;
	}
	
	@Override
	public boolean isValid()
	{
		return (mBitmap != null && !mBitmap.isRecycled());
	}
	
	@Override
	public boolean isRecycled()
	{
		return mIsRecycled;
	}
	
	@Override
	public boolean isLoaded()
	{
		return mIsLoaded;
	}
	
	/**operation methods*/
	
	@Override
	public boolean equals(PLIImage image)
	{	
		if(image.getBitmap() == mBitmap)
			return true;
		if(image.getBitmap() == null || mBitmap == null || image.getHeight() != mHeight || image.getWidth() != mWidth)
			return false;
		ByteBuffer bits = image.getBits();
		ByteBuffer _bits = this.getBits();
		for(int i = 0; i < this.getCount(); i++)
		{
			if(bits.get() != _bits.get())
				return false;
		}
		return true;
	}
	
	@Override
	public PLIImage assign(Bitmap bitmap, boolean copy)
	{
		this.deleteImage();
		this.createWithBitmap(bitmap, copy);
		return this;
	}
	
	@Override
	public PLIImage assign(PLIImage image, boolean copy)
	{
		this.deleteImage();
		this.createWithBitmap(image.getBitmap(), copy);
		return this;
	}
	
	@Override
	public PLIImage assign(byte[] buffer, boolean copy)
	{
		this.deleteImage();
		this.createWithBuffer(buffer);
		return this;
	}
	
	/**crop methods*/
	
	@Override
	public PLIImage crop(CGRect rect)
	{
		return this.crop(rect.x, rect.y, rect.width, rect.height);
	}
	
	@Override
	public PLIImage crop(int x, int y, int width, int height)
	{
		Bitmap croppedBitmap = Bitmap.createBitmap(width, height, mBitmap.getConfig());
		Canvas canvas = new Canvas(croppedBitmap);
		canvas.drawBitmap(mBitmap, new Rect(x, y, x + width, y + height), new Rect(0, 0, width, height), null);
		this.deleteImage();
		this.createWithBitmap(croppedBitmap, false);
		return this;
	}
	
	public static PLIImage crop(PLIImage image, int x, int y, int width, int height)
	{
		Bitmap source = image.getBitmap();
		Bitmap dest = Bitmap.createBitmap(width, height, source.getConfig());
		Canvas canvas = new Canvas(dest);
		canvas.drawBitmap(source, new Rect(x, y, x + width, y + height), new Rect(0, 0, width, height), null);
		return new PLImage(dest, false);
	}
	
	/**scale methods*/
	
	@Override
	public PLIImage scale(CGSize size)
	{
		return this.scale(size.width, size.height);
	}
	
	@Override
	public PLIImage scale(int width, int height)
	{
		if((width < 0 || height < 0) || (width == 0 && height == 0) || (width == mWidth && height == mHeight))
			return this;
		Bitmap image = Bitmap.createScaledBitmap(mBitmap, width, height, true);
	    this.deleteImage();
	    this.createWithBitmap(image, false);
		return this;
	}
	
	/**rotate methods*/
	
	@Override
	public PLIImage rotate(int angle)
	{
		if((angle % 90) != 0)
			return this;
		Matrix matrix = new Matrix();
		matrix.preRotate(angle);
    	Bitmap image = Bitmap.createBitmap(mBitmap, 0, 0, mWidth, mHeight, matrix, true);
    	this.deleteImage();
    	this.createWithBitmap(image, false);
    	return this;
	}
	
	@Override
	public PLIImage rotate(float degrees, float px, float py)
	{
		Matrix matrix = new Matrix();
		matrix.preRotate(degrees, px, py);
    	Bitmap image = Bitmap.createBitmap(mBitmap, 0, 0, mWidth, mHeight, matrix, true);
    	this.deleteImage();
    	this.createWithBitmap(image, false);
    	return this;
	}
	
	/**mirror methods*/
	
	@Override
	public PLIImage mirrorHorizontally()
	{
		return this.mirror(true, false);
	}
	
	@Override
	public PLIImage mirrorVertically()
	{
		return this.mirror(false,true);
	}
	
	@Override
	public PLIImage mirror(boolean horizontally, boolean vertically)
	{
		//-1,1 Horizontal, 1, -1 Vertical, Both = -1,-1
		Matrix matrix = new Matrix();
		matrix.preScale(horizontally ? -1.0f : 1.0f, vertically ? -1.0f : 1.0f);
		Bitmap image = Bitmap.createBitmap(mBitmap , 0, 0, mWidth, mHeight, matrix, false);
		this.deleteImage();
		this.createWithBitmap(image, false);
		return this;
	}
	
	/**sub-image methods*/
	
	@Override
	public Bitmap getSubImage(CGRect rect)
	{
		return this.getSubImage(rect.x, rect.y, rect.width, rect.height);
	}
	
	@Override
	public Bitmap getSubImage(int x, int y, int width, int height)
	{
		int pixels[] = new int[width * height];
		mBitmap.getPixels(pixels, 0, width, x, y, width, height);
		return Bitmap.createBitmap(pixels, 0, width, width, height, mBitmap.getConfig());
	}
	
	public static PLIImage joinImagesHorizontally(PLIImage leftImage, PLIImage rightImage)
	{
	    if(leftImage != null && leftImage.isValid() && rightImage != null && rightImage.isValid())
	    {
	    	Bitmap bitmap = Bitmap.createBitmap(leftImage.getWidth() + rightImage.getWidth(), (leftImage.getHeight() > rightImage.getHeight() ? leftImage.getHeight() : rightImage.getHeight()), Bitmap.Config.ARGB_8888);
	    	Canvas canvas = new Canvas(bitmap);
	    	canvas.drawBitmap(leftImage.getBitmap(), 0.0f, 0.0f, null);
	    	canvas.drawBitmap(rightImage.getBitmap(), leftImage.getWidth(), 0.0f, null);
	    	canvas.save();
	    	return new PLImage(bitmap, false);
	    }
	    return null;
	}
	
	/**recycle methods*/
	
	@Override
	public void recycle()
	{
		if(!mIsRecycled)
			this.deleteImage();
	}
	
	/**delete methods*/
	
	protected void deleteImage()
	{
		if(mBitmap != null)
		{
			if(PLUtils.getAndroidVersion() < 3.0f && !mBitmap.isRecycled())
				mBitmap.recycle();
			mBitmap = null;
			mIsRecycled = true;
			mIsLoaded = false;
		}
	}
	
	/**clone methods*/
	
	@Override
	public Bitmap cloneBitmap()
	{
		return Bitmap.createBitmap(mBitmap);
	}
	
	@Override
	public PLIImage clone()
	{
		return new PLImage(mBitmap, true);
	}
	
	/**dealloc methods*/
	
	@Override
	protected void finalize() throws Throwable
	{
		this.deleteImage();
		super.finalize();
	}
}