/*
   forms.js
   ----------------------------------------
   Form utility functions.
*/

/* Sort direction */
var SORT_ASC  = 'A';
var SORT_DESC = 'D';

/*
   Default decimal separator
*/
var DEFAULT_DECIMAL_SEPARATOR = ".";

/*
   Default thousand separator
*/
var DEFAULT_THOUSAND_SEPARATOR = ",";

/*
   Multiple select values separator
*/
var MULTIPLE_SELECT_VALUES_SEPARATOR = "|";

/*
   Array (ORDERED) with not allowed buttons:
   - Backspace
*/
var keysToIgnore = new Array([8], [116]);


/*
   Return if specified character (ASCII) is a
   meta-character.
*/
function isMetaKey(event) {
   return (event.altKey || event.ctrlKey || event.shiftKey || event.metaKey);
}


/*
   Return if specified character (ASCII) is an arrow.
*/
function isArrowKey(event) {
   if (!event)
      var event = window.event;

   if (event.keyCode)
      code = event.keyCode;
   else if (event.which)
      code = event.which;

   if (!isMetaKey(event) && code >= 37 && code <= 40)
     return true;

   return false;
}


/*
   Return if specified character (ASCII) is to
   ignore.
*/
function isKeyToIgnore(key) {
   for (i = 0; i < keysToIgnore.length; i++) {
      if (key == keysToIgnore[i])
         return true;

      /* The array is ordered */
      if (keysToIgnore[i] > key)
         return false;
   }

   return false;
}


/*
   onKeyDown event listener.
   Intercept and prevent insertion of forbidden
   buttons.
*/
function detectKeysToIgnore(event) {
   if (!event)
      var event = window.event;

   if (event.keyCode)
      code = event.keyCode;
   else if (event.which)
      code = event.which;

   if (isKeyToIgnore(code)) {
      if (event.preventDefault) {  // NS
         /*
            If the event was generated on a textual input, we proceed without
            invalidate bubbling (that is letting the button to be pressed).
            Otherwise we'd prevent the user to use some special buttons
            (eg. BACK button to cancel and correct a value).
         */
         if (event.target.nodeName.toUpperCase() != "INPUT" &&
             event.target.nodeName.toUpperCase() != "TEXTAREA")
            event.preventDefault();
      } else {                     // IE
         /*
            If the event was generated on a textual input, we proceed without
            invalidate bubbling (that is letting the button to be pressed).
            Otherwise we'd prevent the user to use some special buttons
            (eg. BACK button to cancel and correct a value).
         */
         if (event.srcElement.nodeName.toUpperCase() != "INPUT" &&
             event.srcElement.nodeName.toUpperCase() != "TEXTAREA")
            event.keyCode = 0;
      }
   }
}


/*
   Let the user to input only lowercase letters [a-z].
   Otherwise, event is cancelled.
*/
function requestLowercaseLetter(event) {
   if (!event)
      var event = window.event;

   if (event.keyCode)
      code = event.keyCode;
   else if (event.which)
      code = event.which;

   if (!isArrowKey(event) && code >= 32 &&
       (code < 65 || code > 90)) { /* a-z */
      cancelEvent(event);
   }

   return;
}


/*
   Let the user to input only uppercase letters [A-Z].
   Otherwise, event is cancelled.
*/
function requestUppercaseLetter(event) {
   if (!event)
      var event = window.event;

   if (event.keyCode)
      code = event.keyCode;
   else if (event.which)
      code = event.which;

   if (!isArrowKey(event) && code >= 32 &&
       (code < 97 || code > 122)) {
      cancelEvent(event);
   }

   return;
}


/*
   Let the user to input only letters [a-zA-Z].
   Otherwise, event is cancelled.
*/
function requestLetter(event) {
   if (!event)
      var event = window.event;

   if (event.keyCode)
      code = event.keyCode;
   else if (event.which)
      code = event.which;

   if (!isArrowKey(event) && code >= 32 &&
       !((code >= 65 && code <= 90) || (code >= 97 && code <= 122))) {
      cancelEvent(event);
   }

   return;
}


/*
   Let the user to input only positive integers [0-9].
   Otherwise, event is cancelled.
*/
function requestDigit(event) {
   if (!event)
      var event = window.event;

   if (event.keyCode)
      code = event.keyCode;
   else if (event.which)
      code = event.which;

   if (!isArrowKey(event) && code >= 32 &&
       (code < 48 || code > 57)) {
      cancelEvent(event);
   }

   return;
}


/*
   Let the user to input only decimals [-][0-9][.].
   Otherwise, event is cancelled.
*/
function requestDecimalDigit(event) {
   if (!event)
      var event = window.event;

   if (event.keyCode)
      code = event.keyCode;
   else if (event.which)
      code = event.which;

   if (!isArrowKey(event) && code >= 32 &&
       !((code >= 48 && code <= 57) || (code > 44 && code <= 46))) {
      cancelEvent(event);
   }

   return;
}


/*
   Let the user to input only positive decimals [0-9][.].
   Otherwise, event is cancelled.
*/
function requestDecimalPositiveDigit(event) {
   if (!event)
      var event = window.event;

   if (event.keyCode)
      code = event.keyCode;
   else if (event.which)
      code = event.which;

   if (!isArrowKey(event) && code >= 32 &&
       !((code >= 48 && code <= 57) || (code > 44 && code <= 46 && code != 45))) {
      cancelEvent(event);
   }

   return;
}


/*
   Let the user to input only positive integers [0-9] or
   letters [a-zA-Z] (also the underscore symbol).
   Otherwise, event is cancelled.
*/
function requestDigitOrLetter(event,
                              acceptUnderscore) {
   if (!event)
      var event = window.event;

   if (event.keyCode)
      code = event.keyCode;
   else if (event.which)
      code = event.which;

   if (!isArrowKey(event) && code >= 32 &&
       !((code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122)) &&
      (acceptUnderscore && code != 95)) {
      cancelEvent(event);
   }

   return;
}


/*
   Let the user to input only dates [0-9][-./].
   Otherwise, event is cancelled.
*/
function requestDateChar(event) {
   if (!event)
      var event = window.event;

   if (event.keyCode)
      code = event.keyCode;
   else if (event.which)
      code = event.which;

   if (!isArrowKey(event) && code >= 32 &&
       (code < 45 || code > 57)) {
      cancelEvent(event);
   }

   return;
}


/*
   Let the user to input only timetables [0-9][.:].
   Otherwise, event is cancelled.
*/
function requestTimeChar(event) {
   if (!event)
      var event = window.event;

   if (event.keyCode)
      code = event.keyCode;
   else if (event.which)
      code = event.which;

   if (!isArrowKey(event) && code >= 32 &&
       ((code < 48 || code > 57) && code != 46 && code != 58)) {
      cancelEvent(event);
   }

   return;
}


/*
   Let the user to input only ids [a-zA-Z0-9_-.]
   Otherwise, event is cancelled.

   acceptWildcards defines whether jolly characters
   are allowed or not (eg., ? e * during search).
*/
function requestIdCharacter(event,
                            acceptWildcards) {
  if (!event)
    var event = window.event;

    if (event.keyCode)
      code = event.keyCode;
    else if (event.which)
      code = event.which;

    if (!isArrowKey(event) && code >= 32 &&
        !((code >= 48 && code <= 57) || /* 0-9 */
         (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || /* a-zA-Z */
         (code == 95) || /* _ */
         (code == 45) || /* - */
         (code == 46) || /* .*/
         (acceptWildcards && code == 42) || /* * */
         (acceptWildcards && code == 63)    /* ? */)) {
      cancelEvent(event);
    }

    return;
}


/*
   Check whether the given value represents a number [-]?[0-9]+.
   Return true if it's a valid number, false otherwise.
*/
function isInteger(value,
                   zeroAdmitted) {
   if (value.search(/^[\-]?\d+$/) == -1)
      return false;

   if (!zeroAdmitted && Number(value) == 0)
      return false;

   return true;
}


/*
   Check whether the given value represents a decimal [-]?[0-9]*([.][0-9]+)?.
   Return true if it's a valid decimal, false otherwise.
*/
function isDecimal(value,
                   zeroAdmitted) {
   if (value.search(/^[\-]?[\.]\d+$/) == -1 &&
       value.search(/^[\-]?\d+([\.]\d+)?$/) == -1)
      return false;

   if (!zeroAdmitted && Number(value) == 0)
      return false;

   return true;
}


/*
   Check whether the given value represents a positive integer [0-9]+.
   Return true if it's a valid positive integer, false otherwise.
*/
function isPositiveInteger(value,
                           zeroAdmitted) {
   if (value.search(/^\d+$/) == -1)
      return false;

   if (!zeroAdmitted && Number(value) == 0)
      return false;

   return true;
}


/*
   Check whether the given value represents a positive decimal [0-9]*([.][0-9]+)?.
   Return true if it's a valid positive decimal, false otherwise.
*/
function isPositiveDecimal(value,
                           zeroAdmitted) {
   if (value.search(/^\d*([\.]\d+)?$/) == -1)
      return false;

   if (!zeroAdmitted && Number(value) == 0)
      return false;

   return true;
}


/*
   Check whether the given value represents a positive decimal [0-9]*([.][0-9]{1,3})?.
   with up to 3 digits after searator.
   Return true if it's a valid positive decimal, false otherwise.
*/
function isCurrencyChange(value,
                          zeroAdmitted) {
   if (value.search(/^\d*([\.]\d{1,3})?$/) == -1)
      return false;

   if (!zeroAdmitted && Number(value) == 0)
      return false;

   return true;
}


/*
   Check whether the given value is a valid decimal (that is
   a number with up to i digits and up to d decimals).
   Return true if it's a valid decimal, false otherwise.
*/
function isValidDecimal(number, i, d) {
   var pattern = "^[\\-]?\\d{0," + i + "}([\\.]\\d{0," + d + "})?$";
   var re = new RegExp(pattern);
   return re.test(number);
}


/*
   Check whether the given value is a valid positive decimal (that is
   a number with up to i digits and up to d decimals).
   Return true if it's a valid positive decimal, false otherwise.
*/
function isValidPositiveDecimal(number, i, d) {
   var pattern = "^\\d{0," + i + "}([\\.]\\d{0," + d + "})?$";
   var re = new RegExp(pattern);
   return re.test(number);
}


/*
   Check whether the given value is a valid currency (that is
   a number with up to 15 digits and up to 2 decimals).
   Return true if it's a valid currency, false otherwise.
*/
function isCurrency(value) {
   if (value.search(/^\d{0,15}(\.\d{1,2})?$/) == -1)
      return false;

   var nDigits = 0;

   for (var i = 0; i < value.length && value.charAt(i) != "."; i++)
      if (value.charCodeAt(i) >= 48 && value.charCodeAt(i) <= 57)
         nDigits++;

   if (nDigits > 13)
      return false;

   return true;
}


/*
   Check whether the given value is a valid date (day of month up to 2 digits,
   separator, month up to 2 digits, separator, year up to 4 digits).
   Also normalize value to DD/MM/YYYY format.
   Return true if it's a valid date, false otherwise.

   NOTA: NOT i18n-safe.
*/
function isDateField(dateField) {
   /* Pivoting year */
   var pivotYear = 40;

   /* Check string well-formedness */
   if (dateField.value.search(/^\d{1,2}[\/\-\.]\d{1,2}[\/\-\.]\d{2,4}$/) == -1)
      return false;

   /* Split string into fields */
   var fields = dateField.value.split(/[\/\-\.]/);

   /* Day of month normalization */
   if (fields[0].length == 1)
      fields[0] = "0" + fields[0];

   /* Month normalization */
   if (fields[1].length == 1)
      fields[1] = "0" + fields[1];

   /* Year normalization */
   switch (fields[2].length) {
      case 2:
         if (fields[2] <= pivotYear)
            fields[2] = new String(new Date().getFullYear()).substr(0, 2) + fields[2];
         else
            fields[2] = new String(new Date().getFullYear() - 100).substr(0, 2) + fields[2];
         break;

      case 3:
         fields[2] = new String(new Date().getFullYear() - 1000).substr(0, 1) + fields[2];
   }

   /* Merge fields */
   dateField.value = fields.join("/");

   /* Check validity */
   if (!isDate(fields[0], fields[1], fields[2]))
      return false;

   return true;
}


/*
   Check whether the given value is a valid day of month (up to 2 digits).
   Also normalize value to DD format.
   Return true if it's a valid day of month, false otherwise.
*/
function isDayOfMonthField(dayOfMonthField) {
   /* Check string well-formedness */
   if (dayOfMonthField.value.search(/^\d{1,2}$/) == -1)
      return false;

   /* Day of month normalization */
   if (dayOfMonthField.value.length == 1)
      dayOfMonthField.value = "0" + dayOfMonthField.value;

   /* Check validity */
   if (dayOfMonthField.value > 31)
      return false;

   return true;
}


/*
   Check whether the given value is a valid month (up to 2 digits).
   Also normalize value to MM format.
   Return true if it's a valid month, false otherwise.
*/
function isMonthField(monthField) {
   /* Check string well-formedness */
   if (monthField.value.search(/^\d{1,2}$/) == -1)
      return false;

   /* Month normalization*/
   if (monthField.value.length == 1)
      monthField.value = "0" + monthField.value;

   /* Check validity */
   if (monthField.value > 12)
      return false;

   return true;
}


function isDate(day,
                month,
                year) {
   /* Validate day of month range */
   if (day < 1 || day > 31)
      return false;

   /* Validate month range */
   if (month < 1 || month > 12)
      return false;

   /*
      Validate year range

      Check whether year is leap.
      Exceptions years:
      - 1753
      - 4902.
   */
   if (year < 1753 || year > 4902)
      return false;

   switch (Number(month)) {
      case 2:
          if (day > 29)
             return false;

          if (day == 29)
             if (year % 4 != 0 || (year % 100 == 0 && year % 400 !=0))
                return false;

          break;

      case 4:
      case 6:
      case 9:
      case 11:
          if (day > 30)
             return false;
   }

   return true;
}


/*
   Check whether the given value is a valid scheduled time (hour up to 2 digits,
   separator, minutes up to 2 digits).
   Also normalize value to HH:MM format.
   Return true if it's a valid time, false otherwise.
*/
function isTimeField(timeField) {
  /* Check string well-formedness */
   if (timeField.value.search(/^\d{1,2}([\:\.]\d{2})?$/) == -1)
      return false;

   /* Split string into fields */
   var fields = timeField.value.split(/[\:\.]/);

   /* Hour normalization */
   if (fields[0].length == 1)
      fields[0] = "0" + fields[0];

   /* Minutes normalization */
   if (fields[1].length == 1)
      fields[1] = "0" + fields[1];

   /* Merge fields */
   timeField.value = fields.join(":");

   /* Check validity */
   if (fields[0] > 23)
      return false;

   if (fields[1] > 59)
      return false;

   return true;
}


/*
   Check whether the given value is a valid hour (up to 2 digits).
   Also normalize value to HH format.
   Return true if it's a valid hour, false otherwise.
*/
function isHoursField(hoursField) {
   /* Check string well-formedness */
   if (hoursField.value.search(/^\d{1,2}$/) == -1)
      return false;

   /* Hour normalization */
   if (hoursField.value.length == 1)
      hoursField.value = "0" + hoursField.value;

   /* Check validity */
   if (hoursField.value > 23)
      return false;

   return true;
}


/*
   Check whether the given value is a valid minute (up to 2 digits).
   Also normalize value to MM format.
   Return true if it's a valid minute, false otherwise.
*/
function isMinutesField(minutesField) {
   /* Check string well-formedness */
   if (minutesField.value.search(/^\d{1,2}$/) == -1)
      return false;

   /* Minutes normalization */
   if (minutesField.value.length == 1)
      minutesField.value = "0" + minutesField.value;

   /* Check validity */
   if (minutesField.value > 59)
      return false;

   return true;
}


/*
   Check whether the given value is a valid second (up to 2 digits).
   Also normalize value to SS format.
   Return true if it's a valid second, false otherwise.
*/
function isSecondsField(secondsField) {
   /* Check string well-formedness */
   if (secondsField.value.search(/^\d{1,2}$/) == -1)
      return false;

   /* Seconds normalization */
   if (secondsField.value.length == 1)
      secondsField.value = "0" + secondsField.value;

   /* Check validity */
   if (secondsField.value > 59)
      return false;

   return true;
}


/*
   Check whether the given URL is valid.
   Return true if it's a valid URL, false otherwise.
*/
function isValidUrl(address) {
  var urlRE = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;

  if (trim(address).length == "")
    return false;

  if (!urlRE.test(address))
    return false;

  return true;
}


/*
   Check whether the given mail address is valid.
   Return true if it's a valid mail address, false otherwise.
*/
function isValidEmailAddress(address) {
  var emailRE = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;

  if (trim(address).length == "")
    return false;

  if (!emailRE.test(address))
    return false;

  return true;
}


/*
   Compare two dates.
   Return -1 if first date comes before the second one;
           0 if dates are equal;
           1 if first date comes after the second one.
   compareTime defines whether we must also compare time or not.
*/
function compareDate(date1,
                     date2,
                     compareTime) {
   var dapp1, dapp2 = null;

   if (compareTime) {
     dapp1 = new Date(date1.getFullYear() ,date1.getMonth(), date1.getDate(), date1.getHours(), date1.getMinutes(), date1.getSeconds(), date1.getMilliseconds());
     dapp2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate(), date2.getHours(), date2.getMinutes(), date2.getSeconds(), date2.getMilliseconds());
   } else {
     dapp1 = new Date(date1.getFullYear() ,date1.getMonth(), date1.getDate(), 0, 0, 0, 0);
     dapp2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate(), 0, 0, 0, 0);
   }

   if (dapp1.getTime() < dapp2.getTime())
      return -1;

   if (dapp1.getTime() > dapp2.getTime())
      return 1;

   return 0;
}


/*
   Compare two times (HH:MM format).
   Return -1 if first time comes before the second one;
           0 if times are equal;
           1 if first time comes after the second one.
*/
function compareTime(time1,
                     time2) {
   var tapp1 = new Date(0, 0, 0, Number(time1.substr(0, 2)), Number(time1.substr(3, 2)), 0, 0);
   var tapp2 = new Date(0, 0, 0, Number(time2.substr(0, 2)), Number(time2.substr(3, 2)), 0, 0);

   if (tapp1.getTime() < tapp2.getTime())
      return -1;

   if (tapp1.getTime() > tapp2.getTime())
      return 1;

   return 0;
}


/*
   Search within a SELECT an OPTION with the given VALUE.
   Return index of OPTION with given VALUE, otherwise -1
   if OPTION was not found.
*/
function searchSelectOptionByValue(select,
                                   value) {
   for (var i = 0; i < select.options.length; i++)
      if (select.options[i].value == value)
         return i;

   return -1;
}


/*
   Add the specified OPTION to the given SELECT.
   Useful method because IE doesn't manage properly
   appendChild() method.
*/
function addOptionToSelect(select,
                           option) {
   try {
      select.add(option, null);
   } catch(ex) {
      select.add(option);
   }
}


/*
   Copy specified OPTIONS from a SELECT to another one.
   reverseTextValue is useful when we want reverse values
   and texts during copy.
*/
function copySelectOption(selectFrom,
                          selectTo,
                          optionFrom,
                          optionTo,
                          reverseTextValue) {
  if (optionFrom > optionTo)
    return;

  var lowerLimit = Math.min(0, optionFrom);
  var upperLimit = Math.min(optionTo, selectFrom.options.length);
  for (var i = lowerLimit; i < upperLimit; i++) {
    var option = document.createElement("option");

    option.value = (reverseTextValue ? selectFrom.options[i].text : selectFrom.options[i].value);
    option.text = (reverseTextValue ? selectFrom.options[i].value : selectFrom.options[i].text);

    option.selected = selectFrom.options[i].selected;

    addOptionToSelect(selectTo, option);
  }
}


/*
   Helper method to get to the next sibling element.
*/
function tabToNextSibling(currentField, lengthToTab, nextField, event) {
  if (!event)
    var event = window.event;

  if (event.keyCode)
    code = event.keyCode;
  else if (event.which)
    code = event.which;

  if (isArrowKey(code) || isMetaKey(code) || isKeyToIgnore(code) || code == 46) /* 46 = tasto Canc. */
    return;

  if (currentField.value.length == lengthToTab)
    nextField.focus();
}


/*
   Format a size (in bytes).
*/
function formatBytes(value) {
   /* Convert value to number */
   bytes = Number(value);

   /* If value is not a valid number, we return */
   if (isNaN(bytes))
      return value;

   /* Size in byte */
   if  (bytes < 1024)
      return value + " byte";

   /* Size in kilobyte */
   if  (bytes < 1048576)
      return Math.round(bytes / 1024) + " KB";

   /* Size in megabyte */
   if  (bytes < 1073741824)
      return Math.round(bytes / 1048576) + " MB";

   /* Size in gigabyte */
   return Math.round(bytes / 1073741824) + " GB";
}


/*
   Format a number up to the given decimals.
*/
function formatDecimal(number, decimals) {
   return number.toFixed(decimals);
}


/*
   Format a number using thousands separator.
*/
function formatNumber(number) {
   return formatNumberUsingSeparator(number, DEFAULT_THOUSAND_SEPARATOR);
}


/*
   Format a number using given thousands separator.
*/
function formatNumberUsingSeparator(number,
                                    thousandSeparator) {

   var formattedNum = "";

   if (number.match(/^\d+\.\d{0,2}$/)) {
      var strArray = number.split(".");
      for (var i = strArray[0].length - 1; i >= 0; i--) {
         formattedNum = strArray[0].charAt(i) + formattedNum;
         if (i > 0 && ((strArray[0].length - i) % 3) == 0)
            formattedNum = thousandSeparator + formattedNum;
      }
      formattedNum += "." + strArray[1];
   }
   else {
      for (var i = number.length - 1; i >= 0; i--) {
         formattedNum = number.charAt(i) + formattedNum;
         if (i > 0 && ((number.length - i) % 3) == 0)
            formattedNum = thousandSeparator + formattedNum;
      }
   }

   return formattedNum;
}


/*
   Return a number from its string representation.
*/
function toNumber(numberAsString) {
  return Number(numberAsString).valueOf();
}


/*
   Round a number with the given decimals.
*/
function roundNumber(num, dec) {
  return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
}



/*
  Format a date with the DD/MM/YYYY format.
*/
function formatDateAsDDMMYYYY(date) {
   /* If date is null, we return */
   if (date == null)
      return date;

   /* Split string into fields */
   var fields = new Array();
   fields[0] = new String(date.getDate());
   fields[1] = new String(date.getMonth() + 1);
   fields[2] = new String(date.getFullYear());

   /* Day of month normalization */
   if (fields[0].length == 1)
      fields[0] = "0" + fields[0];

   /* Month normalization */
   if (fields[1].length == 1)
      fields[1] = "0" + fields[1];

   /* Merge fields */
   return fields.join("/");
}


/*
  Extract a time (HH:MM format) from a date.
*/
function extractTimeAsHHMM(date) {
    /* If date is null, we return */
   if (date == null)
      return date;

   /* Split string into fields */
   var fields = new Array();
   fields[0] = new String(date.getHours());
   fields[1] = new String(date.getMinutes());

   /* Hour normalization */
   if (fields[0].length == 1)
      fields[0] = "0" + fields[0];

   /* Minutes normalization */
   if (fields[1].length == 1)
      fields[1] = "0" + fields[1];

   /* Merge fields */
   return fields.join(":");
}


/*
  Adds given milliseconds to the specified date
  and return the resulting date.
*/
function rollDate(startDate,
                  millisecondsToRoll) {
   /* If date is null, we return */
   if (startDate == null)
      return startDate;

   /* Convert original data into milliseconds */
   var startDateInMilliseconds = startDate.getTime();

   return new Date(startDateInMilliseconds + millisecondsToRoll);
}


/*
   Compute difference (in days) between two dates.
*/
function calculateDaysBetweenDates(startDate, endDate) {
  var difference = Date.UTC(endDate.getFullYear(), endDate.getMonth(), endDate.getDate(), 0, 0, 0)
                 - Date.UTC(startDate.getFullYear(), startDate.getMonth(), startDate.getDate(), 0, 0, 0);

  if (difference < 0)
    return 0;

  return difference / 1000 / 60 / 60 / 24;
}


/*
   Return a string representation of a duration (in milliseconds)
   formatted as hours, minutes and seconds.
*/
function millisecondsToHours(ms) {
  var t = "";

  var sec = Math.floor(ms / 1000);
  var ms = ms % 1000;
  //t = ((ms > 99) ? "" : "0") + ((ms > 9) ? "" : "0") + ms + "ms";

  var min = Math.floor(sec / 60);
  sec = sec % 60;
  t = ((sec > 9) ? "" : "0") + sec + "s " + t;

  var hr = Math.floor(min / 60);
  min = min % 60;
  t = ((min > 9) ? "" : "0") + min + "m " + t;

  var day = Math.floor(hr / 24);
  hr = hr % 24;
  t = ((hr > 9) ? "" : "0") + hr + "h " + t;
  //t = day + ":" + t;

  return t;
}


/*
   Return a duration as milliseconds starting from hours, minutes and seconds.
*/
function hoursToMilliseconds(hours, minutes, seconds, milliseconds) {
  var timeInMilliseconds = 0;

  timeInMilliseconds += milliseconds;
  timeInMilliseconds += seconds * 1000;
  timeInMilliseconds += minutes * 60 * 1000;
  timeInMilliseconds += hours * 60 * 60 * 1000;

  return timeInMilliseconds;
}


/*
   Removes the default thousands separator from a number.
*/
function removeThousandSeparator(string) {
   return removeCustomThousandSeparator(string, DEFAULT_THOUSAND_SEPARATOR);
}


/*
   Removes the given thousands separator from a number.
*/
function removeCustomThousandSeparator(string,
                                       separator) {
   while (string.indexOf(separator) != -1)
      string = string.replace(separator, separator);

   return string;
}


/*
   Set the field value to uppercase
   (also trim possible blanks).
*/
function fieldToUpper(field) {
   field.value = trim(field.value.toUpperCase());
}


/*
   Set the field value to lowercase
   (also trim possible blanks).
*/
function fieldToLower(field) {
   field.value = trim(field.value.toLowerCase());
}


/*
   Capitalize the field value
   (also trim possible blanks).
*/
function fieldToCapitalize(field) {
   if (field.value.length <= 1)
      field.value = trim(field.value.toUpperCase());
   else
      field.value = trim(field.value.substr(0, 1).toUpperCase() + field.value.substr(1).toLowerCase());
}


/*
   onFocus event listener (callback function)
*/
function focusGainedOnField(field,
                            style) {
  /* Apply given style (if defined) */
   if (style != null)
      field.className = style;

   /* Select content of the focued field (only text/password/file/textarea) */
   if ((field.nodeName.toUpperCase() == "INPUT" && (field.getAttribute("type").toLowerCase() == "text" || field.getAttribute("type").toLowerCase() == "password" || field.getAttribute("type").toLowerCase() == "file")) ||
       (field.nodeName.toUpperCase() == "TEXTAREA"))
      field.select();
}


/*
   onBlur event listener (callback function)
*/
function focusLostOnField(field,
                          style) {
   /* Apply given style (if defined) */
   if (style != null)
      field.className = style;
}


/*
   Return whether a field is empty or not.
*/
function isEmptyField(field) {
   if (field.value == null || field.value == undefined || field.value == "")
      return true;

   return false;
}


/*
   Return whether a value is empty or not.
*/
function isEmptyValue(value) {
   if (value == null || value == undefined || value == "")
      return true;

   return false;
}


/*
   Return whether the given id is a valid identifier or not.
   Characters admitted are: a-zA-Z0-9_-.
*/
function isValidId(idValue) {
  var pattern = /^[a-zA-Z0-9_\-\.]*$/;

  if (idValue.match(pattern))
    return true;

  return false;
}


/*
   Return whether an event is valid or not.
*/
function isValidEvent(event) {
   if (event == null || event == undefined)
      return false;

   return true;
}


/*
   Limit length of textarea content.
   Truncate content of textarea when its length is greater than
   the given number of characters.
*/
function limitContent(textarea,
                      maxLength) {
   if (textarea == null || maxLength == null)
      return;

   if (textarea.value.length > maxLength)
      textarea.value = textarea.value.substr(0, maxLength);
}


/*
   Update remaining characters label for a textarea.
*/
function updateCharsCount(textarea,
                          maxLength,
                          lblCounter) {
   if (textarea == null || maxLength == null || lblCounter == null)
      return;

   var charsTyped = textarea.value.length;
   var charsAvailable = maxLength - charsTyped;

   if (charsAvailable >= 0)
      lblCounter.childNodes[0].nodeValue = charsAvailable;
   else
      lblCounter.childNodes[0].nodeValue = 0;
}


/*
   Update remaining words label for a textarea.
*/
function updateWordsCount(textarea,
                          lblCounter) {
   if (textarea == null || lblCounter == null)
      return;

   var wordsTyped = textarea.value.split(/\s/);

   lblCounter.childNodes[0].nodeValue = wordsTyped.length - 1;
}


/*
   Return caret position within a textual input.
   Return values from 0 up to field.length.
*/
function getCaretPosition(field) {
  var caretPos = 0;

  // IE
  if (document.selection) {
    field.focus();
    var c   = "\001";
    var len = 0;
    var selection = document.selection.createRange();
    var dupSelection = selection.duplicate();
    dupSelection.moveToElementText(field);
    selection.text = c;
    len = (dupSelection.text.indexOf(c));
    selection.moveStart('character', -1);
    selection.text = "";
    caretPos = len;
  }
  // NS
  else if (field.selectionStart || field.selectionStart == '0')
    caretPos = field.selectionStart;

  return(caretPos);
}


/*
   Set caret position within a textual input.
   Admitted values goes from 0 up to field.length.
*/
function setCaretPosition(field, caretPos) {

  // IE
  if (document.selection) {
    field.focus ();
    var selection = document.selection.createRange();
    selection.moveStart('character', -field.value.length);
    selection.moveStart('character', caretPos);
    selection.moveEnd('character', 0);
    selection.select();
  }
  // NS
  else if (field.selectionStart || field.selectionStart == '0') {
    field.selectionStart = caretPos;
    field.selectionEnd = caretPos;
    field.focus();
  }
}


/*
   Explicitly invoke onClick event on the given field.
*/
function invokeOnClickEvent(field) {
  if (field.fireEvent)   // IE
    field.fireEvent("onclick");
  else {                 // NS
     var clickEvent = window.document.createEvent("MouseEvent");
     clickEvent.initEvent("click", false, true);
     field.dispatchEvent(clickEvent);

  }
}


/*
   Explicitly invoke onChange event on the given field.
*/
function invokeOnChangeEvent(field) {
  if (field.fireEvent)   // IE
    field.fireEvent("onchange");
  else {                 // NS
     var changeEvent = window.document.createEvent("MouseEvent");
     changeEvent.initEvent("change", false, true);
     field.dispatchEvent(changeEvent);

  }
}


/*
   Cancel current event.
*/
function cancelEvent(event) {
  if (event.preventDefault)   // NS
     event.preventDefault();
  else                        // IE
     event.keyCode = 0;
}


/*
   Cancel current event propagation.
*/
function cancelPropagation(event) {
  event.cancelBubble = true;
  event.returnValue = false;
}


/*
   Cross-browser nextSibling method.
*/
function getNextSibling(startElement) {
  var endElement = startElement.nextSibling;
  while (endElement != null && endElement.nodeType != 1)
    endElement = endElement.nextSibling;
  return endElement;
}


/*
   Cross-browser previousSibling method.
*/
function getPrevSibling(startElement) {
  var endElement = startElement.previousSibling;
  while (endElement != null && endElement.nodeType != 1)
    endElement = endElement.previousSibling;
  return endElement;
}


/*
   Cross-browser firstChild method.
*/
function getFirstChild(startElement) {
  if (!startElement.childNodes.length)
    return;

  var children = startElement.childNodes.length;
  for (var i = 0; i <= children; ++i) {
    if (startElement.childNodes[i].nodeType == 1)
      return startElement.childNodes[i];
  }

  return;
}


/*
   Simple effect for form fields who gain/lose focus.
*/
function hoverInputField(field, defaultValue, isEntering) {
  if (field.value == defaultValue && isEntering)
    field.value = "";
  else if (field.value == "" && !isEntering)
    field.value = defaultValue;
}
