/**
 * Holds the original positions of the boxes.
 *
 * @var     Positions
 * @type    Object
 */
var Positions = {"main":{"top":132,"left":50,"width":800,"height":405},"news":{"top":296,"left":21,"width":173,"height":110},"admissions":{"top":176,"left":29,"width":215,"height":94},"friends":{"top":99,"left":461,"width":185,"height":65},"gallery":{"top":432,"left":35,"width":176,"height":122},"help":{"top":411,"left":739,"width":126,"height":105},"faculty":{"top":260,"left":702,"width":179,"height":125},"programs":{"top":117,"left":659,"width":161,"height":61},"about":{"top":108,"left":275,"width":173,"height":81}};

/**
 * Contains the id's of all of our boxes with their corresponding colors
 * defined in hex.
 *
 * @var     BoxColors
 * @type    Object
 */
var BoxColors = {
    news:       '#f1ca7b',
    admissions: '#8cb0c1',
    friends:    '#fadf92',
    gallery:    '#dcdd8c',
    help:       '#c5c3b9',
    faculty:    '#bac788',
    programs:   '#b3ccd4',
    about:      '#4d7c86'
};

/**
 * Holds the innerHTML of the different boxes so we don't have repeated Ajax
 * calls.
 *
 * @var     Contents
 * @type    Object
 */
var Contents = {
    main:       null,
    news:       null,
    admissions: null,
    friends:    null,
    gallery:    null,
    help:       null,
    faculty:    null,
    programs:   null,
    about:      null
};

/**
 * Holds the current forward animation.
 *
 * @var     Forward
 * @type    Animation
 */
var Forward;

/**
 * Holds the current backward animation.
 *
 * @var     Backward
 * @type    Animation
 */
var Backward;

/**
 * Holds the currently finishing animation. Useful for rewinding when the user
 * selects a different box while finishing.
 *
 * @var     Finish
 * @type    Animation.Composite
 */
var Finish;

/**
 * Holds styles that should apply to the frontmost box.
 *
 * @var     Front
 * @type    Object
 */
var Front =  {
    top:    132,
    left:   50,
    height: 0,
    width:  800
};

// shorthand for YAHOO.util.*
var C = YAHOO.util.Connect;
var D = YAHOO.util.Dom;
var E = YAHOO.util.Event;

var hideEl = function(el, anim){
    if(anim){
        Animation.Fx.fadeOut(el, {duration: 500}).play();
    }else{
        el.style.visibility = 'hidden';
    }
};

var showEl = function(el, anim, cb){
    if(anim){
        var o = {duration: 500};
        if(typeof cb == 'function') o.callback = cb;
        Animation.Fx.fadeIn(el, 1, o).play();
    }else{
        el.style.visibility = 'visible';
    }
};

var selectSetStyle = function(selector, root, style, value){
    var nodes = DomQuery.select(selector, root);
    for(var i = 0, len = nodes.length; i < len; ++i){
        D.setStyle(nodes[i], style, value);
    }
};

/**
 * Sets all overflow properties of the given element to "hidden" and returns an
 * object containing the old overflow settings.
 *
 * @param   {HTMLElement}   el      The element to clip
 * @return  {Object}                An object with the old overflow settings
 */
var clipEl = function(el){
    var clip = {
       o: D.getStyle(el, 'overflow'),
       x: D.getStyle(el, 'overflow-x'),
       y: D.getStyle(el, 'overflow-y')
    };
    D.setStyle(el, 'overflow', 'hidden');
    D.setStyle(el, 'overflow-x', 'hidden');
    D.setStyle(el, 'overflow-y', 'hidden');
    return clip;
};

/**
 * Restores an element's overflow settings to what they were originally before
 * clipEl() was called.
 *
 * @param   {HTMLElement}   el      The element to unclip
 * @param   {Object}        clip    The element's old clip settings (result of
 *                                  clipEl())
 * @return  void
 */
var unclipEl = function(el, clip){
    D.setStyle(el, 'overflow', clip.o);
    D.setStyle(el, 'overflow-x', clip.x);
    D.setStyle(el, 'overflow-y', clip.y);
};

/**
 * Contains methods for handling the animation of the boxes.
 *
 * @class       Box
 * @singleton
 */
var Box = new function(){

    var load_counter = 0;

    var makeWhite = function(box){
        selectSetStyle('div[class*="top"]', box, 'height', '0px');
        D.addClass(box, 'white');
        D.setStyle(DomQuery.selectNode('div.middle', box), 'background-color', '#ffffff');
    };

    var makeColor = function(box){
        D.removeClass(box, 'white');
        selectSetStyle('div[class*="top"]', box, 'height', '7px');
        D.setStyle(DomQuery.selectNode('div.middle', box), 'background-color', BoxColors[box.id]);
    };

    var getContent = function(box){
        C.asyncRequest('GET', 'http://visualarts.byu.edu/' + box.id + '/index_content', {
            success: function(xhr){
                Contents[box.id] = xhr.responseText;
                loadContent(box);
            },
            failure: function(xhr){
                // redirect on failed requests
                window.location.href = 'http://visualarts.byu.edu/' + box.id + '/index.php';
            }
        });
    };

    var loadContent = function(box){
        if(load_counter++ > 0) fillContent(box);
    };

    var removeContent = function(box){
        D.get(box.id + '-content').innerHTML = '';
    };

    var fillContent = function(box){
        D.get(box.id + '-content').innerHTML = Contents[box.id];

        // automatically calculate content height
        var el = DomQuery.selectNode('div.middle', box);
        var old_h = el.offsetHeight || 0;
        var clip = clipEl(el);
        D.setStyle(el, 'height', Front.height + 'px'); // force clipping
        setTimeout(function(){
            var height = parseInt(el.scrollHeight, 10); // parseInt for Safari
            var duration = Math.min(2000, Math.round(height / 2)); // 2px/millisecond, limit to 2 seconds
            D.setStyle(el, 'height', old_h); // restore original height
            finishContent(box, animate(el, {
                height: [Front.height, height]
            }, {
                duration: duration,
                callback: function(){
                    unclipEl(el, clip);
                }
            }));
        }, 0);
    };

    var finishContent = function(box, anim){
        var anims = [anim, Animation.Fx.fadeIn(D.get(box.id + '-content'), 1, {
            duration: 350,
            callback: function(){
                // refresh to preserve back button functionality, should be transparent
                window.location.href = 'http://visualarts.byu.edu/' + box.id + '/index.php';
            }
        })];
        Finish = new Animation.Composite(anims).play();
    };

    var hideContent = function(box){
        hideEl(D.get(box.id + '-content'), false);
    };

    /**
     * Brings the specified box to the front of the others. If animate is true,
     * the transition will be animated.
     *
     * @method  bringForward
     * @param   {HTMLElement}   box         The box to bring to the front
     * @param   {Boolean}       anim        Whether to animate the transition
     * @return  void
     * @access  public
     */
    this.bringForward = function(box, anim){
        if(!box || D.hasClass(box, 'front')) return; // already in front

        Box.sendBackward(); // send the frontmost to the back
        load_counter = 0; // reset load_counter
        D.replaceClass(box, 'pointer', 'front'); // remove pointer, add front class

        var callback;
        // get the contents for the box (if we don't have them already)
        if(Contents[box.id] == null){
            getContent(box);
            callback = function(){
                makeWhite(box);
                loadContent(box);
            };
        // otherwise, if we're animating, put the contents in when we're done
        }else if(anim){
            callback = function(){
                makeWhite(box);
                fillContent(box);
            };
        }

        // set up the animation
        var position = Positions[box.id];
        Forward = new Animation({
            duration:   750,
            callback:   callback,
            ease:       Animation.Ease.quintOut
        }).add(new Effect(box, {
            top:    [position.top, Front.top],
            left:   [position.left, Front.left],
            width:  [position.width, Front.width]
        }));

        hideEl(D.get(box.id + '-label'), anim); // hide the label

        if(anim){
            hideContent(box); // in case it's not hidden already
            Forward.add(new Effect(DomQuery.selectNode('div.middle', box), {
                height: [position.height, Front.height]
            }));
            Forward.play();
            usedSet = false;
        }else{
            Forward.set(1);
            makeWhite(box);
            usedSet = true;
        }
    };

    var usedSet = false;

    /**
     * Sends the frontmost box to its initial position using Animation.reverse().
     * If set_wrapper is true, will also set the wrapper height when it is
     * finished.
     *
     * @method  sendBackward
     * @return  void
     * @access  public
     */
    this.sendBackward = function(){
        var box = DomQuery.selectNode('div.front');
        if(!box) return; // no box in front

        D.replaceClass(box, 'front', 'pointer'); // remove front, add pointer class
        removeContent(box); // empty contents
        makeColor(box); // color in

        setTimeout(function(){
            showEl(D.get(box.id + '-label'), true); // fade in the label
        }, 500);
        if(Forward.isActive()) Forward.stop();

        Backward = Forward; // break the reference to Forward
        Backward.callback = null; // erase the callback
        Backward.ease = Animation.Ease.quintIn; // reverse the transition
        if(usedSet){
            var position = Positions[box.id];
            Backward.add(new Effect(DomQuery.selectNode('div.middle', box), {
                height: [position.height, Front.height]
            }));
            usedSet = false;
        }
        //Backward.seek(0);
        if(Finish){
            Finish.seek(0);
        }
        Backward.seek(0);
    };

};

E.onDOMReady(function(){


    // some intro here maybe?


    for(var id in BoxColors){
        if(id != 'main'){
            var box = D.get(id);
            D.addClass(box, 'pointer'); // add pointer class
            hideEl(D.get(id + '-content'), false); // hide content div
            D.setStyle(DomQuery.selectNode('div.middle', box), 'background-color',
                BoxColors[id]); // fill middle color
        }
    }

    // listen for box clicks
    E.addListener(document, 'mouseup', function(e){
        var target = E.getTarget(e), match;

        var box = target.id && (match = target.id.match(/([a-z]+)-label/))
            ? D.get(match[1]) // labels are clickable
            : D.getAncestorByClassName(target, 'box');
        if(box){ // box was clicked
            var main = D.get('main');
            if(target.tagName.toUpperCase() == 'AREA'){ // content 'close' button clicked
                // send home
                window.location.href = 'http://visualarts.byu.edu/';

                //showEl(main, true); // show the main box
                //Box.sendBackward(); // send the clicked box to back
                // TODO: get the content of the main box if it's not loaded yet
            }else if(box.id != 'main'){ // a box was clicked, ignore the main box
                if(main.style.visibility != 'hidden') hideEl(main, true); // hide the main box
                Box.bringForward(box, true); // bring the clicked box to front, animate
            }
        }
    });

    // listen for any expandable triggers
    var triggers = DomQuery.select('p.expand-trigger');
    for(var i = 0, len = triggers.length; i < len; ++i){
        E.addListener(triggers[i], 'mouseup', function(e){
            var target = E.getTarget(e);
            if(!D.hasClass(target, 'expand-trigger')){
                target = D.getAncestorByClassName(target, 'expand-trigger');
            }
            if(target){
                var match = target.id.match(/(.+)-trigger$/);
                var t = D.get(match[1] + '-target');
                if(t){
                    var hide = t.style.display == 'block';
                    t.style.display = hide ? 'none' : 'block';
                    D[hide ? 'removeClass' : 'addClass'](target, 'expand-trigger-expanded');
                }
            }
        });
    }

    // table striping
    D.getElementsByClassName('striped', 'table', document, function(tbl){
        var tr = tbl.getElementsByTagName('tr');
        for(var i = 0, len = tr.length; i < len; ++i){
            if(!(i % 2)) D.addClass(tr[i], 'alt');
        }
    });

});