﻿// setInterval fix for IE
/*@cc_on
(function(f){
    window.setTimeout = f(window.setTimeout);
    window.setInterval = f(window.setInterval);
})

(function(f){
    return function(c,t){
        var a = Array.prototype.slice.call(arguments,2);
        if(typeof c != "function")
        c = new Function(c);
        return f(function(){
            c.apply(this, a)
        }, t)
    }
});
@*/

// Set up for prototypal inheritance from book, Javascript: The Good Parts
if (typeof Object.beget !== 'function') {
    Object.beget = function(o) {
        var F = function() { };
        F.prototype = o;
        return new F();
    };
}


var AnimationAlgorithm = {
    Exponential: {
        ValueAtTime: function(time) {
            return Math.pow(time, 4);
        },

        length: 1
    },

    Root: {
        ValueAtTime: function(time) {
            return Math.sqrt(time);
        },

        length: 1
    },

    Bubble: {
        ValueAtTime: function(time) {
            return -1.5* Math.pow(time, 3) + 1.5 * Math.pow(time, 2) + time;
        },

        length: 1
    },

    Linear: {
        ValueAtTime: function(time) {
            return time;
        },

        length: 1
    }
};

/* Object that is capable of making html element containers openable 
and closeable and moderating them so that only one container may 
be open at a time. */
var ExpandableModerator =
{
    MENU_SPEED_MS: 250,
    UPDATE_FREQ_MS: 60,
    AnimationMethod: AnimationAlgorithm.Root,
    TagName: 'div', /* DIV as default container element */
    Moderating: {}, /* List of all expandable divs being moderated */
    AnimatedProperties: ['width'], /* CSS Properties to animate from 0-100% in pixels */

    /* Initializes a container with properties needed to 
    open/close and be moderated by this moderator. 
    Gives container a open() and close() function. 
    Does not change style of container. */
    Init: function(div_id) {
        var div = document.getElementById(div_id);
        if (!this.Moderating[div_id]) {
            this.Moderating[div_id] = div;
            div.Moderator = this;
            div.style.overflow = 'hidden';
            div.style.height = div.clientHeight + 'px';
            div.style.width = div.clientWidth + 'px';
            div.orig = {};
            for (var s in this.AnimatedProperties) {
                div.orig[this.AnimatedProperties[s]] = parseInt(div.style[this.AnimatedProperties[s]]);
            }

            div.time = 0;
            div.delta = this.AnimationMethod.length / (this.MENU_SPEED_MS / this.UPDATE_FREQ_MS);
            div.Tick = this.AnimationMethod.ValueAtTime;

            div.open = function() {
                clearInterval(this.timer);
                this.timer = setInterval(function(that) {
                    that._openDivTick();
                }, this.Moderator.UPDATE_FREQ_MS, this);
            };

            div.close = function() {
                clearInterval(this.timer);
                this.timer = setInterval(function(that) {
                    that._closeDivTick();
                }, this.Moderator.UPDATE_FREQ_MS, this);
            };

            div._openDivTick = function() {
                this.style.visibility = 'visible';
                this.style.display = '';
                this._tick(function(div, prop) {
                    var ret = div.delta * div.time < div.Moderator.AnimationMethod.length;
                    if (!ret) div.style[prop] = div.orig[prop] + 'px';
                    return !ret;
                });

                this.time++;
            };

            div._tick = function(isFinished) {
                var finished = true;
                for (var s in this.Moderator.AnimatedProperties) {
                    var prop = this.Moderator.AnimatedProperties[s];
                    try {
                        this.style[prop] = this.orig[prop] * this.Tick(this.delta * this.time) + 'px';
                    }
                    catch (ex) { alert(this.orig[prop] + ' ' + this.delta + ' ' + this.time + ' ' + this.Tick + ' ' + prop); }
                    finished = isFinished(this, prop);
                }
                if (finished)
                    clearInterval(this.timer);

                return finished;
            };

            div._closeDivTick = function() {
                this.time--;

                if (this._tick(function(div, prop) {
                    var ret = parseInt(div.style[prop]) > 0;
                    if (!ret) div.style[prop] = '0px';
                    return !ret;
                })) {
                    this.style.visibility = 'hidden';
                    this.style.display = 'none';
                }

            };
        }

        return div;
    },

    /* Toggle open/close a container depending on it's current state,
    as well as close all currently open containers in this moderator.
    Note: container.open() and container.close() does not moderate 
    the other containers.
    */
    ToggleById: function(div_id) {
        var div = this.Init(div_id);
        if (div.style.visibility !== 'hidden') {
            div.close();
        }
        else {
            // Close all open containers
            for (openDiv in this.Moderating) {
                if (this.Moderating.hasOwnProperty(openDiv) &&
					this.Moderating[openDiv].style.visibility !== 'hidden') {
                    this.Moderating[openDiv].close();
                }
            }
            setTimeout(function(that) {that.open();}, this.MENU_SPEED_MS+45, div);
        }
    },

    /* Init's all containers with given classname and hides them 
    until a container.open() or this.ToggleById() function is performed. */
    InitByClass: function(classname) {
        var divs = document.getElementsByTagName(this.TagName);
        for (var i = 0; i < divs.length; i++) {
            if (divs[i].className === classname) {
                this.Init(divs[i].id);
                divs[i].style.visibility = 'hidden';
                divs[i].style.display = 'none';
                for (var s in this.AnimatedProperties) {
                    divs[i].style[this.AnimatedProperties[s]] = '0';
                }
            }
        }
    },

    /* Returns a new moderator like this one, 
    except with a separate list of expandable containers */
    NewModerator: function() {
        var ret = Object.beget(this);
        ret.Moderating = [];
        return ret;
    }
};
