package net.flashpunk.utils 
{
    import flash.display.BitmapData;
    import flash.display.Graphics;
    import flash.display.LineScaleMode;
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import net.flashpunk.Entity;
    import net.flashpunk.FP;
    import net.flashpunk.Graphic;
    
    /**
     * Static class with access to miscellanious drawing functions.
     * These functions are not meant to replace Graphic components
     * for Entities, but rather to help with testing and debugging.
     */
    public class Draw 
    {
        /**
         * The blending mode used by Draw functions. This will not
         * apply to Draw.line(), but will apply to Draw.linePlus().
         */
        public static var blend:String;
        
        /**
         * Sets the drawing target for Draw functions.
         * @param    target        The buffer to draw to.
         * @param    camera        The camera offset (use null for none).
         * @param    blend        The blend mode to use.
         */
        public static function setTarget(target:BitmapData, camera:Point = null, blend:String = null):void
        {
            _target = target;
            _camera = camera ? camera : FP.zero;
            Draw.blend = blend;
        }
        
        /**
         * Resets the drawing target to the default. The same as calling Draw.setTarget(FP.buffer, FP.camera).
         */
        public static function resetTarget():void
        {
            _target = FP.buffer;
            _camera = FP.camera;
            Draw.blend = null;
        }
        
        /**
         * Draws a pixelated, non-antialiased line.
         * @param    x1        Starting x position.
         * @param    y1        Starting y position.
         * @param    x2        Ending x position.
         * @param    y2        Ending y position.
         * @param    color    Color of the line.
         */
        public static function line(x1:int, y1:int, x2:int, y2:int, color:uint = 0xFFFFFF):void
        {
            if (color < 0xFF000000) color = 0xFF000000 | color;
            
            // get the drawing positions
            x1 -= _camera.x;
            y1 -= _camera.y;
            x2 -= _camera.x;
            y2 -= _camera.y;
            
            // get the drawing difference
            var screen:BitmapData = _target,
                X:Number = Math.abs(x2 - x1),
                Y:Number = Math.abs(y2 - y1),
                xx:int,
                yy:int;
            
            // draw a single pixel
            if (X == 0)
            {
                if (Y == 0)
                {
                    screen.setPixel32(x1, y1, color);
                    return;
                }
                // draw a straight vertical line
                yy = y2 > y1 ? 1 : -1;
                while (y1 != y2)
                {
                    screen.setPixel32(x1, y1, color);
                    y1 += yy;
                }
                screen.setPixel32(x2, y2, color);
                return;
            }
            
            if (Y == 0)
            {
                // draw a straight horizontal line
                xx = x2 > x1 ? 1 : -1;
                while (x1 != x2)
                {
                    screen.setPixel32(x1, y1, color);
                    x1 += xx;
                }
                screen.setPixel32(x2, y2, color);
                return;
            }
            
            xx = x2 > x1 ? 1 : -1;
            yy = y2 > y1 ? 1 : -1;
            var c:Number = 0,
                slope:Number;
            
            if (X > Y)
            {
                slope = Y / X;
                c = .5;
                while (x1 != x2)
                {
                    screen.setPixel32(x1, y1, color);
                    x1 += xx;
                    c += slope;
                    if (c >= 1)
                    {
                        y1 += yy;
                        c -= 1;
                    }
                }
                screen.setPixel32(x2, y2, color);
            }
            else
            {
                slope = X / Y;
                c = .5;
                while (y1 != y2)
                {
                    screen.setPixel32(x1, y1, color);
                    y1 += yy;
                    c += slope;
                    if (c >= 1)
                    {
                        x1 += xx;
                        c -= 1;
                    }
                }
                screen.setPixel32(x2, y2, color);
            }
        }
        
        /**
         * Draws a smooth, antialiased line with optional alpha and thickness.
         * @param    x1        Starting x position.
         * @param    y1        Starting y position.
         * @param    x2        Ending x position.
         * @param    y2        Ending y position.
         * @param    color    Color of the line.
         * @param    alpha    Alpha of the line.
         * @param    thick    The thickness of the line.
         */
        public static function linePlus(x1:int, y1:int, x2:int, y2:int, color:uint = 0xFF000000, alpha:Number = 1, thick:Number = 1):void
        {
            _graphics.clear();
            _graphics.lineStyle(thick, color, alpha, false, LineScaleMode.NONE);
            _graphics.moveTo(x1 - _camera.x, y1 - _camera.y);
            _graphics.lineTo(x2 - _camera.x, y2 - _camera.y);
            _target.draw(FP.sprite, null, null, blend);
        }
        
        /**
         * Draws a filled rectangle.
         * @param    x            X position of the rectangle.
         * @param    y            Y position of the rectangle.
         * @param    width        Width of the rectangle.
         * @param    height        Height of the rectangle.
         * @param    color        Color of the rectangle.
         * @param    alpha        Alpha of the rectangle.
         */
        public static function rect(x:int, y:int, width:uint, height:uint, color:uint = 0xFFFFFF, alpha:Number = 1):void
        {
            if (alpha >= 1 && !blend)
            {
                if (color < 0xFF000000) color = 0xFF000000 | color;
                _rect.x = x - _camera.x;
                _rect.y = y - _camera.y;
                _rect.width = width;
                _rect.height = height;
                _target.fillRect(_rect, color);
                return;
            }
            if (color >= 0xFF000000) color = 0xFFFFFF & color;
            _graphics.clear();
            _graphics.beginFill(color, alpha);
            _graphics.drawRect(x - _camera.x, y - _camera.y, width, height);
            _target.draw(FP.sprite, null, null, blend);
        }
        
        /**
         * Draws a non-filled, pixelated circle.
         * @param    x            Center x position.
         * @param    y            Center y position.
         * @param    radius        Radius of the circle.
         * @param    color        Color of the circle.
         */
        public static function circle(x:int, y:int, radius:int, color:uint = 0xFFFFFF):void
        {
            if (color < 0xFF000000) color = 0xFF000000 | color;
            x -= _camera.x;
            y -= _camera.y;
            var f:int = 1 - radius,
                fx:int = 1,
                fy:int = -2 * radius,
                xx:int = 0,
                yy:int = radius;
            _target.setPixel32(x, y + radius, color);
            _target.setPixel32(x, y - radius, color);
            _target.setPixel32(x + radius, y, color);
            _target.setPixel32(x - radius, y, color);
            while (xx < yy)
            {
                if (f >= 0) 
                {
                    yy --;
                    fy += 2;
                    f += fy;
                }
                xx ++;
                fx += 2;
                f += fx;    
                _target.setPixel32(x + xx, y + yy, color);
                _target.setPixel32(x - xx, y + yy, color);
                _target.setPixel32(x + xx, y - yy, color);
                _target.setPixel32(x - xx, y - yy, color);
                _target.setPixel32(x + yy, y + xx, color);
                _target.setPixel32(x - yy, y + xx, color);
                _target.setPixel32(x + yy, y - xx, color);
                _target.setPixel32(x - yy, y - xx, color);
            }
        }
        
        /**
         * Draws a circle to the screen.
         * @param    x            X position of the circle's center.
         * @param    y            Y position of the circle's center.
         * @param    radius        Radius of the circle.
         * @param    color        Color of the circle.
         * @param    alpha        Alpha of the circle.
         * @param    fill        If the circle should be filled with the color (true) or just an outline (false).
         * @param    thick        How thick the outline should be (only applicable when fill = false).
         */
        public static function circlePlus(x:int, y:int, radius:Number, color:uint = 0xFFFFFF, alpha:Number = 1, fill:Boolean = true, thick:int = 1):void
        {
            _graphics.clear();
            if (fill)
            {
                _graphics.beginFill(color & 0xFFFFFF, alpha);
                _graphics.drawCircle(x - _camera.x, y - _camera.y, radius);
                _graphics.endFill();
            }
            else
            {
                _graphics.lineStyle(thick, color & 0xFFFFFF, alpha);
                _graphics.drawCircle(x - _camera.x, y - _camera.y, radius);
            }
            _target.draw(FP.sprite, null, null, blend);
        }
        
        /**
         * Draws the Entity's hitbox.
         * @param    e            The Entity whose hitbox is to be drawn.
         * @param    outline        If just the hitbox's outline should be drawn.
         * @param    color        Color of the hitbox.
         * @param    alpha        Alpha of the hitbox.
         */
        public static function hitbox(e:Entity, outline:Boolean = true, color:uint = 0xFFFFFF, alpha:Number = 1):void
        {
            if (outline)
            {
                if (color < 0xFF000000) color = 0xFF000000 | color;
                var x:int = e.x - e.originX - _camera.x,
                    y:int = e.y - e.originY - _camera.y;
                _rect.x = x;
                _rect.y = y;
                _rect.width = e.width;
                _rect.height = 1;
                _target.fillRect(_rect, color);
                _rect.y += e.height - 1;
                _target.fillRect(_rect, color);
                _rect.y = y;
                _rect.width = 1;
                _rect.height = e.height;
                _target.fillRect(_rect, color);
                _rect.x += e.width - 1;
                _target.fillRect(_rect, color);
                return;
            }
            if (alpha >= 1 && !blend)
            {
                if (color < 0xFF000000) color = 0xFF000000 | color;
                _rect.x = e.x - e.originX - _camera.x;
                _rect.y = e.y - e.originY - _camera.y;
                _rect.width = e.width;
                _rect.height = e.height;
                _target.fillRect(_rect, color);
                return;
            }
            if (color >= 0xFF000000) color = 0xFFFFFF & color;
            _graphics.clear();
            _graphics.beginFill(color, alpha);
            _graphics.drawRect(e.x - e.originX - _camera.x, e.y - e.originY - _camera.y, e.width, e.height);
            _target.draw(FP.sprite, null, null, blend);
        }
        
        /**
         * Draws a quadratic curve.
         * @param    x1        X start.
         * @param    y1        Y start.
         * @param    x2        X control point, used to determine the curve.
         * @param    y2        Y control point, used to determine the curve.
         * @param    x3        X finish.
         * @param    y3        Y finish.
         * @param    color    Color of the curve
         * @param    alpha    Alpha transparency.
         */
        public static function curve(x1:int, y1:int, x2:int, y2:int, x3:int, y3:int, thick:Number = 1, color:uint = 0, alpha:Number = 1):void
        {
            _graphics.clear();
            _graphics.lineStyle(thick, color, alpha);
            _graphics.moveTo(x1 - _camera.x, y1 - _camera.y);
            _graphics.curveTo(x2 - _camera.x, y2 - _camera.y, x3 - _camera.x, y3 - _camera.y);
            _target.draw(FP.sprite, null, null, blend);
        }
        
        /**
         * Draws a graphic object.
         * @param    g        The Graphic to draw.
         * @param    x        X position.
         * @param    y        Y position.
         */
        public static function graphic(g:Graphic, x:int = 0, y:int = 0):void
        {
            if (g.visible)
            {
                if (g.relative)
                {
                    FP.point.x = x;
                    FP.point.y = y;
                }
                else FP.point.x = FP.point.y = 0;
                FP.point2.x = FP.camera.x;
                FP.point2.y = FP.camera.y;
                g.render(_target, FP.point, FP.point2);
            }
        }
        
        /**
         * Draws an Entity object.
         * @param    e                    The Entity to draw.
         * @param    x                    X position.
         * @param    y                    Y position.
         * @param    addEntityPosition    Adds the Entity's x and y position to the target position.
         */
        public static function entity(e:Entity, x:int = 0, y:int = 0, addEntityPosition:Boolean = false):void
        {
            if (e.visible && e.graphic)
            {
                if (addEntityPosition) graphic(e.graphic, x + e.x, y + e.y);
                else graphic(e.graphic, x, y);
            }
        }
        
        // Drawing information.
        /** @private */ private static var _target:BitmapData;
        /** @private */ private static var _camera:Point;
        /** @private */ private static var _graphics:Graphics = FP.sprite.graphics;
        /** @private */ private static var _rect:Rectangle = FP.rect;
        /** @private */ private static var _matrix:Matrix = new Matrix;
    }
}