/**
 * californiabicyclemuseum.org site JavaScript.
 *
 * @package    CaliforniaBicycleMuseum
 * @subpackage UI
 * @copyright  Copyright 2009 Spenlen Media, Inc. (http://spenlen.com)
 * @license    This source code file is licensed for the exclusive internal use of
 *             California Bicycle Museum and may not be used for any other purpose.
 * @version    $Id$
 */


// Adapted from DOM Ready extension by Dan Webb
// http://www.vivabit.com/bollocks/2006/06/21/a-dom-ready-extension-for-prototype
// which was based on work by Matthias Miller, Dean Edwards and John Resig
//
// Usage:
//
// Event.onReady(callbackFunction);
Object.extend(Event, {
  _domReady : function() {
    if (arguments.callee.done) { return; }
    arguments.callee.done = true;

    if (this._timer) { clearInterval(this._timer); }

    this._readyCallbacks.each(function(f) { f() });
    this._readyCallbacks = null;
  },
  onDOMReady : function(f) {
    if (!this._readyCallbacks) {
      var domReady = this._domReady.bind(this);

      if (document.addEventListener) {
        document.addEventListener("DOMContentLoaded", domReady, false);
      }
      /*@cc_on @*/
      /*@if (@_win32)
          var dummy = location.protocol == "https:" ?  "https://javascript:void(0)" : "javascript:void(0)";
          document.write("<script id=__ie_onload defer src='" + dummy + "'><\/script>");
          document.getElementById("__ie_onload").onreadystatechange = function() {
              if (this.readyState == "complete") { domReady(); }
          };
      /*@end @*/

      if (/WebKit/i.test(navigator.userAgent)) {
        this._timer = setInterval(function() {
          if (/loaded|complete/.test(document.readyState)) { domReady(); }
        }, 10);
      }

      Event.observe(window, 'load', domReady);
      Event._readyCallbacks =  [];
    }
    Event._readyCallbacks.push(f);
  }
});


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



CBM.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;        
    }

};


CBM.FlashMessage = {

    /**
     * Registers the onclick handler to dismiss the flash message DIV.
     */
    init : function ()
    {
        var message = document.getElementById('flashMessage');
        if (message) {
            Event.observe(message, 'click', CBM.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', CBM.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}
                );
        }
    }

}
Event.onDOMReady(CBM.FlashMessage.init);
Event.observe(window, 'load', CBM.FlashMessage.flash);


CBM.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);
            CBM.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) {
            CBM.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');
            }
        }
    }
};
