/////////////////////
//
//  Utilities
//
/////////////////////

var pi = Math.PI,
    pi2 = pi*2,
    degToRad = pi/180;

Function.prototype.bind = function(scope) {
  var _function = this;
 
  return function() {
    return _function.apply(scope, arguments);
  }
}

/////////////////////
//
//  Frame Counter
//
/////////////////////

var FrameCounter = function(ctx, w, h){
  var _callbacks = [];
  var _invokeCallbacks = function(){
    ctx.clearRect(0,0,w,h);
    var len = _callbacks.length;
    while(len--){
      _callbacks[len]();
    }
  };
  setInterval(_invokeCallbacks, 17);
  return {
    addCallback : function(f){
      _callbacks.push(f);
    },
    removeCallback : function(f){
      var len = _callbacks.length;
      while(len--){
	if(f == _callbacks[len]){
	  _callbacks = _callbacks.slice(0,len).concat(_callbacks.slice(len,_callbacks.length));
	}
      }
    }
  };
};

/////////////////////
//
//  Wander
//
/////////////////////
var Wander = function(target, props, frame_counter){
        
    /////////////////
    //
    // PRIVATE VARIABLES
    //
    /////////////////
    
    var _count,
        _autoUpdate,
        _interval = false;
    
    /////////////////
    //
    // PRIVATE MEMBERS
    //
    /////////////////
    
    this.getShortRotation = function(rot){
	rot %= pi2;
	if (rot > pi) { rot -= pi2; }
	else if (rot < -pi) { rot += pi2; }
	return rot;
    }
    
    this._startUpdate = function(){
        if(!_interval){
            _interval = true;
	    frame_counter.addCallback(this.update.bind(this));
        }
    };
    
    this._stopUpdate = function(){
        if(_interval){
            _interval = false;
	    frame_counter.removeCallback(this.update.bind(this));
        }
    };
    
    /////////////////
    //
    // PUBLIC MEMBERS
    //
    /////////////////
    
    this.setAutoUpdate = function(value){
        _autoUpdate = value;
	if (value) { this._startUpdate();  }
	else if(!value) { this._stopUpdate(); }
    };
    
    this.getAutoUpdate = function(){
        return _autoUpdate;
    };
    
    this.reset = function(props){
        this.targetObject = null;
	this.targetX = this.targetY = this.targetRotation = NaN;
	this.innerRadius = this.outerRadius = this.varySpeed = this.strength = this.count = this.delayCount = _count = 0;
	this.speed = 4;
	this.varyRotation = 0.2;
			
	if (props) {
	    for (var n in props) {
		this[n] = props[n];
	    }
	}
        
	this.oldX = target.x;
	this.oldY = target.y;
        
        if(this.autoUpdate){ this._startUpdate(); }
    };
    
    this.update = function update() {
		    this.oldRotation = this.target.getRotation();
                    this.oldX = this.target.getX();
                    this.oldY = this.target.getY();
                    
                    var rotation = this.target.getRotation()/180*pi;
                    var oldR = rotation;
                    rotation += pi*this.varyRotation*0.5-pi*this.varyRotation*Math.random();
                    
                    // calculate strength:
                    var str = this.strength;
                    var complete = false;
                    if (this.count > 0) {
                            _count++;
                            var c = (_count-this.delayCount)/this.count;
                            if (c > 1) { c = 1; }
                            if (c > 0) { str += (1-str)*c*c; }
                            if (c == 1) { complete=true; }
                    }
                    
                    // targetRotation:
                    if (str > 0 && !isNaN(this.targetRotation)) {
                            rotation += this.getShortRotation(this.targetRotation*degToRad-rotation)*str;
                            if (complete || this.count == 0) { complete = Math.abs(this.targetRotation-rotation)<1; }
                    }
                    
                    // target position / object:
                    if ((str > 0 || this.outerRadius > 0) && (this.targetObject || !isNaN(this.targetX) || !isNaN(this.targetY))) {
                            var tx = this.targetObject ? this.targetObject.getX() : isNaN(this.targetX) ? this.target.getX() : this.targetX;
                            var ty = this.targetObject ? this.targetObject.getY() :isNaN(this.targetY) ? this.target.y : this.targetY;
                            var dx = tx-this.target.getX();
                            var dy = ty-this.target.getY();
                            var d = Math.sqrt(dx*dx+dy*dy);
                            var a = Math.atan2(dy,dx);
                            var pstr = str;
                            
                            if (this.outerRadius > 0) {
                                    var dstr = (d-this.innerRadius)/(this.outerRadius-this.innerRadius);
                                    if (dstr > 1) { dstr = 1; }
                                    if (dstr > 0) { pstr += (1-pstr)*(dstr*dstr); }
                            }
                            rotation += this.getShortRotation(a-rotation)*pstr;
                            complete = c>0 && _count > 1 && d<speed;
                    }
                    
                    // limit rotation:
                    if (!isNaN(this.rotationLimit)) {
                            var rotationD = rotation-oldR;
                            if (Math.abs(rotationD) > this.rotationLimit*degToRad) { 
                                    rotation = oldR+this.rotationLimit*degToRad*(rotationD<0?-1:1);
                            }
                    }
                    
                    if (complete) {
                            if (!isNaN(this.targetX)) { this.target.setX( this.targetX ); }
                            if (!isNaN(this.targetY)) { this.target.setY( this.targetY ); }
                            if (!isNaN(this.targetRotation)) { this.target.setRotation(this.targetRotation); }
                    } else {
                            var sp = this.speed-this.speed*this.varySpeed*Math.random();
                            this.target.setX( this.target.getX() + Math.cos(rotation)*sp );
                            this.target.setY( this.target.getY() + Math.sin(rotation)*sp );
                            this.target.setRotation( rotation/degToRad );
                    }
                    
                    this.target.draw();
                    
                    /* EVENTS
                    if (onChange != null) { onChange(this); }
                    if (complete && onComplete != null) { onComplete(this); }
                    */
    };
    
    /////////////////
    //
    // CONSTRUCTOR
    //
    /////////////////
    props = (props==null?{}:props);
    this.target = target;
    if (!props.autoUpdate) { props.autoUpdate = true; }
    this.reset(props);
    
};

var DisplayObject = function(ctx, x, y, rotation, color, scale, h, w){
    
    /////////////////
    //
    // PRIVATE VARIABLES
    //
    /////////////////
    var _x = x,
    _y = y,
    _rotation = rotation,
    _color = color,
    _scale = scale;
    
    /////////////////
    //
    // PRIVATE MEMBERS
    //
    /////////////////
    
    /////////////////
    //
    // PUBLIC MEMBERS
    //
    /////////////////
    
    this.setX = function(value){
        if(value<0 || value>w)return;
	_x = value;
    };
    this.getX = function(){
        return _x;
    };
    
    this.setY = function(value){
        if(value<0 || value>h)return;
	_y = value;
    };
    this.getY = function(){
        return _y;
    };
    
    this.setRotation = function(value){
        _rotation = value;
    };
    this.getRotation = function(){
        return _rotation;
    };
    
    this.draw = function(){
        var _test = 10000;
        //ctx.save();
        //ctx.scale(_test, _test)
        //ctx.fillStyle = _color;
        //ctx.fillRect(_x/_test, _y/_test, 2*_scale/_test, 2*_scale/_test);
	ctx.beginPath();
	ctx.arc(_x, _y, scale, 0, pi2, true); 
	ctx.closePath();
	ctx.fillStyle = _color;
	ctx.fill();
        //ctx.restore();
    };
    
    /////////////////
    //
    // CONSTRUCTOR
    //
    /////////////////
    
}
 