/**
 * Javascript Date class extensions
 * 
 * @author Brendan, Edd
 */
Object.extend(Date.prototype, {
    getDayName: function(useShort)
    {
        return Utils.dayName(this.getDay(), useShort);
    },

    getMonthName: function(useShort)
    {
        return Utils.monthName(this.getMonth() + 1, useShort);
    },

    addDay: function(numDays)
    {
        numDays = numDays || 1;
        this.setDate(this.getDate() + numDays);
        return this;
    },

    addMonth: function(numMonths)
    {
        numMonths = numMonths || 1;
        this.setMonth(this.getMonth() + numMonths);
        return this;
    },

    isWeekend: function()
    {
        return this.getDay() == 0 || this.getDay() == 6;
    },

    isSameDay: function(date)
    {
        return this.getFullYear() == date.getFullYear()
            && this.getMonth() == date.getMonth()
            && this.getDate() == date.getDate();
    },

    isEarlierDay: function(date) 
    {
        return this.getFullYear() < date.getFullYear() || (
            this.getFullYear() == date.getFullYear() && (
                this.getMonth() < date.getMonth() ||
                this.getMonth() == date.getMonth() && this.getDate() < date.getDate()
            )
        );
    },

    isLaterDay: function(date)
    {
        return this.getFullYear() > date.getFullYear() || (
            this.getFullYear() == date.getFullYear() && (
                this.getMonth() > date.getMonth() ||
                this.getMonth() == date.getMonth() && this.getDate() > date.getDate()
            )
        );
    },

    isBetweenDays: function(fromDate, toDate)
    {
        return (this.isLaterDay(fromDate) || this.isSameDay(fromDate))
            && (!toDate || this.isEarlierDay(toDate) || this.isSameDay(toDate));
    },

    clone: function()
    {
        return new Date(this);
    },

    toMysqlDate: function()
    {
        return this.getFullYear() + '-' +
            (this.getMonth() + 1).toPaddedString(2) + '-' +
            this.getDate().toPaddedString(2);
    },

    toMysqlDateTime: function()
    {
        return this.toMysqlDate() + ' ' +
            this.getHours().toPaddedString(2) + ':' +
            this.getMinutes().toPaddedString(2) + ':' +
            this.getSeconds().toPaddedString(2);
    },

    toUkDate: function()
    {
        return this.getDate().toPaddedString(2) + '/' +
            (this.getMonth() + 1).toPaddedString(2) + '/' +
            this.getFullYear();
    },

    toFullDate: function(includeDayName)
    {
        return (includeDayName ? this.getDayName() + ', ' : '' ) + this.getDate() + ' ' + this.getMonthName() + ' ' + this.getFullYear();
    },

    getShortYear: function()
    {
        return String(this.getFullYear()).substring(2)
    }
});

/**
 * Bank Holiday static class
 *
 * @author Edd
 */
var BankHoliday = {

    bankHolidays: null,

    initialise: function(bankHolidayList)
    {
        BankHoliday.bankHolidays = new Array();
        bankHolidayList.each(function(bankHoliday){
            BankHoliday.bankHolidays[bankHoliday.toMysqlDate()] = true;
        }.bind(this));
    },

    is: function(dateObj)
    {
        if (BankHoliday.bankHolidays[dateObj.toMysqlDate()]) {
            return true;
        }
        return false;
    }

};

/**
 * Prototype Element class extensions
 *
 * @author Edd
 */
Element.addMethods({
    /**
     * Disable text selection
     */
    disableText: function(element){
        element.onselectstart = function() {
            return false;
        };
        element.unselectable = "on";
        element.style.MozUserSelect = "none";
    },
    
    getParams: function(element)
    {
        if (element.allParams) {
            return element.allParams;
        }

        var params = {};

        var relValue = element.readAttribute('rel');
        if (!relValue) {
            return [];
        }
        
        var valuePairs = relValue.split(";");
        for (var i = 0; i < valuePairs.length; i++) {
            var varValue = valuePairs[i].split(':');
            params[varValue[0]] = varValue[1];
        }

        element.allParams = params;

        return params;
    },

    /**
     * Read a custom parameter from an element's 'rel' attribute
     * (attributes should be contained in the rel attribute in the form
     * foo1:bar1;foo2:bar2;...)
     */
    getParam: function(element, paramName) 
    {
        var params = Element.getParams(element);

        for (var param in params) {
            if (paramName == param) {
                return params[paramName];
            }
        }

        return false;
    },

    /**
     * Determine whether an element is visible within the browser viewport
     */
    isWithinViewport: function(element)
    {
        var viewportOffset = document.viewport.getScrollOffsets();
        var viewportDimension = document.viewport.getDimensions();

        var elementOffset = element.cumulativeOffset();
        var elementDimension = element.getDimensions();
            
        if (elementOffset.top + elementDimension.height > viewportOffset.top &&
            elementOffset.top < viewportOffset.top + viewportDimension.height &&
            elementOffset.left + elementDimension.width > viewportOffset.left &&
            elementOffset.left < viewportOffset.left + viewportDimension.width) {
            return true;
        }
        
        return false;
    },

    fireEvent: function(element, event)
    {
        var evt;
        if (document.createEventObject){
            // dispatch for IE
            evt = document.createEventObject();
            return element.fireEvent('on'+event, evt)
        }
        else{
            // dispatch for firefox + others
            evt = document.createEvent("HTMLEvents");
            evt.initEvent(event, true, true );
            return !element.dispatchEvent(evt);
        }
    }
});

/**
 * Some utility methods
 *
 * @author Edd, Brendan
 */
var Utils = {

    disableDebug: false,

    debug: function(msg, clear)
    {
        if (Utils.disableDebug) return;

        var elem = $('debug');
        if (elem) {
            if (clear) {
                elem.update(new Element('div').insert(''+msg));
            } else {
                elem.insert(new Element('div').insert(''+msg));
            }
        }
    },

    /**
     * Some date-related util functions (language specific)
     */
    daysInMonth: function(month, year)
    {
        switch (month) {
            case 9:
            case 4:
            case 6:
            case 11:
                return 30;
            case 1:
            case 3:
            case 5:
            case 7:
            case 8:
            case 10:
            case 12:
                return 31;
            case 2:
                if (year % 4 == 0) {
                    return 29;
                }
                return 28;
        }
    },

    dayName: function(d, shortVersion) {
        if (!shortVersion) {
            switch (d) {
                case 0:
                    return 'Sunday'; //#Lang
                case 1:
                    return 'Monday'; //#Lang
                case 2:
                    return 'Tuesday'; //#Lang
                case 3:
                    return 'Wednesday'; //#Lang
                case 4:
                    return 'Thursday'; //#Lang
                case 5:
                    return 'Friday'; //#Lang
                case 6:
                    return 'Saturday'; //#Lang
            }
        }
        else {
            switch (d) {
                case 0:
                    return 'S'; //#Lang
                case 1:
                    return 'M'; //#Lang
                case 2:
                    return 'T'; //#Lang
                case 3:
                    return 'W'; //#Lang
                case 4:
                    return 'T'; //#Lang
                case 5:
                    return 'F'; //#Lang
                case 6:
                    return 'S'; //#Lang
            }
        }
    },

    monthName: function(month, shortVersion)
    {
        if (!shortVersion) {
            switch (month) {
                case 1:
                    return 'January'; //#Lang
                case 2:
                    return 'February'; //#Lang
                case 3:
                    return 'March'; //#Lang
                case 4:
                    return 'April'; //#Lang
                case 5:
                    return 'May'; //#Lang
                case 6:
                    return 'June'; //#Lang
                case 7:
                    return 'July'; //#Lang
                case 8:
                    return 'August'; //#Lang
                case 9:
                    return 'September'; //#Lang
                case 10:
                    return 'October'; //#Lang
                case 11:
                    return 'November'; //#Lang
                case 12:
                    return 'December'; //#Lang
            }
        }
        else {
            switch (month) {
                case 1:
                    return 'Jan'; //#Lang
                case 2:
                    return 'Feb'; //#Lang
                case 3:
                    return 'Mar'; //#Lang
                case 4:
                    return 'Apr'; //#Lang
                case 5:
                    return 'May'; //#Lang
                case 6:
                    return 'Jun'; //#Lang
                case 7:
                    return 'Jul'; //#Lang
                case 8:
                    return 'Aug'; //#Lang
                case 9:
                    return 'Sep'; //#Lang
                case 10:
                    return 'Oct'; //#Lang
                case 11:
                    return 'Nov'; //#Lang
                case 12:
                    return 'Dec'; //#Lang
            }
        }
    },

    pad: function(number, length)
    {
        var str = '' + number;
        while (str.length < length) {
            str = '0' + str;
        }
        return str;
    },

    pluralise: function(word, count, suffix)
    {
        suffix = suffix || 's';
        return word + (count != 1 ? suffix : '');
    },

    isIE7: function()
    {
        return navigator.userAgent.toLowerCase().indexOf('msie 7') != -1;
    },

    isIE6: function()
    {
        return navigator.userAgent.toLowerCase().indexOf('msie 6') != -1;
    },

    isIE: function()
    {
        return navigator.userAgent.toLowerCase().indexOf('msie') != -1;
    },

    isMobileSafari: function()
    {
        return (navigator.userAgent.indexOf('iPod') != -1 ||
                navigator.userAgent.indexOf('iPhone') != -1);
    }
};

/**
 * Modal popup window
 *
 * @author Edd
 */
var ModalObj = function(options)
{
    /**
     * Default ptions
     */
    this.options = {
        insertElement: $(document.body),
        width: 500,
        height: 100,
        contents: null,
        morphDuration: 0.3,
        style: null,
        growToFitContents: true,
        url: null,
        parameters: {},
        readContentsFromElement:null,
        closeButton: true,
        onClose: null,
        onOpen: null,
        useIframe: true,
        overlayClass: null,
        clickOverlayToClose: false
    };

    /**
     * Configuration
     */
    this.config = {
         padding: {
             top: 15,
             right: 15,
             bottom: 15,
             left: 15
         }
    };

    /**
     * Set up user options
     */
    this.setOptions(options);

    /**
     * Define instance variables
     */
    this.iFrame = null;
    this.overlay = null;
    this.element = null;
    this.bg = null;
    this.corners = [new Array(), new Array()];
    this.scroller = null;
    this.closeButton = null;
    this.content = null;
    this.loader = null;

    this.build();
    this.registerObservers();

    if (this.options.contents) {
        this.setContents(this.options.contents);
    }
    else if (this.options.url) {
        this.setContentsFromUrl(this.options.url, this.options.parameters);
    }
    else if (this.options.readContentsFromElement) {
         this.readContentsFromElement(this.options.readContentsFromElement)
    }
};

ModalObj.prototype = {

    setOptions: function(options)
    {
        Object.extend(this.options, options || {});
    },

    build: function()
    {
        this.options.width = parseInt(this.options.width);
        this.options.height = parseInt(this.options.height);

        // Build the iFrame layer
        if (this.options.useIframe) {
            this.iFrame = new Element('iframe', {'src':'javascript:false', 'class':'modalIFrame'}); //use javascript:false; here to prevent nonsecure warnings in IE
            this.iFrame.hide();
        }

        // Build the overlay
        this.overlay = new Element('div', {'class':'modalOverlay'});
        if (this.options.overlayClass) {
            this.overlay.addClassName(this.options.overlayClass);
        }
        this.overlay.hide();

        if (this.options.clickOverlayToClose) {
            this.overlay.observe('click', function(){
                this.close();
            }.bind(this));
        }

        // Build the main content div
        this.element = new Element('div', {'class':'modal'});

        // Add in the stylised corners
        this.corners[0][0] = new Element('div', {'class':'modalCorner topLeft'});
        this.element.insert(this.corners[0][0]);
        this.corners[0][1] = new Element('div', {'class':'modalCorner topRight'});
        this.element.insert(this.corners[0][1]);
        this.corners[1][0] = new Element('div', {'class':'modalCorner bottomLeft'});
        this.element.insert(this.corners[1][0]);
        this.corners[1][1] = new Element('div', {'class':'modalCorner bottomRight'});
        this.element.insert(this.corners[1][1]);

        // Add in the stylised edges
        this.bg = new Array();
        for (var i = 0; i < 4; i++) {
            this.bg[i] = new Element('div', {'class':'modalBg modalBg'+i});
            this.element.insert(this.bg[i]);
        }

        // Add the close button
        if (this.options.closeButton) {
            this.closeButton = new Element('div', {'class':'modalClose'});
            this.closeButton.observe('click',function(){
                this.close();
            }.bind(this));
            this.element.insert(this.closeButton);
        }

        // Create the scrolling element
        this.scroller = new Element('div', {'class':'modalScroller'});
        this.element.insert(this.scroller);

        // Create the contents container
        this.content = new Element('div',{'class':'modalContent'});
        if (this.options.style) {
              this.addStyle(this.options.style);
        }
        this.scroller.insert(this.content);

        // Insert into the DOM
        //this.overlay.insert(this.element);

        if (this.iFrame) {
            this.options.insertElement.insert(this.iFrame);
        }

        this.options.insertElement.insert(this.overlay);
        this.options.insertElement.insert(this.element);

        // Resize to fit the current window/viewport
        this.windowResize();
        this.resizeContents();

        // Show it!
        this.show();
    },

    /**
     * Handler for ESC keypress
     */
    keyPressHandle: function(e)
    {
        switch (e.keyCode) {
              case 27:
                  if (this.options.closeButton) {
                      this.close();
                  }
                  break;
        }
    },

    addStyle: function(className)
    {
         this.content.addClassName(className);
    },

    removeErrorStyle: function()
    {
        this.content.removeClassName('modalError');
    },

    setContents: function(contents)
    {
        this.content.update(contents);

        if (this.options.onOpen) {
             this.options.onOpen(this);
        }

        if (this.options.growToFitContents) {
            this.growToFit();
        }
    },

    setContentsAsQuestion: function(questionText, buttons, dontAutoClose) {
        questionText = new Element('p').insert(questionText);
        var modalContents = new Element('div').insert(questionText);

        var buttonsElement = new Element('div');
        buttons.each(function(button){
            var buttonElement = new Element('input', {
                'type':'button',
                'class':button.style || 'modalButton',
                'value':button.text
            });
            buttonElement.observe('click', function(){
                if (! dontAutoClose) {
                    this.close();
                }
                if (button.clickHandler) {
                    button.clickHandler();
                }
            }.bind(this));
            buttonsElement.insert(buttonElement);
        }.bind(this));
        modalContents.insert(buttonsElement);

        this.setContents(modalContents);
    },

    setContentsFromUrl: function(url, additionalParams)
    {
        this.loading(1);
        new AjaxRequest(url,{
              parameters: additionalParams,
            onSuccess:function(json){
                this.loading(0);
                this.setContents(json.modalContents);
            }.bind(this)
        });
    },

    readContentsFromElement: function(elementId)
    {
        var newContent;
        if (modal.readElementContents[elementId]) {
            newContent = modal.readElementContents[elementId];
        }
        else {
            var element = $(elementId);
            newContent = element.innerHTML;
            modal.readElementContents[elementId] = newContent;
            element.remove();
        }
        this.setContents(newContent);
    },

    insertContents: function(newContent, growToFit, useMorphToGrow)
    {
         this.content.insert(newContent);
         if (growToFit) {
             this.growToFit(useMorphToGrow)
         }
    },

    growToFit: function(useMorph)
    {
        var newDims = {'height':this.content.getHeight()};
        if (useMorph) {
              this.morphSize(newDims);
        } else {
              this.resizeContents(newDims);
        }
    },

    registerObservers: function()
    {
        // Listen for document resize
        Event.observe(window, 'resize', function(){
            this.windowResize();
        }.bind(this));

        Event.observe(window, 'scroll', function(){
              if (this.pe) {
                  return;
              }
              if (!this.element) {
                  return;
              }
              if (this.element.getHeight() > document.viewport.getHeight() || this.element.getWidth() > document.viewport.getWidth()) {
                  return;
              }
              this.pe = new PeriodicalExecuter(function(){
                  this.resizeContents();
                  this.pe.stop();
                  this.pe = null;
              }.bind(this), 0.5);
        }.bind(this));

        // Listen for escape keypresses
        document.observe('keypress', function(e){this.keyPressHandle(e)}.bind(this));
    },

    windowResize: function()
    {
        if (!this.overlay) {
              return;
        }

        var dims = this.computeOverlayDimensions();

        this.overlay.setStyle({
            'width': dims.width+'px',
            'height': dims.height+'px'
        });

        if (this.iFrame) {
            this.iFrame.setStyle({
                'width': dims.width+'px',
                'height': dims.height+'px'
            });
        }

        this.resizeContents();
    },

    computeOverlayDimensions: function()
    {
        this.overlay.hide();
        this.element.hide();

        if (this.iFrame) {
            this.iFrame.hide();
        }

        var d = document.documentElement;
        var b = document.body;
        var who = d.offsetHeight ? d : b;

        var dims = {
            width: Math.max(who.scrollWidth, who.offsetWidth),
            height: Math.max(who.scrollHeight, who.offsetHeight)
        };

        if (this.iFrame) {
            this.iFrame.show();
        }

        this.overlay.show();
        this.element.show();

        return dims;
    },

    computeContentsStyles: function()
    {
        var viewportDims = Utils.isMobileSafari() ? $(document.body).getDimensions() : document.viewport.getDimensions();
        var scrollOffset = Utils.isMobileSafari() ? {top: 0, left: 0} : document.viewport.getScrollOffsets();

        var totalHeight = this.options.height + this.config.padding.top + this.config.padding.bottom;
        var totalWidth = this.options.width + this.config.padding.left + this.config.padding.right;

        var top = Math.max(25, scrollOffset.top + (viewportDims.height/2) - (totalHeight/2));
        var left = Math.max(0,scrollOffset.left + (viewportDims.width/2) - (totalWidth/2));

        return {
            width: this.options.width+'px',
            height: this.options.height+'px',
            top: top+'px',
            left: left+'px'
        };
    },

    resizeContents: function(dims)
    {
        if (!this.element) {
            return;
        }

        if (dims) {
            Object.extend(this.options, dims);
        }

        // Resize and reposition contents element
        this.element.setStyle(this.computeContentsStyles());

        // Resize the inner contents area
        this.scroller.setStyle({width:this.options.width+'px',height:this.options.height+'px'});

        // Resize bg graphics
        this.bg[0].setStyle({'width':this.options.width+this.config.padding.left+this.config.padding.right+'px'});
        this.bg[1].setStyle({'height':this.options.height+this.config.padding.top+this.config.padding.bottom+'px'});
        this.bg[2].setStyle({'width':this.options.width+this.config.padding.left+this.config.padding.right+'px'});
        this.bg[3].setStyle({'height':this.options.height+this.config.padding.top+this.config.padding.bottom+'px'});

        if (this.loader) {
            this.loader.resize();
        }
    },

    morphSize: function(dims)
    {
        if (dims) {
            Object.extend(this.options, dims);
        }

        this.scroller.setStyle({'overflow':'hidden'});

        new Effect.Morph(this.element, {
            style:this.computeContentsStyles(),
            duration:this.options.morphDuration
        });

        new Effect.Morph(this.scroller, {
            style: {
                width: this.options.width+'px',
                height: this.options.height+'px'
            },
            duration: this.options.morphDuration,
            afterFinish:function(){
                   this.scroller.setStyle({'overflow':'auto'});
            }.bind(this)
        });

        var bgStyle;
        for (var i=0; i<4; i++) {
            if (i%2) {
                bgStyle = {'height':this.options.height+'px'};
            } else {
                bgStyle = {'width':this.options.width+'px'};
            }
            new Effect.Morph(this.bg[i], {
                style: bgStyle,
                duration:this.options.morphDuration
            });
        }
    },

    hide: function()
    {
        if (this.overlay) {
            this.overlay.hide();
            this.element.hide();
        }
        if (this.iFrame) {
            this.iFrame.hide();
        }
    },

    show: function()
    {
        if (this.overlay) {
            this.overlay.show();
            this.element.show();
        }
        if (this.iFrame) {
            this.iFrame.show();
        }
    },

    close: function(noEffect)
    {
        if (!this.element) {
            return;
        }

        this.loading(false);
        this.loader = null;

        if (noEffect) {

            if (this.iFrame) {
                this.iFrame.remove();
                this.iFrame = null;
            }

            if (this.overlay) {
                this.overlay.remove();
                this.overlay = null;
            }

            if (this.element) {
                this.element.remove();
                this.element = null;
            }

            return;
        }

        new Effect.DropOut(this.element, {
            duration: 0.2,
            afterFinish:function(){
                if (this.overlay) {
                    this.overlay.remove();
                    this.overlay = null;

                    this.element.remove();
                    this.element = null;

                    if (this.iFrame) {
                        this.iFrame.remove();
                        this.iFrame = null;
                    }
                }
                if (this.options.onClose) {
                    this.options.onClose(this);
                }
            }.bind(this)
        });
    },

    shake: function()
    {
        new Effect.Shake(this.element, {distance: 5});
    },

    loading: function(show)
    {
        if (!this.loader) {
            this.loader = new Loader({
                elementToOverlay: this.element,
                zIndex: 10000
            });
        }

        if (show) {
            this.loader.show();
        }
        else {
            this.loader.hide();
        }
    },

    isOpen:function()
    {
         if (this.overlay) {
             return true;
         }
         return false;
    },

    select: function(selector)
    {
         return this.element.select(selector);
    },

    down: function(selector)
    {
         return this.element.down(selector);
    }

};

/**
 * Static modal class for quick invokation
 *
 * @author Edd
 */
var modal = {

    lang: {
        confirmOK: 'OK',
        confirmCancel: 'Oops, cancel',
        alertOK: 'OK'
    },

    objects: [],

    activeModal: null,

    readElementContents: new Array(),

    open: function(optionsOrUrl, allowMultiple)
    {
        if (typeof(optionsOrUrl) == 'string') {
            /**
             * We have an expected URL for this modal, attempt to load
             * the options object over AJAX
             */
            modal.activeModal = new ModalObj();
            modal.activeModal.loading(true);
            
            new AjaxRequest(optionsOrUrl, {
                onComplete: function(){
                    modal.loading(false);
                },
                onSuccess: function(json){
                    modal.close(true);
                    modal.open(json.modal);
                }
            });

            return;
        }

        /**
         * Otherwise we have a modal options object, so just open it up!
         */
        if (modal.activeModal) {
            if (allowMultiple) {
                modal.objects.push(modal.activeModal);
            }
            else {
                modal.close();
            }
        }
        
        modal.activeModal = new ModalObj(optionsOrUrl);
    },

    error: function(message, options)
    {
        var title = new Element('h1').insert('oops!'); //#Lang
        var body = new Element('p').insert(message);
        var modalContents = new Element('div').insert(title).insert(body);
        var mOptions = {
            style:'modalError',
            contents:modalContents
        };
        Object.extend(mOptions, options || {});
        this.open(mOptions);
    },

    success: function(title, message, options)
    {
        var titleElement = new Element('h1').insert(title); //#Lang
        var body = new Element('p').insert(message);
        var modalContents = new Element('div').insert(titleElement).insert(body);

        var okButton = new Element('input', {type: 'button', 'class':'modalButton', value: this.lang.alertOK });
        okButton.setStyle({'margin':'10px 0 0 0'});
        okButton.observe('click', function(){
            modal.close();
        });
        modalContents.insert(new Element('div').insert(okButton));

        var mOptions = {
            style: 'modalSuccess',
            contents: modalContents
        };
        Object.extend(mOptions, options || {});
        this.open(mOptions);
    },

    warning: function(message)
    {
         var title = new Element('h1').insert('careful!'); //#Lang
         var body = new Element('p').insert(message);
         var modalContents = new Element('div').insert(title).insert(body);
         this.open({
             style:'modalWarning',
             contents:modalContents
         });
    },

    alert: function(message, options, okButtonUrl)
    {
        var modalContents = new Element('div').insert(message);

        var okButton = new Element('input', {type: 'button', 'class':'modalButton', value: this.lang.alertOK });
        okButton.setStyle({'margin':'10px 0 0 0'});
        okButton.observe('click', function(){
            if (okButtonUrl) {
                document.location = okButtonUrl;
            }
            else {
                modal.close();
            }
        });
        modalContents.insert(new Element('div').insert(okButton));

        var mOptions = {
            style: 'modalWarning'
        };
        Object.extend(mOptions, options || {});


        this.error(modalContents, mOptions);
    },

    question: function(questionText, buttons, options, dontAutoClose)
    {
        var mOptions = {
            style: 'modalQuestion'
        };
        Object.extend(mOptions, options || {});

        this.open(mOptions);

        this.activeModal.setContentsAsQuestion(questionText, buttons, dontAutoClose);
    },

    confirm: function(confirmMessage, confirmedHandler, cancelHandler, additionalModalOptions, okButtonStyle)
    {
        var mOptions = {
            onClose: function(){
                if (cancelHandler) {
                    cancelHandler();
                }
            }
        };
        Object.extend(mOptions, additionalModalOptions);

        this.question(confirmMessage, [
            {
                text: modal.lang.confirmOK,
                clickHandler: function(){
                    if (confirmedHandler) {
                        confirmedHandler();
                    }
                },
                style: okButtonStyle || 'modalButton'
            },
            {
                text: modal.lang.confirmCancel,
                clickHandler: function(){
                    if (cancelHandler) {
                        cancelHandler();
                    }
                },
                style: 'modalButton secondary'
            }
        ], mOptions);

        // Listen for an enter keypress to run the confirmedHandler
        document.observe('keypress', function(e){
            if (e.keyCode==13 && confirmedHandler) {
                confirmedHandler();
                e.stop();
            }
        }.bind(this));
    },

    close: function(noEffect, closeAll)
    {
        if (modal.isOpen()) {
              modal.activeModal.close(noEffect || false);
        }
        modal.activeModal = modal.objects.pop();
    },

    isOpen: function()
    {
         if (this.activeModal && this.activeModal.isOpen()) {
             return true;
         }
         return false;
    }
};

/**
 * Wrapper for Prototype Ajax.Request
 *
 * @author Edd
 */
var AjaxRequest = function(url, options)
{
    this.config = {
        systemQueryStringParams: ['cache', 'qp', 'cp', 'debug', 'external']
    };

    this.options = {
        parameters: {},
        onSuccess: null,
        onFailure: null,
        onError: null,
        onRequestCancelled: null,
        onErrorClose: null,
        onComplete: null,
        onRequestStart: null,
        method: 'post',
        maxActiveConnections: 1
    };
    Object.extend(this.options, options || {});

    // strip out bookmark, IE doesn't handle this
    url = url.replace(/#.*$/, '');
    this.url = url;

    this.request();
};

AjaxRequest.key = null;
AjaxRequest.activeConnections = {};

AjaxRequest.prototype = {

    request: function()
    {
        if (this.hasMaxConnections()) {
            if (this.options.onRequestCancelled) {
                this.options.onRequestCancelled();
            }
            return;
        }

        var params = {};
        if (AjaxRequest.key) {
            params.ajaxKey = AjaxRequest.key;
        }
        
        Object.extend(params, this.options.parameters);

        // Parse the query string to look for system params that might need
        // passing on to the server
        var qStringData = document.location.href.toQueryParams();
        this.config.systemQueryStringParams.each(function(systemParam){
            if (qStringData[systemParam]) {
                params[systemParam] = qStringData[systemParam];
            }
        });

        this.addActiveConnection();

        if (this.options.onRequestStart) {
            this.options.onRequestStart();
        }

        new Ajax.Request(this.url,{
            method: this.options.method,
            parameters: params,
            onSuccess:function(req)
            {
                if (!req.responseJSON) {
                    new AjaxError({onClose:this.options.onErrorClose});
                    return;
                }
                if (req.responseJSON.debugMessages) {
                    req.responseJSON.debugMessages.each(function(msg){
                        debug(msg);
                    })
                }
                if (!req.responseJSON.success) {

                    // Auth is required
                    if (req.responseJSON.requireAuth) {
                        // redirect to login page
                        if (modal) {
                            modal.alert('It looks like you\'re no longer logged in to graze, please try again...', { //#Lang
                                onClose: function(){
                                    window.location.reload();
                                }
                            });
                        }
                        else {
                            alert('Oops! It looks like you\'re no longer logged in to graze, please try again...'); //#Lang
                            window.location.reload();
                        }
                        return;
                    }

                    // Warning message set
                    if (req.responseJSON.warning){
                        if (modal) {
                            modal.alert(req.responseJSON.warning, {}, req.responseJSON.okButtonUrl || null);
                        }
                        else {
                            alert(req.responseJSON.warning);
                        }

                        if (this.options.onFailure) {
                            this.options.onFailure(req.responseJSON);
                        }

                        return;
                    }

                    new AjaxError({
                        json:req.responseJSON,
                        onClose:this.options.onErrorClose
                    });
                    return;
                }
                if (this.options.onSuccess) {
                    this.options.onSuccess(req.responseJSON);
                }
            }.bind(this),
            onFailure:function()
            {
                new AjaxError({onClose:this.options.onErrorClose});
            },
            onComplete:function()
            {
                this.removeActiveConnection();
                if (this.options.onComplete) {
                    this.options.onComplete();
                }
            }.bind(this)
        });

        return;
    },

    hasMaxConnections: function()
    {
        return this.options.maxActiveConnections > 0 && AjaxRequest.activeConnections[this.url] >= this.options.maxActiveConnections;
    },

    resetActiveConnections: function()
    {
        AjaxRequest.activeConnections[this.url] = 0;
    },

    addActiveConnection: function()
    {
        if (!AjaxRequest.activeConnections[this.url]) {
            AjaxRequest.activeConnections[this.url] = 0;
        }
        AjaxRequest.activeConnections[this.url]++;
    },

    removeActiveConnection: function()
    {
        AjaxRequest.activeConnections[this.url]--;
    }

};

/**
 * Ajax error handling object
 */
var AjaxError = function(options)
{
    this.lang = {
        errorTitle: 'oops!', //#Lang
        errorIntro: 'We had some difficulties processing that action.', //#Lang
        errorOutro: 'We suggest you reload the page and try again, sorry.' //#Lang
    };

    this.options = {
        json: null,
        onClose: null
    };
    Object.extend(this.options, options || {});

    this.open();
};

AjaxError.prototype = {
    open: function()
    {
        if (modal.isOpen()) {
            modal.close(true);
        }

        var modalOptions = {
            style:'modalError',
            closeButton: true,
            width: 430,
            onClose: function() {
                if (this.options.onClose) {
                    this.options.onClose();
                }
            }.bind(this)
        };

        modalOptions.contents = new Element('div');

        var modalTitle = new Element('h1').insert(this.lang.errorTitle);
        modalOptions.contents.insert(modalTitle);

        var modalIntro = new Element('p').insert(this.lang.errorIntro);
        modalOptions.contents.insert(modalIntro);

        if (this.options.json && this.options.json.error) {
            modalOptions.width = 800;
            var modalErrorMessage = new Element('p').insert(this.options.json.error);
            if (this.options.json.detailedError) {
                modalErrorMessage.insert(new Element('pre').insert(this.options.json.detailedError));
            }
            modalOptions.contents.insert(modalErrorMessage);
        }

        var modalOutro = new Element('p').insert(this.lang.errorOutro);
        modalOptions.contents.insert(modalOutro);

        modal.open(modalOptions);
    }
};

/**
 * General ajax loader
 *
 * @author Brendan
 */
var Loader = function(options)
{
    this.options = {
        elementToOverlay: null,
        zIndex: null,
        insertElement: $(document.body)
    };
    Object.extend(this.options, options || {});

    this.element = null;
    this.overlayElement = null;

    this.onResizeHandler = this.resize.bind(this);

    this.initialise();
};

Loader.prototype = {

    initialise: function()
    {
        this.element = new Element('div', {'class':'javascriptLoader'});

        if (this.options.elementToOverlay) {
            this.overlayElement = new Element('div', {'class':'javascriptLoaderOverlay'});
            this.overlayElement.insert(this.element);
            this.options.insertElement.insert(this.overlayElement);
        }

        this.hide();
    },

    show: function()
    {
        if (this.overlayElement) {
            this.overlayElement.show();
            this.resize();
            Event.observe(window, 'resize', this.onResizeHandler);
        }
        else {
            this.element.show();
        }
    },

    hide: function()
    {
        if (this.overlayElement) {
            this.overlayElement.hide();
            Event.stopObserving(window, 'resize', this.onResizeHandler);
        }
        else {
            this.element.hide();
        }
    },

    visible: function()
    {
        return (this.overlayElement && this.overlayElement.visible()) || (!this.overlayElement && this.element && this.element.visible());
    },

    resize: function()
    {
        if (this.overlayElement) {
            var offset = this.options.elementToOverlay.cumulativeOffset();
            var zIndex = this.options.elementToOverlay.getStyle('zIndex');
            this.overlayElement.setStyle({
                'width': this.options.elementToOverlay.getWidth() + 'px',
                'height': this.options.elementToOverlay.getHeight() + 'px',
                'top': offset['top'] + 'px',
                'left': offset['left'] + 'px',
                'zIndex': this.options.zIndex || zIndex+1
            });
        }
    }
};

/**
 * Help messages (user closeable)
 *
 * @author Edd
 */
var HelpMessage = {

    initialiseAll: function()
    {
        $(document.body).select('.helpMessage').each(function(helpMessageElement){
            if (!helpMessageElement.hasClassName('helpMessageDisableAuto')) {
                HelpMessage.render(helpMessageElement);
            }
        }.bind(this));
    },

    render: function(element)
    {
        var icon = new Element('div', {'class':'helpMessageIcon'});
        var cornerTopLeft = new Element('div', {'class':'helpMessageCorner topLeft'});
        var cornerBottomLeft = new Element('div', {'class':'helpMessageCorner bottomLeft'});
        var cornerTopRight = new Element('div', {'class':'helpMessageCorner topRight'});
        var cornerBottomRight = new Element('div', {'class':'helpMessageCorner bottomRight'});
        
        var inner = new Element('div', {'class':'helpMessageInner'});

        var contents = element.innerHTML;
        inner.insert(contents);

        element.update(icon);
        element.insert(cornerTopLeft);
        element.insert(cornerBottomLeft);
        element.insert(cornerTopRight);
        element.insert(cornerBottomRight);

        element.insert(inner);
    },

    close: function(name, elementToHide, namespace)
    {
        if (elementToHide) {
            var loader = new Loader({elementToOverlay: elementToHide});
            loader.show();
        }

        var url = '/help/ajaxclosemessage';
        new AjaxRequest(url, {
            parameters: {
                name: name,
                namespace: namespace || null
            },
            onSuccess: function()
            {
                if (elementToHide) {
                    if (loader) {
                        loader.hide();
                    }
                    new Effect.Fade(elementToHide, {
                        duration: 0.2
                    });
                }
            }
        });
        return false;
    }
    
};
document.observe('dom:loaded', function(){
    HelpMessage.initialiseAll.defer();
});

/**
 * Social bookmarking javascript
 *
 * @author Edd
 */
var Bookmarks = {
    config: {
        bookmarkUrl: 'http://www.graze.com',
        bookmarkTitle: 'graze - nature delivered',
        urls: {
            'delicious': 'http://del.icio.us/post?v=4&noui&jump=close&url=[bookmarkUrl]&title=[bookmarkTitle]',
            'stumbleUpon': 'http://www.stumbleupon.com/submit?url=[bookmarkUrl]&title=[bookmarkTitle]',
            'facebook': 'http://www.facebook.com/sharer.php?u=[bookmarkUrl]&t=[bookmarkTitle]',
            'digg': 'http://digg.com/submit?phase=2&url=[bookmarkUrl]&title=[bookmarkTitle]',
            'reddit': 'http://www.reddit.com/submit?url=[bookmarkUrl]&title=[bookmarkTitle]',
            'yahoo': 'http://myweb2.search.yahoo.com/myresults/bookmarklet?u=[bookmarkUrl]&t=[bookmarkTitle]',
            'google': 'http://www.google.com/bookmarks/mark?op=edit&bkmk=[bookmarkUrl]&title=[bookmarkTitle]',
            'twitter': 'http://www.twitter.com/grazedotcom'
        },
        windowOptions: 'top=50,left=150,toolbar=no,width=780,height=420,resizable=yes,scrollbars=yes,status=no'
    },

    open: function(service)
    {
        if (service == 'twitter') {
            window.open(this.config.urls.twitter, service);
            return;
        }
        
        var url = this.config.urls[service].replace('[bookmarkUrl]', encodeURIComponent(this.config.bookmarkUrl)).replace('[bookmarkTitle]', encodeURIComponent(this.config.bookmarkTitle));
        window.open(url, service, this.config.windowOptions);
    }
};

/**
 * Custom check box
 *
 * @author Edd
 */
var CheckBox = function(element, options)
{
    this.options = {
        group: null,
        id: null
    };
    Object.extend(this.options, options || {});

    this.element = element;

    this.checked = false;

    this.value = null;

    this.initialise();
};

CheckBox.prototype = {

    initialise: function()
    {
        this.element.observe('click', function(){
            this.toggle();
        }.bind(this));

        this.value = this.element.getParam('value');
    },

    check: function(ignoreOnChange)
    {
        if (this.checked) {
            return;
        }

        this.element.addClassName('checked');
        this.checked = true;
        
        if (this.options.group) {
            this.options.group.check(this, ignoreOnChange || false);
        }
    },

    uncheck: function(ignoreOnChange)
    {
        if (!this.checked) {
            return;
        }
        
        this.element.removeClassName('checked');
        this.checked = false;

        if (this.options.group) {
            this.options.group.uncheck(this, ignoreOnChange || false);
        }
    },

    toggle: function()
    {
        if (this.checked) {
            this.uncheck();
        }
        else {
            this.check();
        }
    }

};

CheckBoxGroup = function(element, options)
{
    this.options = {
        onChange: null,
        defaultValue: null
    };
    Object.extend(this.options, options || {});

    this.element = element;

    this.checkBoxes = [];

    this.checkedIds = [];

    this.lastChanged = null;

    this.initialise();
};

CheckBoxGroup.prototype = {

    initialise: function()
    {
        var checkBoxElements = this.element.select('.checkBox');
        checkBoxElements.each(function(checkBoxElement){
            var checkBox = new CheckBox(checkBoxElement, {group: this, id: this.checkBoxes.length});
            this.checkBoxes.push(checkBox);
        }.bind(this));

        if (this.options.defaultValue !== null) {
            var defaultValues = this.options.defaultValue.split(',');
            for(var i = 0; i < defaultValues.length; i++) {
                this.checkByValue(defaultValues[i], true);
            }
        }
    },

    check: function(checkBox, ignoreOnChange)
    {
        if (this.checkedIds.indexOf(checkBox.options.id) != -1) {
            return;
        }

        this.checkedIds.push(checkBox.options.id);

        this.lastChanged = checkBox;

        if (this.options.onChange && !ignoreOnChange) {
            this.options.onChange(this);
        }
    },

    uncheck: function(checkBox, ignoreOnChange)
    {
        if (this.checkedIds.indexOf(checkBox.options.id) == -1) {
            return;
        }

        this.checkedIds = this.checkedIds.without(checkBox.options.id);

        this.lastChanged = checkBox;

        if (this.options.onChange && !ignoreOnChange) {
            this.options.onChange(this);
        }
    },

    getChecked: function()
    {
        var checked = [];
        for (var i = 0; i < this.checkedIds.length; i++) {
            checked.push(this.checkBoxes[this.checkedIds[i]].value);
        }
        return checked;
    },

    /**
     * Get value returns a comma-separated list of checked values (note,
     * commas in values aren't escaped and therefore should be avoided!)
     */
    getValue: function()
    {
        return this.getChecked().join(',');
    },

    getCheckBoxByValue: function(value)
    {
        for (var i = 0; i < this.checkBoxes.length; i++) {
            if (this.checkBoxes[i].value == value) {
                return this.checkBoxes[i];
            }
        }
        return null;
    },

    checkByValue: function(value, ignoreOnChange)
    {
        var checkBox = this.getCheckBoxByValue(value);

        if (!checkBox) {
            return false;
        }

        checkBox.check(ignoreOnChange || false);
        return true;
    },

    isChecked: function(value)
    {
        return this.getCheckBoxByValue(value).checked;
    },

    checkAll: function(ignoreOnChange)
    {
        this.checkBoxes.each(function(checkBox){
            checkBox.check(ignoreOnChange || false)
        });
    },

    uncheckAll: function(ignoreOnChange)
    {
        this.checkBoxes.each(function(checkBox){
            checkBox.uncheck(ignoreOnChange || false)
        });
    }
};

/**
 * Custom radio button
 *
 * @author Edd
 */
var RadioButton = function(element, options)
{
    this.options = {
        checked: false,
        group: null
    };
    Object.extend(this.options, options || {});

    this.element = element;
    this.checked = false;
    this.value = null;

    this.initialise();
};

RadioButton.prototype = {
    initialise: function()
    {
        this.element.observe('click', function(){
            this.check();
        }.bind(this));

        this.value = this.element.getParam('value');

        if (this.options.checked) {
            this.check();
        }
    },

    check: function(ignoreOnChange)
    {
        if (this.checked) {
            return;
        }

        if (this.options.group) {
            this.options.group.check(this, ignoreOnChange || false);
        }

        this.element.addClassName('checked');
        this.checked = true;
    },

    uncheck: function()
    {
        if (!this.checked) {
            return;
        }
        this.element.removeClassName('checked');
        this.checked = false;
    }
};

/**
 * Set of radio buttons
 *
 * @author Edd
 */
var RadioButtonGroup = function(element, options)
{
    this.options = {
        onChange: null,
        defaultValue: null
    };
    Object.extend(this.options, options || {});

    this.element = element;

    this.radioButtons = [];
    this.checked = null;

    this.initialise();
};

RadioButtonGroup.prototype = {

    initialise: function()
    {
        var radioButtonElements = this.element.select('.radioButton');
        radioButtonElements.each(function(radioButtonElement){
            var radioButton = new RadioButton(radioButtonElement, {group:this});
            this.radioButtons.push(radioButton);
        }.bind(this));

        if (this.options.defaultValue !== null) {
            this.checkByValue(this.options.defaultValue, true);
        }
    },

    check: function(radioButton, ignoreOnChange)
    {
        if (this.checked) {
            this.checked.uncheck();
        }

        this.checked = radioButton;

        if (this.options.onChange && !ignoreOnChange) {
            this.options.onChange(radioButton);
        }
    },

    getRadioButtonByValue: function(value)
    {
        for (var i = 0; i < this.radioButtons.length; i++) {
            if (this.radioButtons[i].value == value) {
                return this.radioButtons[i];
            }
        }
        return null;
    },

    checkByValue: function(value, ignoreOnChange)
    {
        var radioButton = this.getRadioButtonByValue(value);

        if (!radioButton) {
            return false;
        }

        radioButton.check(ignoreOnChange || false);
        return true;
    },

    getValue: function()
    {
        if (!this.checked) {
            return null;
        }

        return this.checked.value;
    }

};

/**
 * DHTML drop down menu
 *
 * @author Edd
 */
var DropDown = function(element, options)
{
    this.options = {
        showMenuOnMouseover: true,
        menuOffset: [0, 0],
        inModal: false,
        delayBeforeShow: 0.1
    };
    Object.extend(this.options, options || {});

    this.element = element;
    this.linkElement = null;
    this.menuElement = null;

    this.showTimer = null;
    this.hideTimer = null;

    this.initialise();
};

DropDown.prototype = {

    initialise: function()
    {
        /**
         * set up the link element
         */
        this.linkElement = this.element.down('.dropDownLink');

        this.linkElement.observe('click', function(e){
            e.stop();
            this.showMenu();
        }.bind(this));

        if (this.options.showMenuOnMouseover) {
            this.linkElement.observe('mouseover', function(){
                this.setShowTimeout();
                this.killHideTimeout();
            }.bind(this));
            this.linkElement.observe('mouseout', function(){
                this.setHideTimeout();
                this.killShowTimeout();
            }.bind(this));
        }

        var linkArrow = new Element('div', {'class':'dropDownLinkArrow'});
        this.linkElement.insert({top:linkArrow});

        var linkLeft = new Element('div', {'class':'dropDownLinkLeft'});
        var linkRight = new Element('div', {'class':'dropDownLinkRight'});

        this.linkElement.insert(linkLeft);
        this.linkElement.insert(linkRight);

        /**
         * set up the menu element
         */
        var originalMenuElement = this.element.down('.dropDownMenu');

        this.menuElement = new Element('div', {'class':'dropDownMenu'}).insert(originalMenuElement.innerHTML);

        if (this.options.inModal) {
            this.menuElement.setStyle({zIndex: 10000});
        }

        this.menuElement.hide();
        originalMenuElement.remove();

        $(document.body).insert(this.menuElement);

        var menuTopLeft = new Element('div', {'class':'dropDownMenuTopLeft'});
        var menuBottomLeft = new Element('div', {'class':'dropDownMenuBottomLeft'});
        var menuBottomRight = new Element('div', {'class':'dropDownMenuBottomRight'});
        this.menuElement.insert(menuTopLeft);
        this.menuElement.insert(menuBottomLeft);
        this.menuElement.insert(menuBottomRight);

        this.menuElement.observe('mouseover', function(){
            this.killHideTimeout();
        }.bind(this));
        this.menuElement.observe('mouseout', function(){
            this.setHideTimeout();
        }.bind(this));

        this.menuElement.select('a').each(function(menuItem){
            menuItem.observe('click', function(){
                this.hideMenu();
            }.bind(this));
        }.bind(this));

        DropDown.globalStore.push(this);

        this.positionMenu();
    },

    positionMenu: function()
    {
        var offset = this.linkElement.cumulativeOffset();
        var linkSize = this.linkElement.getDimensions();
        var menuSize = this.menuElement.getDimensions();
        
        var top = offset.top + linkSize.height + this.options.menuOffset[0];
        var left = offset.left - (menuSize.width - linkSize.width) + this.options.menuOffset[1];

        this.menuElement.setStyle({top:top+'px', left:left+'px'});
    },

    showMenu: function(){
        DropDown.globalStore.each(function(dd){
            dd.hideMenu();
        });
        
        this.linkElement.addClassName('dropDownLinkHover');

        this.menuElement.show();

        this.positionMenu();
    },

    setShowTimeout: function(){
        var show = function(){
            this.showMenu();
        }.bind(this);

        this.showTimer = show.delay(this.options.delayBeforeShow);
    },

    killShowTimeout: function(){
        if (this.showTimer) {
            window.clearTimeout(this.showTimer);
            this.showTimer = null;
        }
    },
    
    hideMenu: function(){
        this.linkElement.removeClassName('dropDownLinkHover');
        this.menuElement.hide();
    },

    setHideTimeout: function(){
        var hide = function(){
            this.hideMenu();
        }.bind(this);
        
        this.hideTimer = hide.delay(0.3);
    },

    killHideTimeout: function(){
        if (this.hideTimer) {
            window.clearTimeout(this.hideTimer);
            this.hideTimer = null;
        }
    }
};

DropDown.globalStore = [];

/**
 * Initialise all drop down menus on a page
 */
DropDown.initialiseAll = function(){
    $$('.dropDown').each(function(dropDownElement){
        new DropDown(dropDownElement);
    });
};

/**
 * Renders a nice speech bubble pop up!
 *
 * @author Edd
 */
var SpeechBubble = function(options)
{
    this.config = {
        cornerRadius: 10,
        glowWidth : 6,
        tipWidth: 22,
        padding: 10
    };

    this.options = {
        width: 200,
        tipPosition: 10,
        attachment: null,
        attachmentOffset: null,
        position: null,
        contents: '',
        onClose: null,
        inModal: false
    };

    Object.extend(this.options, options || {});

    this.element = null;
    this.sides = null;
    this.tip = null;
    this.content = null;

    this.loader = null;

    this.resizeHandler = null;

    this.build();
};

SpeechBubble.prototype = {

    build: function()
    {
        this.element = new Element('div', {'class': 'speechBubble'});
        if (this.options.inModal) {
            this.element.setStyle({zIndex: 10000});
        }

        var corners = [];
        corners.push(new Element('div', {'class':'speechBubbleCorner'}));
        corners.push(new Element('div', {'class':'speechBubbleCorner topRight'}));
        corners.push(new Element('div', {'class':'speechBubbleCorner bottomRight'}));
        corners.push(new Element('div', {'class':'speechBubbleCorner bottomLeft'}));
        corners.each(function(c){ this.element.insert(c) }.bind(this));

        this.sides = {};
        this.sides.top = new Element('div', {'class':'speechBubbleSideTop'});
        this.sides.left = new Element('div', {'class':'speechBubbleSideLeft'});
        this.sides.right = new Element('div', {'class':'speechBubbleSideRight'});
        this.sides.bottomLeft = new Element('div', {'class':'speechBubbleSideBottom'});
        this.sides.bottomRight = new Element('div', {'class':'speechBubbleSideBottom'});
        Object.keys(this.sides).each(function(k){ this.element.insert(this.sides[k]) }.bind(this));

        this.tip = new Element('div', {'class':'speechBubbleTip'});
        this.element.insert(this.tip);

        this.content = new Element('div', {'class':'speechBubbleContent'});
        this.element.insert(this.content);

        $(document.body).insert(this.element);

        if (Utils.isIE()) {
            this.element.select('div').each(function(element){
                element.addClassName('pngFix');
            });
        }

        this.content.insert(this.options.contents);

        this.resize();
        this.reposition();
        this.register();
    },

    resize: function()
    {
        this.content.setStyle({
            width: (this.options.width + 2 * (this.config.cornerRadius - this.config.glowWidth) - 2 * this.config.padding) + 'px'
        });

        var height = this.content.getHeight();

        this.element.setStyle({
            width: (this.options.width + 2 * this.config.cornerRadius) + 'px',
            height: (height + 2 * this.config.cornerRadius) + 'px'
        });

        this.sides.top.setStyle({
            width: this.options.width + 'px'
        });
        this.sides.left.setStyle({
            height: height + 'px'
        });
        this.sides.right.setStyle({
            height: height + 'px'
        });
        this.sides.bottomLeft.setStyle({
            width: this.options.tipPosition + 'px'
        });
        this.sides.bottomRight.setStyle({
            width: (this.options.width - this.options.tipPosition - this.config.tipWidth) + 'px',
            left: (this.options.tipPosition + this.config.tipWidth + this.config.cornerRadius) + 'px'
        });

        this.tip.setStyle({
            left: (this.options.tipPosition + this.config.cornerRadius) + 'px'
        });

        this.content.setStyle({
            height: (height - 2 * this.config.padding) + 'px'
        });
    },

    reposition: function()
    {
        var position = null;

        if (this.options.attachment) {
            position = this.options.attachment.cumulativeOffset();
            if (this.options.attachmentOffset) {
                position.left += this.options.attachmentOffset.left;
                position.top += this.options.attachmentOffset.top;
            }
        }
        else {
            position = this.options.position;
        }

        if (!position) {
            return;
        }

        this.element.setStyle({
            left: (position.left - (this.tip.getWidth() / 2) - this.options.tipPosition - this.config.cornerRadius) + 'px',
            top: (position.top - this.element.getHeight() - this.tip.getHeight() + 2 * this.config.glowWidth) + 'px'
        });
    },

    register: function()
    {
        this.resizeHandler = function(){
            this.reposition();
        }.bind(this);

        Event.observe(window, 'resize', this.resizeHandler);

        this.content.select('.closeButton').each(function(closeButton){
            closeButton.observe('click', function(e){
                e.stop();
                this.close();
            }.bind(this));
        }.bind(this));

        this.content.select('.loadingButton').each(function(loadingButton){
            loadingButton.observe('click', function(){
                this.loading(true);
            }.bind(this));
        }.bind(this));
    },

    unregister: function()
    {
        Event.stopObserving(window, 'resize', this.resizeHandler);
    },

    close: function(closeEffect)
    {
        if (!this.element) {
            return;
        }

        var onClose = function(){
            this.unregister();

            if (this.element) {
                this.element.remove();
            }
            this.element = null;

            if (this.options.onClose) {
                this.options.onClose();
            }
        }.bind(this);

        switch (closeEffect) {
            // fade effect
            case 'fade':
                if (Utils.isIE()) {
                    this.element.select('.speechBubbleCorner').each(function(cornerElement){
                        cornerElement.setStyle({background:'none'});
                    });
                    Object.keys(this.sides).each(function(k){
                        this.sides[k].setStyle({background:'none'});
                    }.bind(this));
                    this.tip.setStyle({background:'none'});
                }

                new Effect.Fade(this.element, {
                    duration: 0.3,
                    afterFinish: onClose
                });
                break;

            // just hide it
            case 'none':
                this.element.hide();
                onClose();
                break;

            // drop out effect
            default:
                if (Utils.isIE()) {
                    this.element.select('.speechBubbleCorner').each(function(cornerElement){
                        cornerElement.setStyle({background:'none'});
                    });
                    Object.keys(this.sides).each(function(k){
                        this.sides[k].setStyle({background:'none'});
                    }.bind(this));
                    this.tip.setStyle({background:'none'});
                }

                new Effect.DropOut(this.element, {
                    duration: 0.2,
                    afterFinish: onClose
                });
                break;
        }

    },

    loading: function(show)
    {
        if (show) {
            this.loader = new Loader({elementToOverlay: this.content, zIndex: 2000});
            this.loader.show();
        }
        else if (this.loader) {
            this.loader.hide();
            this.loader = null;
        }
    }

};

/**
 * Box details modal
 *
 * @author Edd
 */
var BoxDetailsModal = {
    open: function(linkElement)
    {
        var url = $(linkElement).readAttribute('href');
        modal.open({url: url});
    }
};

/**
 * Product related methods
 *
 * @author Edd
 */
var Product = {
    
    showDetailsModal: function(productId, additionalModalOptions, additionalRequestParams)
    {
        var modalOptions = {
            width: 600,
            onOpen: function(thisModal){
                var ingredientsLink = thisModal.down('.showHideIngredients');
                var ingredients = thisModal.down('.productDetailsFullIngredients');

                ingredientsLink.show();
                ingredients.hide();

                if (ingredientsLink && ingredients) {
                    ingredientsLink.observe('click', function(){
                        Effect.toggle(ingredients, 'blind', {
                            duration: 0.2,
                            afterFinish: function(){
                                thisModal.growToFit();
                            }
                        });
                    });
                }

                thisModal.loading(true);
                var productImage = thisModal.down('.productDetailsPhotoContent img');

                productImage.observe('load', function(){
                    thisModal.loading(false);
                });

                var dropDownElement = thisModal.down('.productDetailsDropDown');
                if (dropDownElement) {
                    new DropDown(dropDownElement, {inModal: true, delayBeforeShow: 0});
                }
            },
            useIframe: false,
            clickOverlayToClose: true
        };

        Object.extend(modalOptions, additionalModalOptions || {});

        var detailsModal = new ModalObj(modalOptions);

        detailsModal.loading(true);

        var params = {
            prid: productId
        };
        Object.extend(params, additionalRequestParams || {});

        new AjaxRequest('/products/details', {
            maxActiveConnections: 0,
            parameters: params,
            onComplete: function(){
                if (detailsModal && detailsModal.isOpen()) {
                    detailsModal.loading(false);
                }
            }.bind(this),
            onSuccess:function(json){
                if (detailsModal && detailsModal.isOpen()) {
                    detailsModal.setContents(json.modalContents);
                }
            }.bind(this)
        });
    }
};

/**
 * Product search
 */
Product.Search = function(element)
{
    this.config = {
        resultsPage: '/products/search/q/%s'
    };

    this.element = element;
    this.initialise();
};

Product.Search.prototype = {
    
    initialise: function()
    {
        this.inputElement = this.element.down('input');
        this.inputElement.blur();

        this.resultsElement = this.element.down('.productSearchResults');

        var newElement = this.element.down('.productSearchNew');

        this.inputElement.observe('focus', function(){
            if (this.inputElement.getValue() == this.inputElement.defaultValue) {
                this.inputElement.value = '';
                if (newElement) {
                    newElement.hide();
                }
            }

            if (this.autoCompleter && this.inputElement.getValue().length > 1) {
                this.autoCompleter.show();
            }
        }.bind(this));

        this.inputElement.observe('blur', function(){
            if (this.inputElement.getValue() == '') {
                this.inputElement.value = this.inputElement.defaultValue;
                if (newElement) {
                    newElement.show();
                }
            }
        }.bind(this));

        this.loader = new Loader({elementToOverlay: this.element, zIndex: 2000});

        this.autoCompleter = new Ajax.Autocompleter(this.inputElement, this.resultsElement, '/products/search', {
            paramName: 'q',
            frequency: 0.2,
            minChars: 2, 
            select: 'productSearchTitle',
            indicator: this.loader.element,
            afterUpdateElement: function(inputElement, selectedElement){
                var urlElement = selectedElement.down('span[rel=url]');
                if (urlElement) {
                    this.loader.show();
                    document.location = urlElement.innerHTML;
                }

                var scriptElement = selectedElement.down('span[rel=script]');
                if (scriptElement) {
                    inputElement.blur();
                    eval(scriptElement.innerHTML);
                    (function(){
                        inputElement.value = '';
                    }).delay(1);
                }
            }.bind(this)
        });

        // Stop the form submitting if it's not been filled in
        this.element.down('form').observe('submit', function(e){
            if (this.inputElement.value == this.inputElement.defaultValue) {
                e.stop();
            }
        }.bind(this));
    },

    viewAll: function()
    {
        var query = this.inputElement.getValue();
        if (! query.length) {
            return;
        }
        
        document.location = this.config.resultsPage.replace('%s', query);
    }
};

var Wizard = function(options)
{
    this.options = {
        requestUrl: null,
        basicRequestParameters: null, // Request parameters present in every stage
        initialRequestParameters: null, // Request paramters on the initial stage only
        defaultStage: 'initialise',
        onClose: null,
        modalOptions: null,
        onNextAction: null
    };
    Object.extend(this.options, options || {});

    this.modal = null;
    this.modalStyle = 'modalQuestion';

    this.lastJsonResponse = null;

    this.initialise();
};

Wizard.prototype = {
    initialise: function()
    {
        var mOptions = {
            width: 550,
            onClose: function(){
                if (this.options.onClose) {
                    this.options.onClose(this);
                }
                if (this.options.onNextAction) {
                    this.options.onNextAction();
                    this.options.onNextAction = null;
                }
                if (this.processActionOnClose) {
                    this.processAction({stage:this.processActionOnClose}, null, true);
                }
                Wizard.activeWizard = null;
            }.bind(this)
        };
        Object.extend(mOptions, this.options.modalOptions || {});

        this.modal = new ModalObj(mOptions);

        Wizard.activeWizard = this;

        this.processAction({stage: this.options.defaultStage});
    },

    processAction: function(buttonParams, modalClosing)
    {
        if (this.options.onNextAction) {
            if (! this.options.onNextAction()) {
                this.options.onNextAction = null;
                return;
            }

            this.options.onNextAction = null;
        }

        this.modal.loading(true);

        var ajaxParams = {};
        Object.extend(ajaxParams, this.options.basicRequestParameters || {});
        Object.extend(ajaxParams, this.options.initialRequestParameters || {});
        Object.extend(ajaxParams, buttonParams || {});
        Object.extend(ajaxParams, this.getFormValues());

        this.options.initialRequestParameters = null;

        new AjaxRequest(this.options.requestUrl, {
            parameters: ajaxParams,
            onComplete: function(){
                this.modal.loading(false);
            }.bind(this),
            onSuccess: function(json){
                this.lastJsonResponse = json;

                if (modalClosing) {
                    return;
                }

                if (json.modalClose) {
                    this.modal.close();
                    return;
                }

                if (json.modalContents) {
                    this.modal.setContents(json.modalContents);
                }
                else if (json.modalInnerContents) {
                    this.modal.down('.wizardModalInner').update(json.modalInnerContents);
                    this.modal.growToFit();
                } else if (json.documentLocation) {
                    document.location = json.documentLocation;
                    return;
                }

                if (json.errorMessage) {
                    this.showErrorMessage(json.errorMessage);
                }

                if (json.modalStyle) {
                    var container = this.modal.down('.wizardModal');
                    container.removeClassName(this.modalStyle);
                    container.addClassName(json.modalStyle);
                    this.modalStyle = json.modalStyle;
                }

                if (json.modalWidth) {
                    this.modal.resizeContents({width: json.modalWidth});
                    this.modal.growToFit();
                }

                this.processActionOnClose = json.processActionOnClose || false;

                this.registerActionListeners();
            }.bind(this)
        });
    },

    registerActionListeners: function()
    {
        this.modal.select('.wizardAction').each(function(wizardActionElement){
            wizardActionElement.observe('click', function(e){
                e.stop();
                this.processAction(wizardActionElement.getParams());
            }.bind(this))
        }.bind(this));

        this.modal.select('.closeButton').each(function(closeButton){
            closeButton.observe('click', function(e){
                e.stop();
                this.modal.close();
            }.bind(this))
        }.bind(this));
    },

    getFormValues: function()
    {
        var formParams = {};
        this.modal.select('input[type!=button],select,textarea').each(function(formElement){
            formParams[formElement.readAttribute('name')] = formElement.getValue();
        });
        return formParams;
    },

    showErrorMessage: function(errorMessage)
    {
        var errorMessageElement = this.modal.down('.wizardModalError');
        if (errorMessageElement) {
            errorMessageElement.hide();

            if (errorMessage) {
                errorMessageElement.update(errorMessage);
            }

            new Effect.Appear(errorMessageElement, {
                duration: 0.2,
                afterFinish: function(){
                    this.modal.growToFit();
                }.bind(this)
            });
        }
    },

    hideErrorMessage: function()
    {
        var errorMessageElement = this.modal.down('.wizardModalError');
        if (errorMessageElement) {
            errorMessageElement.hide();
        }
    }

};

Wizard.activeWizard = null;

/**
 * Google Anayltics tools
 */
var GoogleAnalytics = {

    track: function(path)
    {
        try {
            try {
                console.log('GA: tracking click to "' + path + '"...');
            } catch (e1){}
            pageTracker._trackPageview(path);
        }
        catch(e2){}
    }

}
