Form = function(params) {
    $.extend(this, params);
}

Form.prototype = {

    formID       : '',
    proxyURL     : 'http://www.socialtext.com/cgi-bin/provisioning-proxy.cgi',
    proxyVersion : 2,
    validators   : { },

    registerValidator: function(ids, callback) {
        var self = this;
        if (! $.isArray(ids) ) { ids = [ids]; } // pass in string or array.

        $.each(ids, function(i, id) {
            if (!self.validators[id]) { self.validators[id] = [ ] }
            self.validators[id].push(callback);
        });
    },

    toUrl: function(value) {
       return value.toLowerCase()
             .replace(/(^\s+|\s+$)/g, '')
             .replace(/\s+/g, '-')
             .replace(/[^-_a-z0-9]+/g, '')
             .substring(0, 30);
    },

    submitForm: function() {
        var self   = this;

        self.hideFormInvalid();
        self.hideErrors();

        var local = $(self.formID).find('.field');
        if (!self.localDataIsValid(local)) return false;

        var remote = $(self.formID).find('.field.remote');
        var fields = self.coerceFields(remote);

        self.validateRemoteResources(remote, function(results, status) {
            var isValid = true;

            $.each(fields, function(i, field) {
                if (!self.remoteValidForField(field, results)) isValid = false;
            });

            if (isValid) {
                var form = $(self.formID);

                form.find('.button a')
                   .unbind().click(function() { return false; });

                form.submit();
            }
            else {
                self.formInvalid();
            }
        });
    },

    remoteValidForField: function(field, results) {
       var self = this;
       var result = results[field.id];

       if (!result || field.value != result.checked) {
           self.fieldMessage(field, 'Error checking field', 'red');
           return false;
       }

       if (!result.available) {
           self.fieldMessage(field, 'Unavailable', 'red');
           return false;
       }

       self.fieldMessage(field, 'Available', 'green');
       return true;
    },

    validateRemoteResources: function(nodes, callback) {
        var self = this;
        data = self.nodesToData(nodes);
        data.version = self.proxyVersion;

        $.ajax({
            url        : self.proxyURL,
            method     : 'GET',
            contentType: 'application/json',
            data       : data,
            success    : callback
        });
    },

    nodesToData: function(nodes) {
        var self = this;
        var data = { };

        $(nodes).each(function() {
            field = self.coerceField(this);
            self.fieldMessage(field, 'Checking...', 'black');

            data[field.id] = field.value;
        });
        return data;
    },

    localDataIsValid: function(nodes) {
        var self  = this;
        var isValid = 1;

        nodes.each(function() {
            var field = self.coerceField(this);
            if (self.validateField(field)) return;

            isValid = 0;
            self.showError(field);
        });

        if (isValid) return true;

        self.formInvalid();
        return false;
    },

    inlineValidate: function(input) {
        var self = this;
        var field = self.coerceField($(input).parent());

        if (!self.validateLocalField(field)) return false;

        if (field.node.hasClass('remote')) {
            if (!self.validateRemoteField(field)) return false;
        }
    },

    validateLocalField: function(field) {
        var self = this;

        if (self.validateField(field)) {
            self.hideError(field);
            return true;
        }
        self.showError(field);
        return false;       
    },

    validateRemoteField: function(field) {
        var self = this;

        self.validateRemoteResources([field.node], function(results, status) {
            self.remoteValidForField(field, results);
        });
    },

    validateField: function(field) {
        var self = this;
        if (field.isRequired && !field.value) return false;

        // not required, not needed, next!
        if (!field.value) return true;

        return self.validateCustom(field);
    },

    validateCustom: function(field) {
        var self = this;
        if (!self.validators[field.id]) return true;

        var isValid = true;
        $.each(self.validators[field.id], function(i, callback) {
            if (!callback(field.value)) { isValid = false }
        });

        return isValid;
    },

    coerceFields: function(nodes) {
        var self = this;
        var result = [];

        $.each(nodes, function(i, node) {
            result.push(self.coerceField(node) );
        });

        return result;
    },

    coerceField: function(field) {
        var $node = $(field);
        var fieldID = $('label', $node).attr('for');
        return {
            node      : $node,
            id        : fieldID,
            isRequired: $node.hasClass('required'),
            value     : $('#' + fieldID).attr('value')
        };
    },

    hideErrors: function() {
        $('.field', self.formID).removeClass('error');
    },

    hideError: function(field) {
        field.node.removeClass('error');
    },

    showError: function(field) {
        field.node.addClass('error');
    },

    fieldMessage: function(field, msg, color) {
        field.node.find('.message')
            .text(msg).css('color', color).show();
    },

    formInvalid: function() {
        $(this.formID).find('.form-error').show();
        // it would be nice if we could fade this after a little while.
    },

    hideFormInvalid: function() {
        $(this.formID).find('.form-error').hide();
    }
}

