/**
 * wedding-event-directory.org site JavaScript.
 *
 * @package    WeddingEventDirectory
 * @subpackage UI
 * @copyright  Copyright 2009 Spenlen Media, Inc. (http://spenlen.com)
 * @license    This source code file is licensed for the exclusive internal use of
 *             Rashell Choo 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);
  }
});


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



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

};


WED.FlashMessage = {

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


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


WED.Rating = {};

WED.Rating.StarsController = Class.create();
WED.Rating.StarsController.prototype = {

  /**** Instance Variables ****/

    /**
     * Form element corresponding to the rating stars.
     * @var Element
     */
    formElement : null,

    /**
     * Rating stars element.
     * @var Element
     */
    ratingStars : null,



  /**** Initialization ****/

    /**
     * Sets the initial state of the rating stars and installs the event
     * handlers responsible for changing the rating value.
     *
     * @var String formElementName
     */
    initialize : function (formElementName)
    {
        this.formElement = $(formElementName);
        if (! this.formElement) {
            alert('Cannot find form element: ' + formElementName);
            return;            
        }
        
        this.ratingStars = $(formElementName + '-RatingStars');
        if (! this.ratingStars) {
            alert('Cannot find rating stars for form element: ' + formElementName);
            return;            
        }

        this.updateStars(this.formElement.value);

        Event.observe(this.ratingStars, 'mousemove', this.starsOnMouseMove.bind(this));
        Event.observe(this.ratingStars, 'mouseout',  this.starsOnMouseOut.bind(this));
        Event.observe(this.ratingStars, 'click',     this.starsOnClick.bind(this));
    },



  /**** Event Handlers ****/

    /**
     * onmousemove handler for the rating stars. Updates the number of
     * highlighted stars to match the pointer offset within the rating
     * stars rectangle.
     *
     * @param Event event
     */
    starsOnMouseMove : function (event)
    {
        this.updateStars(this.calculateRatingValue(event));
    },

    /**
     * onmouseout handler for the rating stars. Ensures the number of stars
     * highlighted match the current value of the form element.
     *
     * @param Event event
     */
    starsOnMouseOut : function (event)
    {
        this.updateStars(this.formElement.value);
    },

    /**
     * onclick handler for the rating stars. Sets the new rating value of the
     * form element.
     *
     * @param Event event
     */
    starsOnClick : function (event)
    {
        this.formElement.value = this.calculateRatingValue(event);
    },



  /**** Stars Display ****/

    /**
     * Calculates the mouse pointer offset within the rating stars rectangle
     * and returns the corresponding rating value.
     *
     * @param Event event
     * @return integer
     */
    calculateRatingValue : function (event)
    {
        var rectOffset = Position.cumulativeOffset(this.ratingStars);
        var offset = Event.pointerX(event) - rectOffset[0];
        var ratingValue = Math.ceil((offset / this.ratingStars.getWidth()) * 5);
        return ratingValue;
    },
    
    /**
     * Updates the number of highlighted stars to match the new rating value.
     *
     * @param integer ratingValue
     */
    updateStars : function (ratingValue)
    {
        var newClassName = 'ratingStars-' + ratingValue;
        if (this.ratingStars.hasClassName(newClassName)) {
            return;
        }
        if (ratingValue != '') {
            this.ratingStars.addClassName(newClassName);
        }
        if (ratingValue != 0) {
            this.ratingStars.removeClassName('ratingStars-0');
        }
        if (ratingValue != 1) {
            this.ratingStars.removeClassName('ratingStars-1');
        }
        if (ratingValue != 2) {
            this.ratingStars.removeClassName('ratingStars-2');
        }
        if (ratingValue != 3) {
            this.ratingStars.removeClassName('ratingStars-3');
        }
        if (ratingValue != 4) {
            this.ratingStars.removeClassName('ratingStars-4');
        }
        if (ratingValue != 5) {
            this.ratingStars.removeClassName('ratingStars-5');
        }
    }

};


/* Fix for IE's lack of support for the :after CSS selector.
 */
/*@cc_on @*/
/*@if (@_win32)
Event.onDOMReady(function () {
    $$('#pageContent .requiredField').each(function (element) {
        element.innerHTML += '*';
    });
});
/*@end @*/

