/**
* This is a set of validation functions. 
*
* Validation is done mostly autmatically by calling validateForm(). This in turn searches for the components
* that are subject to validation and validates them.
*
*/

/**
* checks if given node is part of a given form
*
* @param class el element on a form
* @param class f form that should contain el
*/
function checkParent(el, f)
{
    while (el != null && el != f)
    {
        el = el.parentNode;    
    }
    
    return (el != null);
}

/**
* Runs validation on a given form. Finds only components that have "validate" attribute
* equal to "yes"
*
* @param class f form to be validated
*/
function validateForm(f)
{
    
    var result = true;
    
    var inputs = document.getElementsByTagName("input");
    if (inputs)
    {   
        for (var i = 0; i < inputs.length; i++)
        {
          
           if (checkParent(inputs[i], f)
                && inputs[i].attributes["validate"]
                && "yes" == inputs[i].attributes["validate"].value
            )
            {
                
                if (!validateControl(inputs[i]))
                {
                    return false;
                }
            }
        }    
    }   
    
    var tas = document.getElementsByTagName("textarea");
    if (tas)
    {   
        for (var i = 0; i < tas.length; i++)
        {
          
           if (checkParent(tas[i], f)
                && tas[i].attributes["validate"]
                && "yes" == tas[i].attributes["validate"].value
            )
            {
                
                if (!validateControl(tas[i]))
                {
                    return false;
                }
            }
        }    
    }   
    
    return true;
}

/**
* Validates a specific control
*
* Every control that should be validated must have an 'validate' attribute set to 'yes'. This
* automatically puts it into a queue of controls that are validated. Some other attributes are:
* - 'datatype' which is compulsory for text input fields and can have any of the following values
*       - 'text' this input contains ordinary text
*       - 'numeric' this input is checked for a valid numeric value
*       - 'alpha' this input is validated for containing alphabetic characters
*       - 'alphanumeric' input is validated for alphabetic or numeric characters
*       - 'date' input is validated for a date entry
* - 'required' can be set to 'yes' which throws an error if input is empty
*
* @param class ctrl control to be validated
* @return bool true if data is valid or false if it is not
*/
function validateControl(ctrl)
{
    var result = true;
    
    checkAttribute(ctrl, "datatype", null, true);
    
    var description = getDescription(ctrl);
    
    
    if (validateIsEmpty(ctrl.value))
    {
        if ( ctrl.attributes["required"]
            && "yes" == ctrl.attributes["required"].value
        )
        {        
            alert(description + KValidateStrings["required"]);
            return false;
        }
        else
        {
            return true;    
        }
        
    }
    
    switch (ctrl.tagName.toLowerCase())
    {       
        case "input":
        case "textarea":
            switch (ctrl.attributes["datatype"].value.toLowerCase())
            {                
                case "text":
                    //return validateIsAlphaNumeric(ctrl.value, description);
                    break;
                case "numeric":
                    result = validateIsNumeric(ctrl.value, description);
                    break; 
                case "alpha":
                    result = validateIsAlpha(ctrl.value, description);
                    break;  
                case "alphanumeric":
                    result = validateIsAlphaNumeric(ctrl.value, description);
                    break; 
                case "email":
                    result = validateIsEmail(ctrl.value, description);
                    break;  
                case "date":
                    checkAttribute(ctrl, "format", null, true);                    
                    result = validateIsDate(ctrl.value, ctrl.attributes["format"].value, description);
                    break;
                case "currency":
                    checkAttribute(ctrl, "format", null, true);                    
                    result = validateIsNumeric(ctrl.numericValue, description)
                    break;        
            }   
            break;     
        case "select":
            break;          
    }    
    
    if (!result)
    {
        ctrl.focus();    
    }
    return result;
}

/**
* Gets description of the control or at least its name
*
* @param class ctrl ctrl to be checked
* @return string description of the control
*/
function getDescription(ctrl)
{
    if (!ctrl)
    {
        return null;    
    }
    
    if (!ctrl.attributes["description"])
    {
        return ctrl.name;    
    }
    else
    {
        return ctrl.attributes["description"].value;    
    }    
}



/**
* Check and validate for emptiness
* 
* @param string value value to be checked
* @return bool true if value is empty
*/
function validateIsEmpty(value)
{
    return (0 == value.length) 
}

/**
* Check and validate for numeric value
* 
* @param string value value to be checked
* @param string fieldName name of the field being checked
*/

function validateIsNumeric(value, fieldName)
{
    var charpos = value.match("^[0-9]*(,[0-9]+|.[0-9]+)?$"); 
    if(value.length > 0 && !charpos) 
    { 
        alert(fieldName + KValidateStrings["numeric"]);
        return false;    
    }  
    
    return true;
}

/**
* Check and validate for numeric value or alphabetic character
* 
* @param string value value to be checked
* @param string fieldName name of the field being checked
*/
function validateIsAlphaNumeric(value, fieldName)
{
    var charpos = value.search("[^A-Za-z0-9]"); 
    if(value.length > 0 &&  charpos >= 0) 
    { 
        alert(fieldName + KValidateStrings["alphanumeric"]);
        return false;    
    }  
    
    return true;
}

/**
* Check and validate for alphabetic character
* 
* @param string value value to be checked
* @param string fieldName name of the field being checked
*/
function validateIsAlpha(value, fieldName)
{
    var charpos = value.search("[^A-Za-z]"); 
    if(value.length > 0 &&  charpos >= 0) 
    { 
        alert(fieldName + KValidateStrings["alpha"]);
        return false;    
    }
    
    return true;
}

/**
* Check for a valid email address
* 
* @param string email value to be checked
* @param string fieldName name of the field being checked
*/
function validateIsEmail(email, fieldName)
{
    var result = false;
    var regexp_user=/^\"?[\w-_\.]*\"?$/;
    var regexp_domain=/^[\w-\.]+\.[A-Za-z]{2,4}$/;
    var regexp_ip =/^\[\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\]$/;
    
    var parts = email.match("^(.+)@(.+)$");
    
    
    if(parts
        && parts[1]
        && parts[2] 
        && parts[1].match(regexp_user) 
        && (parts[2].match(regexp_domain) || parts[2].match(regexp_ip))
    )
    {            
        return true; 
    }
    else
    {
        alert(fieldName + KValidateStrings["email"]);
        return false;   
    }
       
}

/**
* Check for a valid date. Format is an additional constraint to which val must conform
* 
* @param string val value to be checked
* @param string format format of the date. Format consists of letters d (day), m (month) and y (year)
*       in any order and separated by dot (.), slash (/) or a hyphen (-) signs. Examples of valid formats
*       are d.m.yyyy or yyyy-mm-dd. Months and day can have one or two letters. If there are two, it means that 
*       values smaller than 10 must have a leading zero (e.g. 04, 03). Year designator can only be four digit 
*       which means that date must always be given by four numbers.
* @param string fieldName name of the field being checked
*/
function validateIsDate(val, format, fieldName)
{
    //determine given format
    var arr = /^([d|m|y]+)(\.|-|\/)([d|m|y]+)(\2)([d|m|y]+)$/i.exec(format);
    
    if (arr)
    {
        //separator used in format
        var separator = arr[2];
        
        var pattern = "";
        var dayIndex = -1;
        var monthIndex = -1;
        var yearIndex = -1;
        
        //arr has 7 elements. First is the whole match, odd are separators, even are format specifiers
        for (var i = 1; i < 6; i++)
        {
            pattern += "(";
            
            switch (arr[i])
            {
                case "d":
                    pattern += "[0-9]{1,2}";
                    dayIndex = i;
                    break;
                case "m":
                    pattern += "[0-9]{1,2}";
                    monthIndex = i;
                    break;
                case "dd":
                    pattern += "[0-9]{2}";
                    dayIndex = i;
                    break;
                case "mm":
                    pattern += "[0-9]{2}";
                    monthIndex = i;
                    break;
                case "yyyy":
                    pattern += "[0-9]{4}";
                    yearIndex = i;
                    break;    
                case ".":
                    pattern += "\\.";
                    break;                
                case "-":
                    pattern += "-";
                    break;       
                case "/":
                    pattern += "\\/";
                    break;  
                default:
                    alert("Error in given date format: '" + format + "'");
                    return false;
                    break;
            }
            
            pattern += ")";
        }
        
        var regexp = new RegExp(pattern, "i");        
        var valarr = regexp.exec(val);
        //alert(valarr[yearIndex] + ":" + valarr[monthIndex] + ":" + valarr[dayIndex]);
                
        if (valarr 
            && validateDateNumbers(valarr[yearIndex], valarr[monthIndex], valarr[dayIndex])
        )
        {                      
            return true;
        }   
        else
        {
            alert(fieldName + KValidateStrings["date"]);    
        }             
    }   
    else
    {
        alert("Invalid date format: '" + format + "'");
    }   
}

/**
* This function ensures that date is indeed valid. It takes into account month days, leap years, 
* full moons and werewolves.
*
* @param int year year
* @param int month month
* @param int day day
* @return bool true if date is valid
*/
function validateDateNumbers(year, month, day)
{
    var months = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    
    if (month < 1 || month > 12)
    {
        return false;    
    }
    
    if (year < 1)
    {
        return false;    
    }   
    
    if (day < 1 || day > months[month - 1])
    {
        if ((0 == year % 4 || 0 == year % 100 || 0 == year % 400)
            && 2 == month
            && 29 == day
        )
        {
            return true;
        }
        
        return false;    
    }
    
    return true;    
}

/**
* Creates a currency regular expression with a given format. 
*
* Allowed format is composed
* of a prefix, number format and a suffix. While prefix and suffix can be any strings, a 
* number format is composed of a hash followed by a dot (.) or a comma (,) denotting a
* decimal separator and an optional number of hashes representing accuracy (decimal places).
*
* @param string format format for which a reg. exp is sought 
*/
/*
function createCurrencyRegExp(format)
{
    return new RegExp("^([0-9]{1,3})([0-9]{3}|\\.[0-9]{1,2})?"
        + "([0-9]{3}|\\.[0-9]{1,2})?"
        + "([0-9]{3}|\\.[0-9]{1,2})?"
        + "([0-9]{3}|\\.[0-9]{1,2})?"
        + "([0-9]{3}|\\.[0-9]{1,2})?"
        + "([0-9]{3}|\\.[0-9]{1,2})?"
        + "([0-9]{3}|\\.[0-9]{1,2})?"
        + "([0-9]{3}|\\.[0-9]{1,2})?"
        + "$"
    , "");
    
    //return new RegExp("^([0-9]{1,3})(\\.[0-9]{3})*,([0-9]){1,4}[ ][S][I][T]$", "i");
     
    var arr  = format.match(/(.*)#(\.|,)(#*)(.*)/i);
    
    //we know what arr must contain
    if (arr && 5 == arr.length )
    {                
        //first element is not important, second is a prefix       
        var str = "^" + encloseChars("[", arr[1], "]") + "([0-9]{1,3})";

        //third is a decimal separator 
        if ("," == arr[2])
        {
            decimalSep = arr[2];
            numberSep = "\\.";    
        }
        else
        {
            numberSep = arr[2];
            decimalSep = "\\.";    
        }
        
        str += "(" + numberSep + "([0-9]{3}))*" + decimalSep;
        
        //fourth is number of decimals
        str += "[0-9]{1," + arr[3].length + "}"
        
        //fifth is the suffix
        str += encloseChars("[", arr[4], "]") + "$";               
       
        alert (str);
        return new RegExp(str, "g");
    }
    else
    {
        return null;    
    }    
}
*/

/**
* Transforms numeric val to a currency representation using format
*
* @param float val number used as currency
* @param string format format that specifies how a number should look. Allowed format is composed
*       of a prefix, number format and a suffix. While prefix and suffix can be any strings, a 
*       number format is composed of a hash followed by a dot (.) or a comma (,) denotting a
*       decimal separator and an optional number of hashes representing accuracy (decimal places).
* @return string formatted string or null
*/
function displayCurrency(val, format)
{
    var arr  = format.match(/(.*)#(\.|,)(#*)(.*)/i);
    
    //:TODO: a bad way to split up a number
    var regexp =  new RegExp("^([0-9]{1,3})([0-9]{3}|\\.[0-9]+)?"
        + "([0-9]{3}|\\.[0-9]+)?"
        + "([0-9]{3}|\\.[0-9]+)?"
        + "([0-9]{3}|\\.[0-9]+)?"
        + "([0-9]{3}|\\.[0-9]+)?"
        + "([0-9]{3}|\\.[0-9]+)?"
        + "([0-9]{3}|\\.[0-9]+)?"
        + "([0-9]{3}|\\.[0-9]+)?"
        + "([0-9]{3}|\\.[0-9]+)?"
        + "$"
    , "");
    
    var parts = val.match(regexp);
    
    //we know what arr must contain
    if (arr 
        && 5 == arr.length 
        && parts
    )
    {                
        //first element is not important, second is a prefix       
        var result = arr[1] + parts[1];
        
        //third is a decimal separator 
        if ("," == arr[2])
        {               
            numberSep = ".";
            decimalSep = arr[2];  
        }
        else
        {
            decimalSep = arr[2];
            numberSep = ",";   
        }
        
        //first element is not important
        for (var i = 2 ; i < parts.length - 1; i++)
        {
            result += numberSep + parts[i];                        
        }
        
        //alert(parts.length);
        //last number is a decimal?
        if ("." == parts[parts.length - 1].charAt(0))
        {
            //padd a string with zeros or trunc it.
            var dec = parts[parts.length - 1].substring(1, arr[3].length + 1);
            dec = padstr(dec, "0", arr[3].length - dec.length, true);
            result += decimalSep + dec;
        }
        else
        {
            if (parts.length > 2)
            {
                result += numberSep + parts[parts.length - 1];
            }
            result += decimalSep + padstr("", "0", arr[3].length, true);
        }
        
        //fifth is the suffix
        result += arr[4];
       
        return result;        
    }
    else
    {
        return null;    
    }       
}

/**
* Pads a string with characters to the left or right
*
* @param string str string to be padded
* @param string char string used as padding
* @param int count number of times to repeat padding
* @param bool right pad right if true
*/
function padstr(str, chr, count, right)
{
    var result = "";
    for (var i = 0; i < count; i++)
    {
        result += chr;    
    }
    
    return (right) ? 
        str + result : 
        result + str;
}

/**
* This method is called when a currency field is focused.
*
* It changes the number being shown to an editable value
* @param class ctrl control that got focus
*/
function focusCurrencyField(ctrl)
{
    ctrl.value = ctrl.numericValue;
}

/**
* This method is called when a currency field loses focus.
*
* It changes the number being shown to a nicer currency representation given
* by the format attribute of the control
*
* @param class ctrl control that lost focus
*/
function blurCurrencyField(ctrl)
{   
    if (!validateIsNumeric(ctrl.value, getDescription(ctrl))
        || !checkAttribute(ctrl, "format", null, true)
    )
    {
        ctrl.focus();
        return false;
    }
    
    //change commas to dots for a float number
    ctrl.numericValue = ctrl.value.replace(/,/g, ".");
    ctrl.value = displayCurrency(ctrl.numericValue, ctrl.attributes["format"].value);
}

