


//
// Build and return a tag descriptor.
//

function TagDescriptor (_name, _tagName, _type, _extendedType,
                        _description, _displayType,
                        _isPrimaryKey, _foreignKeyToTable, _maxChars,
                        _precision, _nullable) { 
    this.name          = _name;
    this.tagName       = _tagName;
    this.description   = _description;
    this.displayType   = _displayType;
    this.maxChars      = _maxChars;
    this.precision     = _precision;
    this.isNullable    = _nullable;
    this.type          = _type;
    this.isPrimaryKey  = _isPrimaryKey;
    this.foreignKeyToTable = _foreignKeyToTable;
    this.extendedType  = _extendedType;
    return this;
}



function debugAlert (x) {
    var s = "";
    var i = 0;
    for (var y in x) {
        s += y + ' --> ' + x[y] + (i % 3 == 0 ? '\n' : '\t\t\t');
        i++;
    }
    alert (i + " elements:\n" + s);
}


function getZcFormElement (form, tagName) {
    // MS IE has a bug: If the tag name of a form element begins with
    // a numeric (e.g., "0-1-5"), attempting to access the element via the
    // construct form.elements[tagName] fails: it always returns the first form
    // element. Hence this workaround.
    return document.all ? form.elements (tagName) : form.elements[tagName];
}



function initializeTag (descriptor, todaysDate, timeNow) {
    var value;
    var form = document.mainForm;
    var fld = getZcFormElement (form, descriptor.tagName); 
    switch (descriptor.type) {
    case "int":
    case "long":
        if (!descriptor.isPrimaryKey && !descriptor.foreignKeyToTable) {
            if (!descriptor.isNullable && fld && (!fld.value ||
                                                  fld.value.length == 0)) {
                // fld.value = "0"; // Pre-fill non-nullable fields
            }
        }
        break;


    case "java.math.BigDecimal":
        switch (descriptor.extendedType) {
        case "money":
            if (fld && (!fld.value || fld.value.length == 0) &&
                !descriptor.isNullable) {
                fld.value = "$0.00"; // Pre-fill non-nullable fields
            }
            break;
            
        case "decimal":
            if (!descriptor.isNullable && fld &&
                (!fld.value || fld.value.length == 0)) { 
                fld.value = "0.00"; // Pre-fill non-nullable fields
            }
            break;
            
        }
        
        break;

        
    case "java.util.Date":
        switch (descriptor.displayType) {
        case "textBox":
            if (fld && (!fld.value || fld.value.length == 0) &&
                !descriptor.isNullable) {
                fld.value = nowString;
            }
            break;
      

        case "dateOnly":
        case "timeOnly":
        case "dateAndTime":
            var dateFld = getZcFormElement(form,descriptor.tagName + "__date");
            var timeFld = getZcFormElement(form,descriptor.tagName + "__time");
            if (fld && fld.value && fld.value.length > 0 &&
                (value = Date.parse (fld.value)) != 0) {
                // The server provided a date value, so unpack it into the text
                // boxes
                var valueDate = new Date (value);
                if (dateFld) {
                    var valueDateYear = valueDate.getFullYear();
                    dateFld.value = fmt (valueDate.getMonth() + 1) + "/" +
                        fmt (valueDate.getDate()) + "/" + valueDateYear;
                }
                if (timeFld) {
                    timeFld.value = fmt (valueDate.getHours()) + ":" +
                        fmt (valueDate.getMinutes()) + ":" +
                        fmt (valueDate.getSeconds());
                }
            } else if (!descriptor.isNullable) {
                // The server did not provide a value, probably because this is
                // an add UDM. So we prefill it with the current time and date
                // if it is not nullable. 
                if (dateFld && (!dateFld.value ||
                                dateFld.value.length == 0)) {
                    dateFld.value = todaysDate;
                }
                if (timeFld && (!timeFld.value ||
                                timeFld.value.length == 0)) {
                    timeFld.value = timeNow;
                }
            }
            break;
        }
    }
}




function InvalidFieldDescriptor (field, message) {
    this.field = field;
    this.message = message;
    return this;
}




function validateTag (descriptor) {
    var invalidFieldDesc = verifyValidity (descriptor);
    if (invalidFieldDesc != null) {
        alert (invalidFieldDesc.message);
        if (invalidFieldDesc.field) {
            //invalidFieldDesc.field.focus();
            // invalidFieldDesc.field.style.border = "2px outset red";
        }
        return false;
    }
    return true;
}



function verifyValidity (descriptor) {
    
    var form = document.mainForm;

    var fld = getZcFormElement (form, descriptor.tagName);
    var msg = null;
    switch (descriptor.displayType) {
    case "dropDownList":
      if (!descriptor.isNullable && fld &&
          (fld.selectedIndex == null ||
           fld.options[fld.selectedIndex].value == "")) {
          msg = "Non-null value needed for '" + descriptor.description + "'";
      }
      break;
      
    case "textBox":
    case "textArea":
        if (descriptor.maxChars != "0") {
            if (fld && fld.value &&
                fld.value.length > parseInt (descriptor.maxChars)) {
                msg = "At most " + descriptor.maxChars + " chars allowed " +
                    "for field '" + descriptor.description + "'";
                break;
            }
        }
        if (!descriptor.isNullable && fld &&
            (!fld.value || fld.value.length <= 0)) {
            msg = "Non-empty value needed for field '" + descriptor.description +
                "'";
            break;
        }
        
        switch (descriptor.extendedType) {
        case "money":
            if (fld && !isMoney (fld.value, descriptor.isNullable)) {
                msg = "Valid currency value needed for field '" +
                    descriptor.description + "'" +
                    " (negative values must be parenthesized).";
                break;
            }
            if (fld && fld.value && fld.value && fld.value.length > 0 &&
                fld.value.charAt (0) != '$' && fld.value.charAt (0) != '(') {
                fld.value = '$' + fld.value;
            }
            break;

        case "decimal":
            if (descriptor.type == "int" || descriptor.type == "long") {
                if (fld && !isInteger (fld.value, descriptor.isNullable)) {
                    msg = "Valid integer needed for field '" +
                        descriptor.description + "'";
                    break;
                }
            }
            if (descriptor.precision != "") {
                if (fld && !hasFraction (fld.value, descriptor.isNullable,
                                         parseInt (descriptor.precision))) {
                    msg = "Valid number needed for field '" +
                        descriptor.description + "'";
                    break;
                }
            }
            break;
    
        case "US phone":
            if (fld && !isAmericanPhone (fld.value, descriptor.isNullable)) {
                msg = "Valid phone number needed for field '" +
                    descriptor.description + "' in the format " +
                       "(nnn) nnn-nnnn or nnn-nnn-nnnn";
                break;
            }
            break;

        case "US zip code":
            if (fld && !isZipCode (fld.value, descriptor.isNullable)) {
                msg = "Valid zip code needed for field '" +
                    descriptor.description + "' in the format " +
                    "nnnnn or nnnnn-nnnn";
                break;
            }
            break;

        case "float":
        case "double":
            if (fld && !isFloat (fld.value, descriptor.isNullable)) {
                msg = "Valid number needed for field '" +
                    descriptor.description + "'";
                break;
            }
            break;
        }
        break;

    case "dateAndTime":
    case "dateOnly":
    case "timeOnly":
        var dateStr = null, timeStr = null;
        var dateFld = getZcFormElement (form, descriptor.tagName + "__date");
        if (dateFld) {
            dateStr = ZcDate__getNormalizedDate (dateFld.value);
            if (!dateStr && (!descriptor.isNullable || dateFld.value != "")) {
                msg = "Valid date needed for field '" +
                    descriptor.description + "' in the format mm/dd/yyyy.";
                fld = dateFld;
                break;
            }
            // dateFld.value = dateStr; No need to show normalized form
        }
        var timeFld = getZcFormElement (form, descriptor.tagName + "__time");
        if (timeFld) {
            timeStr = ZcDate__getNormalizedTime (timeFld.value);
            if (!timeStr && (!descriptor.isNullable || timeFld.value != "")) {
                msg = "Valid time needed for field '" +
                    descriptor.description + "' in the format hh:mm:ss " +
                    "or hh:mm am or hh:mm pm.";
                fld = timeFld;
                break;
            }
            // timeFld.value = timeStr; No need to show normalized form
        }
        if (descriptor.displayType == "dateOnly") {
            if (fld && dateStr) fld.value = dateStr;
        } else if (descriptor.displayType == "dateAndTime") {
            dateValue = dateStr ? dateStr : "";
            timeValue = timeStr ? timeStr : "";
            if (dateValue.length > 0 && timeValue.length == 0) {
                msg = "Valid time needed for field '" +
                    descriptor.description + "' in the format hh:mm:ss " +
                    "or hh:mm am or hh:mm pm.";
                fld = timeFld;
                break;
            } else if (dateValue.length == 0 && timeValue.length > 0) {
                msg = "Valid date needed for field '" +
                    descriptor.description + "' in the format mm/dd/yyyy.";
                fld = dateFld;
                break;
            }
            if (fld) {
                fld.value =  (dateValue.length > 0 && timeValue.length > 0)
                    ? (dateValue + " " + timeValue)
                    : ((dateValue.length > 0) ? dateValue : timeValue);
            }
        } else if (descriptor.displayType == "timeOnly") {
            if (fld) fld.value = timeStr ? timeStr : "";
        }
        break;
    }
    return msg != null ? new InvalidFieldDescriptor (fld, msg) : null;
}





// Return the value of the query string parameter with the specified name.
function getQueryParameter (parameterName) {
    var qs = window.location.search;
    var result = null;
    if (qs && qs != "") {
        if (qs.charAt (0) == '?') qs = qs.substr(1);
        var params = qs.split ("&");
        for (var i = 0; i < params.length; i++) {
            var substring = params[i].substr (0, parameterName.length + 1);
            if (substring == parameterName + "=") {
                result = unescape (params[i].substr(parameterName.length + 1));
                break;
            }
        }
    }
    return result;
}




// Decode the query string and return a map of query parameters and associated values.
function getQueryParameterPacket () {
    var qs = window.location.search;
    var result = null;
    if (qs && qs != "") {
        result = new Array();
        if (qs.charAt (0) == '?') qs = qs.substr(1);
        var params = qs.split ("&");
        for (var i = 0; i < params.length; i++) {
            var str = params[i];
            var index = str.indexOf ('=');
            if (index > 0) {
                var key = str.substr (0, index);
                var value = unescape (str.substr (index+1));
                result[key] = value;
            }
        }
    }
    return result;
}


// Construct a query string from a map of query parameters.
function makeQueryString (parameterPacket) {
    var strg = "";
    for (var key in parameterPacket) {
        if (strg != "") {
            strg += "&";
        }
        strg += key + "=" + escape (parameterPacket[key]);
    }
    if (strg != "") strg = "?" + strg;
    return strg;
}


// Provide the ability to trim a string, by removing leading and trailing spaces.
// For example:
//        var s2 = s1.trim();
String.prototype.trim = function () {
    if (this.length <= 0) return this;
    var left, right;
    for (var left = 0; left < this.length && this.charAt(left) == ' '; left++);
    for (var right = this.length-1; right >= 0 && this.charAt(right) == ' ';
         right--);
    return left <= right ? this.substring (left, right+1) : "";
}



function Zc__parseInt (s) {
    // IE 5's parseInt is broken: it stops if there are leading zeros. :-(
    if (typeof (s) == "number") return s;
    var n = 0;
    var i = 0;
    if (s == null || s.length == 0) {
        return 0;
    }
    var sign = 1;
    if (s.charAt(0) == '-') {
        sign = -1;
        i++;
    }
    for (; i < s.length; i++)  {
        var c = s.charAt(i);
        if (c < '0' || c > '9') {
            break;
        }
        n = n * 10 + (c - '0');
    }
    return n * sign;
}





//
// Support for date and time parsing in zeroCode applications
//


var zcDate__dateRE = new Array();
zcDate__dateRE[0] = new RegExp("^[0-1]?[0-9]/[0-3]?[0-9]/[0-9]{1,4}$");
zcDate__dateRE[1] = new RegExp("^[0-1]?[0-9]\-[A-Za-z]{3}\-[0-9]{1,4}$");
zcDate__dateRE[2] = new RegExp("^[A-Za-z]+ +[0-3]?[0-9]\, *[0-9]{1,4}$");

var zcTime__RE1 = new RegExp ("^[0-2]?[0-9]:[0-5]?[0-9](:[0-5]?[0-9])?$");
// ----------------------------------------------^------------^
// Don't take those out, because there are some parts of Chorus that pass
// single-digit values in those positions.
var zcTime__RE2 = new RegExp ("^[0-1]?[0-9]:[0-5]?[0-9]( *[AaPp][Mm]$)?");
var zcTime__RE3 = new RegExp ("^[0-1]?[0-9] *[AaPp][Mm]$");

var zcDate__shortMonthNames = new Array();
var zcDate__monthNames = new Array();

zcDate__shortMonthNames = new Array();
zcDate__shortMonthNames["jan"] = 0;
zcDate__shortMonthNames["feb"] = 1;
zcDate__shortMonthNames["mar"] = 2;
zcDate__shortMonthNames["apr"] = 3;
zcDate__shortMonthNames["may"] = 4;
zcDate__shortMonthNames["jun"] = 5;
zcDate__shortMonthNames["jul"] = 6;
zcDate__shortMonthNames["aug"] = 7;
zcDate__shortMonthNames["sep"] = 8;
zcDate__shortMonthNames["oct"] = 9;
zcDate__shortMonthNames["nov"] = 10;
zcDate__shortMonthNames["dec"] = 11;

zcDate__monthNames = new Array();
zcDate__monthNames ["january"   ] = 0;
zcDate__monthNames ["february"  ] = 1;
zcDate__monthNames ["march"     ] = 2;
zcDate__monthNames ["april"     ] = 3;
zcDate__monthNames ["may"       ] = 4;
zcDate__monthNames ["june"      ] = 5;
zcDate__monthNames ["july"      ] = 6;
zcDate__monthNames ["august"    ] = 7;
zcDate__monthNames ["september" ] = 8;
zcDate__monthNames ["october"   ] = 9;
zcDate__monthNames ["november"  ] = 10;
zcDate__monthNames ["december"  ] = 11;


zcDate__wkDayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];


// Parse the given string as a date, and return a corresponding Date
// object. If unable to parse, return null. The second parameter determines the
// handling of dates in the form dd/dd/dddd: if the parameter is true, such a
// date is treated as dd/mm/yyyy, otherwise as mm/dd/yyyy. This function can
// also parse dates in the form dd-MMM-yyyy, e.g., 10-Sep-2002.
function ZcDate__parseDate (dateString, asNonAmericanDate) {
    if (zcDate__dateRE[0].test (dateString)) {
        var pieces = dateString.split ("/");
        var year = parseInt (pieces[2]);
        if (pieces[2].length == 2) year += 2000;
        return asNonAmericanDate
            ? new Date (year, pieces[1]-1, pieces[0], 0, 0, 0)
            : new Date (year, pieces[0]-1, pieces[1], 0, 0, 0);
    }
    if (zcDate__dateRE[1].test (dateString)) {
        var pieces = dateString.split ("-");
        var month = zcDate__shortMonthNames[pieces[1].toLowerCase()];
        var year = parseInt (pieces[2]);
        if (pieces[2].length == 2) year += 2000;
        return month
            ? new Date (year, month, pieces[0], 0, 0, 0)
            : null;
    }
    if (zcDate__dateRE[2].test (dateString)) {
        var pieces = dateString.split (/ +/);
        var month = zcDate__monthNames[pieces[0].toLowerCase()];
        if (!month) zcDate__shortMonthNames[pieces[0].toLowerCase()];
        if (!month) return null;
        if (pieces.length == 3) {
            var day = pieces[1].substring (0, pieces[1].length-1);
            return new Date (pieces[2], month, day, 0, 0, 0);
        }
        var pieces2 = pieces[1].split (",");
        var year = parseInt (pieces2[1]);
        if (pieces2[1].length == 2) year += 2000;
        return new Date (year,  month, pieces2[0], 0, 0, 0);
    }
    return null;
}

function _debugShow (msg, pc) {
    var s = "\n";
    for (var i = 0; i < pc.length; i++) {
        s += "[" + i + "] = '" + pc[i] + "'\n";
    }
    alert (msg + s);
}



// Parse the date and return a string representation of it.
// If unable to parse, return null. The second parameter determines the
// handling of dates in the form dd/dd/dddd: if the parameter is true, such a
// date is treated as dd/mm/yyyy, otherwise as mm/dd/yyyy.
function ZcDate__parseDateToString (dateStr, asNonAmerican) {
    var date = ZcDate__parseDate (dateStr, asNonAmerican);
    if (!date) return null;
    var monthName = null;
    for (var month in zcDate__shortMonthNames) {
        if (zcDate__shortMonthNames[month] == date.getMonth()) {
            monthName = month;
            break;
        }
    }
    return date.getDate() + "-" + monthName + "-" + date.getFullYear();
}



function ZcDate__getNormalizedDate (dateStr) {
    // Take a string in any of the forms accepted by the date parsing routine,
    // and return a 'normalized' date in the form mm/dd/yyyy. Return null if
    // the parse failed.
    var theDate = ZcDate__parseDate (dateStr);
    if (!theDate) return null;
    
    return __ZcDate__2Digits (theDate.getMonth()+1) + "/" +
        __ZcDate__2Digits (theDate.getDate()) + "/" + theDate.getFullYear();
}


function ZcDate__getNormalizedTime (timeStr) {
    // Take a string representing a time, in any of the forms hh:mm:ss or
    // hh:mm am or hh:mm pm, and return the corresponding time in the
    // "normalized" 24-hour form hh:mm:ss. Return null if the parse failed.
    
    var hours = -1;
    var minutes = 0;
    var seconds = 0;

    if (zcTime__RE1.test (timeStr)) {
        var pieces = timeStr.split (":");
        if (pieces.length != 2 && pieces.length != 3) {
            return null;
        }
        hours   = parseInt (pieces[0], 10);
        minutes = parseInt (pieces[1], 10);
        seconds = (pieces.length == 3) ?  parseInt (pieces[2], 10) : 0;
        if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59 ||
            seconds < 0 || seconds > 59) return null;
    } else if (zcTime__RE2.test (timeStr)) {
        // See if it's in the hh:mm [am/pm] format
        var n = timeStr.length;
        var amPm = "am";
        var amPmStr = timeStr.substring (n-2, n).toLowerCase();
        if (amPmStr == "am" || amPmStr == "pm") {
            amPm = amPmStr;
            timeStr = timeStr.substring (0, n-2);
        }
        var pieces = timeStr.split (":");
        if (!pieces.length || pieces.length != 2) return null;
        hours = parseInt (pieces[0], 10);
        minutes = parseInt (pieces[1], 10);
        if (isNaN (hours) || isNaN (minutes)) return null;
        if (hours < 0 || hours > 12 || minutes < 0 || minutes > 59) {
            return null;
        }
        if (amPm == "pm" && hours < 12) {
            hours += 12;
        }
    } else if (zcTime__RE3.test (timeStr)) {
        var n = timeStr.length;
        var amPm = timeStr.substring (n-2, n).toLowerCase();
        var hourStr = timeStr.substring (0, n-2);
        hours = parseInt (hourStr, 10);
        if (amPm == "pm") {
            hours += 12;
        }
    }
    return hours < 0 || hours > 23
        ? null
        : (__ZcDate__2Digits (hours) + ":" + __ZcDate__2Digits (minutes) +
           ":" + __ZcDate__2Digits (seconds));

}


function __ZcDate__2Digits (amt) {
    return (amt >= 10) ? ("" + amt) : ("0" + amt);
}



function ZcDate__parseDateTime (dateStr, timeStr, asNonAmerican) {
    var dt = ZcDate__parseDate (dateStr, asNonAmerican);
    if (!dt) return null;

    var year = dt.getFullYear();
    if (!timeStr || timeStr == "") {
        return new Date (year, dt.getMonth(), dt.getDate(), 0, 0, 0);
    }
    
    timeStr = ZcDate__getNormalizedTime (timeStr);
    if (!timeStr) return null;

    var pieces = timeStr.split (":");
    var hours   = parseInt (pieces[0], 10);
    var minutes = parseInt (pieces[1], 10);
    var seconds = parseInt (pieces[2], 10);
    return new Date (year, dt.getMonth(), dt.getDate(), hours, minutes,
                     seconds);
}



function ZcDate__shortMonthNameFor (monthNumber) {
    for (var m in zcDate__shortMonthNames) {
        if (zcDate__shortMonthNames[m] == monthNumber) {
            var name = m;
            name = name.substring(0,1).toUpperCase() + name.substring(1);
            return name;
        }
    }
    return null;
}


function ZcDate__shortWkDayNameFor (dayNumber) {
    return (dayNumber >= 0 && dayNumber <= 6) ? zcDate__wkDayNames[dayNumber] :
        "(unknown)";
}

