function msFormEnhancements(args){
    this.properties          = args;
    this.form_object         = null;
    this.form_id             = args.form_id;
    this.max_part_no         = args['max_part_no'];
    this.error_message       = '';
    this.error_messages      = {};
    this.refer               = {};
    this.form_elements       = {};
    this.form_fields         = {};
    this.form_parts          = [];
}
function msFormPart(form, number) {
    this.form    = form;
    this.number  = number;
    this.status  = '';
    this.stepTip = null;
    this.valid   = 0;
    var part     = this;

    this.show = function () {
        // console.log('show: ' + number + ' - ' + part.status);
        if (part.status !== 'new'){
            part.form.showPart(number);
        }
    }
}
msFormEnhancements.prototype.addPart = function ( part, number ) {
    this.form_parts[number] = part;
};
msFormEnhancements.prototype.getPart = function ( number ) {
    return this.form_parts[number];
};

msFormEnhancements.prototype.init = function () {
    this.trackEvent('Form opened.');
    this.form_object = this.getElement(this.form_id);

    if (this.max_part_no > 1){
        var steps_elem = new Element('ul');
            steps_elem.id = 'steps-indicator';
        for (var i = 1; i <= this.max_part_no; i++){
            var part     = new msFormPart( this, i );
                part.status  = 'new';
            var step_id  = 'step-indicator-' + i;
            var step_obj = new Element('li', {'id': step_id});
                step_obj.addEvent('click', part.show);
            var text_obj = new Element('span');
                text_obj.appendChild(document.createTextNode(i));

            if (i > 1) {
                var tip_obj  = new Element('div', {'class': 'step-tip new'});
                step_obj.appendChild(tip_obj);
                part.stepTip = tip_obj;
            }

            step_obj.appendChild(text_obj);
            steps_elem.appendChild(step_obj);
            this.form_elements[step_id] = step_obj;
            this.addPart(part, i);
        }
        var clear_div = new Element('div');
            clear_div.className = 'clear';
        this.form_object.appendChild(steps_elem);
        this.form_object.appendChild(clear_div);
    }
    else {
        var part     = new msFormPart( this, i );
            part.status  = 'new';
        this.addPart(part, 1);
    }

    document.addEvent('domready', function() {
        msForm.start(msForm.properties.show_part);
    });
    this.form_object.addEvent('submit', function(event) {
        if (!msForm.submitCheck(msForm.max_part_no)){
            var event = new Event(event);
            event.stop();
            return false;
        }
        return true;
    });

};

msFormEnhancements.prototype.error_loc = function (){
    return this.properties.error_message_location;
};
msFormEnhancements.prototype.start = function (part) {
    this.showJSrequiredContent();
    this.showPart(part);
};

msFormEnhancements.prototype.getElement = function (field){
    if (this.form_elements[field] == null){
        this.form_elements[field] = $(field);
//        this.form_elements[field] = document.getElementById(field);
    }
    return this.form_elements[field];
};

msFormEnhancements.prototype.handleOptOut = function (field){
    var optOut = this.getElement(field + '-opt-out');
    var optIn  = this.getElement(field + '-opt-in');
    optOut.checked = !optIn.checked;
    return 1;
};

msFormEnhancements.prototype.limitSize = function (txtarea, maxSize){
    if(txtarea.value.length > maxSize){
        txtarea.value = txtarea.value.substring(0, maxSize-1);
    }
};

msFormEnhancements.prototype.nextPart = function (this_part, next_part) {
    if (this.checkPart(this_part)) {
        this.trackEvent('Step ' + this_part + ' passed validation.');
        this.showPart(next_part);
    }
    else {
        this.trackEvent('Step ' + this_part + ' failed validation.');
        this.showPart(this_part);
    }
};

msFormEnhancements.prototype.checkPart = function (part) {
    var valid = true;
    var partDiv = this.getElement('part' + part);

    this.error_message = '';

    var inputElements = partDiv.getElementsByTagName('input');
    for (var i = 0; i < inputElements.length; ++i) {
        if (!this.checkElement(part, partDiv, inputElements[i])) {
            valid = false;
        }
    }

    var selectElements = partDiv.getElementsByTagName('select');
    for (var i = 0; i < selectElements.length; ++i) {
        if (!this.checkElement(part, partDiv, selectElements[i])) {
            valid = false;
        }
    }

    var error_div = this.getElement('part' + part + '-message');
    if (this.error_message != null && this.error_message.length > 0) {
        this.error_message += "<br/>";
        error_div.innerHTML = this.error_message;
    }
    else {
        error_div.innerHTML = '';
    }

    return this.getPart(part).valid = valid;
};

msFormEnhancements.prototype.hasClass = function (element, name) {
    return element.className.match(new RegExp('(\\s|^)' + name + '(\\s|$)'));
}
msFormEnhancements.prototype.addClass = function (element, name) {
    if (!this.hasClass(element,name)) {
        var spacing = element.className.length > 0 ? ' ' : '';
        element.className += spacing + name;
    }
}
msFormEnhancements.prototype.delClass = function (element, name) {
    if (this.hasClass(element, name)) {
        var regex = new RegExp('(\\s|^)' + name + '(\\s|$)');
        element.className = element.className.replace(regex,' ');
        regex = new RegExp('(^\\s)|(\\s$)');
        element.className = element.className.replace(regex,'');
    }
}


msFormEnhancements.prototype.checkElement = function (part, partDiv, element) {
    var valid = true;
    var name = element.name;
    var error_name = name;
    var show_inline = this.error_loc() == 'inline' ? 1 : 0;
    var element_label;
    var min_val;
    var max_val;
    var match_field_label;
    var error_messages = '';

    if (name in this.form_fields) {
        this.delClass(element, 'missing');
        this.delClass(element, 'invalid_email');
        this.delClass(element, 'no_match');
        if (this.form_fields[name].required && element.value.length == 0) {
            if (this.form_fields[name].dependent_on != null && this.form_fields[name].dependent_value != null) {
                var selectElements = partDiv.getElementsByTagName('select');
                for (var i = 0; i < selectElements.length; ++i) {
                    if (this.form_fields[name].dependent_on == selectElements[i].name) {
                        if (this.form_fields[name].dependent_value == selectElements[i].value) {
                            this.addClass(element, 'missing');
                            error_messages += this.error_messages['required'] + "<br/>";
                            this.trackEvent('Step ' + part + ' - Field ' + name + ' failed required validation');
                            valid = false;
                        }
                    }
                }
            }
            else {
                this.addClass(element, 'missing');
                error_messages += this.error_messages['required'] + "<br/>";
                this.trackEvent('Step ' + part + ' - Field ' + name + ' failed required validation');
                valid = false;
            }
        }

        if (this.form_fields[name].type == 'email' && !this.checkEmailElement(element)) {
            this.addClass(element, 'invalid_email');
            error_messages += this.error_messages['email'] + "<br/>";
            this.trackEvent('Step ' + part + ' - Field ' + name + ' failed email validation');
            valid = false;
        }

        if (this.form_fields[name].type == 'credit_card' && element.value.length > 0) {
            var card_type = element.form[name + '_TYPE'];
             if (!this.checkCreditCard( element.value, card_type.value )){
                this.addClass(element, 'invalid_credit_card');
                error_messages += this.error_messages['invalid'] + "<br/>";
                this.trackEvent('Step ' + part + ' - Field ' + name + ' failed credit_card validation');
                valid = false;
            }
        }

        var bla = this.form_fields[name].regex;
        if (this.form_fields[name].regex != null && element.value.length > 0) {
            var regex = new RegExp(this.form_fields[name].regex);
            if (!regex.test(element.value)) {
                this.addClass(element, 'missing');
                error_messages += this.error_messages['invalid'] + "<br/>";
                valid = false;
            }
        }

        if (this.form_fields[name].type == 'number' && element.value.length > 0) {
            var pattern = /^\d*$/;
            if (!pattern.test(element.value)) {
                this.addClass(element, 'missing');
                error_messages += this.error_messages['number'] + "<br/>";
                valid = false;
            }
            else {
                if (this.form_fields[name].min_val != null && element.value < this.form_fields[name].min_val) {
                    this.addClass(element, 'missing');
                    error_messages += this.error_messages['min_val'] + "<br/>";
                    min_val = this.form_fields[name].min_val;
                    valid = false;
                }
                if (this.form_fields[name].max_val != null && element.value > this.form_fields[name].max_val) {
                    this.addClass(element, 'missing');
                    error_messages += this.error_messages['max_val'] + "<br/>";
                    max_val = this.form_fields[name].max_val;
                    valid = false;
                }
            }
        }

        // last check - changes name to second element for error presentation
        if (this.form_fields[name].match) {
            var match_name = this.form_fields[name].match;
            var second_element = element.form[match_name];
            match_field_label = this.getElementLabel(match_name);

            // changes name to second element for error presentation
            if (element.value != second_element.value) {
                this.addClass(element, 'no_match');
                error_messages += this.error_messages['match'] + "<br/>";
                this.trackEvent('Step ' + part + ' - Field ' + name + ' failed match validation');
                valid = false;
                error_name = match_name;
            }
        }

        if ( valid == false) {
            var field_label = this.getElementLabel(name);
            error_messages = error_messages.replace(/\#field1\#/g, field_label);
            error_messages = error_messages.replace(/\#field2\#/g, match_field_label);
            error_messages = error_messages.replace(/\#min_val\#/g, min_val);
            error_messages = error_messages.replace(/\#max_val\#/g, max_val);

            if (show_inline){
                var error_msg = this.getElement(error_name + '-error');
                // handle botblock fields (match)
                // append vs replace error in that case
                if ( this.form_fields[name].match && error_msg && error_msg.innerHTML.length > 0 ){
                    if (error_msg){
                        error_msg.style.display = '';
                        error_msg.innerHTML += error_messages;
                    }
                }
                else {
                    if (error_msg){
                        error_msg.style.display = '';
                        error_msg.innerHTML = error_messages;
                    }
                }

            }
            else {
                this.error_message += error_messages + "<br/>";
            }

        }
        else if (show_inline){
            // handle botblock fields
            if (!this.form_fields[name].match){
                var error_msg = this.getElement(name + '-error');
                    error_msg.style.display = 'none';
                    error_msg.innerHTML = '';
            }
        }
    }

    return valid;
};

msFormEnhancements.prototype.checkEmailElement = function (element) {
    if (element.value.length == 0) return true;
    var at  = element.value.indexOf('@');
    if (at == -1) return false;
    var dot = element.value.indexOf('.', at);
    if (dot == -1) return false;
    return true;
};

// toggle parts
msFormEnhancements.prototype.showPart = function ( step ){
    this.trackEvent('Step ' + step + ' displayed.');

    if (this.max_part_no < 2){
        return;
    }

    var display     = 'none';
    var prev_status = 'spacer';
    for (var i = 1; i <= this.max_part_no; i++){
        var part = this.getPart(i);
        var status    = 'new';
	var className  = null;
        if ( step > i ){
            status = 'done';
        }

        if (step == i){
            status    = 'current';
            className = 'current';
        }

        if (!part.valid) {
            status = 'error';
        }

        part.status  = status;
        if (part.stepTip){
            part.stepTip.className = 'step-tip ' + prev_status;
        }
        prev_status = status;

        var stepDiv = this.getElement('step-indicator-' + i);
        if (stepDiv) {
            stepDiv.className = ['step-indicator', status, className].join(' ');
        }

        display = step == i ? '': 'none';
        var partDiv = this.getElement('part' + i);
        if (partDiv) {
            partDiv.style.display = display;
        }

        var partDivText = this.getElement('part' + i + '-text');
        if (partDivText) {
            partDivText.style.display = display;
        }
    }
};

// get document path from full URL
msFormEnhancements.prototype.getDocumentPath = function (){
    var url = document.location.href;
    var path = url.split('/');
        path.shift();path.shift();path.shift(); // remove protocol//domain/
    var page = path.join('/').split('.');
        page.pop();                             // strip off extension
    return page.join('.');
};

// submit form after verification
msFormEnhancements.prototype.submitCheck = function (part){
    if(this.checkPart(part)){
        this.trackEvent('Step ' + part + ' passed validation.');
        this.trackEvent('Form submitted.');
        return true;
    }
    this.trackEvent('Step ' + part + ' failed validation.');
    return false;
};

msFormEnhancements.prototype.showJSrequiredContent = function () {
    // show next button if JS is available
    for (var i = 2; i <= this.max_part_no; i++){
        var nextInput = this.getElement('next' + i);
        var backInput = this.getElement('back' + i);
        nextInput.style.display = "";
        backInput.style.display = "";
    }

    // switch opt out with opt in
    var rows = this.form_object.getElementsByTagName ('tr');
    for (i in rows) {
        var row = rows[i];
        if (row.className){
            if (this.hasClass(row, 'opt_in_tr')){
                row.style.display  = "";
            }
            else if (this.hasClass(row, 'opt_out_tr')){
                row.style.display = "none";
            }
        }
    }
};

msFormEnhancements.prototype.setCountryValue = function (CountryName, CountryID) {
    var CountryIdObj = this.getElement(CountryName);
    var Country = CountryIdObj.value;
    if (Country == 'US' || Country == 1) {
        CountryIdObj.value = '1';
    } else if (Country == 'CA' || Country == 2) {
        CountryIdObj.value = '2';
    } else {
        CountryIdObj.value = '1';
    }
};

msFormEnhancements.prototype.refreshStates = function (StatesReferName, CountryId, StateId, ZipId, StateValue) {
    var StatesRefer = this.refer[StatesReferName];
    var Country     = this.getElement(CountryId).value;
    var StateObj    = this.getElement(StateId);
    var ZipObj      = this.getElement(ZipId);
    if ( Country == 'US' || Country == 1 ) {
        ZipObj.maxLength = 5;
        if(ZipObj.value.length > 5){
            ZipObj.value = '';
        }
    } else {
        ZipObj.maxLength = 7;
    }

    StateObj.innerHTML = "";
    if ( Country ) {
        for (var i=0; i < StatesRefer.length; i++) {
            if ( Country == StatesRefer[i][2] ) {
                var opt = document.createElement('option');
                opt.innerHTML = StatesRefer[i][1];
                opt.value = StatesRefer[i][0];
                if ( StateValue == opt.value )
                    opt.selected = true;
                StateObj.appendChild(opt);
            }
        }
    } else {
        emptyOption.innerHTML = "";
        emptyOption.value = "";
    }
    return;
};

msFormEnhancements.prototype.showHideOptions = function (selectObj) {
    for (val in this.show_hide_options) {
        if (selectObj.value == val) {
            this.getElement(this.show_hide_options[val]).style.display = '';
        }
        else {
            this.getElement(this.show_hide_options[val]).style.display = 'none';
        }
    }

    return true;
};

msFormEnhancements.prototype.getElementLabel = function (name) {
    var field_label = this.getElement(name + '_label');
    if (field_label) {
        label = field_label.innerHTML;
    }
    else {
        label = name;
    }
    return label;
};

msFormEnhancements.prototype.addReferValue = function (args) {
    var name  = args['name'];
    var value = args['value'];

    if (this.refer[name] == null){
        this.refer[name] = [];
    }

    this.refer[name].push(value);

    return value;
};

msFormEnhancements.prototype.addField = function (args) {
    var name  = args['name'];
    var value = args['definition'];

    this.form_fields[name] = value;

    return value;
};

msFormEnhancements.prototype.addErrorMessage = function (type, message) {
    this.error_messages[type] = message;

    return message;
};

msFormEnhancements.prototype.trackEvent = function (args) {
//    var event = args['event'];
//    console.info('Recording: ' + args);
};

msFormEnhancements.prototype.countryChangeHook = function ( id ) {
    return true;
};

/*

This routine checks the credit card number. The following checks are made:

1. A number has been provided
2. The number is a right length for the card
3. The number has an appropriate prefix for the card
4. The number has a valid modulus 10 number check digit if required

*/
msFormEnhancements.prototype.checkCreditCard = function ( cardnumber, cardType ) {
    // Array to hold the permitted card characteristics
    var cards = new Array();

    // Define the cards we support. You may add addtional card types as follows.

    //  Array key:    Card Type value from r_credit_card_type_ri
    //  Name:         Name of the card type
    //  Length:       List of possible valid lengths of the card number for the card
    //  prefixes:     List of possible prefixes for the card
    //  checkdigit:   Boolean to say whether there is a check digit

    cards [1] = {name: "Visa",
                 length: "13,16",
                 prefixes: "4",
                 checkdigit: true};
    cards [2] = {name: "MasterCard",
                 length: "16",
                 prefixes: "51,52,53,54,55",
                 checkdigit: true};
    cards [3] = {name: "American Express",
                 length: "15",
                 prefixes: "34,37",
                 checkdigit: true};
    cards [4] = {name: "Discover",
                 length: "16",
                 prefixes: "6011,622,64,65",
                 checkdigit: true};

    // Ensure the card type is valid
    if( cards[cardType] === undefined ) {
        return false;
    }

    // Ensure that the user has provided a credit card number
    if ( cardnumber.length == 0 )  {
        return false;
    }

    // Now remove any spaces from the credit card number
    cardnumber = cardnumber.replace (/\s/g, "");

    // Check that the number is numeric
    var cardNo = cardnumber
    var cardexp = /^[0-9]{13,19}$/;
    if ( !cardexp.exec(cardNo) )  {
        return false;
    }

    // Now check the modulus 10 check digit - if required
    if ( cards[cardType].checkdigit ) {
        var checksum = 0;   // running checksum total
        var mychar = "";    // next char to process
        var j = 1;          // takes value of 1 or 2

        // Process each digit one by one starting at the right
        var calc;
        for ( i = cardNo.length - 1; i >= 0; i-- ) {
            // Extract the next digit and multiply by 1 or 2 on alternative digits.
            calc = Number(cardNo.charAt(i)) * j;

            // If the result is in two digits add 1 to the checksum total
            if ( calc > 9 ) {
                checksum = checksum + 1;
                calc = calc - 10;
            }

            // Add the units element to the checksum total
            checksum = checksum + calc;

            // Switch the value of j
            if ( j == 1 ) {
                j = 2;
            } else {
                j = 1;
            }
        }

        // All done - if checksum is divisible by 10, it is a valid modulus 10.
        // If not, report an error.
        if ( checksum % 10 != 0 )  {
            return false;
        }
    }

    // The following are the card-specific checks we undertake.
    var LengthValid = false;
    var PrefixValid = false;
    var undefined;

    // We use these for holding the valid lengths and prefixes of a card type
    var prefix = new Array ();
    var lengths = new Array ();

    // Load an array with the valid prefixes for this card
    prefix = cards[cardType].prefixes.split(",");

    // Now see if any of them match what we have in the card number
    for ( i=0; i<prefix.length; i++ ) {
        var exp = new RegExp ("^" + prefix[i]);
        if ( exp.test (cardNo) ) {
            PrefixValid = true;
        }
    }

    // If it isn't a valid prefix there's no point at looking at the length
    if ( !PrefixValid ) {
        return false;
    }

    // See if the length is valid for this card
    lengths = cards[cardType].length.split(",");
    for ( j=0; j<lengths.length; j++ ) {
        if ( cardNo.length == lengths[j] ) {
            LengthValid = true;
        }
    }

    // See if all is OK by seeing if the length was valid. We only check the length if all else was good.
    if ( !LengthValid ) {
        return false;
    }

    // The credit card is in the required format.
    return true;
}

