    /**
     * 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");
          }
        }
      }
    });