(function() {


    /*
     * Create custome onChange events for make, model, style drop down. When any parent dorp down in the tree has only one
     * result.  It will fire onChange events on dependent drop down, because there will be no user interaction.
     * see function updateDropDown()
     */
    var cMakeEvent = new YAHOO.util.CustomEvent("onMakeChangeEvent");
    var cModelEvent = new YAHOO.util.CustomEvent("onModelChangeEvent");
    var cStyleEvent = new YAHOO.util.CustomEvent("onStyleChangeEvent");

    /**
     * FormController will submit the formRef when both zip is validated and has maintenance records
     * It contains a custom event that will be fired whenever a validation request is completed.  Once all the required
     * validations are completed, the custom even will submit the form.
     */
    function FormController(){
        this.formRef;
        this.hasMaintenance = false;
        this.isValidZIP = false;
        this.submitEvent = new YAHOO.util.CustomEvent("submitEvent", this);
        this.submitEvent.subscribe(function(type, args){
            if (args[0] == 'zip') {
                this.isValidZIP = true;
            } else if (args[0] == 'maintenance'){
                this.hasMaintenance = true;
            }

            if (this.hasMaintenance && this.isValidZIP && typeof this.formRef != 'undefined'){
                this.formRef.submit();
            }
        })
    }
    var fController = new FormController();

    /**
     * A default callback used by all the event listeners.  It contains a default failure, timeout function, and empty
     * success function
     */
    function DefaultCallback(){
        this.success;
        this.failure = function (o) {
            alert("Please make your selection again");
            return;
        };
        this.timeout = 15000;
    }

    // Register OnSubmit event to validate field
    YAHOO.util.Event.on('maintenanceSelectForm', 'submit', function (e) {
        // stop submit event, form will be submitted programatically if validation passes
        YAHOO.util.Event.stopEvent(e);
        // Handle find target for both browsers
        var f = e.target || e.srcElement;

        // Update form reference for Controller
        fController.formRef = f;

        if (f.year.selectedIndex == 0) {
            f.year.focus();
            alert("Please Select a Year");
            return;
        }
        if (f.make.selectedIndex == 0) {
            f.make.focus();
            alert("Please Select a Make");
            return;
        }
        if (f.model.selectedIndex == 0) {
            f.model.focus();
            alert("Please Select a Model");
            return;
        }
        var modelValue = f.model.options[f.model.selectedIndex].value;

        if (f.styleId.selectedIndex == 0) {
            f.styleId.focus();
            alert("Please Select a Style");
            return;
        }
        if (f.engCode.selectedIndex == 0 && f.engCode.options.length > 1) {
            f.engCode.focus();
            alert("Please Select an Engine");
            return;
        }
        var engValue = f.engCode.options[f.engCode.selectedIndex].value;

        if (f.transCode.selectedIndex == 0 && f.transCode.options.length > 1) {
            f.transCode.focus();
            alert("Please Select a Transmission");
            return;
        }
        var transValue = f.transCode.options[f.transCode.selectedIndex].value;

        var mileageStr = f.mileage.value.replace(",", ""); // removes all comma in the mileages
        if (mileageStr==""){
          alert("Please input desired miles");
          return;
        }

        // Validate mileage data type
        if (isNaN(mileageStr)){
            alert("Please fill in Valid Miles without exceeding 300000 miles ");
            f.mileage.select();
            f.mileage.focus();
            return;
        }

        // Validate mileage between 0-300k
        var mileageInt = parseInt(mileageStr);
        if (mileageInt < 0 || mileageInt > 300000) {
            alert("Information is not available at this time on recommended services to be performed at intervals above 300,000 miles.  It will be added soon.  Please check back again.");
            f.mileage.select();
            f.mileage.focus();
            return;
        } else {
            f.mileage.value = mileageInt;
        }

        var zipStr = f.zip.value;
        if (zipStr==""){
          alert("Please input your Zip Code");
          return;
        }

        if (zipStr.length != 5){
          alert("Please input your Zip Code with 5 digits");
          return;
        }

        // Validate ZIP to be either XXXXX or XXXXX-XXXX
        var reZip = new RegExp(/^\d{5}([-]\d{4})?$/);
        if (!reZip.test(f.zip.value)) {
            alert("Please input your Zip Code in proper number format");
            f.zip.focus();
            return;
        }

        // Validate if there are maintenance for the selected style/engine/transmission combination
        var oMaintCB = new DefaultCallback();
        oMaintCB.success = function (o) {
            // Process the JSON data returned from the server
            try {
                // i.e. {maintenance:1, recall:2, bulletin:1}
                var messages = YAHOO.lang.JSON.parse(o.responseText)[0];
                if (messages.maintenance + messages.recall + messages.bulletin == 0) {
                    alert('There are no maintenance record for this model. It will be added soon. Please check back again.');
                } else {
                    fController.submitEvent.fire('maintenance');
                }
            }
            catch (x) {
                alert("Maintenance callback failed!");
                return;
            }
        };

        // make AJAX call to verify if maintenance data exists
        YAHOO.util.Connect.asyncRequest( 'GET',
            "select.json?modelYearId=" + modelValue + "&engine=" + engValue + "&transmission=" + transValue,
            oMaintCB);

        var oZipCB = new DefaultCallback();
        oZipCB.success = function(o){
            // Process the JSON data returned from the server
            try {
                var stateEle = o.responseXML.documentElement.getElementsByTagName("state")[0];
                if (!stateEle.firstChild) {
                    alert("Please input a valid ZIP Code");
                } else {
                    fController.submitEvent.fire('zip');
                }
            }
            catch (x) {
                alert("ZIP callback failed!");
                return;
            }
        }
        // If we are unable to validate the zipcode, then allow the user to proceed
        oZipCB.failure = function (o) {
            fController.submitEvent.fire('zip');
        };
        YAHOO.util.Connect.asyncRequest( 'GET',"/zipcode/queryZIP.jsp?zipcode="+ f.zip.value, oZipCB);
        return;
    });

    /**
     * Clear the drop down for the selectElementId
     * @param selectElementId
     */
    function clearDropDown(selectElementId) {
        var selectElement = YAHOO.util.Dom.get(selectElementId);
        for (var i = selectElement.options.length - 1; i >= 0; i--) {
            selectElement.options[i] = null;
        }
        selectElement.disabled = true;
    }

    /**
     * Update the drop down with new content
     * @param selectElementId is the element id for the drop down
     * @param newOptionsArray is the new option data array
     * @param fieldName is the drop down name (use to create initial 'Please select <fieldName> option
     * @param cEvent is the custom event to fire when only one result is in newOptionsArray.  So it will continusly trigger
     *  the loading of drop down chains
     */
    function updateDropDown(selectElementId, newOptionsArray, fieldName, cEvent) {
        // clear drop down first
        clearDropDown(selectElementId);

        // update drop down with new content
        var selectElement = YAHOO.util.Dom.get(selectElementId);
        // always populate initial 'Please select <fieldName>' option, so select option[0] will not be a valid choice.
        // this is because AJAX request will not fire if user selects option[0]. (see all the fn_*Select)
        selectElement.options[0] = new Option("Select " + fieldName, "");
        for (var i = 0; i < newOptionsArray.length; i++) {
            selectElement.options[selectElement.options.length] = new Option(newOptionsArray[i].name, newOptionsArray[i].id);
        }

        // load the next drop down if only one result in the current one
        if (newOptionsArray.length == 1) {
            selectElement.selectedIndex = 1;
            if (typeof cEvent != 'undefined') {
              cEvent.fire();
            }
        }

        // enable drop down for user to change
        selectElement.disabled = false;
    }

    /**
     * On year drop down change, it will clear entire drop down chains.  Make AJAX call to get content for Make drop down
     * @param e
     */
    function fn_yearSelect(e) {
        clearDropDown("makeSelect");
        clearDropDown("modelSelect");
        clearDropDown("styleSelect");
        clearDropDown("engineSelect");
        clearDropDown("transmissionSelect");

        var yearSelect = YAHOO.util.Dom.get('yearSelect');
        var oYearCB = new DefaultCallback();
        oYearCB.success = function (o) {
            // Process the JSON data returned from the server
            var messages = [];
            try {
                messages = YAHOO.lang.JSON.parse(o.responseText);
            }
            catch (x) {
                alert("Year callback failed!");
                return;
            }
            updateDropDown("makeSelect", messages, "Make ", cMakeEvent);
        };
        // if we've changed the select to something other than the Select X option (index 0)
        if (yearSelect.selectedIndex > 0) {
            var yearValue = yearSelect.options[yearSelect.selectedIndex].value;
            YAHOO.util.Connect.asyncRequest('GET', "select.json?year=" + yearValue, oYearCB);
        }
    }
    YAHOO.util.Event.on('yearSelect', 'change', fn_yearSelect);

    /**
     * On make drop down change, it will clear following drop down chains.  Make AJAX call to get content for model drop down
     * @param e
     */
    function fn_makeSelect(e) {
        clearDropDown("modelSelect");
        clearDropDown("styleSelect");
        clearDropDown("engineSelect");
        clearDropDown("transmissionSelect");

        var makeSelect = YAHOO.util.Dom.get('makeSelect');
        var yearSelect = YAHOO.util.Dom.get('yearSelect');

        // Define the callbacks for the asyncRequest
        var oMakeCB = new DefaultCallback();
            oMakeCB.success = function (o) {
            // Process the JSON data returned from the server
            var messages = [];
            try {
                messages = YAHOO.lang.JSON.parse(o.responseText);
            }
            catch (x) {
                alert("Make callback failed!");
                return;
            }
            updateDropDown("modelSelect", messages, "Model ", cModelEvent);
        };
        // if we've changed the select to something other than the Select X option (index 0)
        if (makeSelect.selectedIndex > 0) {
            var yearValue = yearSelect.options[yearSelect.selectedIndex].value;
            var makeValue = makeSelect.options[makeSelect.selectedIndex].value;
            YAHOO.util.Connect.asyncRequest('GET', "select.json?year=" + yearValue + "&makeId=" + makeValue, oMakeCB);
        }
    }
    YAHOO.util.Event.on('makeSelect', 'change', fn_makeSelect);
    // populate cMakeEvent to be use in function updateDropDown
    cMakeEvent.subscribe(fn_makeSelect);

    /**
     * On model drop down change, it will clear entire drop down chains.  Make AJAX call to get content for style drop down
     * @param e
     */
    function fn_modelSelect(e) {
        clearDropDown("styleSelect");
        clearDropDown("engineSelect");
        clearDropDown("transmissionSelect");

        var modelSelect = YAHOO.util.Dom.get('modelSelect');

        var oModelCB = new DefaultCallback();
        oModelCB.success = function (o) {
            // Process the JSON data returned from the server
            var messages = [];
            try {
                messages = YAHOO.lang.JSON.parse(o.responseText);
            }
            catch (x) {
                alert("Model callback failed!");
                return;
            }
            updateDropDown("styleSelect", messages, "Style ", cStyleEvent);
        };
        // if we've changed the select to something other than the Select X option (index 0)
        if (modelSelect.selectedIndex > 0) {
            var modelValue = modelSelect.options[modelSelect.selectedIndex].value;
            YAHOO.util.Connect.asyncRequest('GET', "select.json?modelYearId=" + modelValue, oModelCB);
        }
    }
    YAHOO.util.Event.on('modelSelect', 'change', fn_modelSelect);
    // populate cModelEvent to be use in function updateDropDown
    cModelEvent.subscribe(fn_modelSelect);

    /**
     * On style drop down change, it will clear entire drop down chains.  Make AJAX call to get content for engine/transmission
      *  drop down
     * @param e
     */
    function fn_styleSelect(e) {
        clearDropDown("engineSelect");
        clearDropDown("transmissionSelect");

        var styleSelect = YAHOO.util.Dom.get('styleSelect');
        var oStyleCB = new DefaultCallback();
        oStyleCB.success = function (o) {
            // Process the JSON data returned from the server
            var messages = new Object();
            try {
                messages = YAHOO.lang.JSON.parse(o.responseText);
            }
            catch (x) {
                alert("Style callback failed!");
                return;
            }

            var dupEngMap = {};
            var dupIndices = {};
            for (var i = 0; i < messages.engines.length; i++) {
                if (typeof dupEngMap[messages.engines[i].name] != 'undefined') {
                    dupIndices[dupEngMap[messages.engines[i].name]] = 1; // push the first duplicate (will be run multiple time)
                    dupIndices[i] =1; // push the current duplicate
                } else {
                    dupEngMap[messages.engines[i].name] = i;
                }
            }
            // update all the duplicate
            for (var idx in dupIndices){
                messages.engines[idx].name = messages.engines[idx].name + ' - ' +messages.engines[idx].discriminator;
            }

            updateDropDown("engineSelect", messages.engines, "Engine");
            updateDropDown("transmissionSelect", messages.transmissions, "Transmission");
        };
        // if we've changed the select to something other than the Select X option (index 0)
        if (styleSelect.selectedIndex > 0) {
            var styleValue = styleSelect.options[styleSelect.selectedIndex].value;
            YAHOO.util.Connect.asyncRequest('GET', "select.json?styleId=" + styleValue, oStyleCB);
        }
    }
    YAHOO.util.Event.on('styleSelect', 'change', fn_styleSelect);
    // populate cStyleEvent to be use in function updateDropDown
    cStyleEvent.subscribe(fn_styleSelect);
})();