//
// Requires the ui-dynamics.jsp to be loaded.
//
// h_* for helpers.
//
// 
// All Validation functions are prefixed with 'v_' and has this spec:
//
// v_${functionName}(${ matching form element }, ${ ValidationErrors instance }, ${ fieldNamePrefix or '' }){
//
//   // If required add errors not trapped by default message here.
//   // Return true if the default message is NOT wanted.
//   validationErrors.addError(element, uiKey);
//
//   return true;   // if OK
//   return false;  // if NOT OK
// }
//

// Helper Methods. for validation

/**
 * Finds ONE value for given field.
 *
 *  - If multiselect, first selected.
 *  - If Checkbox, the value of it, not nessecarily "true".
 *  - If radio, the first checked value of radios with the same name as the one given.
 *
 * Special treatment for nice default behaviour:
 *  - if given element is an array of Radio:s, get its checked value if any
 *  - if given element is an array of Option:s, get value for first selected if any
 *
 * @param theField to get Value for
 *
 * @return not null value, default is empty string.
 */
function h_getValue(theField) {
  // Return empty if the Field is not valid of any reason
  if (!theField) return '';
  var t = theField.type;
  var v = '';
  if (!t) {
    if (h_isRadioArray(theField)) {
      // Radio
      v = h_getRadioValue(theField);
    } else if (theField.selected && theField.index >= 0) {
      // Option for Multiselect
      v = theField.value;
    }
    return v;
  }

  //
  // Figure out the value of the current field
  if (t == "text") {
    v = theField.value;
  } else if (t == "textarea") {
    v = theField.value;
  } else if (t == "hidden") {
    v = theField.value;
  } else if (t == "select-one") {
    v = h_getSelectValue(theField);
  } else if (t == "select-multiple") {
    v = h_getSelectValue(theField);
  } else if (t == "checkbox") {
    if (theField.checked) {
      v = theField.value;
    }
  } else if (t == "radio") {
    v = h_getRadioValue(theField);
  } else if (t == "password") {
    v = theField.value;
  } else if (t == "button") {
    v = theField.value;
  } else if (t == "submit") {
    v = theField.value;
  } else if (t == "reset") {
    v = theField.value;
  } else if (t == "file") {
    v = theField.value;
  } else {
    // If we gotten here,we do not handle the type
  }

  return v ? v : '';
}

// Gets the selected value in a select-one form-element
function h_getSelectValue(select) {
  if (!select)return '';
  var o = select.options;
  if (o) {
    var l = o.length;
    for (var i = 0; i < l; i++) {
      if (o[i].selected) {
        return o[i].value;
      }
    }
  }
  return "";
}

// Gets the checked value of a RadioGroup
function h_getRadioValue(rdo) {
  var elems;
  if (h_isRadioArray(rdo)) {
    elems = rdo;
  } else
    return h_getCheckedRadioValue(rdo.form, rdo.name);
  var l = elems.length;
  var v = '';
  for (var i = 0; i < l; i++) {
    if (elems[i].checked) {
      v = elems[i].value;
      break;
    }
  }
  return v;
}

function h_getCheckedRadioValue(f, n) {
  if (!f || !n || !f.elements)return null;
  var e = f.elements;
  var l = e.length;
  for (j = 0; j < l; j++) {
    if (e[j].name == n && e[j].checked)
      return e[j].value;
  }
  return null;
}

function h_isRadioArray(arr) {
  return arr && arr.length > 0 && arr[0].type == 'radio';
}

// Gets the checked values of a set of Checkboxes with the same name
function h_getCheckboxValues(cb) {
  var vals = new Array();
  if (h_isCheckboxArray(cb)) {
    cb = cb[0];
  }
  if (cb.type == 'checkbox') {
    var elems = cb.form.elements;
    for (var i = 0; i < elems.length; i++) {
      if (elems[i].name == cb.name &&
          elems[i].type == 'checkbox' &&
          elems[i].checked) {
        vals[vals.length] = elems[i].value;
      }
    }
  }
  return vals;
}

function h_isCheckboxArray(arr) {
  return arr && arr.length > 0 && arr[0].type == 'checkbox';
}

function h_elemFocus(e) {
  if (e && e.focus && e.type != "hidden") {
    try {
      e.focus();
    } catch(err) {}
  }
}

function h_elemError(e) {
  if (e && e.style && e.type != "hidden") {
    e.style.border = 'solid red 1px;';
    e.hadError = true;
  }
}

function h_elemReset(e) {
  if (e && e.style && e.type != "hidden" && e.hadError) {
    e.style.border = 'solid #cacaca 1px;';
  }
  e.hadError = false;
}

function h_trim(str) {
  if (!str) { return ''; }
  if (typeof str != "string") { return ''; }
  if (!str.replace) { return ''; }
  if (!str.length) { return ''; }
  return str.replace(/^\s+|\s+$/g, "");
}

function h_hasValue(str) {
  var v = h_trim(str);
  return v && v.length > 0;
}

function h_keepDigits(str) {
  if (!str) { return ''; }
  if (typeof str != "string") { return ''; }
  if (!str.replace) { return ''; }
  if (!str.length) { return ''; }
  return str.replace(/[^\d]/g, "");
}


// validates alphabetic string (can only contain letters and space)
function h_isAlphabeticString(alphaString, minLen) {
  if (!minLen) minLen = 1;
  var re = WebConstants.REGEXP_JS_ALPHABETIC_STRING;
  var trimmedStr = h_trim(alphaString);
  return trimmedStr.length >= minLen && re.test(trimmedStr);
}


// validates numeric string (can only contain numbers and space)
function h_isNumericString(numString) {
  var re = /^[-]?[\d\s]+$/;
  return re.test(numString);
}

// validates alphabetic and numeric string (can only contain letters, numbers and space)
function h_isAlphaNumericString(alphaNumString, minLen) {
  if (!minLen) minLen = 1;
  var re = WebConstants.REGEXP_JS_ALPHANUMERIC_STRING;
  var trimmedStr = h_trim(alphaNumString);
  return trimmedStr.length >= minLen && re.test(trimmedStr);
}

// Splits on last dot. unless dot, first returned ard in the array is ''.
function h_resolveFieldNameParts(field) {
  var name = field.name;
  var idxOfDot = name.lastIndexOf('.');
  if (idxOfDot >= 0) {
    var namePrefixPart = name.substring(0, idxOfDot + 1);
    var nameSuffixPart = name.substring(idxOfDot + 1, name.length);
    return [namePrefixPart, nameSuffixPart];
  }
  return ['', name];
}

function h_toNum(value) {
  if (h_isNumericString(value)) {
    return new Number(value);
  }
  return new Number(-1);
}

// All Dates are yyyy-mm-dd
function h_toDate(s) {
  if (s) {
    var parts = s.split('-');

    if (parts && parts.length == 3) {
      var dateParts = [-1,-1,-1];
      for (var i = 0; i < 3; i++) {
        dateParts[i] = h_toNum(parts[i]);
        if (dateParts[i] == -1) return null;
      }
      return new Date(dateParts[0], dateParts[1] - 1, dateParts[2]);
    }
  }
  return null;
}

/**
 * @param d1 earliest date
 * @param d2 latest date
 *
 * @return -1 if unparsable or non-dates
 */
function h_daysUntil(d1, d2) {
  if (d1 && d2 && d1.getTime && d2.getTime) {
    var d1UTC = new Date(Date.UTC(d1.getFullYear(), d1.getMonth(), d1.getDate(), 0, 0, 0));
    var d2UTC = new Date(Date.UTC(d2.getFullYear(), d2.getMonth(), d2.getDate(), 0, 0, 0));
    var dm = d2UTC.getTime() - d1UTC.getTime();
    return parseInt('' + (dm / h_ms(1)));
  }
  return -1;
}

function h_ms(off) {return(off * 60 * 60 * 24 * 1000);}

function h_m(d) {return(d.getMonth() < 9) ? "0" + (1 + d.getMonth()) : (1 + d.getMonth()) + ""}

function h_d(d) {return(d.getDate() < 10) ? "0" + (0 + d.getDate()) : (0 + d.getDate()) + ""}

function h_y(d) {return(d.getFullYear() + "");}

function h_date2String(d) {
  if (d) {
    return h_y(d) + '-' + h_m(d) + '-' + h_d(d);
  }
  return null;
}

function h_matchesRegExp(pattern, s) {
  return pattern.test(s);
}

function h_checkExpiryDate(month, year) {
  var now = new Date();
  var currentMonth = now.getMonth() + 1;
  var currentYear = now.getFullYear();

  if (month < 1 || 12 < month) return false;
  if (year < currentYear) return false;

  return year > currentYear || month >= currentMonth;
}

function h_minlength(str, minLen) {
  if (!minLen) minLen = 1;
  return str && str.length >= minLen;
}

function h_maxlength(str, maxLen) {
  return !str || str.length <= maxLen;
}

// ***********************************************************************************
//
// Validation methods
//
// ***********************************************************************************

function v_maxlength(field, maxLen) {
  var value = h_getValue(field);
  return h_maxlength(value, maxLen);
}

// Element As arguments
function v_required(field) {
  var value = h_getValue(field);
  var minLength = 1;
  return value && h_minlength(value, minLength);
}

function v_enabled(field) {
  var value = h_getValue(field);
  return !field.disabled;
}

function v_orderNumber(field) {
  var value = h_getValue(field);
  return value && h_minlength(value, 6);
}

function v_isAlphabetic(field) {
  var value = h_getValue(field);
  return value && h_isAlphabeticString(value);
}

function v_isAlphaNumeric(field) {
  var value = h_getValue(field);
  return value && h_isAlphaNumericString(value);
}

function v_isNumeric(field) {
  var value = h_getValue(field);
  if (value && value.length > 0) {
    return h_isNumericString(value);
  }
  return false;
}


function v_toNum(field) {
  return h_toNum(h_getValue(field));
}


function v_toDate(field) {
  var value = h_getValue(field);
  var date = h_toDate(value);
  if (date && value && value == h_date2String(date)) return date;
  return null;
}


function v_isPhoneFax(field) {
  var value = h_getValue(field);
  return (value ? isValidPhoneNumber(value) : false);
}

function v_isEmail(field) {
  var value = h_getValue(field);
  if (value && value.length > 0) {
    return isValidEmail(value);
  }
  return false;
}

function v_isAddress(field) {
  return v_required(field) && v_maxlength(field, 32);
}

function v_isTitle(field) {
  if (field.type != 'select-one') return true;
  var r = v_enabled(field) ? v_required(field) : true;
  return r;
}

function v_isTown(field) {
  return v_required(field) && v_maxlength(field, 32);
}

function v_isZipCode(field) {
  if (WebConstants.ALLOW_LETTERS_IN_ZIPCODE) {
    return v_maxlength(field, 10);
  }
  return v_isNumeric(field) && v_maxlength(field, 10);
}


// checks Country field
function v_isCountry(field) {
  return v_isAlphabetic(field) && v_maxlength(field, 32);
}


//checking if the customer has accepted the travel conditions
function v_isTravelCond(field) {
  if (field && !field.checked) {
    return false;
  }
  return true;
}

// checks paytype element
function is_validatePaytype(field) {
  return v_isAlphabetic(field);
}

function v_isCVVCode(field) {
  if (field.type != 'text') return true;
  return v_isNumeric(field);
}

function v_isEmailVerify(field, validationErrors, prefix) {
  var thisEmail = h_getValue(field);
  var firstEmail = h_getValue(validationErrors.getFirstFormField('email', prefix));
  return firstEmail.toUpperCase() == thisEmail.toUpperCase();
}

function v_isCityWithCode(field, validationErrors, prefix) {
  var value = h_getValue(field);
  if (h_minlength(value, 2)) {
    validationErrors.debug('v_isCityWithCode : field ' + field.name + ' is Alpha: ' + value, 2);
    var cityCodeElem = validationErrors.getFirstFormField(field.name + 'Code', '');
    validationErrors.debug('v_isCityWithCode : cityCodeElem: ' + cityCodeElem, 2);
    var code = h_getValue(cityCodeElem);
    if (h_minlength(code, 2)) {
      return code.length == 3;
    }
    return value.length > 2; // Allow also empty.
  }
  return false;
}

function v_isCityWithId(cityElem, validationErrors, prefix) {
  var value = h_getValue(cityElem);
  if (h_minlength(value, 2)) {
    validationErrors.debug('v_isCityWithId : field ' + cityElem.name + ' is Alpha: ' + value, 2);
    var cityIdElem = validationErrors.getFirstFormField(cityElem.name.replace('cityName', 'city') + 'Id', '');
    validationErrors.debug('v_isCityWithId : cityIdElem: ' + cityIdElem, 2);
    if (v_isNumeric(cityIdElem)) {
      return true;
    }
    return value.length > 2; // Allow also empty.
  }
  return false;

}

// checking we don't get too many children per adult.
// checks the selects/drop downs for children. user
// must choose an age if she has chosen to include children.
function v_validatePaxAndChildAges(field, validationErrors, prefix) {
  var maxSeats = 9;
  var noChildren = v_toNum(field);
  var noAdults = v_toNum(validationErrors.getFirstFormField('adults', prefix));
  var noInfants = 0;

  for (var i = 0; i < noChildren; i++) {
    var ageName = 'childRefs[' + i + '].age';
    var ageElem = validationErrors.getFirstFormField(ageName, prefix);
    validationErrors.debug('ageElem   : ' + ageElem, 2);
    var age = v_toNum(ageElem);
    validationErrors.debug(ageName + '  : ' + age, 2);
    if (age < 0) {
      return false;
    } else if (age < 2) {
      noInfants++;
    }
  }
  validationErrors.debug('noChildren : ' + noChildren, 2);
  validationErrors.debug('noAdults   : ' + noAdults, 2);
  validationErrors.debug('noInfants  : ' + noInfants, 2);

  // invalid no of adults and children
  if (noAdults + noChildren > maxSeats) {
    validationErrors.addError(ageElem, UiText.get('Javascript.Validation.MaxNumSeats') +
                                       maxSeats +
                                       UiText.get('Javascript.Validation.Pieces'));
    return true;
  } else if (noInfants > noAdults) {
    validationErrors.addError(ageElem, 'Javascript.Validation.MaxNumInfantsAdult');
    return true;
  }
  return true;
}

// also see v_validatePaxAndChildAges
function v_validateComboNumPassengers(field, ve, prefix) {
  if (!ve.getFirstFormField('fromCity', 'air.')) return true;// if hotel only
  var maxSeats = " 9 ";
  var numberOfRooms = (+h_getValue(field));
  var adultPrefix = 'adult';
  var childPrefix = 'child';
  var infantPrefix = 'infant';
  var nAdult = 0, nChild = 0, nInfant = 0;

  for (var v = 0; v < numberOfRooms; v++) {
    nAdult += (+h_getValue(getObj(adultPrefix + v)));
    var numChildrenInRoom = (+h_getValue(getObj(childPrefix + v)));
    nChild += numChildrenInRoom;
    for (var i = 0; i < numChildrenInRoom; i++) {
      if ((+h_getValue(getObj(infantPrefix + v + i))) < 2) {
        nInfant++;
        nChild--;
      }
    }
  }

  if (nAdult + nChild > (+maxSeats)) {
    ve.addError(field, UiText.get('Javascript.Validation.MaxNumSeats') +
                       maxSeats +
                       UiText.get('Javascript.Validation.Pieces'));
    return true;
  } else if (nInfant > nAdult) {
    ve.addError(field, 'Javascript.Validation.MaxNumInfantsAdult');
    return true;
  }
  return true;
}

function v_validateAirDates(field, ve, prefix) {
  var depDate = v_toDate(field);
  ve.debug('depDate: ' + depDate);
  if (!depDate) return false;

  var firstPossibleDate = v_toDate(ve.getFirstFormField('firstPossibleDate', prefix));
  if (h_daysUntil(firstPossibleDate, depDate) < 0) {
    ve.addError(field, UiText.get('Air.Search.EarlyTripToDate', h_date2String(firstPossibleDate)));
    return true; // Indicate error message was added
  }

  var lastPossibleDate = v_toDate(ve.getFirstFormField('lastPossibleDate', prefix));
  if (h_daysUntil(depDate, lastPossibleDate) < 0) {
    ve.addError(field, UiText.get('Air.Search.LateTripToDate', h_date2String(lastPossibleDate)));
    return true; // Indicate error message was added
  }

  var oneway = ve.getFirstFormField('oneway', prefix);
  var oneWayVal = h_getValue(oneway);
  var isOneWay = oneWayVal == 'true';
  if (isOneWay) return true;

  var retDateElem = ve.getFirstFormField('retDate', prefix);
  var retDate = v_toDate(retDateElem);
  if (!retDate) {
    ve.addError(retDateElem, 'Air.Search.InvalidReturnTripDate');
    return true; // Indicate error message was added
  }
  if (h_daysUntil(depDate, retDate) < 0) {
    ve.addError(retDateElem, 'Air.Search.ReturnTripDateBeforeTripToDate');
    return true; // Indicate error message was added
  }

  return true;
}

function v_validateHotelDates(field, ve, prefix) {
  var checkIn = v_toDate(field);

  var checkOutElem = ve.getFirstFormField('checkOut', prefix);
  var checkOut = v_toDate(checkOutElem);

  var useAirDatesElem = ve.getFirstFormField('partialHotelDate', 'hotel.');
  var useAirDates = null;
  if (useAirDatesElem) {
    useAirDates = h_getValue(ve.getFirstFormField('partialHotelDate', 'hotel.')) != 'true';
    if (useAirDates) {
      field = ve.getFirstFormField('depDate', 'air.');
      checkOutElem = ve.getFirstFormField('retDate', 'air.');
      checkOut = v_toDate(checkOutElem);
      checkIn = v_toDate(field);
    }
  }

  ve.debug('checkIn: ' + checkIn);
  ve.debug('checkOut: ' + checkOut);
  if (!checkIn) return false;

  var firstPossibleDate = v_toDate(ve.getFirstFormField('firstPossibleDate', prefix));
  if (h_daysUntil(firstPossibleDate, checkIn) < 0) {
    ve.addError(field, UiText.get('Hotel.Search.EarlyCheckInDate', h_date2String(firstPossibleDate)));
    return true; // Indicate error message was added
  }

  var lastPossibleDate = v_toDate(ve.getFirstFormField('lastPossibleDate', prefix));
  if (h_daysUntil(checkIn, lastPossibleDate) < 0) {
    ve.addError(field, UiText.get('Hotel.Search.LateCheckOutDate', h_date2String(lastPossibleDate)));
    return true; // Indicate error message was added
  }

  if (!checkOut) {
    ve.addError(checkOutElem, 'Hotel.Search.InvalidCheckOutDate');
    return true; // Indicate error message was added
  }
  if (h_daysUntil(checkIn, checkOut) < 1) {
    ve.addError(checkOutElem, 'Combo.Search.CheckOutDateBeforeCheckInDate');
    return true; // Indicate error message was added
  }

  if (h_daysUntil(checkIn, checkOut) > 30) {
    ve.addError(checkOutElem, 'Hotel.Search.TooManyNights');
    return true; // Indicate error message was added
  }

  var depDateElem = ve.getFirstFormField('depDate', 'air.');
  var retDateElem = ve.getFirstFormField('retDate', 'air.');
  if (depDateElem && !useAirDates) { // Combo
    var depDate = v_toDate(depDateElem);
    var retDate = v_toDate(retDateElem);
    var oneway = h_getValue(ve.getFirstFormField('oneway', 'air.')) == 'true';
    ve.debug('oneway: ' + oneway);
    if (depDate) {
      ve.debug('h_daysUntil(depDate, checkIn): ' + h_daysUntil(depDate, checkIn));
      if (h_daysUntil(depDate, checkIn) < 0) {
        ve.addError(field, 'Combo.Search.CheckInDateBeforeDepartureDate');
        return true; // Indicate error message was added
      }
    }
    if (!oneway && retDate) {
      ve.debug('h_daysUntil(checkOut, retDate): ' + h_daysUntil(checkOut, retDate));
      if (h_daysUntil(checkOut, retDate) < 0) {
        ve.addError(field, 'Combo.Search.CheckOutDateAfterReturnDate');
        return true; // Indicate error message was added
      }
    }
  }

  return true;
}

/**
 *  This function checks that the first guest name for each room is unique.
 *  Ex: If room 1 has first guest named KALLE kula and room 3 has first guest named kalle KULA: we indicate error situation.*/
function v_validateFirstNameAllRooms(validationErrors) {
  var firstNameSuffix = "firstName";
  var lastNameSuffix = "lastName";
  var uniqueSelector = "[0]\.";
  var findFirstRoomFirstName = uniqueSelector + firstNameSuffix;
  var elems = validationErrors.getFormFields(firstNameSuffix, null, false);
  var i, names = [];
  for (i = 0; i < elems.length; i++) {
    var parts = elems[i].name.split(findFirstRoomFirstName);
    if (parts.length > 1) {
      var firstName = h_trim(h_getValue(elems[i]));
      var lastName = h_trim(h_getValue(validationErrors.getFormFields(parts[0] + uniqueSelector + lastNameSuffix, null, false)));
      names[names.length] = {"name":(firstName + lastName).toLowerCase(), "field": elems[i]};
    }
  }
  var k;
  do {
    var first = names.shift();
    for (k = 0; k < names.length; k++) {
      if (first.name === names[k].name) {
        validationErrors.addError(names[k].field, 'Javascript.Validation.SameNameRestriction');
        return true;// continue with other validators (most likely v_validateAnyNameSameRestriction)
      }
    }
  } while (names.length > 0);

  return true;
}
/**
 * Checks that all names are unique.
 * @param validationErrors */
function v_validateAnyNameSameRestriction(validationErrors) {
  var firstNameSuffix = "firstName";
  var lastNameSuffix = "lastName";
  var elems = validationErrors.getFormFields(firstNameSuffix, null, false);
  var i, names = [];
  for (i = 0; i < elems.length; i++) {
    var parts = elems[i].name.split(firstNameSuffix);
    if (parts.length > 1) {
      var firstName = h_trim(h_getValue(elems[i]));
      var lastName = h_trim(h_getValue(validationErrors.getFormFields(parts[0] + lastNameSuffix, null, false)));
      names[names.length] = {"name":(firstName + lastName).toLowerCase(), "field": elems[i]};
    }
  }
  var k;
  do {
    var first = names.shift();
    for (k = 0; k < names.length; k++) {
      if (first.name === names[k].name) {
        validationErrors.addError(names[k].field, 'Javascript.Validation.AnySameNameRestriction');
        return true;
      }
    }
  } while (names.length > 0);

  return true;
}

function v_validateCarPickupCity(field, validationErrors) {
  var value = h_getValue(field);
  if (h_minlength(value, 2)) {
    var cityIdElem = validationErrors.getFirstFormField(field.name.replace('pickupCityName', 'city') + 'Id', '');
    validationErrors.debug('v_isCityWithId : cityIdElem: ' + cityIdElem, 2);
    if (v_isNumeric(cityIdElem)) {
      return true;
    }
    return value.length > 2; // Allow also empty.
  }
  return false;
}

function v_validateCarDates(field, ve, prefix) {
  var pickupDate = v_toDate(field);
  ve.debug('pickupDate: ' + pickupDate);
  if (!pickupDate) return false;

  var firstPossibleDate = v_toDate(ve.getFirstFormField('firstPossibleDate', prefix));
  if (h_daysUntil(firstPossibleDate, pickupDate) < 0) {
    ve.addError(field, UiText.get('Car.Search.EarlyPickupToDate', h_date2String(firstPossibleDate)));
    return true; // Indicate error message was added
  }

  var lastPossibleDate = v_toDate(ve.getFirstFormField('lastPossibleDate', prefix));
  if (h_daysUntil(pickupDate, lastPossibleDate) < 0) {
    ve.addError(field, UiText.get('Car.Search.LatePickupToDate', h_date2String(lastPossibleDate)));
    return true; // Indicate error message was added
  }

  var returDateElem = ve.getFirstFormField('returnDate', prefix);
  var returnDate = v_toDate(returDateElem);
  if (!returnDate) {
    ve.addError(returDateElem, 'Car.Search.InvalidReturnDate');
    return true; // Indicate error message was added
  }
  if (h_daysUntil(pickupDate, returnDate) < 0) {
    ve.addError(returDateElem, 'Car.Search.ReturnDateBeforePickupDate');
    return true; // Indicate error message was added
  }

  return true;
}

function v_validateCVC(cvcElem, ve, prefix) {
  var value = h_getValue(cvcElem);
  return value && v_isNumeric(cvcElem) && h_minlength(value, 3);
}

function v_validatePaymentMethod(methodElem, ve, prefix) {
  var method = h_getValue(methodElem);
  ve.debug('payment-method: \'' + method + '\'' + ', id: ' + methodElem.id);
  if (method == 'BANK') {
    var elem = ve.getFirstFormField('code', 'bank.');
    if (!v_required(elem)) {
      ve.addError(elem, 'Javascript.Validation.InvalidBank');
    }
    return true;
  } else if (method == 'CARD') {
    var elem = ve.getFirstFormField('cardType', 'creditCard.');
    if (!v_required(elem)) {
      ve.addError(elem, 'Javascript.Validation.InvalidCardType');
    }

    elem = ve.getFirstFormField('cardNumber', 'creditCard.');
    if (elem) { // Only for payment providers supporting sending CC details
      if (!v_validateCreditCard(elem, ve, 'creditCard.')) {
        ve.addError(elem, 'Javascript.CC.InvalidCardNum');
      }
      elem = ve.getFirstFormField('cvcCode', 'creditCard.');
      if (!elem || !v_validateCVC(elem, ve, 'creditCard.')) {
        ve.addError(elem, 'Javascript.Validation.InvalidCVVCode');
      }
    }

    return true;
  } else {
    ve.addError(methodElem, 'Javascript.Validation.InvalidPaymentMethod');
    return true;
  }
}

function v_validateCreditCard(cardElem, ve, prefix) {
  var cn = h_getValue(cardElem);
  var re = /^[0-9]{13,16}$/i;
  if (! h_matchesRegExp(re, cn)) {
    return false;
  }

  if (true) {
    var prefixes =
      [ "5019","4571","402005","4711","541303","512586","5100",
        "361480","3040","3747","3700","676927","5020","417500",
        "3528","600722"];
    var i = 0;
    OUTER:
      for (; i < prefixes.length; i++) {
        if (cn.indexOf(prefixes[i]) == 0) {
          var rest = cn.substring(prefixes[i].length);
          var firstChar = rest.charAt(0);
          if (firstChar == '0' || firstChar == '1') {
            var len = (rest.length - 1);
            for (var x = 1; x < len; x++) {
              if (rest.charAt(x) != '0') {
                continue OUTER;
              }
            }
            return true;
          }
        }
      }
  }
  var sum = 0;
  var mul = 1;
  var l = cn.length;
  for (i = 0; i < l; i++) {
    var digit = cn.substring(l - i - 1, l - i);
    var tproduct = parseInt(digit, 10) * mul;
    if (tproduct >= 10) sum += (tproduct % 10) + 1;
    else sum += tproduct;
    if (mul == 1) mul++;
    else mul--;
  }

  if (!(sum % 10) == 0) {
    return false;
  }

  var monthElem = ve.getFirstFormField('expiryMonth', prefix);
  var yearElem = ve.getFirstFormField('expiryYear', prefix);
  var month = v_toNum(monthElem);
  var year = v_toNum(yearElem);

  if (!h_checkExpiryDate(month, year)) {
    ve.addError(monthElem, 'Javascript.CC.InvalidExpiryDate');
    return true; // Indicate error message was added
  }

  return true;
}


function v_paxFirstName(field, ve, prefix) {
  var firstNameFieldVal = h_getValue(field);
  if (v_enabled(field) &&
      firstNameFieldVal &&
      h_isAlphabeticString(firstNameFieldVal) &&
      h_minlength(firstNameFieldVal, 2)) {
    var maxLength = 28;

    var lastNameElem = ve.getFirstFormField('lastName', prefix);
    var titleElem = ve.getFirstFormField('title', prefix);
    var lastNameFieldVal = h_getValue(lastNameElem);
    var titleFieldVal = h_getValue(titleElem);

    var paxtup = firstNameFieldVal + lastNameFieldVal + titleFieldVal;
    var isOk = (paxtup.length <= maxLength);

    if (!isOk) {
      ve.addError(field, 'Javascript.Util.PaxNameTooLong');
      return true; // Indicate error message was added
    }
    return h_maxlength(firstNameFieldVal, 32);
  }
  return v_enabled(field) ? false : true;
}

function v_paxLastName(field, ve, prefix) {
  var lastNameFieldVal = h_getValue(field);
  if (v_enabled(field) &&
      lastNameFieldVal &&
      h_isAlphabeticString(lastNameFieldVal) &&
      h_minlength(lastNameFieldVal, 2)) {
    return h_maxlength(lastNameFieldVal, 48);
  }
  return v_enabled(field) ? false : true;
}

function v_childBirthDay(field, ve, prefix) {
  var yearElem = ve.getFirstFormField('birthYear', prefix);
  var monthElem = ve.getFirstFormField('birthMonth', prefix);
  var statedAge = h_getValue(ve.getFirstFormField('statedAge', prefix));
  var title = h_getValue(ve.getFirstFormField('title', prefix));
  var year = h_getValue(yearElem);
  var month = h_getValue(monthElem);
  var monthNum = h_toNum(month);
  var day = h_getValue(field);
  var givenDate = monthNum == -1 ? null : (h_toDate(year + '-' + (monthNum + 1) + '-' + day));

  ve.debug('givenDate: ' + givenDate, 2);
  ve.debug('monthNum: ' + monthNum, 2);
  ve.debug('year: ' + year, 2);
  ve.debug('day: ' + day, 2);

  var firstPossibleDate = v_toDate(ve.getFirstFormField('firstPossibleBirthDate', prefix));
  var lastPossibleDate = v_toDate(ve.getFirstFormField('lastPossibleBirthDate', prefix));
  var isAdult = (title && (title == 'MR' || title == 'MS'));

  if (firstPossibleDate) {
    if ((new Date().getFullYear() - firstPossibleDate.getFullYear()) > 20) {
      isAdult = true;
    }
  }
  if (!givenDate) {
    ve.addError(field, isAdult ? 'Javascript.Validation.AdultDateOfBirthRequired' : 'Input.Validation.ChildAgesRequired');
    return true; // Indicate error message was added
  }
  ve.debug('firstPossibleDate: ' + firstPossibleDate, 2);
  ve.debug('lastPossibleDate : ' + lastPossibleDate, 2);
  if (firstPossibleDate && lastPossibleDate) { // For some reason not calculated correctly
    ve.debug('h_daysUntil(firstPossibleDate/' + firstPossibleDate + ', givenDate/' + givenDate + '): ' + h_daysUntil(firstPossibleDate, givenDate), 2);
    ve.debug('h_daysUntil(lastPossibleDate/' + lastPossibleDate + ', givenDate/' + givenDate + '): ' + h_daysUntil(lastPossibleDate, givenDate), 2);
    if (!isAdult) {
      if (h_daysUntil(firstPossibleDate, givenDate) < 0) {
        ve.addError(monthElem, UiText.get('Javascript.Validation.ChildAgeMismatchTooOld', h_date2String(givenDate), statedAge, h_date2String(firstPossibleDate)));
        return true; // Indicate error message was added
      }
    }
    if (h_daysUntil(lastPossibleDate, givenDate) > 0) {
      ve.addError(monthElem, UiText.get(isAdult ? 'Javascript.Validation.AdultAgeMismatchTooYoung' : 'Javascript.Validation.ChildAgeMismatchTooYoung', h_date2String(givenDate), statedAge, h_date2String(lastPossibleDate)));
      return true; // Indicate error message was added
    }
  }

  return true;
}

/*
 <s:hidden name="travellers.travellers[%{#status.index}].firstPossibleBirthDate"
 value="%{#bdContent.firstPossibleBirthDate}"/>
 <s:hidden name="travellers.travellers[%{#status.index}].lastPossibleBirthDate"
 value="%{#bdContent.lastPossibleBirthDate}"/>
 <s:select name="travellers.travellers[%{#status.index}].birthYear"
 list="%{#bdContent.yearOptions}"
 emptyOption="true"/>
 <s:select name="travellers.travellers[%{#status.index}].birthMonth"
 list="%{#bdContent.monthOptions}"
 emptyOption="true"/>
 <s:select name="travellers.travellers[%{#status.index}].birthDay"
 list="%{#bdContent.days}"
 emptyOption="true"/>
 */
