function MozBrowser () { }

MozBrowser.prototype.trapDragEvents = function (moveFun, finishFun) {
    document.addEventListener('mousemove', moveFun, true);
    document.addEventListener('mouseup', finishFun, true);
};

MozBrowser.prototype.untrapDragEvents = function (move, up) {
    document.removeEventListener('mousemove', move, true);
    document.removeEventListener('mouseup', up, true);
};

MozBrowser.prototype.inhibitDefault = function (event) {
    return event.preventDefault();
};

MozBrowser.prototype.newXHR = function () {
    return new XMLHttpRequest();
};


function MSIEBrowser() { }

MSIEBrowser.prototype.inhibitDefault = function (event) {
    event.cancelBubble = true;
    event.returnValue = false;
};

MSIEBrowser.prototype.trapDragEvents = function (move, up) {
    document.attachEvent('onmousemove', move);
    document.attachEvent('onmouseup', up);
};

MSIEBrowser.prototype.untrapDragEvents = function (move, up) {
    document.detachEvent('onmousemove', move);
    document.detachEvent('onmouseup', up);
};

MSIEBrowser.prototype.newXHR = function () {
    return new ActiveXObject("Microsoft.XMLHTTP");
};




function Util () { }

if (navigator.userAgent.indexOf("MSIE") > 0) {
    Util.browser = new MSIEBrowser();
} else {
    Util.browser = new MozBrowser();
}

Util.clamp = function (a, v, b) {
    return Math.max(Math.min(v, b), a);
};

Util.forceInteger = function (str) {
    var val = parseInt(str);
    if (isNaN(val)) {
        return 0;
    } else {
        return val;
    }
};

Util.hide = function (elt) {
    elt.style.visibility = 'hidden';
};

Util.show = function (elt) {
    elt.style.visibility = 'visible';
};

Util.xmljs = function (doc) {
   var obj = new Object;
   var elt = doc.firstChild;
   var i;

   obj._type = elt.tagName;
   var attrs = elt.attributes;

   for (i = 0; i < attrs.length; i++) {
      obj[attrs[i].nodeName] = attrs[i].nodeValue;
   }

   return obj;
};


Util.ajaxCall = function (path, args, callback, data) {
    var req = Util.browser.newXHR();

    req.onreadystatechange = function () {
        if (req.readyState == 4) {
            callback(Util.xmljs(req.responseXML), data);
        }
    };

    var argstring = "_=1";
    for (name in args) {
        argstring += "&" + encodeURIComponent(name) + "=" +
            encodeURIComponent(args[name]);
    }

    req.open("POST", Util.ajaxCall.base + path);
    req.setRequestHeader('Content-Type',
                         'application/x-www-form-urlencoded');
    req.send(argstring);
};

Util.ajaxCall.base = 'http://wigflip.com';



function Point (x, y) {
    this.x = x;
    this.y = y;
}

Point.origin = new Point(0, 0);

Point.fromEvent = function (e) {
    return new Point(e.clientX, e.clientY);
};

Point.fromElement = function (elt) {
    return new Point(Util.forceInteger(elt.style.left),
                     Util.forceInteger(elt.style.top));
};

Point.fromElementLayout = function (elt) {
    return new Point(elt.offsetLeft, elt.offsetTop);
};

Point.fromElementLayoutCenter = function (elt) {
    var width = elt.clientWidth;
    var height = elt.clientHeight;
    var left = elt.offsetLeft;
    var top = elt.offsetTop;
    
    return new Point(left + (width / 2.0),
                     top + (height / 2.0));
};

Point.fromElementCenter = function (elt) {
    var width = elt.clientWidth;
    var height = elt.clientHeight;
    var left = Util.forceInteger(elt.style.left);
    var top = Util.forceInteger(elt.style.top);
    
    return new Point(left + (width / 2.0),
                     top + (height / 2.0));
};

Point.fromVector = function (magnitude, theta) {
    return new Point(Math.cos(theta) * magnitude,
                     Math.sin(theta) * magnitude);
};

Point.prototype.isValid = function () {
    return !(isNaN(this.x) || isNaN(this.y));
};

Point.prototype.add = function (p) {
    return new Point(this.x + p.x,
                     this.y + p.y);
};

Point.prototype.sub = function (p) {
    return new Point(this.x - p.x,
                     this.y - p.y);
};

Point.prototype.mul = function (p) {
    return new Point(this.x * p.x,
                     this.y * p.y);
};

Point.prototype.div = function (p) {
    return new Point(this.x / p.x,
                     this.y / p.y);
};

Point.prototype.neg = function () {
    return new Point(-this.x,
                     -this.y);
};

Point.prototype.distance = function (p) {
    var sub = this.sub(p);
    return Math.sqrt(Math.pow(sub.x, 2) + Math.pow(sub.y, 2));
};


Point.prototype.midpoint = function (p2) {
    var divider = new Point(2, 2);
    return this.add(p2).div(divider);
};


Point.prototype.constrainedTo = function (b) {
    return new Point(Util.clamp(b.xmin, this.x, b.xmax),
                     Util.clamp(b.ymin, this.y, b.ymax));
};

Point.prototype.toStyleOf = function (elt) {
    elt.style.left = Math.round(this.x) + 'px';
    elt.style.top  = Math.round(this.y) + 'px';
};

Point.prototype.toStyleCenterOf = function (elt) {
    var w2 = elt.clientWidth / 2.0;
    var h2 = elt.clientHeight / 2.0;
    elt.style.left = Math.round(this.x - w2) + 'px';
    elt.style.top = Math.round(this.y - h2) + 'px';
};

Point.prototype.atan = function (p) {
    var sub = this.sub(p);
    var atan = Math.atan2(sub.y, sub.x);
    return atan;
};


function Box (xmin, ymin, xmax, ymax) {
    this.xmin = xmin;
    this.ymin = ymin;
    this.xmax = xmax;
    this.ymax = ymax;
}

Box.fromPoint = function (p) {
    return new Box(p.x, p.y, p.x, p.y);
};

Box.fromElementLayout = function (elt) {
    var xmin = elt.offsetLeft;
    var ymin = elt.offsetTop;
    var xmax = xmin + elt.clientWidth;
    var ymax = ymin + elt.clientHeight;
    return new Box(xmin, ymin, xmax, ymax);
};


Box.prototype.height = function () {
    return this.ymax - this.ymin;
};

Box.prototype.width = function () {
    return this.xmax - this.xmin;
};

Box.prototype.constrainedInto = function (container) {
    // Create a new box that suffices to act as a container for the
    // top left point of "this" box so "this" is always within
    // "container".
    var xmin = container.xmin;
    var ymin = container.ymin;
    var xmax = xmin + (container.width() - this.width());
    var ymax = ymin + (container.height() - this.height());

    return new Box(xmin, ymin, xmax, ymax);
};

Box.prototype.expand = function (amount) {
    return new Box(this.xmin - amount,
		   this.ymin - amount,
		   this.xmax + amount,
		   this.ymax + amount);
};

Box.prototype.contract = function (amount) {
    return this.expand(-amount);
};

Box.prototype.constrict = function (top, right, bottom, left) {
    return new Box(this.xmin + left,
		   this.ymin + bottom,
		   this.xmax - right,
		   this.ymax - top);
};

Box.prototype.minpoint = function () {
    return new Point(this.xmin, this.ymin);
};

Box.prototype.maxpoint = function () {
    return new Point(this.xmax, this.ymax);
};

Box.prototype.centerpoint = function () {
    return new Point((this.xmin + this.xmax) / 2.0,
		     (this.ymin + this.ymax) / 2.0);

};


function Draggable () {
    this.origin = null;
    this.data = new Object;
    this.observers = [];
}

Draggable.prototype.ondragbegin = function () {
};

Draggable.prototype.ondragmotion = function (delta) {
};

Draggable.prototype.ondragend = function () {
};

Draggable.prototype.doObservers = function (fun) {
    for (i in this.observers) {
        fun(this.observers[i]);
    }
};

Draggable.prototype.init = function (e) {
    this.origin = Point.fromEvent(e);
    this.doObservers(function (observer) { 
            observer.init(e);
        });
};

Draggable.prototype.filter = function (p) {
    return p;
};

Draggable.prototype.notify = function (delta) {
    delta = this.filter(delta);
    this.ondragmotion(delta);
    this.doObservers(function (observer) {
            observer.ondragmotion(observer.filter(delta));
        });
};


Draggable.prototype.attachTo = function (elt) {
    var drag = this;
    var start = null;
    var move = null;
    var end = null;

    start = function (e) {
        if (!e) { e = window.event; }
        drag.init(e);
        drag.ondragbegin();
        drag.doObservers(function (observer) {
                observer.ondragbegin();
            });
        Util.browser.trapDragEvents(move, end);
        Util.browser.inhibitDefault(e);
    };
    
    move = function (e) {
        if (!e) { e = window.event; }
        var delta = Point.fromEvent(e).sub(drag.origin);
        drag.notify(delta);
        Util.browser.inhibitDefault(e);
    };

    end = function (e) {
        if (!e) { e = window.event; }
        var delta = drag.filter(Point.fromEvent(e).sub(drag.origin));
        drag.ondragend(delta);
        drag.doObservers(function (observer) {
                observer.ondragend(observer.filter(delta));
            });
        Util.browser.untrapDragEvents(move, end);
    }; 
   
    elt.onmousedown = start;
};

Draggable.prototype.observe = function (parent) {
    parent.observers.push(this);
};

Draggable.prototype.ignore = function (parent) {
    var remaining = [];
    for (i in parent.observers) {
        var obj = parent.observers[i];
        if (obj !== this) {
            remaining.push(this);
        }
    }

    parent.observers = remaining;
};

                



// Ellipse code

function Quad () { }

Quad.minroot = function (a, b, c) {
    var discriminant = Math.sqrt((b * b) - (4 * a * c));
    var r1 = (-b + discriminant) / (2 * a);
    var r2 = (-b - discriminant) / (2 * a);
    if (Math.abs(r1) < Math.abs(r2)) {
        return r1;
    } else {
        return r2;
    }
};

Util.debug = function (obj) {
    var string = '';
    for (i in obj) {
        string += i + '=' + obj[i] + '; ';
    }

    $('status').innerHTML = string;
};


Quad.intersection = function (r, z, theta, ae, flatness) {
    with (Math) {
        var costheta = cos(theta);
        var sintheta = sin(theta);
        var fminus = 1.0 - flatness;
        var fmsquared = fminus * fminus;
        
        var a = (fmsquared * costheta * costheta) + (sintheta * sintheta);
        var b = (fmsquared * r * costheta) + (z * sintheta);
        var c = (fmsquared * ((r * r) - (ae * ae))) + (z * z);
        
        var k = Quad.minroot(a, -2 * b, c);

        var rr = r - (k * costheta);
        var zz = z - (k * sintheta);

        //Util.debug({r: r, z: z,  theta: theta, ae: ae, flatness: flatness, k: k});
                 

        return new Point(rr, zz);
    }
};
