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


/* Detect Internet Explorer to work around bugs.
 */
var isInternetExplorer = /*@cc_on!@*/false;


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



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

};


SME.FlashMessage = {

    /**
     * Starting color for the color fade animation.
     */
    startColor: '#ffff33',

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

    /**
     * If the flash message DIV is present on the page, animates the background
     * color fading effect.
     */
    flash : function ()
    {
        var message = $('flashMessage');
        if (message) {
            var endColor = message.getStyle('backgroundColor');
            if (! endColor) {
                endColor = '#ffffda';
            } else {
                endColor = endColor.parseColor('#ffffda');
            }
            new Effect.Highlight(
                message,
                {duration:     2.0,
                 startcolor:   SME.FlashMessage.startColor,
                 endcolor:     endColor,
                 restorecolor: endColor}
                );
        }
    },

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


SME.ModalDialog = {

    inDialog: false,
    refocusElement: null,

    init : function ()
    {
        $$('A.runModal').each(function (link) {
            Event.observe(link, 'click', SME.ModalDialog.runModalLinkClick.bindAsEventListener(link));
        });
    },

    /**
     * 'onclick' handler for "runModal" navigation links. Opens the link's href
     * in a "modal" dialog DIV.
     *
     * @param Event theEvent
     */
    runModalLinkClick : function (theEvent)
    {
        if (theEvent.preventDefault) {
          theEvent.preventDefault();
        } else {
          theEvent.returnValue = false;
        }
        SME.ModalDialog.start(this.href);
    },

    start : function (requestURI)
    {
        if (SME.ModalDialog.inDialog) {
            return;
        }
        SME.ModalDialog.inDialog = true;
        new Ajax.Updater('modalDialogContent', requestURI,
            {evalScripts: true,
             onComplete: function () {
                 var focusElement = SM_FormMagic_GetElementWithFocus();
                 if (focusElement) {
                     SME.ModalDialog.refocusElement = focusElement;
                     elementWithFocus.blur();
                 }
                 var link = document.createElement('a');
                 link.appendChild(document.createTextNode('Close'));
                 link.id    = 'modalDialogCloseBox';
                 link.title = 'Close';
                 $('modalDialogContent').appendChild(link);
                 SME.ModalDialog.wireDefaultEvents();
                 new Effect.Opacity('modalDialog',
                     {duration: 0.4, from: 0, to: 1,
                      beforeStart: function () {
                          $('modalDialog').setStyle({opacity: 0, display: 'block'});
                      }
                     }
                 );
             }
            }
        );

    },

    wireDefaultEvents : function ()
    {
        var link = $('modalDialogCloseBox');
        if (link) {
            Event.observe(link, 'click', SME.ModalDialog.end.bindAsEventListener(link));
        }
        $$('#modalDialogContent .cancelButton').each(function (button) {
            Event.observe(button, 'click', SME.ModalDialog.end.bindAsEventListener(button));
        });
    },

    /**
     * @param Event theEvent (optional)
     */
    end : function (theEvent)
    {
        if (theEvent) {
            Event.stop(theEvent);
        }
        if (! SME.ModalDialog.inDialog) {
            return;
        }
        if (SME.ModalDialog.refocusElement) {
            SME.ModalDialog.refocusElement.focus();
            SME.ModalDialog.refocusElement = null;
        }
        new Effect.Opacity('modalDialog',
            {duration: 0.4, to: 0,
             afterFinish: function () {
                 $('modalDialog').setStyle({display: 'none'});
                 $('modalDialogContent').update('');
                 var successAlert = $('modalDialogSuccessAlert');
                 if (successAlert) {
                     successAlert.remove();
                 }
                 SME.ModalDialog.inDialog = false;
             }
            }
        );
    },

    successAlert : function (messageText)
    {
        var message = $(document.createElement('p'));
        message.appendChild(document.createTextNode(messageText));
        message.id = 'modalDialogSuccessAlert';
        // message.setStyle({opacity: 0});
        $('modalDialog').appendChild(message);

        new Effect.Highlight(
            message,
            {duration:     1,
             startcolor:   '#ffff33',
             endcolor:     '#ffffda',
             restorecolor: '#ffffda',
             afterFinish:  function () { window.setTimeout(SME.ModalDialog.end, 1000); }
            }
        );
    }

};
document.observe('dom:loaded', SME.ModalDialog.init);



if (Autocompleter.Base) {
    Autocompleter.Base.addMethods({
        startIndicator : function()
        {
            if (this.options.indicator) {
                if (this.options.onStartIndicator) {
                    this.options.onStartIndicator(this.options.indicator);
                }
                Element.show(this.options.indicator);
            }
        }
    });
}


SME.Form = {
    submitAction : function (button, preSubmitCallback)
    {
        var el = $(button.form).down('input[name="submitButton"]');
        if (el) {
            /* IE 7 and earlier completely discard the button's value attribute;
             * there's no way to retrieve it, and you cannot add custom attributes
             * to button elements in IE. Fall back to class name for all browsers.
             */
            el.value = button.className;
        }
        if (SM_FormMagic_validateForm(button.form)) {
            if (preSubmitCallback) {
                preSubmitCallback(button);
            }
            button.form.submit();
        }
    }
};


SME.Form.Utility = {

    queuedExpandingTextareas: [],
    progressIndicator: null,

    init : function ()
    {
        $$('TEXTAREA').each(function (el) {
            switch (SM_Form_Utility_GetSuffix(el.id)) {
            case 'Comment':
                SME.Form.Utility.queuedExpandingTextareas.push(el);
            }
        });
        while (SME.Form.Utility.queuedExpandingTextareas.length > 0) {
            var el = $(SME.Form.Utility.queuedExpandingTextareas.pop());
            if (el) {
                SME.Form.Utility.makeExpandingTextarea(el)
            }
        }
    },

    makeExpandingTextarea : function (el)
    {
        textarea = $(el);
        if (! textarea) {
            SME.Form.Utility.queuedExpandingTextareas.push(el);
            return;
        }
        
        if (textarea.hasClassName('sme_autoexpanding')) {
            return;
        }
        
        var wrapper = new Element('div');
        wrapper.id = textarea.id + '-Wrapper';
        wrapper.setStyle({'marginBottom': '0.5em', 'position': 'relative'});
        Element.wrap(textarea, wrapper);

        var shadow = new Element('div');
        shadow.id = textarea.id + '-Shadow';
        shadow.setStyle({
            'paddingTop': parseFloat(textarea.getStyle('paddingTop')) + 'px',
            'paddingRight': parseFloat(textarea.getStyle('paddingRight')) + 'px',
            'paddingBottom': '1em',
            'paddingLeft': parseFloat(textarea.getStyle('paddingLeft')) + 'px',
            'border': '1px solid transparent',
            'fontSize': parseFloat(textarea.getStyle('fontSize')) + 'px',
            'lineHeight': parseFloat(textarea.getStyle('lineHeight')) + 'px',
            'whiteSpace': 'pre-wrap',
            'width': '96.5%',
            'minHeight': textarea.getHeight() + 'px',
            'zIndex': '-1',
            'visibility': 'hidden'
            });
        wrapper.insert(shadow);

        textarea.setStyle({
            'margin': '0',
            'width': '97%',
            'height': '100%',
            'position': 'absolute',
            'top': '0',
            'left': '0',
            'resize': 'none'
            });
        textarea.addClassName('sme_autoexpanding');

        SME.Form.Utility.expandTextarea.bind(textarea)();
        textarea.observe('keydown', SME.Form.Utility.expandTextarea);
        textarea.observe('keyup', SME.Form.Utility.expandTextarea);
        textarea.observe('change', SME.Form.Utility.expandTextarea);
    },

    expandTextarea : function (event)
    {
        $(this.id + '-Shadow').update(this.value.replace(/\</g, '&lt;').replace(/\>/g, '&gt;').replace(/\n/g, '<br>') + '<br>');
    },

    getProgressIndicator : function ()
    {
        if (! SME.Form.Utility.progressIndicator) {
            SME.Form.Utility.progressIndicator = $('SME_Form_Utility_ProgressIndicator');
            if (! SME.Form.Utility.progressIndicator) {
                SME.Form.Utility.progressIndicator = $(document.createElement('DIV'));
                SME.Form.Utility.progressIndicator.id = 'SME_Form_Utility_ProgressIndicator';
                SME.Form.Utility.progressIndicator.setStyle({
                    backgroundImage: "url('/images/progress_indicator_form.gif')",
                    backgroundPosition: 'center center',
                    backgroundRepeat: 'no-repeat',
                    display: 'none',
                    width: '16px',
                    height: '16px',
                    position: 'absolute'
                });
                document.body.appendChild(SME.Form.Utility.progressIndicator);
            }
        }
        return SME.Form.Utility.progressIndicator;
    }

};
document.observe('dom:loaded', SME.Form.Utility.init);


SME.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);
            SME.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) || (element.visible())) {
            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) || (! element.visible())) {
            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) {
            SME.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');
            }
        }
    }
};

