function _trace(msg) {
    setTimeout(function() {
        throw new Error("TRACE: " + msg);
    }, 0);
}

var copper = (function()
{
    
//---------------------------------------------------------------------------------------------------------
    
    var Bezier = function()
    {
        this.points    = {};
        this.t         = 0;
        this.direction = 0;
        this.angle     = 0;
        this.dtl       = 0;
        this.speed     = 0.1;
        this.cvpoints  = false;
    }
    
    Bezier.prototype.drawSpline = function(context, points)
    {
        context.beginPath();
        for (var t = 0; t < 1; t += 0.1) {
            var ax = (-points[0].x + 3*points[1].x - 3*points[2].x + points[3].x) / 6;
            var ay = (-points[0].y + 3*points[1].y - 3*points[2].y + points[3].y) / 6;
            var bx = (points[0].x - 2*points[1].x + points[2].x) / 2;
            var by = (points[0].y - 2*points[1].y + points[2].y) / 2;
            var cx = (-points[0].x +points[2].x) / 2;
            var cy = (-points[0].y +points[2].y) / 2;
            var dx = (points[0].x + 4*points[1].x + points[2].x) / 6;
            var dy = (points[0].y + 4*points[1].y + points[2].y) / 6;
            context.moveTo(
                ax*Math.pow(t, 3) + bx*Math.pow(t, 2) + cx*t + dx,
                ay*Math.pow(t, 3) + by*Math.pow(t, 2) + cy*t + dy
            );
            context.lineTo(
                ax*Math.pow(t+0.1, 3) + bx*Math.pow(t+0.1, 2) + cx*(t+0.1) + dx,
                ay*Math.pow(t+0.1, 3) + by*Math.pow(t+0.1, 2) + cy*(t+0.1) + dy
                );
          }
          context.stroke();
    }
    
    Bezier.prototype.startSpline = function(points, speed)
    {
        this.points    = points;
        this.t         = 0;
        this.direction = 1.0;
        this.speed     = speed;        
    } 
    
    Bezier.prototype.restartSpline = function()
    {
        this.t = 0;
    }
    
    Bezier.prototype.reverseSpline = function()
    {
        this.direction *= -1.0;
    }
    
    Bezier.prototype.onBeforeDraw = function() {};
	Bezier.prototype.onAfterDraw = function() {};
	Bezier.prototype.onSplineEnd = function() {};
	
    Bezier.prototype.draw = function(context)
    {
        this.onBeforeDraw();
        
        if (this.t >= 1.0 && this.direction > 0)
            return ;
        else if (this.t <= 0.0 && this.direction < 0)
            return ;
            
        if (this.cvpoints)
        {
            for (var i=0 ; i < this.points.length ; i++)
            {
                context.beginPath();
                context.moveTo(this.points[i].x - 2, this.points[i].y - 2);
                context.lineTo(this.points[i].x + 2, this.points[i].y - 2);
                context.lineTo(this.points[i].x + 2, this.points[i].y + 2);
                context.lineTo(this.points[i].x - 2, this.points[i].y + 2);
                context.lineTo(this.points[i].x - 2, this.points[i].y - 2);
                context.closePath();
                context.stroke();                
            }
        }
        
        //context.rotate(this.angle);
        
        var ax = (-this.points[0].x + 3*this.points[1].x - 3*this.points[2].x + this.points[3].x) / 6;
        var ay = (-this.points[0].y + 3*this.points[1].y - 3*this.points[2].y + this.points[3].y) / 6;
        var bx = ( this.points[0].x - 2*this.points[1].x + this.points[2].x) / 2;
        var by = ( this.points[0].y - 2*this.points[1].y + this.points[2].y) / 2;
        var cx = (-this.points[0].x + this.points[2].x) / 2;
        var cy = (-this.points[0].y + this.points[2].y) / 2;
        var dx = ( this.points[0].x + 4*this.points[1].x + this.points[2].x) / 6;
        var dy = ( this.points[0].y + 4*this.points[1].y + this.points[2].y) / 6;
        
        context.beginPath();
        
        context.moveTo(
            ax*Math.pow(this.t, 3) + bx*Math.pow(this.t, 2) + cx*this.t + dx,
            ay*Math.pow(this.t, 3) + by*Math.pow(this.t, 2) + cy*this.t + dy
        );
        
        context.lineTo(
            ax*Math.pow(this.t+this.speed, 3) + bx*Math.pow(this.t+this.speed, 2) + cx*(this.t+this.speed) + dx,
            ay*Math.pow(this.t+this.speed, 3) + by*Math.pow(this.t+this.speed, 2) + cy*(this.t+this.speed) + dy
        );
        
        context.stroke();
        
        this.t += (this.speed * this.direction);    
                
        this.onAfterDraw();
        
        if (this.t >= 1.0 && this.direction > 0)
            this.onSplineEnd();
        else if (this.t <= 0.0 && this.direction < 0)
            this.onSplineEnd();
                        
    }
    
    Bezier.prototype.isEnd = function() 
    {
        return (this.direction > 0) ? (this.t >= 1.0) : (this.t <= 0); 
    }

//---------------------------------------------------------------------------------------------------------    

    var Vine = function(lattice, size, speed, points)
    {
        this.lattice  = lattice;
        this.speed    = speed;
        this.branches = new Array();
        this.ltpoints = false;
        
        for (var i=0 ; i < size ; i++)
        {
            ns = new Bezier();
            ns.startSpline(points, this.speed);
            this.branches.push(ns);
        }
        this.t        = 0;
        
    }

    Vine.prototype.distancePointToLine = function(point, line)
    {
        // Length of line segment
        var L = Math.sqrt(Math.pow(line[1].x - line[0].x, 2) + Math.pow(line[1].y - line[0].y, 2));
        // Calculate position of projection along line segment
        var r = ((point.x - line[0].x) * (line[1].x - line[0].x) + (point.y - line[0].y) * (line[1].y - line[0].y)) / Math.pow(L, 2);
        // Calculate distance of point to projection
        var s = ((line[0].y - point.y) * (line[1].x - line[0].x) - (line[0].x - point.x) * (line[1].y - line[0].y)) / Math.pow(L, 2);
        // If perpendicular projection on line segment
        if (r >= 0 && r <= 1) {
            return Math.abs(s) * L;
            // If off line segment
        } else {
            return Math.min(
                Math.sqrt(Math.pow(point.x - line[0].x, 2) + Math.pow(point.y - line[0].y, 2)),
                Math.sqrt(Math.pow(point.x - line[1].x, 2) + Math.pow(point.y - line[1].y, 2)));
        }
    }
    
    Vine.prototype.onBeforeDraw = function() {}
    Vine.prototype.onAfterDraw  = function() {}
    Vine.prototype.draw = function(ctx)
    {
        //_trace("Num brances "+this.branches.length);
        
        if (this.ltpoints)
        {
            for (var l=0 ; l < this.lattice.length ; l++)
            {
                ctx.beginPath();
                ctx.moveTo(this.lattice[l][0].x, this.lattice[l][0].y);
                ctx.lineTo(this.lattice[l][1].x, this.lattice[l][1].y);
                ctx.closePath();
                ctx.stroke();
            }
        }
        
        for (var j=0 ; j < this.branches.length ; j++)
            this.branches[j].draw(ctx);
            
        this.t += this.speed;
        
        // If finished drawing branch
        if (this.t >= 1)
        {
            var new_branches = new Array();
            
            for (var j=0 ; j < this.branches.length ; j++)
            {
                // Replace with two branches
                for (var k = 0; k < 2; k++)
                {
                    // Generate random length and direction
                    var angle = this.branches[j].angle - (Math.random() * 180 - 90);
                    
                    var length = Math.random() * 20 + 5;
                    
                    // Calculate new point
                    var new_point = {
                        x:this.branches[j].points[3].x + Math.sin(Math.PI * angle / 180) * length,
                        y:this.branches[j].points[3].y - Math.cos(Math.PI * angle / 180) * length
                    }

                    var distanceToLattice = 100000;
                    
                    for (var l=0 ; l < this.lattice.length ; l++) {
                      var result = this.distancePointToLine(this.branches[j].points[3], this.lattice[l]);
                      if (result < distanceToLattice) distanceToLattice = result;
                    }
                    
                    
                    //_trace("New branch ("+new_point.x+","+new_point.y+")");
                    
                    nspline = new Bezier();
                    nspline.startSpline(new Array(
                        this.branches[j].points[1],
                        this.branches[j].points[2],
                        this.branches[j].points[3],
                        new_point), this.speed);
                        
                    nspline.angle = angle;
                    nspline.dtl   = distanceToLattice;
                    
                    // Add to new branch array
                    new_branches.push(nspline);
                }
            }
            
            new_branches.sort(function(a, b) {
                return a.dtl - b.dtl;
            });
            
            while (new_branches.length > 20)
                new_branches.pop();
            
            /*
            while (new_branches.length > 10) {
                new_branches.splice(Math.floor(Math.random() * new_branches.length), 1);
            }
            */
            this.branches = new_branches;
            
            this.t = 0;
        }
                
    }
    
//---------------------------------------------------------------------------------------------------------    
    
    var Container = function(width, height) {
		this.canvas = document.createElement("canvas");
		this.canvas.setAttribute('width', width);
		this.canvas.setAttribute('height', height);
		this.context = this.canvas.getContext('2d');
		this.displayList = [];
		this.continuous = false;
		this.length = 0;
	};
	
	Container.prototype.draw = function(target) {
		var max;
		var i;

		this.onBeforeDraw();
		// clear the canvas
		if (!this.continuous) {
			this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
		}

		max = this.displayList.length;

		for (i=0; i<max; ++i) {
			this.displayList[i].draw(this.context);
		}
		
		if (target != null) {
			target.drawImage(this.canvas, 0, 0);
		}
		
		this.onAfterDraw();
	};
	
	Container.prototype.addChild = function(child) {
		++this.length;
		this.displayList.push(child);
	};
	
	Container.prototype.removeChild = function(child) {
		var index = this.displayList.indexOf(child);
		
		if (index > -1) {
			--this.length;
			this.displayList.splice(index, 1);
			return true;
		} else {
			return false;
		}
	};
	
	Container.prototype.removeChildFromIndex = function(index) {
		if (this.length > index && index > -1) {
			--this.length;
			this.displayList.splice(index, 1);
			return true;
		} else {
			return false;
		}
	};
	
	Container.prototype.removeAll = function() {
		while (this.removeChildFromIndex(0)){}
	};
	
	Container.prototype.onBeforeDraw = function() {};
	Container.prototype.onAfterDraw = function() {};
	
	
//---------------------------------------------------------------------------------------------------------
    
    var Engine = function (canvas) {
		this.runId       = -1;
		this.displayList = [];
		this.continuous  = false;
		this.canvas      = canvas;
		this.context     = canvas.getContext('2d');
	};
	
	Engine.prototype = new Container();
	
	Engine.prototype.start = function(interval) {
		var that = this;
		
		this.draw();
		clearInterval(this.runId);
		this.runId = setInterval(function() {
			that.draw.apply(that);
		}, interval);
	};
	
	Engine.prototype.clear = function() {
		this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
	}
	
	Engine.prototype.stop = function() {
		clearInterval(this.runId);
	};
    
    var api = {};
    api.Engine  = Engine;
    api.BSpline = Bezier;
    api.Vine    = Vine;
    
    api.debug = function(msg) {
        _trace(msg);
    }    
    
    return api;        
}());
