/**
 * Script to populate the Location and relocation combo boxes
 *
 * Copyright Sean Chalmers <seandchalmers@yahoo.ca> 2007
 */
function log(message) {

    if (!log.window_ || log.window_.closed) {
        var win = window.open("", null, "width=400,height=200," +
                              "scrollbars=yes,resizable=yes,status=no," +
                              "location=no,menubar=no,toolbar=no");

        if (!win) return;

        var doc = win.document;
        doc.write("<html><head><title>Debug Log</title></head>" +
                  "<body></body></html>");
        doc.close();
        log.window_ = win;
    }

    var logLine = log.window_.document.createElement("div");
    logLine.appendChild(log.window_.document.createTextNode(message));
    log.window_.document.body.appendChild(logLine);
}

/**
 * BoundText
 *
 * <p>This is a wrapper around textarea elements and input elements of type text
 * that are bound to a datasource.</p>
 */
var BoundText = Class.create();
BoundText.prototype = {

    /**
    * Initializes this data bound text element.
    *
    * @param Element element A required Prototype Element object or string
    * containing the id attribute of the HTML element which this object wraps.
    */
    initialize : function(element) {

        // If passed an Element use it else get the Element based on string and set
        // its type
        if (typeof element === 'object') {
            this.element = element;
        }
        else {
            this.element = $(element);
        }

        // Associate this control with its form.
        this.form = this.element.up('form');
    },

    /**
    * Sets the value of the element.
    */
    setValue : function (value) {
        this.element.value = value;
    }
}

/**
 * BoundSelect
 *
 * <p>This is a wrapper around a select HTML element.  It will fetch a set of static
 * data which will populate the select element option's.</p>
 */
var BoundSelect = Class.create();
BoundSelect.prototype = {

    /**
    * Initializes this data bound select element.
    *
    * @param Element element A required Prototype Element object which is
    * this element.
    * @param Element boundTo An optional Element object to which this element
    * may be bound.  Correpsonds to an element having 'bound-field' in its class
    * attribute.
    */
    initialize : function(element, boundTo) {

        // If passed an Element use it else get the Element based on string and set
        // its type
        if (typeof element === 'object') {
            this.element = element;
        }
        else {
            this.element = $(element);
        }

        // Turn off indicator if present
        this.setRequestState(false);

        // Associate this control with its form.
        this.form = this.element.up('form');

        // If the custom attribute dataUrl is present then use it to fetch form data
        if (this.element.hasAttribute('dataUrl')) {
            this.dataUrl = this.element.readAttribute('dataUrl');
        }
        else {
            this.dataUrl = 'form_data.php';
        }

        // Set the element we are bound to an listen for its change event
        if (boundTo) {
            if (typeof boundTo === 'object') {
                this.boundToElem = boundTo;
            }
            else {
                this.boundToElem = $(boundTo);
            }
            Event.observe(this.boundToElem.id, 'change', this.fetch.bind(this));
        }
    },

    /*
    * Fetches the static data from the server side script.
    */
    fetch : function() {

        // Turn on indicator while fetching
        this.setRequestState(true);

        // Build common request parameters
        var parameters = {
            form : this.form.id,
            form_field : this.element.id
        };

        // If this control is bound to another then get the bound value
        if (this.boundToElem) {
            parameters.depend_value = this.boundToElem.getValue();
        }

        var ajax = new Ajax.Request(this.dataUrl, {
            method: 'get',
            parameters: parameters,
            onComplete: this.populate.bind(this)
        });
    },

    /*
    * Populates this element with the static data returned from the server side
    * script.
    *
    * @param Object response The AJAX response object.
    */
    populate : function(response) {

        var option;

        try {
            var options = eval('(' + response.responseText + ')');

            // If options already exist remove them
            if (this.element.options.length > 0) {

                this.element.selectedIndex = 0;
                var length = this.element.options.length;
                for (var i = length - 1;  i >= 0 ; i--) {
                    this.removeOption(i);
                }
            }

            // If this is a single select then add the required first option
            if (this.element.type === 'select-one') {
                this.addOption(0, 'Select...');
            }

            // For each value returned for server
            var func = this.addOption.bind(this);
            $H(options.data).each(function(pair) {
                func(pair.key, pair.value);
            });
        }
        catch (e) {
        }

        // Turn on indicator while fetching
        this.setRequestState(false);

        // Check if there is an id key
        var id = $F('data_id');
        if (id != null) {

            var parameters = {
                form : this.form.id,
                form_field : this.element.id,
                retrieval_id : id
            };

            var ajax = new Ajax.Request(this.dataUrl, {
                method: 'get',
                parameters: parameters,
                onComplete: this.selectOptions.bind(this)
            });
        }
    },

    /**
    * Toggle the fetching indicator
    */
    setRequestState : function(flag) {

        indicator = 'progress-bar-' + this.element.id;

        if ($(indicator) !== null) {
            if (flag) {
                $(indicator).show();
            }
            else {
                $(indicator).hide();
            }
        }
    },

    /**
    *
    */
    selectOptions : function(response) {

        try {
            var values = eval('(' + response.responseText + ')');

            var length = this.element.options.length;

            var numValues = values.size();
            for (var i = 0; i < numValues; i++) {
                for (var j = 0; j < length; j++) {
                    if (this.element.options[j].value === values[i]) {
                        this.element.selectedIndex = j;
                    }
                }
            }
        }
        catch (e) {
        }
    },

    /*
    * Adds an option element to a select element
    *
    * @param string value The value the new option element is to have.
    * @param string text The text the new option element is to have.
    */
    addOption: function(value, text) {

        var option = document.createElement('option');
        option.value = value;
        option.text = text;

        try {
            this.element.add(option); // IEWin browser
        }
        catch (e) {
            this.element.appendChild(option); // Compliant browser entry
        }
    },

    /*
    * Removes an option element from a select element.
    *
    * @param int index The index of the option to remove.
    */
    removeOption: function(index) {

        var numOptions = this.element.options.length;

        // If there are options to delete and given index is in range
        if (index >= 0 && index < numOptions) {
            this.element.options[index] = null;
        }
    }
};

/**
 * BoundComposite
 *
 * <p>This is a wrapper around a select HTML element.  It will fetch a set of static
 * data which will populate the select element option's.</p>
 *
 * <p>A composite bound select is a select element in which the set of options it
 * contains are dependent on another select element's selected option.</p>
 */
var BoundComposite = Class.create();
BoundComposite.prototype = {

    /**
    * Initializes this data bound select element.
    *
    * @param Form form The required prototype Form object.
    * @param Element element A required Prototype Element object which is
    * this element.
    * @param Element boundTo An optional Element object to which this element
    * may be bound.  Correpsonds to an element having 'bound-field' in its class
    * attribute.
    */
    initialize : function(container, boundTo) {

        // A composite control is contained by div tag which contains the source,
        // destination selects and the add/remove input buttons
        this.container = container;

        this.srcElem = new BoundSelect(
                this.container.down('select.bound-composite-src'), boundTo);
        this.destElem = new BoundSelect(
                this.container.down('select.bound-composite-dest'));

        // Add the add/remove event listeners
        if (this.container.down('input.add-selected')) {
            Event.observe(this.container.down('input.add-selected').id, 'click',
                this.addSelected.bindAsEventListener(this));
        }
        if (this.container.down('input.add-all')) {
            Event.observe(this.container.down('input.add-all').id, 'click',
                this.addAll.bindAsEventListener(this));
        }
        if (this.container.down('input.remove-selected')) {
            Event.observe(this.container.down('input.remove-selected').id, 'click',
                this.removeSelected.bindAsEventListener(this));
        }
        if (this.container.down('input.remove-all')) {
            Event.observe(this.container.down('input.remove-all').id, 'click',
                this.removeAll.bindAsEventListener(this));
        }
    },

    /*
    * Fetches the data from the server side script, indicaticated by the
    * action attribute of this element's form.
    */
    fetch : function() {
        this.srcElem.fetch();
    },

    /**
    *
    */
    selectOptions : function(value) {

        var length = this.element.options.length;

        var values = [];

        // Convert value to array of options or use array passed in
        if (typeof value !== 'object') {
            values = value;
        }
        else {
            values.push(value);
        }

        var numValues = values.length;
        for (var i = 0; i < numValues; i++) {
            for (var j = 0; j < length; j++) {
                if (this.element.options[j].value === values[i]) {
                    this.element.selectedIndex = j;
                }
            }
        }
    },
    /*
    * Adds selected options to the list of the destination element.
    *
    * @param HTMLEvent event The event object generated by click event.
    */
    addSelected: function(event) {
        this.moveOption(this.srcElem, this.destElem);
        this.selectAll(this.destElem);
    },

    /*
    * Removes selected options from the list of the destination element.
    *
    * @param HTMLEvent event The event object generated by click event.
    */
    removeSelected: function(event) {
        this.moveOption(this.destElem, this.srcElem);
    },

    /*
     * Moves selected options from one select element to another select element.
     *
     * @param HTMLSelectElement source The source select element.
     * @param HTMLSelectElement destination The destination select element.
     */
    moveOption: function(source, dest) {

        optionCount = source.element.options.length;

        // Loop through, in reverse order, to find selected elements.  Reverse order
        // is necessary as removal changes index numbers
        var removal = [];
        for (i = 0; i < optionCount; i++) {

            // If option is selected and to dest and remove from source
            if (source.element.options[i].selected) {

                var sourceOption = source.element.options[i];

                dest.addOption(sourceOption.value, sourceOption.text);
                removal.push(i);
            }
        }

        for (i = removal.length - 1; i >= 0 ; i--) {
            source.removeOption(removal[i]);
        }
    },

    /*
     * Selects some of the elements in a select elements.
     *
     * @param array select The array of values which correspond to values in the
     * select options.
     */
    selectOptions : function(value) {

        var length = this.element.options.length;

        var values = [];

        // Convert value to array of options or use array passed in
        if (typeof value !== 'object') {
            values = value;
        }
        else {
            values.push(value);
        }

        var numValues = values.length;
        for (var i = 0; i < numValues; i++) {
            for (var j = 0; j < length; j++) {
                if (this.element.options[j].value === values[i]) {
                    this.element.selectedIndex = j;
                }
            }
        }
    },

    /*
     * Selects all of the elements in a select elemens.
     *
     * @param HTMLSelectElement select The select element to select all of its
     * options.
     */
    selectAll: function(select) {

        var options = select.element.options;
        for (i = 0; i < options.length; i++) {
            Try.these(
                function() {options[i].selected = true;}, // Compliant browser
                function() {select.element.selectedIndex = i;} // IE browser
            );
        }
    }
};

/**
 * Formulator
 *
 *<p>A wrapper class for a form which contains data which may be bound to a
 * data source either through static values to populate select HTML elements or
 * to populate the entire form with the data from a record.</p>
 */
var Formulator = Class.create();
Formulator.prototype = {

    /**
    * Constructor
    *
    * @param string form The form HTML element's ID attribute.
    * @param boolean validation A flag indicating whether the form should perform
    * validation prior to submitting.
    * @param boolean submit The form HTML element's ID attribute.
    */
    initialize : function(form, validation, submit) {


        this.form = form;

        this.validation = validation;
        this.submit = submit;

        // Load the form when available
        Event.observe(window, 'load', this.setup.bind(this));
    },

    /**
    * Initializes the remaining propertis of this form that require the form DOM
    * element to be in existence.
    */
    setup : function() {

        // Add the location of where the form should GET/POST from/to
        this.formUrl = $(this.form).readAttribute('action');
        this.process = $(this.form).readAttribute('process');
        if (typeof this.process === 'object') {
            if ($(this.form).id === 'candidate_search') {
                this.process = 'query-candidate';
            }
        }

        // Listen for submit event and stop the event from "bubbling"
        if (this.submit) {
            Event.observe(this.form, 'submit', this.submitForm.bindAsEventListener(this), false);
        }

        // Elem ID for progress of submission
        this.setRequestState(false);

        // Add validation to the form if requested and avaiable
        if (this.validation) {

            this.validator = new Validation(this.form, {
                immediate : false,
                focusOnError : false,
                stopOnFirst : false,
                onSubmit : false
            });

            // Add validation for phone numbers
            phone_options = {
                minLength : 12,
                maxLength : 14
            };
            Validation.add('validate-phone', 'Error', function(v) {
                    return v.match(/^(\()?([0-9]{3})(\))?[- ]?([1-9]{3})([- ])?([0-9]{4})$/g);
                },
            phone_options);

            // Add validation for single line strings
            string_options = {
                maxLength : 32
            };
            Validation.add('validate-string', 'Error', function(v) {
                    return v.match(/^[a-zA-Z \-']+$/g);
                },
            string_options);

            // Add validation for multi-line strings
            text_options = {
                maxLength : 65535
            };
            Validation.add('validate-text', 'Error', function(v) {
                    return v.match(/^[a-zA-Z0-9 &\',;:().\n\r]+$/gm);
                },
            text_options);
        }

        // Load data bound elements
        this.loadFormElem();
    },

    /**
    * Loads the form elements and populates the data bound fields once the document
    * has been loaded.
    */
    loadFormElem : function() {

        var boundElems = $A($(this.form).getElementsByClassName('bound-data'));

        // IEWin55 hack - no elements from getElements
        if (boundElems.size() === 0) {
            boundElems = this.getElements();
        }

        // Create the array of data bound elements
        boundElems.each(function(elem) {

            var classnames = elem.classNames();
            var dataBound = classnames.find(function(classname) {
                return ('bound-data' === classname);
            });

            if (dataBound) {
                var boundElem;

                if (elem.tagName === 'SELECT') {
                    boundElem = new BoundSelect(elem);
                }
                else {
                    boundElem = new BoundText(elem);
                }
                boundElem.fetch();
            }
        });

        // Create list of all data bound dependent elements
        var dependElems = $(this.form).getElements();

        // IEWin55 hack - no elements from getElements
        if (dependElems.size() === 0) {
            dependElems = this.getElements();
        }

        // Create the array of data bound elements
        dependElems.each(function(elem) {

            var classnames = elem.classNames();
            var fieldBound = classnames.find(function(classname) {
                return ('bound-field-' === classname.substr(0, 12));
            });

            if (fieldBound) {
                var fieldBoundName = fieldBound.substring(12);
                var boundElem = new BoundSelect(elem, $(fieldBoundName));
            }
        });

        // Create list of all data bound dependent elements
        var compositeElems = $(this.form).getElementsByClassName('bound-composite');

        // IEWin55 hack - no elements from getElements
        if (compositeElems.size() === 0) {
            compositeElems = this.getElements();
        }

        // Create the array of data bound elements
        compositeElems.each(function(elem) {

            var classnames = elem.classNames();
            var fieldBound = classnames.find(function(classname) {
                return ('bound-composite' === classname);
            });

            if (fieldBound) {
                var boundElem = new BoundComposite(elem);
                boundElem.fetch();
            }
        });
    },

    getElements : function() {

            var temp = [];

            var children = document.body.getElementsByTagName('select');
            var length = children.length;
            for (var i = 0; i < length; i++) {
                temp.push(Element.extend(children[i]));
            }

            children = document.body.getElementsByTagName('input');
            length = children.length;
            for (var i = 0; i < length; i++) {
                temp.push(Element.extend(children[i]));
            }

            children = document.body.getElementsByTagName('textarea');
            length = children.length;
            for (var i = 0; i < length; i++) {
                temp.push(Element.extend(children[i]));
            }

            children = document.body.getElementsByTagName('div');
            length = children.length;
            for (var i = 0; i < length; i++) {
                temp.push(Element.extend(children[i]));
            }

            return $A(temp);
    },

    /**
    * Submits the form data to the server for final verificationnd submission.
    *
    * The event is stopped at end of fucntion to prevent the HTML form from also
    * processing the event.  The HTML form tag should have a action and method
    * attribute so the form degrades if no javascript.
    *
    * @param HTMLEvent event The event object generated by click event.
    */
    submitForm: function(event) {

        var result = true;
        if (this.validation) {
            this.validator.reset();
            result = this.validator.validate();
        }

        if (result) {

            this.setRequestState(true);

            var ajax = new Ajax.Request(this.formUrl, {
                method: 'post',
                parameters: {
                    form : this.form,
                    process : this.process,
                    data: $(this.form).serialize(false)
                },
                onComplete: this.completeSubmit.bind(this)
            });
        }

        Event.stop(event);
    },

    completeSubmit : function(response) {
        this.setRequestState(false);
        var baseRef = location.href.substring(0,location.href.lastIndexOf("/") + 1);
        location.href = baseRef + 'index.html';
    },

    /**
    * Toggles the indicator to show that control is retrieving data.
    */
    setRequestState : function(flag) {

        indicator = 'progress-bar-' + $(this.form).id;

        if ($(indicator) !== null) {
            if (flag) {
                $(indicator).show();
            }
            else {
                $(indicator).hide();
            }
        }
    }
};