/**
 * spenlen.com site JavaScript.
 *
 * @package    SpenlenMediaWeb
 * @subpackage UI
 * @copyright  Copyright 2009 Spenlen Media, Inc. (http://spenlen.com)
 * @version    $Id$
 */


/**
 * Spenlen global namespace object.
 * @var Object
 */
var SM = {};



SM.Cookie = {
    
    /**
     * Returns the value of the specified cookie.
     *
     * @return String
     */
    get : function (cookieName)
    {
        var cookieValue = document.cookie.match(new RegExp('(^|;)\\s*' + escape(cookieName) + '=([^;\\s]*)'));
        return (cookieValue ? unescape(cookieValue[2]) : '');        
    },
    
    /**
     * Sets the specified browser cookie to the specified value.
     *
     * @param String cookieName
     * @param String cookieValue
     * @param Date   expirationDate (optional) If omitted, defaults to one year
     *   from today.
     */
    set : function (cookieName, cookieValue, expirationDate)
    {
        if (! expirationDate) {
            var expirationDate = new Date();
            expirationDate.setFullYear(expirationDate.getFullYear() + 1);
        }

        /** @todo Write a more appropriate path calculation. */
        newCookie = escape(cookieName) + '=' + escape(cookieValue)
                  + '; expires=' + expirationDate.toUTCString()
                  + '; path=/';
                  
        document.cookie = newCookie;        
    },

    /**
     * Sets the specified browser cookie to the specified value. Does not set
     * an expiration date, so the cookie will die with the browser session.
     *
     * @param String cookieName
     * @param String cookieValue
     */
    setSession : function (cookieName, cookieValue)
    {
        /** @todo Write a more appropriate path calculation. */
        newCookie = escape(cookieName) + '=' + escape(cookieValue)
                  + '; path=/';
                  
        document.cookie = newCookie;        
    }

};


SM.NavigationTabs = {
    
    isTrackingWindowSize : false,
    openAnimations  : {},
    closeAnimations : {},
    
    init : function ()
    {
        $$('#siteNavLinks A').each(function (link) {
            Event.observe(link, 'mouseover', SM.NavigationTabs.open.bind(link));
            Event.observe(link, 'mouseout', SM.NavigationTabs.close.bind(link));
        });
        
        Event.observe(window, 'resize', SM.NavigationTabs.windowOnResize);
        SM.NavigationTabs.windowOnResize();
    },
    
    open : function ()
    {
        if (this.hasClassName('current')) {
            return;
        }
        
        var linkName = this.id.slice(11);
        
        if ((SM.NavigationTabs.closeAnimations[linkName]) &&
            (SM.NavigationTabs.closeAnimations[linkName].state !== 'finished')) {
                SM.NavigationTabs.closeAnimations[linkName].cancel();
        }

        if (SM.NavigationTabs.openAnimations[linkName]) {
            SM.NavigationTabs.openAnimations[linkName].start({duration: 0.5});
        } else {
            SM.NavigationTabs.openAnimations[linkName] =
                new Effect.Morph('siteNavBackground' + linkName, {duration: 0.5, style: {width: '85px'}});
        }
        
        this.setStyle({width: '75px'});
    },
    
    close : function ()
    {
        if (this.hasClassName('current')) {
            return;
        }
        
        var linkName = this.id.slice(11);

        if ((SM.NavigationTabs.openAnimations[linkName]) &&
            (SM.NavigationTabs.openAnimations[linkName].state !== 'finished')) {
                SM.NavigationTabs.openAnimations[linkName].cancel();
        }

        if (SM.NavigationTabs.closeAnimations[linkName]) {
            SM.NavigationTabs.closeAnimations[linkName].start({duration: 0.3});
        } else {
            SM.NavigationTabs.closeAnimations[linkName] =
                new Effect.Morph('siteNavBackground' + linkName, {duration: 0.3, style: {width: '60px'}});
        }
        this.setStyle({width: '50px'});
    },
    
    windowOnResize : function ()
    {
        var delta = 660 - document.viewport.getHeight();
        if (delta < 0) {
            if (SM.NavigationTabs.isTrackingWindowSize) {
                SM.NavigationTabs.isTrackingWindowSize = false;
                Event.stopObserving(window, 'scroll', SM.NavigationTabs.windowOnScroll);
                $('siteNavBackgrounds').setStyle({top: '0px'});
                $('siteNavLinks').setStyle({top: '0px'});
            }
            return;
        }
        if (! SM.NavigationTabs.isTrackingWindowSize) {
            SM.NavigationTabs.isTrackingWindowSize = true;
            Event.observe(window, 'scroll', SM.NavigationTabs.windowOnScroll);
        }
        SM.NavigationTabs.windowOnScroll();
    },
    
    windowOnScroll : function ()
    {
        var newTop = '-'
                   + Math.min(660 - document.viewport.getHeight(),
                              document.viewport.getScrollOffsets().top)
                   + 'px';
        $('siteNavBackgrounds').setStyle({top: newTop});
        $('siteNavLinks').setStyle({top: newTop});
    }
    
};
document.observe('dom:loaded', SM.NavigationTabs.init);


SM.FlashMessage = {

    /**
     * Registers the onclick handler to dismiss the flash message DIV.
     */
    init : function ()
    {
        var message = document.getElementById('flashMessage');
        if (message) {
            Event.observe(message, 'click', SM.FlashMessage.dismiss);
        }
    },

    /**
     * If the flash message DIV is present on the page, animates the background
     * color fading effect.
     */
    flash : function ()
    {
        var message = document.getElementById('flashMessage');
        if (message) {
            new Effect.Highlight(
                message,
                {duration:     2.0,
                 startcolor:   '#ffff33',
                 endcolor:     '#ffffda',
                 restorecolor: '#ffffda'}
                );
        }
    },

    /**
     * Dismisses the flash message.
     */
    dismiss : function ()
    {
        var message = document.getElementById('flashMessage');
        if (message) {
            Event.stopObserving(message, 'click', SM.FlashMessage.dismiss);
            new Effect.Opacity(
                message,
                {duration:   0.5,
                 transition: Effect.Transitions.linear,
                 from:       1,
                 to:         0}
                );
        }
        var container = document.getElementById('flashMessageContainer');
        if (container) {
            new Effect.Morph(
                container,
                {duration: 0.6,
                 style: {height: '0px'},
                 delay: 0.2}
                );
        }
    }

}
document.observe('dom:loaded', SM.FlashMessage.init);
Event.observe(window, 'load', SM.FlashMessage.flash);


SM.Effects = {

    /**
     * Animates the removal of a table row. First fades out the visible content,
     * then substitues an empty table row, animating it's height to zero. When
     * complete, the row element is removed from the document.
     */
    removeTableRow : function (row, animationDuration, fadeDuration)
    {
      if (! animationDuration) {
          animationDuration = 0.3;
      }
      if (! fadeDuration) {
          fadeDuration = 0.5;
      }

      row = $(row);
      var subRow = document.createElement('tr');

      var cells = row.immediateDescendants();
      for (var i = 0; i < cells.length; i++) {
        var subCell = document.createElement(cells[i].tagName);
        $(subCell).setStyle({height: cells[i].getHeight() + 'px', padding: '0px'});
        subRow.appendChild(subCell);

        new Effect.Opacity(cells[i], {duration: fadeDuration, to: 0});
      }

      setTimeout(function () {

        row.parentNode.replaceChild(subRow, row);
        $(subRow).immediateDescendants().each(function (cell) {
          new Effect.Morph(cell, {duration: animationDuration, style:{height: '0px'}});
        });

        setTimeout(function () {
            tbody = subRow.parentNode;
            tbody.removeChild(subRow);
            SM.Effects.stripeTableRows(tbody);
        }, (animationDuration * 1000));

      }, (fadeDuration * 1000));

    },

    /**
     * Pulses the background color of the specified row from yellow to white.
     */
    flashTableRow : function (row)
    {
        new Effect.Highlight(row, {duration: 1.5, restoreColor: 'transparent'});
    },

    /**
     * Animates the appearance of a block-level element (DIV, P, etc.) onto the
     * page. The element must already exist at its desired location and have
     * its inline "display" style property set to "none".
     *
     * Options:
     *   duration - Duration in seconds of the insertion. Defaults to 0.7.
     *   afterFinish - Function to execute after the animation has completed.
     *
     * @todo Calculate additional animation height due to target element's
     *   border heights and vertical padding.
     *
     * @param Element element HTML element or element ID.
     * @param Object  options
     */
    showElement : function (element)
    {
        element = $(element);
        if (! element) {
            return;
        }

        var options = Object.extend({
          duration: 0.7,
          afterFinish: function () {}
        }, arguments[1] || {});

        var animationDiv = $(document.createElement('DIV'));
        animationDiv.setStyle({overflow: 'hidden', height: '0px'});
        element.parentNode.insertBefore(animationDiv, element);

        var newHeight = element.getHeight() + 'px';

        element.parentNode.removeChild(element);
        element.setStyle({opacity: 0, display: 'block'});

        /* To avoid a sudden appearance of the animation DIV due to margins
         * that may be present in the target element, wait for a bit before
         * inserting it back into the page. By this time, the animation DIV
         * will have expanded to about 1/3 its height which should be enough
         * to absorb most margins.
         */
        window.setTimeout(function () {
            animationDiv.appendChild(element);
            new Effect.Opacity(
                element,
                {duration: (options.duration * 0.6),
                 to: 1,
                 afterFinish: function () {
                     element.setStyle({opacity: ''});  // without this, Firefox only goes to 0.999999
                 }
                });
        }, (options.duration * 400));

        new Effect.Morph(
            animationDiv,
            {duration: options.duration,
             style: {height: newHeight},
             afterFinish: function () {
                 animationDiv.parentNode.replaceChild(element, animationDiv);
                 options.afterFinish();
             }
            });
    },

    /**
     * Animates the disappearance of a block-level element (DIV, P, etc.) from
     * the page. When the animation is complete, it inline "display" style
     * property will be set to "none".
     *
     * Options:
     *   duration - Duration in seconds of the insertion. Defaults to 0.7.
     *   afterFinish - Function to execute after the animation has completed.
     *   removeWhenDone - Remove the element when the animation has completed?
     *       Default is false.
     *
     * @param Element element HTML element or element ID.
     * @param Object  options
     */
    hideElement : function (element)
    {
        element = $(element);
        if (! element) {
            return;
        }

        var options = Object.extend({
          duration: 0.7,
          afterFinish: function () {},
          removeWhenDone: false
        }, arguments[1] || {});

        var animationDiv = $(document.createElement('div'));
        animationDiv.setStyle({overflow: 'hidden', height: element.getHeight() + 'px'});

        element.parentNode.insertBefore(animationDiv, element);
        animationDiv.appendChild(element.parentNode.removeChild(element));

        var afterFinishFunction;
        if (options.removeWhenDone) {
            afterFinishFunction = function () {
                animationDiv.parentNode.removeChild(animationDiv);
                options.afterFinish();
            };
        } else {
            afterFinishFunction = function () {
                element.setStyle({display: 'none', opacity: 1});
                animationDiv.parentNode.replaceChild(element, animationDiv);
                options.afterFinish();
            };
        }

        /* So that any margins in the target element do not prevent the
         * animation DIV from animating all the way to a zero height,
         * remove the target element once the opacity transition is complete.
         * It may be re-inserted again by the afterFinishFunction above.
         */
        new Effect.Opacity(
            element,
            {duration: (options.duration / 2),
             to: 0,
             afterFinish : function () {
                 element.parentNode.removeChild(element);
             }
            });

        new Effect.Morph(
            animationDiv,
            {duration: options.duration,
             style: {height: '0px'},
             afterFinish: afterFinishFunction
            });
    },

    /**
     * Disclosure triangle onclick handler. Toggles the state of the triangle and
     * hides or shows the content controlled by it as appropriate
     */
    doDisclosureTriangle : function (triangle) {
        triangle = $(triangle);
        if (triangle.hasClassName('open')) {
            triangle.removeClassName('open');
            $$('.' + triangle.id).each(function (element) {
                element.addClassName('removed');
            });

        } else {
            triangle.addClassName('open');
            $$('.' + triangle.id).each(function (element) {
                element.removeClassName('removed');
            });
        }
        var tbody = triangle.up('tbody');
        if (tbody) {
            SM.Effects.stripeTableRows(tbody);
        }
    },

    /**
     * Updates the even/odd row striping for a table body.
     *
     * @param Element TBODY element
     */
    stripeTableRows : function (tbody)
    {
        var rows = $(tbody).immediateDescendants();
        var counter = 0;
        for (var i = 0; i < rows.length; i++) {
            if (rows[i].hasClassName('removed')) {
                rows[i].removeClassName('even');
                rows[i].removeClassName('odd');
            } else if ((++counter % 2) == 0) {
                rows[i].removeClassName('odd');
                rows[i].addClassName('even');
            } else {
                rows[i].removeClassName('even');
                rows[i].addClassName('odd');
            }
        }
    }
};

