package net.flashpunk.graphics 
{
	import flash.display.BitmapData;
	import flash.display.Graphics;
	import flash.geom.ColorTransform;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import net.flashpunk.FP;
	import net.flashpunk.Graphic;
	
	/**
	 * A  multi-purpose drawing canvas, can be sized beyond the normal Flash BitmapData limits.
	 */
	public class Canvas extends Graphic
	{
		/**
		 * Optional blend mode to use (see flash.display.BlendMode for blending modes).
		 */
		public var blend:String;
		
		/**
		 * Constructor.
		 * @param	width		Width of the canvas.
		 * @param	height		Height of the canvas.
		 */
		public function Canvas(width:uint, height:uint) 
		{
			_width = width;
			_height = height;
			_refWidth = Math.ceil(width / _maxWidth);
			_refHeight = Math.ceil(height / _maxHeight);
			_ref = new BitmapData(_refWidth, _refHeight, false, 0);
			var x:uint, y:uint, w:uint, h:uint, i:uint,
				ww:uint = _width % _maxWidth,
				hh:uint = _height % _maxHeight;
			if (!ww) ww = _maxWidth;
			if (!hh) hh = _maxHeight;
			while (y < _refHeight)
			{
				h = y < _refHeight - 1 ? _maxHeight : hh;
				while (x < _refWidth)
				{
					w = x < _refWidth - 1 ? _maxWidth : ww;
					_ref.setPixel(x, y, i);
					_buffers[i] = new BitmapData(w, h, true, 0);
					i ++; x ++;
				}
				x = 0; y ++;
			}
		}
		
		/** @private Renders the canvas. */
		override public function render(target:BitmapData, point:Point, camera:Point):void 
		{
			// determine drawing location
			_point.x = point.x + x - camera.x * scrollX;
			_point.y = point.y + y - camera.y * scrollY;
			
			// render the buffers
			var xx:int, yy:int, buffer:BitmapData, px:Number = _point.x;
			while (yy < _refHeight)
			{
				while (xx < _refWidth)
				{
					buffer = _buffers[_ref.getPixel(xx, yy)];
					if (_tint || blend)
					{
						_matrix.tx = _point.x;
						_matrix.ty = _point.y;
						target.draw(buffer, _matrix, _tint, blend);
					}
					else target.copyPixels(buffer, buffer.rect, _point, null, null, true);
					_point.x += _maxWidth;
					xx ++;
				}
				_point.x = px;
				_point.y += _maxHeight;
				xx = 0;
				yy ++;
			}
		}
		
		/**
		 * Draws to the canvas.
		 * @param	x			X position to draw.
		 * @param	y			Y position to draw.
		 * @param	source		Source BitmapData.
		 * @param	rect		Optional area of the source image to draw from. If null, the entire BitmapData will be drawn.
		 */
		public function draw(x:int, y:int, source:BitmapData, rect:Rectangle = null):void
		{
			var xx:int, yy:int;
			for each (var buffer:BitmapData in _buffers)
			{
				_point.x = x - xx;
				_point.y = y - yy;
				buffer.copyPixels(source, rect ? rect : source.rect, _point, null, null, true);
				xx += _maxWidth;
				if (xx >= _width)
				{
					xx = 0;
					yy += _maxHeight;
				}
			}
		}
		
		/**
		 * Fills the rectangular area of the canvas. The previous contents of that area are completely removed.
		 * @param	rect		Fill rectangle.
		 * @param	color		Fill color.
		 * @param	alpha		Fill alpha.
		 */
		public function fill(rect:Rectangle, color:uint = 0, alpha:Number = 1):void
		{
			var xx:int, yy:int, buffer:BitmapData;
			_rect.width = rect.width;
			_rect.height = rect.height;
			if (alpha >= 1) color |= 0xFF000000;
			else if (alpha <= 0) color = 0;
			else color = (uint(alpha * 255) << 24) | (0xFFFFFF & color);
			for each (buffer in _buffers)
			{
				_rect.x = rect.x - xx;
				_rect.y = rect.y - yy;
				buffer.fillRect(_rect, color);
				xx += _maxWidth;
				if (xx >= _width)
				{
					xx = 0;
					yy += _maxHeight;
				}
			}
		}
		
		/**
		 * Draws over a rectangular area of the canvas.
		 * @param	rect		Drawing rectangle.
		 * @param	color		Draw color.
		 * @param	alpha		Draw alpha. If < 1, this rectangle will blend with existing contents of the canvas.
		 */
		public function drawRect(rect:Rectangle, color:uint = 0, alpha:Number = 1):void
		{
			var xx:int, yy:int, buffer:BitmapData;
			if (alpha >= 1)
			{
				_rect.width = rect.width;
				_rect.height = rect.height;
				
				for each (buffer in _buffers)
				{
					_rect.x = rect.x - xx;
					_rect.y = rect.y - yy;
					buffer.fillRect(_rect, 0xFF000000 | color);
					xx += _maxWidth;
					if (xx >= _width)
					{
						xx = 0;
						yy += _maxHeight;
					}
				}
				return;
			}
			for each (buffer in _buffers)
			{
				_graphics.clear();
				_graphics.beginFill(color, alpha);
				_graphics.drawRect(rect.x - xx, rect.y - yy, rect.width, rect.height);
				buffer.draw(FP.sprite);
				xx += _maxWidth;
				if (xx >= _width)
				{
					xx = 0;
					yy += _maxHeight;
				}
			}
			_graphics.endFill();
		}
		
		/**
		 * Fills the rectangle area of the canvas with the texture.
		 * @param	rect		Fill rectangle.
		 * @param	texture		Fill texture.
		 */
		public function fillTexture(rect:Rectangle, texture:BitmapData):void
		{
			var xx:int, yy:int;
			for each (var buffer:BitmapData in _buffers)
			{
				_graphics.clear();
				_graphics.beginBitmapFill(texture);
				_graphics.drawRect(rect.x - xx, rect.y - yy, rect.width, rect.height);
				buffer.draw(FP.sprite);
				xx += _maxWidth;
				if (xx >= _width)
				{
					xx = 0;
					yy += _maxHeight;
				}
			}
			_graphics.endFill();
		}
		
		/**
		 * Draws the Graphic object to the canvas.
		 * @param	x			X position to draw.
		 * @param	y			Y position to draw.
		 * @param	source		Graphic to draw.
		 */
		public function drawGraphic(x:int, y:int, source:Graphic):void
		{
			var xx:int, yy:int;
			for each (var buffer:BitmapData in _buffers)
			{
				_point.x = x - xx;
				_point.y = y - yy;
				source.render(buffer, _point, FP.zero);
				xx += _maxWidth;
				if (xx >= _width)
				{
					xx = 0;
					yy += _maxHeight;
				}
			}
		}
		
		/**
		 * The tinted color of the Canvas. Use 0xFFFFFF to draw the it normally.
		 */
		public function get color():uint { return _color; }
		public function set color(value:uint):void
		{
			value %= 0xFFFFFF;
			if (_color == value) return;
			_color = value;
			if (_alpha == 1 && _color == 0xFFFFFF)
			{
				_tint = null;
				return;
			}
			_tint = _colorTransform;
			_tint.redMultiplier = (_color >> 16 & 0xFF) / 255;
			_tint.greenMultiplier = (_color >> 8 & 0xFF) / 255;
			_tint.blueMultiplier = (_color & 0xFF) / 255;
			_tint.alphaMultiplier = _alpha;
		}
		
		/**
		 * Change the opacity of the Canvas, a value from 0 to 1.
		 */
		public function get alpha():Number { return _alpha; }
		public function set alpha(value:Number):void
		{
			if (value < 0) value = 0;
			if (value > 1) value = 1;
			if (_alpha == value) return;
			_alpha = value;
			if (_alpha == 1 && _color == 0xFFFFFF)
			{
				_tint = null;
				return;
			}
			_tint = _colorTransform;
			_tint.redMultiplier = (_color >> 16 & 0xFF) / 255;
			_tint.greenMultiplier = (_color >> 8 & 0xFF) / 255;
			_tint.blueMultiplier = (_color & 0xFF) / 255;
			_tint.alphaMultiplier = _alpha;
		}
		
		/**
		 * Shifts the canvas' pixels by the offset.
		 * @param	x	Horizontal shift.
		 * @param	y	Vertical shift.
		 */
		public function shift(x:int = 0, y:int = 0):void
		{
			drawGraphic(x, y, this);
		}
		
		/**
		 * Width of the canvas.
		 */
		public function get width():uint { return _width; }
		
		/**
		 * Height of the canvas.
		 */
		public function get height():uint { return _height; }
		
		// Buffer information.
		/** @private */ private var _buffers:Vector.<BitmapData> = new Vector.<BitmapData>;
		/** @private */ protected var _width:uint;
		/** @private */ protected var _height:uint;
		/** @private */ protected var _maxWidth:uint = 4000;
		/** @private */ protected var _maxHeight:uint = 4000;
		
		// Color tinting information.
		/** @private */ private var _color:uint = 0xFFFFFF;
		/** @private */ private var _alpha:Number = 1;
		/** @private */ private var _tint:ColorTransform;
		/** @private */ private var _colorTransform:ColorTransform = new ColorTransform;
		/** @private */ private var _matrix:Matrix = new Matrix;
		
		// Canvas reference information.
		/** @private */ private var _ref:BitmapData;
		/** @private */ private var _refWidth:uint;
		/** @private */ private var _refHeight:uint;
		
		// Global objects.
		/** @private */ private var _rect:Rectangle = new Rectangle;
		/** @private */ private var _graphics:Graphics = FP.sprite.graphics;
	}
}