;(function($) {
// What does the tv2panel plugin do?
$.fn.tv2panel = function(options) {
    var opts = $.extend({}, $.fn.tv2panel.defaults, options),

    panel = (function(){
        var current = {}, pointer,
            className = 'tv2-sky-box',

        position = function(obj){
            var o = obj.caller.offset(), top, left;

            /* If the panel is bigger than the width of the boundaries, it
             * should calculate a new width for the panel. */
            if($(opts.boundaries).width() < current.element.outerWidth()){
                // console.log('Too wide; recalculate width of the panel');
                current.element.css('width',
                    $(opts.boundaries).width()
                    - (current.element.outerWidth() - current.element.innerWidth())
                    - parseInt(current.element.css('paddingLeft'),10)
                    - parseInt(current.element.css('paddingRight'),10)
                    + 'px');
            }

            /* calculate the panels vertical position */
            // console.log('Calculate the vertical position of the panel');
            if(obj.position.indexOf('N') !== -1){
                top = o.top - current.element.outerHeight();
                // console.log('calculating north-facing box');
                pointer.addClass('pointer-s')
                       .css({
                    'left' : 0,
                    'marginLeft' : (-1*(pointer.width()/2)) + 'px',
                    'bottom': (-1 * pointer.height()) +'px'
                });
            }
            else if(obj.position.indexOf('S') !== -1){
                top = o.top + obj.caller.height();
                // console.log('calculating south-facing box');
                pointer.addClass('pointer-n')
                       .css({
                    'left' : 0,
                    'marginLeft' : (-1*(pointer.width()/2)) + 'px',
                    'top': (-1 * pointer.height()) +'px'
                });
            }
            else {
                top = o.top
                    - (current.element.outerHeight() / 2)
                    + (obj.caller.outerHeight() / 2);
            }

            /* calculate the panels horizontal position */
            if(obj.position.indexOf('W') !== -1){
                // console.log('calculating west-facing box');
                left = o.left - current.element.outerWidth();

                pointer.addClass('pointer-e')
                       .css({
                    'right' : (-1 * pointer.width()) + 'px',
                    'top' : (o.top - (obj.caller.height() / 2) - top)
                });
            }
            else if(obj.position.indexOf('E') !== -1){
                // console.log('calculating east-facing box');
                left = o.left + obj.caller.outerWidth();

                pointer.addClass('pointer-w')
                       .css({
                    'left' : (-1 * pointer.width()) + 'px',
                    'top': (o.top - (obj.caller.height() / 2) - top)
                });
            }
            else {
                left = o.left
                     - (current.element.outerWidth() / 2)
                     + (obj.caller.outerWidth() / 2);

                /* position the pointer */
                pointer.css({
                    'left': o.left + (obj.caller.width()/2) - left
                });
            }

            /* todo, comment */
            var offsetLeft = $(opts.boundaries).offset().left;
            if(left < offsetLeft){
                left = $(opts.boundaries).offset().left;
                /* reposition the pointer */
                pointer.css({
                    'left': o.left + (obj.caller.width()/2) - left
                });
            }

            /* todo, comment */
            if(left + current.element.outerWidth() > $(opts.boundaries).width()){

                left = $(opts.boundaries).offset().left
                     - opts.distanceToBoundary
                     + $(opts.boundaries).width()
                     - current.element.outerWidth();
                // console.log('repositioning the pointer');
                /* reposition the pointer */

                var padding = parseInt(
                    parseInt(obj.caller.css('paddingLeft'),10) +
                    parseInt(obj.caller.css('paddingRight'),10)
                , 10);

                pointer.css({
                    'left': o.left + ( obj.caller.width()-padding / 2 ) - left
                });
            }

            return { 'left': left + 'px', 'top': top + 'px' };
        },

        timer = (function(){
            var tid;

            return{
                start : function(t){
                    if(tid) window.clearTimeout(tid);
                    tid = window.setTimeout(
                        panel.close,
                        (typeof t == 'number') ? t : opts.killTimeout
                    );
                },
                stop : function(){
                    if(tid) window.clearTimeout(tid);
                    tid = null;
                }
            };
        }()),

        close = function(){
            if(current.element && current.rtnMethod && current.home){
                /* remove event handlers and class */
                current.element.removeClass(className)
                    .unbind('mouseout')
                    .unbind('mouseover');

                /* return the panel to its original position in the DOM */
                current.home[current.rtnMethod](current.element);

                current.element = current.rtnMethod = current.home = null;
            }
        },

        open = function(s,caller){
            if(s[0] == undefined) {
                // the panel is already displayed, should reset the timer
                return;
            }
            if(current.element && current.element !== $(s)){
                timer.stop(); close();
            }

            current.element = $(s);

            if(current.element.prev()){
                current.home = current.element.prev();
                // remember the node before
                current.rtnMethod = 'after';
            }
            else if(current.element.next()){
                current.home = current.element.next();
                // remember the node after
                current.rtnMethod = 'before';
            }
            else {
                // remember the parent node
                current.home = current.element.parent();
                current.rtnMethod = 'append';
            }

            pointer = pointer || $('<div class="pointer"></div>');
            current.element.append(pointer);

            current.element.addClass(className).appendTo('body').css(
                position({ position: opts.position, caller: caller })
            );

            current.element
                .bind('mouseover',timer.stop)
                .bind('mouseout',timer.start);
        };

        return{
            open : open,
            close : close,
            setTimer : timer.start
        };
    }());

    return this.each(function() {
        var $this = $(this);

        if(opts.shouldActivateOnHover){
            $('li:has('+opts.panelSelector+') a', $this).hover(
                function(e){
                    $target = $(e.target);
                    panel.open($(opts.panelSelector,$target.parent()), $target);
                },
                panel.setTimer
            );
        }

        $('li:has('+opts.panelSelector+') > a', $this).click(
            function(e){
                $target = $(e.target);
                panel.open($(opts.panelSelector,$target.parent()), $target);
                e.preventDefault();
            }
        ).mouseleave(panel.setTimer);

        /* add a class to all the elements in the list, that has a panel */
        $('li:has(.panel)',$this).addClass('has-panel').hover(
            function(){ $(this).addClass('hover'); },
            function(){ $(this).removeClass('hover'); }
        );

        // Support for the Metadata Plugin.
        var o = $.meta ? $.extend({}, opts, $this.data()) : opts;
    });

};

// default options
$.fn.tv2panel.defaults = {
    killTimeout: 750,
    position : 'below',
    boundaries : 'html',
    distanceToBoundary : 5,
    panelSelector : 'ul',
    shouldActivateOnHover : false
};

})(jQuery);
