//####### Copyright Mavice LLC 2009 – www.mavice.com ########

function HorizontalNodeBrowser(container_id, visible){
    this.container_id = container_id;
    this.visible = visible;
    this.position = 0;
    this.determine_arrow_visibility();
}

HorizontalNodeBrowser.prototype.container_id;
HorizontalNodeBrowser.prototype.position;
HorizontalNodeBrowser.prototype.visible;

/*
 * Move the images in the image browser left or right
 * @param   direction: the direction to move in (left or right)
 */
HorizontalNodeBrowser.prototype.animate = function(direction){
    var nodes = $("#" + this.container_id + " div.nodes_inner");
    var marginLeft = parseInt(nodes.css("margin-left").replace("px", ""));
    if(!marginLeft) marginLeft = 0;
    
    if(direction == "right"){
        if(this.position < this.get_node_count()-1){
            nodes.animate({
                marginLeft: "-=" + this.get_movement_size() + "px"
            });
            this.position += 1;
        }
    }
    else if(direction == "left"){
        if(this.position > 0){
            nodes.animate({
                marginLeft: "+=" + this.get_movement_size() + "px"
            });
            this.position -= 1;
        }
    }
    this.determine_arrow_visibility();
}

/*
 * Calculate the number of image nodes in the given container
 * @returns integer number of nodes
 */
HorizontalNodeBrowser.prototype.get_node_count = function(){
    return parseInt($("#" + this.container_id + " div.node").size());
}

/*
 * Calculate the number of pixels to move each image by.  Amount is width of node + margin_left + margin_right
 * @returns integer pixel amount (i.e. 99)
 */
HorizontalNodeBrowser.prototype.get_movement_size = function(){
    var node = $("#" + this.container_id + " div.node");
    var width = parseInt(node.css("width"));
    var margin = parseInt(node.css("marginRight")) * 2; //assume left margin is the same as the right margin
    if(!margin) margin = 0;
    
    return (width + margin);
}

/*
 * Determine and enact whether the left/right arrows should be visible
 */
HorizontalNodeBrowser.prototype.determine_arrow_visibility = function(){
    var nodes = this.get_node_count();

    if(this.position == 0 || nodes <= this.visible)
        $("#" + this.container_id + " .left").css("visibility", "hidden");
    else
        $("#" + this.container_id + " .left").css("visibility", "visible");
 
    if(this.position+this.visible == nodes || nodes <= this.visible)
        $("#" + this.container_id + " .right").css("visibility", "hidden");
    else
        $("#" + this.container_id + " .right").css("visibility", "visible");
}

/************** Accordion *****************/

ParallelAnimations = function(animations, opts){
    this.init(animations, opts);
};

$.extend(ParallelAnimations.prototype, {
    options: {
        duration: 250
    },
    rules: {},

    init: function(animations, opts){
 
        // Overwrite the default options
        $.extend(this.options, opts);

        // Create a set of rules to follow in our animation
        for(var i in animations){
            this.rules[i] = {
                element: animations[i].element,
                changes: new Array()
            };
            
            for(var style in animations[i].styles){
            
                // Calculate the start and end point values for the given style change
                var from = this.parse_style_value(animations[i].element, style, "");
                var to = this.parse_style_value(animations[i].element, style, animations[i].styles[style]);

                this.rules[i].changes.push({
                    from: from,
                    to: to,
                    style: style
                });
            }
        }

        this.start();
    },
    
    /*
     * Does some parsing of the given and real style values
     * Allows for pixel and percentage-based animations
     */
    parse_style_value: function(element, style, given_value){
        var real_value = element.css(style);

        if(given_value.indexOf("px") != -1){
            return {
                amount: given_value.substring(0, (given_value.length - 2)),
                unit: "px"
            };
        }

        if(real_value == "auto"){
            return {
                amount: 0,
                unit: "px"
            };
        }
        
        if(given_value.indexOf("%") != -1){
            var fraction = given_value.substring(0, given_value.length - 1) / 100;
            
            return {
                amount: (real_value.substring(0, real_value.length - 2) * fraction),
                unit: "px"
            };
        }
        
        if(!given_value){
            return {
                amount: real_value.substring(0, real_value.length - 2),
                unit: "px"
            };
        }
    },
    
    /*
     * Start the animation
     */
    start: function(){
        var self = this;
        var start_time = new Date().getTime();
        var freq = (1 / this.options.duration);

        var interval = setInterval(function(){
            var elapsed_time = new Date().getTime() - start_time;
        
            if(elapsed_time < self.options.duration){
                var f = elapsed_time * freq;

                for(var i in self.rules){
                    for(var j in self.rules[i].changes){
                        self.step(self.rules[i].element, self.rules[i].changes[j], f);
                    }
                }
            }
            else{
                clearInterval(interval);

                for(var i in self.rules){
                    for(var j in self.rules[i].changes)
                        self.step(self.rules[i].element, self.rules[i].changes[j], 1);
                }
            }
        }, 10);
    },

    /*
     * Perform an animation step
     * Only works with position-based animations
     */ 
    step: function(element, change, fraction){
    
        var new_value;
        switch(change.style){
            case 'height':
            case 'width':
            case 'top':
            case 'bottom':
            case 'left':
            case 'right':
            case 'marginTop':
            case 'marginBottom':
            case 'marginLeft':
            case 'marginRight':
                new_value = Math.round(change.from.amount - (fraction * (change.from.amount - change.to.amount))) + change.to.unit;
                break;
        }
        
        if(new_value)
            element.css(change.style, new_value);
    }
});


Accordion = function(container_id, options){
    if(!$(container_id).length > 0)
        return;

    this.init(container_id, options);
}

$.extend(Accordion.prototype, {
    container_id: '',
    options: {},
    active_tab: 0,    
    animating: false,
    button_position: 'below',
    duration: 250,
    height: 100,
    event_type: 'mouseover',

    handle_class: ".handle",
    section_class: ".section",
    
    init: function(container_id, options){
        var self = this;
        this.container_id = container_id;
        this.button_position = this.get_button_position();

        $(this.container_id).hide();

        // The height of each section, use the height specified in the stylesheet if possible
        this.height = $(this.container_id + " " + this.section_class).css("height");
        
        if(options && options.duration)    this.duration = options.duration;
        if(options && options.active_tab)  this.active_tab = options.active_tab;
        if(options && options.event_type)  this.event_type = options.event_type;

        // Set the first section to have a height and be "open"
        // All the rest of the sections should have 0px height
        $(this.container_id).children(this.section_class).eq(this.active_tab)
            .addClass("open")
            .css("height", this.height)
            .siblings(this.section_class)
            .css("height", "0px");

        // figure out the state of the handles
        this.do_handle_logic($(this.container_id).children(this.handle_class).eq(this.active_tab));

        // Set up an event handler to animate each section
        $(this.container_id + " " + this.handle_class).bind(this.event_type, function(){

            if(self.animating)
                return;
                
            self.animate($(this));
        });
        
        if(this.event_type != "mouseover"){
            $(this.container_id + " " + this.handle_class).mouseover(function(){
                self.add_hover($(this));
            }).mouseout(function(){
                self.remove_hover($(this));
            });
        }

        $(this.container_id).show();
    },

    /*
     * Determines whether handles are above or below their associated section
     */     
    get_button_position: function(){
        return ($(this.container_id).children(":first").hasClass(this.handle_class) ? 'above' : 'below');
    },
    
    /*
     * Animate the accordion from one node to another
     */ 
    animate: function(handle){
        var active_section = (this.button_position == 'below' ? handle.prev() : handle.next());    
        var open_section = handle.siblings().andSelf().filter(".open");
        
        if(active_section.hasClass("open"))
            return;

        this.animating = true;

        open_section.removeClass("open");
        active_section.addClass("open");

        // figure out the state of the handles
        this.do_handle_logic(handle);

        // Close the open section
        var arr = new Array();
        arr.push({
            element: open_section,
            styles: {
                "height": "0px"
            }
        });
        arr.push({
            element: active_section,
            styles: {
                "height": this.height
            }
        });

        new ParallelAnimations(arr, {duration: this.duration});

        var self = this;
        window.setTimeout(function(){
            self.animating = false;
        }, this.duration);
    },

    /*
     * Update the current class or "state" of each handle
     */ 
    do_handle_logic: function(handle){
        var all_handles = handle.siblings(".handle").andSelf();
        var above_handles = handle.prevAll(this.handle_class);
        var below_handles = handle.nextAll(this.handle_class);

        // Remove all obsolete handles
        all_handles
            .removeClass("handle_on_above")
            .removeClass("handle_on_below")
            .removeClass("handle_off_below")
            .removeClass("handle_off_above");
        
        // Apply the "on" state to the current handle
        if(this.button_position == 'below'){
            handle
                .addClass("handle_on_below");
        }
        else{
            handle
                .addClass("handle_on_above");
        }
        
        // Apply the off above/below state to the rest of the handles
        above_handles
            .addClass("handle_off_above");
        
        below_handles
            .addClass("handle_off_below");
    },
    
    add_hover: function(handle){
        if(!handle.next().hasClass("open")){
            if(handle.hasClass("handle_off_above")){
                handle
                    .removeClass("handle_off_above")
                    .removeClass("handle_off_below")
                    .addClass("handle_on_above");
            }
            else if(handle.hasClass("handle_off_below")){
                handle
                    .removeClass("handle_off_above")
                    .removeClass("handle_off_below")
                    .addClass("handle_on_below");
            }
        }
    },

    remove_hover: function(handle){
        if(!handle.next().hasClass("open")){
            if(handle.hasClass("handle_on_above")){
                handle
                    .removeClass("handle_on_above")
                    .removeClass("handle_on_below")
                    .addClass("handle_off_above");
            }
            else if(handle.hasClass("handle_on_below")){
                handle
                    .removeClass("handle_on_above")
                    .removeClass("handle_on_below")
                    .addClass("handle_off_below");
            }
        }
    }
});



/**
 *  Some scrollboxes (like MyMail) have things above them that force the box to be hidden at the bottom
 *  This adjusts the height of the box so it does not get hidden
 */
function adjust_scrollbox_height(id){
    if(!$(id).length > 0)
        return;
        
    var offset_y = $(id).position().top;
    var height = $(id).height();
    $(id).height((height - offset_y) + "px");
}

function refresh_arrow_visibility(container_id){
    if(!$(container_id).length > 0)
        return;
    
    var up = $(container_id).children(".up");
    var down = $(container_id).children(".down");
    var body = $(container_id).children(".body");
    var scrollTop = body.scrollTop();
    var container_height = body.height();
    var actual_height = $(container_id + " .scrollbox_container:visible").height();

    if(actual_height > container_height){
        up.hide();
        down.show();
    }
    else{
        up.hide();
        down.hide();
    }
}

function VerticalScrollBox(container_id, pixelsToScroll){
    if(!$(container_id).length > 0)
        return;

    if(!pixelsToScroll)
        pixelsToScroll = 150;

    var up = $(container_id + " .up");
    var body = $(container_id + " .body");
    var down = $(container_id + " .down");
    var actual = $(container_id + " .scrollbox_container");
    
    // Automatically scrolls the body to the top
    body.scrollTop("0px");
    down.hide();

    up.mousedown(function(){
        down.show();

        body
            .animate({"scrollTop": "-=" + pixelsToScroll + "px"}, 500);
        
        if(body.scrollTop() <= pixelsToScroll){
            setTimeout(function(){
                up.hide();
            }, 100);
        }
        else{
            setTimeout(function(){
                up.show();
            }, 100);
        }
    });
    
    down.mousedown(function(){
        up.show();

        body
            .animate({"scrollTop": "+=" + pixelsToScroll + "px"}, 500);

        if(body.scrollTop()+pixelsToScroll+body.height() > actual.height()){
            setTimeout(function(){
                down.hide();
            }, 100);
        }
        else{
            setTimeout(function(){
                down.show();
            }, 100);
        }
    });
}

/*
 * Similar to a scroll box, except this highlights a selected item in the box and
 * moves the list around a center-point, keeping the highlighted item always in the center
 */
VerticalSpinBox = function(container_id, display_id, options){
    if(!$(container_id).length > 0)
        return;

    this.init(container_id, display_id, options);
}

$.extend(VerticalSpinBox.prototype, {
    dummy_height: 0,
    off_height: 0,
    on_height: 0,
    display_id: '',
    container_id: '',
    options: {},

    /*
     * Initialize the VerticalSpinBox
     * @param container_id: The container_id of the spinbox (contains the up and down buttons and the body)
     * @param display_id: The display element used to highlight the current "on" item
     */
    init: function(container_id, display_id, options){
        var self = this;

        this.container_id = container_id;
        this.display_id = display_id;
        this.options = options;
        
        var current = (options && options.current ? options.current : 10);
        
        // Get the height of the on and off states of each option
        this.dummy_height = $(container_id + " .body div.dummy").height();
        this.off_height = $(container_id + " .body div.option:last").height();

        this.refresh_display();

        // Make sure we're always at default when reloading the page
        $(container_id + " .body .options_container").css("marginTop", -1*(this.dummy_height-($.browser.msie ? 149 : 173)) + "px");
        
        this.random_spin();

        $(container_id + " .down").mousedown(function(){
            var current = $(self.container_id + " .body .options_container .on");
            if(current.next(".option").size() > 0)
                self.jump(current.prevAll(".option").size()+1);
        });
        $(container_id + " .up").mousedown(function(){
            var current = $(self.container_id + " .body .options_container .on");
            if(current.prev(".option").size() > 0)
                self.jump(current.prevAll(".option").size()-1);
        });
        $(container_id + " .body .option").click(function(){
            self.jump($(this).prevAll(".option").size());
        });
    },
    
    jump: function(dest){
        var body = $(this.container_id + " .body .options_container");
        var current = $(this.container_id + " .body .options_container .on");
        var index = 0;

        if(current.size() > 0){
            index = current.prevAll(".option").size();
            
            current
                .removeClass("on");
        }

        body
            .css("marginTop", parseInt(body.css("marginTop").replace("px", ""))-(this.off_height*(dest-index))+"px")

        $(this.container_id + " .body .options_container .option:eq(" + dest + ")")
            .addClass("on");
        
        this.refresh_display();

        if(this.options.afterMove)
            this.options.afterMove();
    },
    
    /*
     * Refresh the given display entry to display the current "on" item
     */
    refresh_display: function(str){
        if(this.display_id){
            var word = $(this.container_id + " .body .options_container .on").text();
            word = word.substr(0, 1).toUpperCase() + word.substr(1);
            $("#"+ this.display_id).text(word);
        }
    },
    
    /*
     * "Spin" the options to a random item
     */
    random_spin: function(){
        var count = $(this.container_id + " .body .options_container .option").size();
        var dest = Math.floor(Math.random() * count);
        this.jump(dest);
    }
});
