/**
 * Helper functions for ELOwf forms
 *
 * @author MW, ELO Digital Office GmbH
 * @version 1.0
 *
 * @elowf
 * @requires sol.common.DateUtils
 */
sol.define("sol.contract.forms.Utils", {
  singleton: true,

  unitSuffix: "_UNIT",
  localizedKwlSeparator: "-",

  /**
   * Iterates over a table.
   * @param {String} indicatorColumnName Name of a column to check if the line exists.
   * @param {Function} func Callback function for the iteration.
   * @param {Object} ctx Execution context.
   */
  forEachRow: function (indicatorColumnName, func, ctx) {
    if (!indicatorColumnName) {
      throw "The indicator column name is empty.";
    }
    if (!func) {
      throw "The function parameter is emtpy.";
    }

    var i = 1;
    while ($var(indicatorColumnName + i)) {
      func.call(ctx, i++);
    }
  },

  /**
   * Checks if the specific field has been changed or the form is loaded
   * @param {Object} source Changed HTML element.
   * @param {Array} fieldNames Array of field names.
   * @return {undefined}
   */
  isFieldChangedOrFormLoaded: function (source, fieldNames) {
    var me = this;
    if (!me.checkSource(source)) {
      return true;
    }
    return me.isFieldChanged(source, fieldNames);
  },

  /**
   * Checks if the specific field has been changed
   * @param {Object} source Changed HTML element.
   * @param {Array} fieldNames Array of field names.
   * @return {undefined}
   */
  isFieldChanged: function (source, fieldNames) {
    var me = this;
    if (!source) {
      return false;
    }
    return fieldNames.some(function (fieldName) {
      var fieldBaseName = me.getBaseFieldName(source.name);
      return ((fieldBaseName == fieldName) || (fieldBaseName + me.unitSuffix == fieldName));
    });
  },

  /**
   * Checks if the changed input field es set
   * @param {Object} source Changed input field
   * @return {Boolean}
   */
  checkSource: function (source) {
    return (source && source.name);
  },

  /**
   * Returns the field name without unit suffix and trailing numbers
   * @param {Object|String} source Source element or field name
   * @returns {String}
   */
  getBaseFieldName: function (source) {
    var me = this,
        fieldName, unitSuffixRegExp;

    if (me.checkSource(source)) {
      fieldName = source.name;
    } else {
      fieldName = source;
    }

    if (!fieldName) {
      return "";
    }
    fieldName = fieldName.replace(/\d*$/g, "");
    unitSuffixRegExp = new RegExp(me.unitSuffix + "$");
    fieldName = fieldName.replace(unitSuffixRegExp, "");
    return fieldName;
  },

  /**
   * Returns the field index
   * @param {String} fieldName Field name
   * @returns {String}
   */
  getFieldNameIndex: function (fieldName) {
    if (!fieldName) {
      return "";
    }
    var pos = fieldName.search(/\d+$/);
    if (pos > 0) {
      return parseInt(fieldName.substring(pos), 10);
    }
    return "";
  },

  /**
   * Returns the first part of a string
   * @param {String} str String
   * @param {String} separator Separator
   * @return {String}
   */
  getFirstStringPart: function (str, separator) {
    var separatorPos;
    if (!str) {
      return "";
    }
    separator = separator || "-";
    separatorPos = str.indexOf(separator);
    if (separatorPos < 0) {
      return str;
    }
    return str.substring(0, separatorPos).trim();
  },

  /**
   * Calculates a date
   * @param {String} srcDateFieldName Source date field name
   * @param {String} durationFieldName Duration field name
   * @param {String} dstDateFieldName Destination date field name
   * @param {Object} params Parameters
   * @param {String} params.minTermStartFieldName
   * @param {String} params.minTermNumberFieldName
   * @param {String} params.endOfFieldName
   * @param {String} params.endOfUnit
   * @param {String} params.substractOneDay
   * @param {String} params.dontSetIfDurationIsEmpty
   * @param {String} params.minToday
   */
  setCalculatedDate: function (srcDateFieldName, durationFieldName, dstDateFieldName, params) {
    var me = this,
        srcIsoDate, durationNumber, durationUnit, dstIsoDate, minTermNumber, minTermUnit, minTermStartIsoDate, minTermIsoDate, todayIso;

    params = params || {};

    todayIso = sol.common.DateUtils.dateToIso(new Date(), { startOfDay: true });

    if (srcDateFieldName == "$NOW") {
      srcIsoDate = todayIso;
    } else {
      srcIsoDate = sol.common.forms.Utils.getIsoDate(srcDateFieldName, { startOfDay: true });
    }

    if (srcIsoDate) {
      durationNumber = $val(durationFieldName) || 0;
      if (!durationNumber && params.dontSetIfDurationIsEmpty) {
        return;
      }

      durationUnit = me.getKwlKey(durationFieldName + me.unitSuffix);
      if (params.endOfFieldName) {
        params.endOfUnit = me.getKwlKey(params.endOfFieldName) || "d";
      }

      if (params.substractOneDay) {
        srcIsoDate = sol.contract.DurationUtils.calculateDate(srcIsoDate, -1, "d");
      }

      dstIsoDate = sol.contract.DurationUtils.calculateDate(srcIsoDate, durationNumber, durationUnit, params);

      if (params.minTermNumberFieldName) {
        minTermStartIsoDate = sol.common.forms.Utils.getIsoDate(params.minTermStartFieldName, { startOfDay: true });
        minTermNumber = $val(params.minTermNumberFieldName) || 0;
        minTermUnit = me.getKwlKey(params.minTermNumberFieldName + me.unitSuffix);
        minTermIsoDate = sol.contract.DurationUtils.calculateDate(minTermStartIsoDate, minTermNumber, minTermUnit);

        if (minTermIsoDate > dstIsoDate) {
          dstIsoDate = minTermIsoDate;
        }

        if (params.minToday && (dstIsoDate < todayIso)) {
          dstIsoDate = todayIso;
        }
      }

      if (!dstIsoDate) {
        console.warn("Calculated date is empty");
      }
      sol.common.forms.Utils.setIsoDate(dstDateFieldName, dstIsoDate);
    } else {
      $update(dstDateFieldName, "");
    }
    inputChanged($var(dstDateFieldName));
  },

  /**
   * Set a destination field writeable if it's empty
   * @param {String} srcFieldName Source field name
   * @param {String} dstFieldName Destination field name
   */
  setDstFieldWriteableIfEmpty: function (srcFieldName, dstFieldName) {
    var me = this;
    me.setReadOnly(dstFieldName, !!$val(srcFieldName));
  },

  /**
   * Sets an input field read-only
   * @param {String} fieldName
   * @param {Boolean} readOnly Read-only
   */
  setReadOnly: function (fieldName, readOnly) {
    if (!fieldName) {
      throw "Field name is emtpy";
    }
    var field = $var(fieldName);
    if (!field) {
      throw "Field not found";
    }
    readOnly = (typeof readOnly == "undefined") ? true : readOnly;
    field.readOnly = readOnly;
    if (readOnly) {
      field.parentElement.setAttribute("isreadonly", readOnly);
    } else {
      field.parentElement.removeAttribute("isreadonly");
    }
  },

  /**
   * Returns the keyword list key of a localized keyword list field
   * @param {String} fieldName Field name
   * @return {String}
   */
  getKwlKey: function (fieldName) {
    var me = this,
        value;
    value = $val(fieldName);
    return me.getFirstStringPart(value, me.localizedKwlSeparator);
  },

  /**
   * Returns the field name of the unit field for the given field name
   * @param {String} fieldName Field name
   * @return {String}
   */
  getUnitFieldName: function (fieldName) {
    var me = this;

    if (!fieldName) {
      throw "Field name is empty";
    }
    return me.getBaseFieldName(fieldName) + me.unitSuffix + me.getFieldNameIndex(fieldName);
  },

  /**
   * @deprecated Use {@link sol.common.forms.Utils#fieldExists} instead (from `common` version `1.01.002`)
   * Checks wether a field exists
   * @param {String} fieldName
   * @return {Boolean}
   */
  fieldExists: function (fieldName) {
    if (!fieldName) {
      throw "Field name is empty";
    }
    return !!$var(fieldName);
  },

  /**
   * Checks wether a field exists
   * @param {Object|String} source element or current field name
   * @param {String} fieldName Field name to check
   * @return {Boolean}
   */
  isField: function (source, fieldName) {
    var me = this,
        currentFieldName;

    if (!fieldName) {
      return false;
    }
    currentFieldName = me.getBaseFieldName(source);
    return (currentFieldName == fieldName);
  },

  /**
   * Sets a localized keyword list field
   * @param {String} fieldName Field name
   * @param {String} key Key
   * @param {Object} config Configuration
   */
  setLocalizedKwlField: function (fieldName, key, config) {
    var field, kwlElement, kwlName;
    config = config || {};

    if (config.onlyIfEmpty && ($val(fieldName) || ELO_PARAMS[fieldName])) {
      return;
    }

    field = $var(fieldName);
    if (!field) {
      return;
    }

    kwlElement = field.nextSibling;
    if (!kwlElement) {
      return;
    }

    kwlName = kwlElement.getAttribute("swlname");
    if (!kwlName) {
      return;
    }
    if (kwlName.indexOf("DYNSWL_") != 0) {
      return;
    }
    kwlName = kwlName.substring(7);

    $update(fieldName, key);

    $listDyn(kwlName, fieldName, undefined,
      function (data) {
        var i, cells;
        if (!data || !data.table || (data.table.length == 0)) {
          return;
        }
        for (i = 0; i < data.table.length; i++) {
          cells = data.table[i];
          if (cells[0] == key) {
            $update(fieldName, cells[2]);
            return;
          }
        }
      },
      function () {
        console.warn("Can't load dynamic keyword list '" + kwlName + "'");
      }
    );
  },

  localCurrencySuffix: "_LOCAL_CURR",

  /**
   * Calculates and sets the local currency amount
   * @param {String} amountFieldName Amount field name
   * @param {String} exchangeRateFieldName Exchange rate field name
   * @param {Object} config Configuration
   * @param {String} config.currencyCodeFieldName Amount field name
   * @param {String} config.baseCurrencyCode Base currency code
   * @param {String} config.baseCurrencyCodeFieldName Base currency code field name
   * @param {String} config.localCurrencyAmountFieldName Local currency amount field name
   * @param {Array} config.dependentElementNames This fields will be shown resp. hidden
   */
  calcLocalCurrencyAmount: function (amountFieldName, exchangeRateFieldName, config) {
    var me = this,
        currencyCode, exchangeRate, localCurrencyAmountFieldName, amount, localCurrencyAmount;

    if (!amountFieldName) {
      throw "Amount field name is empty";
    }

    if (!exchangeRateFieldName) {
      throw "Exchange rate field name is empty";
    }

    config = config || {};
    localCurrencyAmountFieldName = config.localCurrencyAmountFieldName || amountFieldName + me.localCurrencySuffix;


    if (config.currencyCodeFieldName && config.baseCurrencyCode) {
      currencyCode = $val(config.currencyCodeFieldName);

      if (!currencyCode || (currencyCode == config.baseCurrencyCode)) {
        $update(config.currencyCodeFieldName, config.baseCurrencyCode);
        $update(exchangeRateFieldName, "1");
      }
    }
    me.updateBaseCurrencyCodeField(config);
    amount = $num(amountFieldName);
    exchangeRate = $num(exchangeRateFieldName);

    localCurrencyAmount = sol.contract.DurationUtils.calcLocalCurrencyAmount(amount, exchangeRate);
    $update(localCurrencyAmountFieldName, localCurrencyAmount);

    me.enableElements(config.dependentElementNames, Boolean(exchangeRate != 1), { parentTagName: "td" });
  },

  /**
   * Reads base currency code from configuration and writes this to map field
   * @param {Object} config Configuration
   * @param {String} config.baseCurrencyCode Base currency code
   * @param {String} config.baseCurrencyCodeFieldName Base currency code field name
   */
  updateBaseCurrencyCodeField: function (config){
    config = config || {};
    if (config.baseCurrencyCodeFieldName && config.baseCurrencyCode && sol.common.forms.Utils.fieldExists(config.baseCurrencyCodeFieldName)){
      $update(config.baseCurrencyCodeFieldName, config.baseCurrencyCode);
    }
  },

  /**
   * Calculates a date by start date an duration
   * @param {String} startDateBaseFieldName Start date field name without index
   * @param {String} endDateFieldName
   */
  calcDateByDuration: function (startDateBaseFieldName, endDateFieldName) {
    var me = this,
        startDateIso, endDateValue, durationNumber, durationUnit, endDateIso,
        index;

    if (!startDateBaseFieldName) {
      throw "Start date base field name is empty";
    }

    if (!endDateFieldName) {
      throw "End date field name is empty";
    }

    index = me.getFieldNameIndex(endDateFieldName);
    startDateIso = sol.common.forms.Utils.getIsoDate(startDateBaseFieldName + index, { startOfDay: true });

    if (!startDateIso) {
      return;
    }

    endDateValue = $val(endDateFieldName);
    if (!me.isValidDurationUnit(endDateValue)) {
      return;
    }

    durationNumber = me.getDurationNumber(endDateValue);
    durationUnit = me.getDurationUnit(endDateValue);
    durationUnit = me.normalizeDurationUnit(durationUnit);

    endDateIso = sol.contract.DurationUtils.calculateDate(startDateIso, durationNumber, durationUnit);
    endDateIso = sol.contract.DurationUtils.calculateDate(endDateIso, -1, "d");

    sol.common.forms.Utils.setIsoDate(endDateFieldName, endDateIso);
  },

  /**
   * Checks whether a string is a duration
   * @param {String} str Input string
   * @return {Boolean}
   */
  isValidDurationUnit: function (str) {
    return /^\d+[y|m|w|d]$/i.test(str);
  },

  /**
   * Normalize duration unit
   * @param {String} durationUnit Duration unit
   * @return {String}
   */
  normalizeDurationUnit: function (durationUnit) {
    if (!durationUnit) {
      return;
    }
    if (durationUnit == "m") {
      return "M";
    } else {
      return durationUnit.toLowerCase();
    }
  },

  /**
   * Returns the duration number of a duration string
   * @param {String} str Input string
   * @returns {String}
   */
  getDurationNumber: function (str) {
    var matches;

    matches = str.match(/^\d+/);

    return (matches && (matches.length > 0)) ? matches[0] : "";
  },

  /**
   * Returns the duration unit of a duration string
   * @param {String} str Input string
   * @returns {String}
   */
  getDurationUnit: function (str) {
    var matches;

    matches = str.match(/[y|M|w|d|m|s|ms]$/);

    return (matches && (matches.length > 0)) ? matches[0] : "";
  },

  /**
   * Shows or hides elements
   * @param {Array} elementNames Array of field names
   * @param {Boolean} show True if the element should be shown.
   * @param {Object} config Configuration
   * @param {String} config.parentTagName Parent tag name
   */
  enableElements: function (elementNames, show, config) {
    var element, i, elementName;

    if (!elementNames) {
      return;
    }

    config = config || {};

    for (i = 0; i < elementNames.length; i++) {
      elementName = elementNames[i];

      element = $var(elementName);

      if (!element) {
        continue;
      }

      if (config.parentTagName) {
        element = sol.common.forms.Utils.getParentByTagName(element, "td");
      }

      if (show) {
        element.classList.remove("hidden");
      } else {
        element.classList.add("hidden");
      }
    }
  }
});