/*
   Copyright 2010 Hanov Solutions Inc. All Rights Reserved

   steve.hanov@gmail.com
 */

/** @constructor */
function Point( x, y )
{
    this.x = x;
    this.y = y;
}

function CalcLineLength( x1, y1, x2, y2 )
{
    return Math.sqrt( (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2) );
}

function CreateVector( x1, y1, x2, y2 )
{
    var len = CalcLineLength( x1, y1, x2, y2 );
    return {
        x: ((x2 !== x1) ? (x2 - x1)/len : 1.0),
        y: ((y2 !== y1) ? (y2 - y1)/len : 0.0)
    };
}

/** @constructor */
function Rectangle( x, y, w, h )
{
    this.x = x;
    this.y = y;
    this.width = w;
    this.height = h;
    this.repair();
}

Rectangle.load = function( s )
{
    return new Rectangle( s["x"], s["y"], s["width"], s["height"] );
};

Rectangle.prototype = {

    save: function()
    {
        return { "x": this.x, "y":this.y, "width":this.width, "height": this.height };
    },

    union: function( other )
    {
        if ( other.x < this.x ) {
            this.width += this.x - other.x;
            this.x = other.x;
        }
        if ( other.y < this.y ) {
            this.height += this.y - other.y;
            this.y = other.y;
        }

        if ( other.x + other.width > this.x + this.width ) {
            this.width += other.x + other.width - this.x - this.width;
        }

        if ( other.y + other.height > this.y + this.height ) {
            this.height += other.y + other.height - this.y - this.height;
        }
    },

    unionPoint: function( x, y )
    {
        if ( x < this.x ) {
            this.width += this.x - x;
            this.x = x;
        } else if ( x > this.x + this.width ) {
            this.width = x - this.x;
        }

        if ( y < this.y ) {
            this.height += this.y - y;
            this.y = y;
        } else if ( y > this.y + this.height ) {
            this.height = y - this.y;
        }
    },

    contains: function( other )
    {
        return this.x <= other.x && 
               this.x + this.width > other.x + other.width &&
               this.y <= other.y &&
               this.y + this.height > other.y + other.height;
    },

    containsPoint: function( x, y )
    {
        return this.x <= x && this.x + this.width > x &&
               this.y <= y && this.y + this.height > y;
    },


    repair: function()
    {
        if ( this.width < 0 ) {
            this.x += this.width;
            this.width = -this.width;
        }

        if ( this.height < 0 ) {
            this.y += this.height;
            this.height = -this.height;
        }
    },

    inflate: function( dx, dy )
    {
        this.x -= dx/2;
        this.y -= dy/2;
        this.width += dx;
        this.height += dy;
    }

};

function Clone( obj )
{
    var copy; 
    if ( obj instanceof Function ) {
        return obj;
    } else if ( obj instanceof Array ) {
        copy = obj.concat();
    } else if ( obj instanceof Object ) {
        copy = {};
        for ( var prop in obj ) {
            if ( 1 ) {
                copy[prop] = Clone( obj[prop] );
            }
        }
    } else {
        copy = obj;
    }

    return copy;
}


/** @constructor */
function IdentityMatrix()
{
    this.m11 = 1;
    this.m12 = 0;
    this.m21 = 0;
    this.m22 = 1;
    this.dx = 0;
    this.dy = 0;
}

IdentityMatrix.load = function( s )
{
    var ss = new IdentityMatrix();
    ss.m11 = s["m11"];
    ss.m12 = s["m12"];
    ss.m21 = s["m21"];
    ss.m22 = s["m22"];
    ss.dx = s["dx"];
    ss.dy = s["dy"];
    return ss;
};

IdentityMatrix.prototype = {

    save: function() {
        return {
            "m11": this.m11,
            "m12": this.m12,
            "m21": this.m21,
            "m22": this.m22,
            "dx": this.dx,
            "dy": this.dy
        };
    },

    multiply: function( o )
    {
        var m = new IdentityMatrix();
        m.m11 = this.m11 * o.m11 + this.m12 * o.m21;
        m.m21 = this.m21 * o.m11 + this.m22 * o.m21;
        m.m12 = this.m11 * o.m12 + this.m12 * o.m22;
        m.m22 = this.m21 * o.m12 + this.m22 * o.m22;
        m.dx = this.m11 * o.dx + this.m12 * o.dy + this.dx;
        m.dy = this.m21 * o.dx + this.m22 * o.dy + this.dy;

        //dbg.printf("[ %s %s %s ]\n", m.m11, m.m12, m.dx);
        //dbg.printf("[ %s %s %s ]\n", m.m21, m.m22, m.dy);
        //dbg.printf("[ %s %s %s ]\n\n", 0,0, 1);
        return m;
    },

    apply: function( x, y )
    {
        return new Point( this.m11 * x + this.m12 * y + this.dx,
                          this.m21 * x + this.m22 * y + this.dy );
    }
};

/** @constructor */
function ScaleMatrix( sx, sy, ox, oy )
{
    if ( ox === undefined ) {
        this.m11 = sx;
        this.m12 = 0;
        this.m21 = 0;
        this.m22 = sy;
        this.dx = 0;
        this.dy = 0;
    } else {
        this.m11 = sx;
        this.m12 = 0;
        this.m21 = 0;
        this.m22 = sy;
        this.dx = ox-sx * ox;
        this.dy = oy-sy * oy;
    }
}

ScaleMatrix.prototype = IdentityMatrix.prototype;

/** @constructor */
function RotateMatrix( angle, x, y )
{
    var cosa = Math.cos( angle );
    var sina = Math.sin( angle );

    this.m11 = cosa;
    this.m12 = sina;
    this.m21 = -sina;
    this.m22 = cosa;
    //this.dx = x * ( 1 - cosa ) + y * sina;
    //this.dy = y * ( 1 - cosa ) - x * sina;
 //   this.dx = -x * cosa + y * sina + x;
 //   this.dy = x * sina - y * cosa + y;
    this.dx = -x * cosa - y * sina + x;
    this.dy = x * sina - y * cosa + y;

}

RotateMatrix.prototype = IdentityMatrix.prototype;

/** @constructor */
function TranslateMatrix( dx, dy )
{
    this.m11 = 1;
    this.m12 = 0;
    this.m21 = 0;
    this.m22 = 1;
    this.dx = dx;
    this.dy = dy;
}

TranslateMatrix.prototype = IdentityMatrix.prototype;

/**
    max_error of 4 is good, 32 is rough
 */

function recursive_bezier(points, x1, y1, x2, y2, x3, y3, x4, y4, max_error)
{

    // Calculate all the mid-points of the line segments
    //----------------------
    var x12   = (x1 + x2) / 2;
    var y12   = (y1 + y2) / 2;
    var x23   = (x2 + x3) / 2;
    var y23   = (y2 + y3) / 2;
    var x34   = (x3 + x4) / 2;
    var y34   = (y3 + y4) / 2;
    var x123  = (x12 + x23) / 2;
    var y123  = (y12 + y23) / 2;
    var x234  = (x23 + x34) / 2;
    var y234  = (y23 + y34) / 2;
    var x1234 = (x123 + x234) / 2;
    var y1234 = (y123 + y234) / 2;

    // Try to approximate the full cubic curve by a single straight line
    //------------------
    var dx = x4-x1;
    var dy = y4-y1;

    var d2 = Math.abs(((x2 - x4) * dy - (y2 - y4) * dx));
    var d3 = Math.abs(((x3 - x4) * dy - (y3 - y4) * dx));

    if((d2 + d3)*(d2 + d3) < max_error * (dx*dx + dy*dy))
    {
        points.push( new Point( x1234, y1234 ) );
        return;
    }

    // Continue subdivision
    //----------------------
    recursive_bezier(points, x1, y1, x12, y12, x123, y123, x1234, y1234,
            max_error); 
    recursive_bezier(points, x1234, y1234, x234, y234, x34, y34, x4, y4,
            max_error); 
}


function bezier(points, x1, y1, x2, y2, x3, y3, x4, y4, max_error)
{
    if ( x1 !== x4 && y1 !== y4 ) {
        recursive_bezier(points, x1, y1, x2, y2, x3, y3, x4, y4, 
                max_error * max_error);
    }
    points.push( new Point( x4, y4 ) );
}

function PointInPolygon( poly, x, y, radius )
{
     var xnew,ynew;
     var xold,yold;
     var x1,y1;
     var x2,y2;
     var i;
     var inside=0;

     if (poly.length < 3) {
          return 0;
     }

     xold = poly[poly.length-1].x;
     yold = poly[poly.length-1].y;
     for (i=0 ; i < poly.length; i++) {
          xnew=poly[i].x;
          ynew=poly[i].y;
          if (xnew > xold) {
               x1=xold;
               x2=xnew;
               y1=yold;
               y2=ynew;
          } else {
               x1=xnew;
               x2=xold;
               y1=ynew;
               y2=yold;
          }
          if ((xnew < x) === (x <= xold) && 
                (y-y1)*(x2-x1) < (y2-y1)*(x-x1)) {
               inside=!inside;
          }
          xold=xnew;
          yold=ynew;
     }
     return(inside);
}

function PointNearPath( path, x3, y3, radius )
{
    radius = radius * radius;
    for( var i = 1; i < path.length; i++ ) {
        var x1 = path[i-1].x;
        var y1 = path[i-1].y;
        var x2 = path[i].x;
        var y2 = path[i].y;

        var px = x2-x1;
        var py = y2-y1;

        var something = px*px + py*py;

        var u =  ((x3 - x1) * px + (y3 - y1) * py) / something;

        if ( u > 1 ) {
            u = 1;
        } else if ( u < 0 ) {
            u = 0;
        }

        var x = x1 + u * px;
        var y = y1 + u * py;

        var dx = x - x3;
        var dy = y - y3;

        var dist = dx*dx+dy*dy;

        if ( dist <= radius ) {
            return true;
        }
    }

    return false;
}

/** @constructor */
function ImageManipulator( imageData )
{
    this.image = imageData;
}

ImageManipulator.prototype =
{
    clear: function()
    {
        for ( var i = 0; i < this.image.width * this.image.height * 4; i++ ) {
            this.image.data[i] = 0;
        }
    },

    getImageData: function()
    {
        return this.image;
    },

    width: function()
    {
        return this.image.width;
    },

    height: function()
    {
        return this.image.height;
    },

    get: function(x,y)
    {
        var i = this.image.width * y * 4 + x * 4;
        return ( this.image.data[i  ]       ) | 
               ( this.image.data[i+1] <<  8 ) |
               ( this.image.data[i+2] << 16 ) |
               ( this.image.data[i+3] << 24 );
    },

    set: function(x,y,clr)
    {
        var i = this.image.width * y * 4 + x * 4;
        this.image.data[i  ] = ( clr       ) & 0xff;
        this.image.data[i+1] = ( clr >>  8 ) & 0xff;
        this.image.data[i+2] = ( clr >> 16 ) & 0xff;
        this.image.data[i+3] = ( clr >> 24 ) & 0xff;
    },

    invertScanline: function( y, x1, x2 )
    {
        
    }
};

/** @constructor */
function BitmapImage( width, height )
{
    this._width = width;
    this._height = height;
    this.data = [];
}

BitmapImage.prototype = {
    width: function() 
    {
        return this._width;
    },

    height: function() 
    {
        return this._height;
    },

    getImageData: function()
    {
        // create an image data object and fill it in
        var canvas = document.createElement("canvas");
        canvas.width = this._width;
        canvas.height = this._height;
        var context = canvas.getContext("2d");
        
        var image = context.getImageData(0, 0, this._width, this._height);
        var size = this._width * this._height;

        for( var i = 0; i < size; i++ ) {
            var clr = this.data[i] === true ? 255 : 0;
            image.data[i*4  ] = clr;
            image.data[i*4+1] = clr;
            image.data[i*4+2] = clr;
            image.data[i*4+3] = 255;
        }
        return image;
    },

    get: function(x,y)
    {
        return this.data[this._width*y+x] === true;
    },

    set: function(x,y,isSet)
    {
        this.data[this._width*y+x] = isSet;
    },

    invertScanline: function( y, x1, x2 )
    {
        var start = this._width * y + x1;
        var end = this._width * y + x2;
        for ( var i = start; i < end; i++ ) {
            this.data[i] = !this.data[i];
        }
    },

    findFirstOnPixel: function()
    {
        for ( var i = 0; i < this.data.length - 1; i++ ) {
            if ( this.data[i+1] ) {
                return {
                    x: i % this._width,
                    y: Math.floor( i / this._width )
                };
            }
        }
        return null;
    }
};


/** @constructor */
function Path()
{
    this.closed = false;
    this.commands = [];
}

Path.MOVE = 0;
Path.LINE = 1;
Path.BEZIER_CURVE = 2;
Path.CLOSE = 4;

Path.prototype = 
{
    moveTo: function( x, y )
    {
        this.commands.push( Path.MOVE, x, y );
    },

    lineTo: function( x, y )
    {
        this.commands.push( Path.LINE, x, y );
    },

    bezierCurveTo: function( x2, y2, x3, y3, x4, y4 )
    {
        this.commands.push( Path.BEZIER_CURVE, x2, y2, x3, y3, x4, y4 );
    },

    close: function()
    {
        this.commands.push( Path.CLOSE );
    },

    draw: function(ctx)
    {
        var i = 0;
        while( i < this.commands.length ) {
            switch( this.commands[i] ) {
                case Path.MOVE:
                    ctx.moveTo( 
                        this.commands[i+1],
                        this.commands[i+2]);
                    i += 3;
                break;

                case Path.LINE: 
                    ctx.lineTo( 
                        this.commands[i+1],
                        this.commands[i+2]);
                    i += 3;
                break;

                case Path.BEZIER_CURVE:
                    ctx.bezierCurveTo( 
                        this.commands[i+1],
                        this.commands[i+2],
                        this.commands[i+3],
                        this.commands[i+4],
                        this.commands[i+5],
                        this.commands[i+6]);
                    i += 7;
                break;

                case Path.CLOSE:
                    ctx.closePath();
                    i += 1;
                break;

                default:
                    alert("Error!");
                    i += 1;
                break;
            }
        }
    },

    drawControls: function(ctx)
    {
        var i = 0;
        var x = 0;
        var y = 0;
        while( i < this.commands.length ) {
            switch( this.commands[i] ) {
                case Path.MOVE:
                    x = this.commands[i+1];
                    y = this.commands[i+2];
                    i += 3;
                break;

                case Path.LINE: 
                    x = this.commands[i+1];
                    y = this.commands[i+2];
                    i += 3;
                break;

                case Path.BEZIER_CURVE:
                    ctx.rect( x-2, y-2, 4, 4 );
                    ctx.moveTo( x, y );
                    ctx.lineTo( 
                        this.commands[i+1],
                        this.commands[i+2] );
                    ctx.moveTo( 
                        this.commands[i+3],
                        this.commands[i+4] );
                    ctx.lineTo( 
                        this.commands[i+5],
                        this.commands[i+6] );
                      
                    x = this.commands[i+5];
                    y = this.commands[i+6];
                    i += 7;
                break;

                case Path.CLOSE:
                    ctx.closePath();
                    i += 1;
                break;
            }
        }
    }
};
