// Creates a wrapper around an existing control to validate the data entered in
// the control.
//
// Parameters:
//     control:  The control to validate.
//     validate: The validation function, which checks the values that are
//               entered into the control.
function Validate(control, validate)
{
    if (arguments.length == 0)
        return;
        
    control.validate   = validate;
    control.hasChanged = Validate.prototype.hasChanged;
    control.showStatus = Validate.prototype.showStatus;

    control.addEventHandler("keyUp",  function() { control.hasChanged(false); });
    control.addEventHandler("change", function() { control.hasChanged(true);  });

    // Wrap the control's setValue function to call hasChanged after every call.
    var     setValue = control.setValue;
    control.setValue = function(value)
    {
        setValue.call(this, value);
        this.triggerEvent("change");
    }

    control.hasChanged(false);

    return control;

}

// Call this function whenever the value of the control changes to validate the
// input and reformat it if correct.
//
// Parameters:
//     reformat: True to reformat the value if it is correct, false to skip
//               updating and just validate it.
//
// Postconditions:
//     - this.isValid will be set to true or false depending on whether the
//       control's value is valid or not.
//     - this.showStatus is called.
Validate.prototype.hasChanged = function(reformat)
{
    var oldValue = this.getValue();
    var newValue = this.validate(oldValue);

    this.isValid = (newValue != null);
    
    if (reformat && this.isValid && newValue != oldValue)
        this.setValue(newValue);

    this.showStatus();
}

// Show the status of the control. The default implementation changes the color
// of the control to black if it's valid and red if it's invalid.
Validate.prototype.showStatus = function()
{
    if (this.getValue != SelectBox.prototype.getValue)
    {
        this.style.color = this.isValid || this.getValue() == ""
            ? "black"
            : "red";
    }
}


// Useful validation functions for common data types. Access them like
// validate.string, validate.time, etc. (lowercase 'v').

// Any value is valid.
Validate.prototype.anything = function(value)
{
    return value;
}

// Checks that value is the empty string.
Validate.prototype.isEmpty = function(value)
{
    return value != "" ? null : value;
}

// Checks that value isn't the empty string.
Validate.prototype.notEmpty = function(value)
{
    return value == "" ? null : value;
}

// Checks that any of the specified validation functions are satisfied.
//
// Return value:
//     The return value of the first function that validates, otherwise null.
Validate.prototype.any = function(/* validate1, validate2, ..., validateN */)
{
    var functions = arguments;

    return function(value)
    {
        for (var i = 0; i < functions.length; ++i)
        {
            if (functions[i](value) != null)
                return functions[i](value);
        }

        return null;
    };
}

// Checks that all of the specified validation functions are satisfied. The
// return value of each validation function is used as the input to the next.
//
// Return value:
//     null if any of the functions fail, otherwise the return value of
//     validateN.
Validate.prototype.all = function(/* validate1, validate2, ..., validateN */)
{
    var functions = arguments;

    return function(value)
    {
        for (var i = 0; i < functions.length; ++i)
        {
            value = functions[i](value);

            if (value == null)
                break;
        }

        return value;
    };
}

Validate.prototype.ifThen = function(condition, thenConstraint, elseConstraint)
{
    if (elseConstraint == null)
        elseConstraint = validate.anything;

    return function(value)
    {
        return condition(value)
            ? thenConstraint(value)
            : elseConstraint(value);
    };
}

// Checks that the given value is equal to the value of another control.
Validate.prototype.equalTo = function(control)
{
    return function(value)
    {
        return value == control.getValue() ? value : null;
    };
}

// Returns a function that checks for a string between minLength and maxLength
// characters long.
Validate.prototype.string = function(minLength, maxLength)
{
    return function(string)
    {
        if (minLength != null && string.length < minLength) return null;
        if (maxLength != null && string.length > maxLength) return null;

        return string;
    };
}


Validate.prototype.match = function(regExp)
{
    return function(value)
    {
        return regExp.test(value)
            ? value
            : null;
    };
}

// Returns a function that checks for a number with at least "digits" digits to
// the left of the decimal point and decimalPlaces digits to the right.
Validate.prototype.number = function(digits, decimalPlaces)
{
    return function(number)
    {
        return isValidNumber(number)
            ? formatNumber(number, digits, decimalPlaces, true)
            : null;
    };
}

// Like number(), but formats the number as currency.
Validate.prototype.currency = function(digits, decimalPlaces)
{
    return function(number)
    {
        return isValidNumber(number, true)
            ? "$" + formatNumber(number, digits, decimalPlaces, true)
            : null;
    };
}

// Checks for a valid time in either military or standard format. Formats the
// value in military time.
Validate.prototype.time = function(time)
{
    if (!time.match(/^(\d?\d)[^a-z\d]*(\d\d)?\s*([ap]\W*[m]?\W*|)$/i))
        return null;

    var hours   = RegExp.$1 * 1;
    var minutes = RegExp.$2 * 1;
    var amOrPm  = RegExp.$3.toLowerCase();

    if (amOrPm == "")
    {
        if (hours >= 24 || minutes >= 60)
            return null;
    }
    else
    {
        if (hours < 1 || hours > 12 || minutes >= 60)
            return null;

        if (hours            == 12)  hours  = 0;
        if (amOrPm.charAt(0) == "p") hours += 12;
    }

    return formatNumber((hours + 11) % 12 + 1, 0, 0) + ":"
         + formatNumber(minutes,               2, 0)
         + (hours >= 12 ? "pm" : "am");
}

// Checks for a valid date. The function accepts any string the Date constructor
// accepts, and so should be quite liberal in what it accepts. Formats the date
// as "Month Day, Year."
Validate.prototype.date = function(date)
{
    return isValidDate(date)
        ? formatDate(date)
        : null;
}

// Checks for a valid e-mail address.
Validate.prototype.emailAddress = function(emailAddress)
{
    return emailAddress.match(/^\w+([-.+]\w+)*@(\w([-.+]?\w+)*\.\w+|\[\d+(\.\d+){3}\])$/)
         ? emailAddress
         : null;
}

// Checks for a valid phone number.
Validate.prototype.phoneNumber = function(phoneNumber)
{
    if (!phoneNumber.match(/^\D*?(1\W*?)?([\da-z]{3})\D*?([\da-z]{3})\D*?([\da-z]{4})\D*?(\d+)?\D*$/i))
        return null;

    var number = "(" + RegExp.$2.toUpperCase() + ") " + RegExp.$3.toUpperCase() + "-" + RegExp.$4.toUpperCase();

    if (RegExp.$5 != "")
        number += " x" + RegExp.$5;            
                
    return number;
}

// Checks for a valid social security number.
Validate.prototype.socialSecurityNumber = function(socialSecurityNumber)
{
    if (!socialSecurityNumber.match(/^(\d\d\d)\D*(\d\d)\D*(\d\d\d\d)$/))
        return null;

    return RegExp.$1 + "-" + RegExp.$2 + "-" + RegExp.$3;
}

var validate = new Validate();
