// Created By: Chris Campbell
// www.particletree.com
//
// Modified By: Bobby Jack
// bobbykjack@yahoo.co.uk
//
// The way that required fields are currently handled is slightly confusing,
// mainly because the standard type checking is divorced from the 'required'
// checking.
window.onload = attachFormHandlers;

//
// Run an initial check on validating fields and attach validating function to
// their change event.
//
function attachFormHandlers()
{
    formId = 'standard';

    // Get the form
    var form = document.getElementById(formId);

    // Ensure we're on a DOM-compatible browser
    if (document.getElementsByTagName)
    {
        var elementsToCheck = new Array('input', 'select');

        for (var j = 0; j < elementsToCheck.length; j++)
        {
            elementsToCheck[j] = form.getElementsByTagName(elementsToCheck[j]);
        }

        for (var j = 0; j < elementsToCheck.length; j++)
        {
            var elements = elementsToCheck[j];

            for (var iCounter = 0; iCounter < elements.length; iCounter++)
            {
                var element = elements[iCounter];

                // Validate required inputs that already have a value. This fixes a
                // bug in which pre-filled values aren't recognised as being valid.
                var rules = parseRules(element.className);

                // Attach the onchange to each input field
                if (rules.validate && rules.type != 'radio')
                {
                    element.onchange = function() { return attach(this); }
                }

                if (rules.type != 'radio' && rules.validate && element.value != '' && (!rules.def || element.value != element.defaultValue))
                {
                    var labelText = getLabelText(element.id);
                    var valid = validateByType(rules.type, element.value, labelText);
                    var feedback = document.getElementById(rules.feedback);
                    feedback.innerHTML = valid;
                    feedback.style.color = '#F00';
                }
            }
        }
    }

    // Attach validate() to the form
    form.onsubmit = function() { return validateSubmit(form); }
}

//
// Validate form, on submission
//
function validateSubmit(form)
{
    var numErrors = 0;
    var valid = '';

    // Ensure we're on a DOM-compatible browser
    if (document.getElementsByTagName)
    {
        var elementsToCheck = new Array('input', 'select');

        // Replace each element type with an array of elements of that type
        for (var j = 0; j < elementsToCheck.length; j++)
        {
            elementsToCheck[j] = form.getElementsByTagName(elementsToCheck[j]);
        }

        for (var j = 0; j < elementsToCheck.length; j++)
        {
            var elements = elementsToCheck[j];

            for (var iCounter = 0; iCounter < elements.length; iCounter++)
            {
                var element = elements[iCounter];
                valid = '';

                // Horrible hack to allow for checking of minimum donation
                if (element.name.substring(0, 4) == 'min:')
                {
                    var lookFor = element.name.substring(4);
                    var lookForOther = false;

                    for (var tmpCounter = 0; tmpCounter < elements.length; tmpCounter++)
                    {
                        var tmpElement = elements[tmpCounter];

                        if (tmpElement.name == lookFor)
                        {
                            if (tmpElement.checked)
                            {
                                if (tmpElement.value == "generalother")
                                {
                                    lookForOther = true;
                                }
                                else
                                {
                                    if (tmpElement.value < element.value)
                                    {
                                        valid = "Please make a minimum donation of &pound;40";
                                    }
    
                                    break;
                                }
                            }
                        }
                        
                        if (lookForOther && tmpElement.id == 'other-amount')
                        {
                            //alert("[2] entered value [" + tmpElement.value + "], minimum value [" + element.value + "]");
    
                            if (tmpElement.value < element.value)
                            {
                                valid = "Please make a minimum donation of &pound;40";
                            }
    
                            break;
                        }
                    }
                }

                var rules   = parseRules(element.className);

                if (rules.validate)
                {
                    // Special-case handling for groups of radio buttons
                    if (rules.type == 'radio')
                    {
                        var oneChecked = false;

                        // Get all radio buttons whose name matches this one
                        for (var tmpCounter = 0; tmpCounter < elements.length; tmpCounter++)
                        {
                            var tmpElement = elements[tmpCounter];

                            if (tmpElement.type == 'radio' && tmpElement.name == element.name && tmpElement.checked)
                            {
                                oneChecked = true;
                                break;
                            }
                        }

                        if (!oneChecked)
                        {
                            valid = "Please select an option";
                        }
                    }
                    // Ignore minimum 'metadata' rule
                    else if (rules.type == 'minimum')
                    {
                    }
                    // Ignore empty inputs if they're not required
                    else if (rules.required != 'required' && element.value == '')
                    {
                    }
                    // Ignore defaulted values, if they're not required
                    else if (rules.def && rules.required != 'required' && element.value == element.defaultValue)
                    {
                    }
                    else
                    {
                        valid = validateByType(rules.type, element.value, '');
                    }

                    feedback = document.getElementById(rules.feedback);

                    if (element.value != '')
                    {
                        feedback.innerHTML = valid;
                    }

                    feedback.style.color = '#F00';

                    if (valid != '' || (rules.required == 'required' && element.value == ''))
                    {
                        //alert('Error in ' + element.name + ':' + rules + ':' + rules.required + ':' + valid + ':' + element.value);
                        numErrors++;
                    }
                }
            }
        }
    }
    // If there are any errors give a message
    if (numErrors > 0)
    {
        alert ("Please make sure all fields are properly completed. Errors are marked in red!");
        return false;
    }
    return true;
}

//
// Convert a 'rule string' into its corresponding 'rule object'. Since we're
// going to be passing in the value of the 'class' attribute, let's do as much
// checking as possible to ensure this really looks like a valid 'rule string'.
//
function parseRules(str)
{
    var rules = str.split(' ');
    // Return an empty object if the string doesn't look like a valid 'rule
    // string'.
    if (rules.length < 4 || rules[0] != 'validate'
        || (rules[1] != 'required' && rules[1] != 'notrequired')
        || !validType(rules[2]))
    {
        return { };
    }

    var def = rules.length > 4 && rules[4] == 'default-text';

    return {
        validate: rules[0],
        required: rules[1],
        type:     rules[2],
        feedback: rules[3],
        def:  def
    };
}

// Global variable to control whether or not type-checking should take place
// following a 'required' check. This over-complicates things, so it would be
// nice to do away with.
var gContinue = true;

//
// Get the first element matching by name and attributes.
// NB IE seems to have problems with this!
//
function getElement(name, attrs)
{
    var els = document.getElementsByTagName(name);
    main:
    for (i = 0; i < els.length; i++)
    {
        var el = els.item(i);
        for (j in attrs)
        {
            if (el.getAttribute(j) != attrs[j])
            {
                continue main;
            }
        }
        return el;
    }
}

//
// Get the text of a label for a given input id.
//
// N.B. This is not currently used. It was intended to be used to display the
// input's label text in the error message, but this was changed in favour of
// using the type name instead (reason unknown). Moreover, getElement() appears
// to fail in IE.
//
function getLabelText(id)
{
    return '';
    var labelText = '';
    
    // Get the label associated with this input
    var label = getElement('label', {'for': id});
    // Assume label is of the simplest structure: <label>text</label>
    if (label.childNodes.length == 1)
    {
        var node = label.childNodes[0];
        // http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-1841493061
        // interface Node { const unsigned short TEXT_NODE = 3; }
        if (node.nodeType == 3)
        {
            labelText += node.nodeValue;
        }
    }
    return labelText;
}

//
// Validate given input object according to 'required' status and type.
//
function attach(objInput)
{
    var labelText = getLabelText(objInput.id);

    // Get value inside of input field
    sVal = objInput.value;
    gContinue = true; 
    var rules = parseRules(objInput.className);
    // validateRequired() checks if it is required and then sends back feedback
    var sFeedback = validateRequired(rules.required, sVal, rules.type);
    // If it is required and blank, gContinue is false and we don't validate
    // anymore. This is done because if it is blank it will also fail other
    // tests. We don't want to spam the user with INVALID EMAIL! if the field
    // is still blank.
    if (gContinue)
    {
        sFeedback = validateByType(rules.type, sVal, labelText);
    }
    // Once validation is complete, return the feedback
    var feedback = document.getElementById(rules.feedback);
    feedback.innerHTML = sFeedback;
    feedback.style.color = '#F00';
}

//
// Supported 'types' are all represented by Regular Expressions
//
var typePatterns = 
{
    age:       new Array('Age',       /.+/),
    date:      new Array('Date',      /.*/),
    email:     new Array('Email',     /^.+@.+\.[a-z]+$/i),
    name:      new Array('Name',      /^([a-zA-z\s|-]{1,32})$/),
    numeric:   new Array('Number',    /^(\d|-)?(\d|,)*\.?\d*$/),
    password:  new Array('Password',  /.*/),
    phone:     new Array('Phone',     /^([0-9\s]{8,20})$/),
    postcode:  new Array('PostCode',  /^([a-zA-z0-9\s]{1,12})$/),
    radio:     new Array('Option',    /.+/),
    dobday:    new Array('Day',       /[0-9]+/),
    dobmonth:  new Array('Month',     /[0-9]+/),
    dobyear:   new Array('Year',      /[0-9]+/)
};

//
// Return true if the given type is recognised.
//
function validType(type)
{
    return type == 'none' || type == 'minimum' || typePatterns[type];
}

//
// Validate a value according to a specific type.
//
function validateByType(type, value, desc)
{
    var pattern = typePatterns[type];
    return !pattern || pattern[1].test(value) ? '' : 'Invalid ' + pattern[0];
}

//
// Validate value for required fields, return appropriate 'feedback'. Note that
// this is a complicated way of doing things which we should be able to
// simplify.
//
function validateRequired(sRequired, sVal, sTypecheck)
{
    // Check if required if not, continue validation script
    if (sRequired == "required")
    {
        // If it is required and blank then it is an error and continues to be
        // required
        if (sVal == "")
        {
            gContinue = false;
            return  "*";
        }
        // If it's not blank and has no other validation requirements the field
        // passes
        else if (sTypecheck == "none")
        {
            return "";
        }
    }
    // Non-required inputs that don't have a value should pass
    else if (sVal == '')
    {
        gContinue = false;
        return '';
    }
    // Get here if the value's not required OR it's required + has a value + has
    // other validation requirements.
}