var defaultMessages = {
    "form-invalid"  : "Please correct invalid fields.",
    "form-required" : "Please supply missing fields.",
    "form-invalid-required" : "Please correct missing and invalid fields.",
    "form-submit" : "Submitting form...", 
    
    "invalid"  : "invalid",
    "required" : "required"
};

var messages = defaultMessages;
var formFields = { };
      
function getLocalizedMessage(id) {
    return messages[id];
}

function gotfocus(e) {
    var input = eventTarget(e);
    var label = findInputLabel(input);

    if (isRadioInput(input)) {
        radios = collectRadios(input.getAttribute("name"));
        for (var i = 0; i < radios.length; i++) {
            removeClass(radios[i], "invalid");
            removeClass(findInputLabel(radios[i]), "invalid");
        }
    } else {
        removeClass(input, "invalid");
        removeClass(label, "invalid");
    }                   

    addClass(label, "focused");
    if (isTextInput(input)) {
        addClass(input, "focused");
    }
}

function lostfocus(e) {
    var input = eventTarget(e);
    var label = findInputLabel(input);

    removeClass(label, "focused");
    if (isTextInput(input)) {
        removeClass(input, "focused");
    }
}

function setMessage(messageId, cls) {
    var messages = document.getElementById("messages");
    var text = document.createTextNode(getLocalizedMessage(messageId));
        
    while (m = messages.firstChild) {
        messages.removeChild(m);
    }
    messages.appendChild(text);
    messages.className = cls;
}

function addError(input, messageId) {
    var span = findInputMessage(input);
    var text = document.createTextNode(getLocalizedMessage(messageId));
    span.appendChild(text);
}

function clearErrors() {
    var span = document.getElementsByTagName("span");
    for (var i = 0; i < span.length; i++) {
        if (span[i].className == "message") {
            while (msg = span[i].firstChild) {
                span[i].removeChild(msg);
            }
        }
    }

    setMessage("", "");
}

function validate() {
    var invalid = false;
    var missing = false;

    clearErrors();

    var f = function(input) {
        // ignore buttons
        if (input.getAttribute("type") == "submit") {
            return null;
        }

        var label = findInputLabel(input);
        var value = inputValue(input);                            

        removeClass(input, "invalid");
        removeClass(label, "invalid");

        if (hasClass(input, "required")) {
            if (!value) {
                addClass(input, "invalid");
                addClass(label, "invalid");
              
                addError(input, "required");

                missing = true;
            }
        }

        var regex = formFields[input.getAttribute("name")];
        if (value && regex) {
            if (!regex.test(value)) {
                addClass(input, "invalid");
                addClass(label, "invalid")
                    addError(input, "invalid");
                invalid = true;
            }
        }
    }
                            
    mapelements(f, "input");
    mapelements(f, "select");
    mapelements(f, "textarea");

    if (missing) {
        if (invalid) {
            setMessage("form-invalid-required", "error");
        } else {
            setMessage("form-required", "error");
        }
    } else if (invalid) {
        setMessage("form-invalid", "error");
    } else {
        setMessage("form-submit", "update");
        f = function(e) {
            if (e.getAttribute("type") == "submit") {
                e.disabled = true;
            }
        }
        mapelements(f, "input");
    }

    return !invalid && !missing;
}

function findInputLabel(e) {
    e = (e ? e.parentNode.firstChild : null);
    while (e && e.nodeName != "LABEL") {
        e = e.nextSibling;
    }
    return e;
}

function findInputMessage(e) {
    e = (e ? e.parentNode.firstChild : null);
    while (e && e.nodeName != "SPAN" && e.className != "message") {
        e = e.nextSibling;
    }
    return e;
}

function collectRadios(name) {
    var radios = new Array();
        
    input = document.getElementsByTagName("input");
    for (var i = 0; i < input.length; i++) {
        if (isRadioInput(input[i]) && input[i].getAttribute("name") == name) {
            radios.push(input[i]);
        }
    }

    return radios;
}

function eventTarget(e) {
    var event  = (e != null ? e : window.event);
    var target = (event.target != null ? event.target : event.srcElement);
    return target;
}

function isTextInput(e) {
    return e.getAttribute("type") == "text"
        || e.getAttribute("type") == "password"
        || e.nodeName == "TEXTAREA";
}

function isRadioInput(e) {
    return e.getAttribute("type") == "radio";
}

function inputValue(input) {
    switch (input.getAttribute("type")) {
        case "checkbox":
            return input.checked;
        case "radio":
            return radioValue(input.getAttribute("name"));
        case "password":
        case "text":            
            input.value = input.value.replace(/^\s*(.*?)\s*$/, "$1");
            return (input.value.length == 0 ? false : input.value);
    }

    switch (input.nodeName) {
        case "TEXTAREA":
            input.value = input.value.replace(/^\s*(.*?)\s*$/, "$1");
        case "SELECT":
            return (input.value.length == 0 ? false : input.value);
    }
}

function radioValue(radioName) {
    var radios = collectRadios(radioName);
    for (var i = 0; i < radios.length; i++) {
        if (radios[i].checked) {
            return radios[i].value;
        }
    }
    return null;
}

function addClass(e, cls) {
    if (e && e.className) {
        return e.className = e.className + " " + cls;
    } else if (e) {
        e.className = cls;
    }
}

function removeClass(e, cls) {
    if (e && e.className) {
        e.className = e.className.replace(cls, "");
    }
}

function hasClass(e, cls) {
    if (e && e.className) {
        return e.className.indexOf(cls) >= 0;
    }
}

function mapelements(f, name) {
    return map(f, document.getElementsByTagName(name));
}

function map(f, list) {
    if (list) {
        var results = new Array(list.length);
        for (var i = 0; i < list.length; i++) {
            results[i] = f(list[i]);
        }
        return results;
    }
}

function reduce(f, list, initial) {
    if (list) {
        var result = initial;
        for (var i = 0; i < list.length; i++) {
            result = f(result, list[i]);
        }
        return result;
    }
}

function setInputHooks(e) {
    if (e.getAttribute("type") != "submit") {
        e.onblur = lostfocus;
        e.onfocus = gotfocus;

        if (isTextInput(e)) {
            addClass(e, "text");
        }
    }
    return e;
}

window.onload = function () {
    mapelements(setInputHooks, "input");
    mapelements(setInputHooks, "select");
    mapelements(setInputHooks, "textarea");

    mapelements(function(e) { e.onsubmit = validate }, "form");

    // focus first element
    var inputs = document.getElementsByTagName("input");
    for (var i = 0; i < inputs.length; i++) {
        if (inputs[i].getAttribute("type") != "hidden") {
            inputs[i].focus();
            break;
        }
    }
}
