//@include lib_Class.js

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

  /**
   * Returns the "end of" date
   * @param {Date} date Date
   * @param {String} unit Unit
   * @returns {Date} End of date
   */
  getEndOfDate: function (date, unit) {
    var momentDate;
    momentDate = moment(date.getTime());
    return momentDate.endOf(unit).startOf("day").toDate();
  },

  allUnits: ["y", "hy", "Q", "M", "w", "d"],

  /**
   * Returns the smallest duration unit
   * @param {Array} unitStrings Units strings
   * @return {String}
   */
  getSmallestUnitString: function (unitStrings) {
    var me = this,
        unit, smallestUnit, unitString,
        i, j, u = 0;

    if (!unitStrings) {
      throw "Units strings are empty";
    }

    if (unitStrings.length == 1) {
      return unitStrings[0];
    }

    for (i = 0; i < unitStrings.length; i++) {
      unit = me.getKwlKey(unitStrings[i]);
      for (j = u; j < me.allUnits.length; j++) {
        if (unit == me.allUnits[j]) {
          u = j;
        }
      }
    }

    smallestUnit = me.allUnits[u];

    for (i = 0; i < unitStrings.length; i++) {
      unitString = unitStrings[i];
      unit = me.getKwlKey(unitString);
      if (smallestUnit == unit) {
        return unitString;
      }
    }
  },

  /**
   * Calculates a duration
   * @param {String} startDateIso Start date
   * @param {String} endDateIso End date
   * @param {String} unit
   * @return {Number}
   */
  calcDuration: function (startDateIso, endDateIso, unit) {
    var me = this,
        startDate, endDate, number;

    unit = me.getKwlKey(unit);

    if (!unit || (unit == "O")) {
      return 1;
    }

    if (!startDateIso || !endDateIso) {
      return "";
    }

    startDate = sol.common.DateUtils.isoToDate(String(startDateIso));
    endDate = sol.common.DateUtils.isoToDate(String(endDateIso));

    number = sol.common.DateUtils.diff(startDate, endDate, unit, { roundUp: true });

    return number;
  },

  /**
   * Returns the keyword list key
   * @param {String} str String
   * @param {String} separator Separator
   * @return {String}
   */
  getKwlKey: function (str, separator) {
    var separatorPos;

    if (!str) {
      return "";
    }

    str = String(str);
    separator = separator || "-";
    separatorPos = str.indexOf(separator);
    if (separatorPos < 0) {
      return str;
    }
    return str.substring(0, separatorPos).trim();
  },

  /**
   * Calculates the row total
   * @param {String} startDateIso Start date
   * @param {String} endDateIso End date
   * @param {Decimal} singleAmount Single amount
   * @param {String} unit Unit
   * @return {Decimal}
   */
  calcRowTotal: function (startDateIso, endDateIso, singleAmount, unit) {
    var me = this,
        count, rowTotal;

    count = me.calcDuration(startDateIso, endDateIso, unit);
    if (count == "") {
      return new Decimal(0);
    }

    if (typeof singleAmount == "number") {
      singleAmount = new Decimal(singleAmount);
    }

    rowTotal = singleAmount.times(count);
    return rowTotal;
  },

  /**
   * Returns the base currency
   * @return {String} Base currency
   */
  getBaseCurrency: function () {
    return "EUR";
  },

  /**
   * Calculates the local currency amount
   * @param {Number} amount Amount
   * @param {Number} exchangeRate Exchange rate
   * @return {Number}
   */
  calcLocalCurrencyAmount: function (amount, exchangeRate) {
    var localCurrencyAmount;
    localCurrencyAmount = amount / exchangeRate;
    return localCurrencyAmount;
  },

  /**
   * Calculates contract data
   * @param {Object} tplSord Template Sord
   * @param {Object} updates Template Sord
   * @return {Object} updates Updates
   */
  calcContract: function (tplSord, updates) {
    var me = this,
        additionalShiftCounter = 0,
        permanent, specialTerminationRight, terminationDate;

    if (!tplSord) {
      throw "Template Sord is empty";
    }

    permanent = (tplSord.mapKeys.DURATION_TYPE == "sol.contract.form.permanent");

    if (permanent) {
      do {
        me.calcNextPossibleContractEnd(tplSord, updates, additionalShiftCounter);
        terminationDate = me.calcTerminationDateFromNow(tplSord, updates);
        additionalShiftCounter++;
      } while (terminationDate && terminationDate.isBefore(moment().startOf("d")) && (additionalShiftCounter <= 10));
    } else {
      me.calcContractEnd(tplSord, updates);
      specialTerminationRight = (tplSord.mapKeys.SPECIAL_TERMINATION_RIGHT == "1");
      if (specialTerminationRight) {
        me.calcNextPossibleContractEnd(tplSord, updates, 0);
        me.calcTerminationDateFromNow(tplSord, updates);
      } else {
        me.calcTerminationDateFixed(tplSord, updates);
      }
    }
    me.calcReminderDate(tplSord, updates);

    return updates;
  },

  /**
   * @private
   * Converts an ISO date to a moment date
   * @param {String} isoDate ISO date
   * @return {Object} Moment date
   */
  isoToMoment: function (isoDate) {
    var mom;

    if (!isoDate || (isoDate.length < 8)) {
      return;
    }

    isoDate = isoDate.substr(0, 8);

    mom = moment(isoDate, "YYYYMMDD");

    return mom;
  },

  /**
   * @private
   * Converts a moment date to an ISO date
   * @param {Object} mom Moment date
   * @return {String} ISO date
   */
  momentToIso: function (mom) {
    var isoDate;
    if (!mom) {
      return "";
    }
    isoDate = mom.format("YYYYMMDD") + "000000";
    return isoDate;
  },

  /**
   * @private
   * Clears the contract end date
   * @param {Object} tplSord Tempate Sord
   */
  clearContractEnd: function (tplSord) {
    if (!tplSord.objKeys.CONTRACT_START) {
      tplSord.objKeys.CONTRACT_END = "";
    }
  },

  /**
   * @private
   * @param {Object} tplSord Tempate Sord
   * @param {Object} updates Updates
   * @param {Number} additionalShiftCounter Additional shift counter
   */
  calcNextPossibleContractEnd: function (tplSord, updates, additionalShiftCounter) {
    var me = this,
        nextPossibleContractEndIso = "",
        nextPossibleContractEnd, noticePeriodEnd,
        now, noticePeriod, noticePeriodNumber, noticePeriodUnit, contractEnd, contractStart, startDate,
        contractMinTermNumber, contractMinTermUnit, contractMinTerm, contractMinEnd, terminationPoint,
        contractEndIso;

    contractEndIso = tplSord.objKeys.CONTRACT_END;

    if (contractEndIso && (tplSord.mapKeys.DURATION_TYPE == "sol.contract.form.permanent")) {
      me.addUpdate(tplSord, updates, { type: "MAP", key: "NEXT_POSSIBLE_CONTRACT_END", value: "" });
      return;
    }

    contractEnd = me.isoToMoment(contractEndIso);
    if (contractEnd) {
      contractEnd = contractEnd.endOf("d");
    }
    now = moment();

    noticePeriodNumber = tplSord.mapKeys.NOTICE_PERIOD || 0;
    noticePeriodUnit = me.getKwlKey(tplSord.mapKeys.NOTICE_PERIOD_UNIT);

    contractStart = me.isoToMoment(tplSord.objKeys.CONTRACT_START);
    if (!contractStart) {
      return;
    }

    contractMinTermNumber = tplSord.mapKeys.CONTRACT_MIN_TERM;
    contractMinTermUnit = me.getKwlKey(tplSord.mapKeys.CONTRACT_MIN_TERM_UNIT);
    contractMinTerm = sol.common.DateUtils.prepareDuration(contractMinTermNumber, contractMinTermUnit);

    startDate = (contractStart.isAfter(now.clone().add(contractMinTerm))) ? contractStart : now;

    noticePeriod = sol.common.DateUtils.prepareDuration(noticePeriodNumber, noticePeriodUnit);
    noticePeriodEnd = startDate.clone().add(noticePeriod);

    if (contractMinTerm) {
      contractMinEnd = contractStart.add(contractMinTerm);
    }

    terminationPoint = me.getKwlKey(tplSord.mapKeys.TERMINATION_POINT);

    nextPossibleContractEnd = noticePeriodEnd;

    if (contractMinEnd && contractMinEnd.isAfter(noticePeriodEnd)) {
      nextPossibleContractEnd = contractMinEnd;
    }

    terminationPoint = terminationPoint || "d";
    nextPossibleContractEnd = nextPossibleContractEnd.add(additionalShiftCounter, terminationPoint);

    nextPossibleContractEnd = nextPossibleContractEnd.add(-1, "d");

    nextPossibleContractEnd = sol.common.DateUtils.endOf(nextPossibleContractEnd, terminationPoint);

    nextPossibleContractEndIso = me.momentToIso(nextPossibleContractEnd);

    if ((tplSord.mapKeys.DURATION_TYPE == "sol.contract.form.fixedTerm") && (tplSord.mapKeys.SPECIAL_TERMINATION_RIGHT == "1")) {
      if (nextPossibleContractEndIso && nextPossibleContractEnd.isAfter(contractEnd)) {
        nextPossibleContractEndIso = "";
      }
    }

    if (nextPossibleContractEnd.isSame(now) || nextPossibleContractEnd.isBefore(now) || !noticePeriod) {
      nextPossibleContractEndIso = "";
    }

    me.addUpdate(tplSord, updates, { type: "MAP", key: "NEXT_POSSIBLE_CONTRACT_END", value: nextPossibleContractEndIso });
  },

  /**
   * @private
   * Add update
   * @param {Object} tplSord Template Sord
   * @param {Array} updates Updates array
   * @param {Object} update Update
   */
  addUpdate: function (tplSord, updates, update) {
    if (update.type == "GRP") {
      tplSord.objKeys[update.key] = update.value;
      updates.objKeys[update.key] = update.value;
    } else {
      tplSord.mapKeys[update.key] = update.value;
      updates.mapKeys[update.key] = update.value;
    }
  },

  /**
   * @private
   * @param {Object} tplSord Tempate Sord
   * @param {Object} updates Updates
   * @param {Number} additionalShiftCounter Additional shift counter
   */
  calcContractEnd: function (tplSord, updates, additionalShiftCounter) {
    var me = this,
        contractStart, contractDuration, contractEnd, contractEndIso, unitKey,
        extensionFlag, extensionInterval, extensionIntervalUnit, extensionStatusValues, contractStatus,
        extension, noticePeriod, nextPossibleTermination, nextPossibleTerminationStartOfDay, nowMoment;

    contractStart = me.isoToMoment(tplSord.objKeys.CONTRACT_START);

    if (!contractStart) {
      return;
    }

    contractDuration = sol.common.DateUtils.prepareDuration(tplSord.mapKeys.CONTRACT_DURATION, tplSord.mapKeys.CONTRACT_DURATION_UNIT);

    contractEnd = contractStart.clone().add(contractDuration);

    extensionInterval = tplSord.mapKeys.EXTENSION_INTERVAL;
    extensionIntervalUnit = tplSord.mapKeys.EXTENSION_INTERVAL_UNIT;

    extensionFlag = (tplSord.mapKeys.EXTENSION_FLAG == "1");
    contractStatus = me.getKwlKey(tplSord.objKeys.CONTRACT_STATUS);

    extensionStatusValues = ["D", "I", "A", "N", "S"];

    if (extensionFlag && (+extensionInterval > 0) && extensionIntervalUnit && (extensionStatusValues.indexOf(contractStatus) > -1)) {
      extension = sol.common.DateUtils.prepareDuration(extensionInterval, extensionIntervalUnit);

      noticePeriod = sol.common.DateUtils.prepareDuration(tplSord.mapKeys.NOTICE_PERIOD, tplSord.mapKeys.NOTICE_PERIOD_UNIT);

      if (noticePeriod) {
        nextPossibleTermination = contractEnd.clone().subtract(noticePeriod);
      } else {
        nextPossibleTermination = contractEnd.clone();
      }

      nextPossibleTerminationStartOfDay = nextPossibleTermination.clone().startOf("d");
      nowMoment = moment();

      while (nextPossibleTerminationStartOfDay.isBefore(nowMoment)) {
        contractEnd.add(extension);
        if (noticePeriod) {
          nextPossibleTermination = contractEnd.clone().subtract(noticePeriod);
        } else {
          nextPossibleTermination = contractEnd.clone();
        }
        nextPossibleTerminationStartOfDay = nextPossibleTermination.clone().startOf("d");
      }

      me.logger.debug(["ExtendContract: newContractEnd={0}]", contractEnd.toString()]);
    }

    contractEndIso = me.momentToIso(contractEnd);

    if (contractDuration) {
      unitKey = me.getKwlKey(tplSord.mapKeys.CONTRACT_DURATION_UNIT);
      if ((contractStart.date() == contractEnd.date()) || !unitKey || (unitKey == "d")) {
        contractEnd = contractEnd.add(-1, "d");
      }
    } else {
      if (tplSord.objKeys.CONTRACT_END) {
        contractEnd = me.isoToMoment(tplSord.objKeys.CONTRACT_END);
      } else {
        return;
      }
    }

    contractEndIso = me.momentToIso(contractEnd);

    me.addUpdate(tplSord, updates, { type: "MAP", key: "NEXT_POSSIBLE_CONTRACT_END", value: "" });
    me.addUpdate(tplSord, updates, { type: "GRP", key: "CONTRACT_END", value: contractEndIso });
  },

  /**
   * @private
   * @param {Object} tplSord Tempate Sord
   * @param {Object} updates Updates
   * @return {Moment} Next possible termination date
   */
  calcTerminationDateFromNow: function (tplSord, updates) {
    var me = this,
        nextPossibleContractEnd, noticePeriod, nextPossibleTermination, nextPossibleTerminationIso;

    nextPossibleContractEnd = me.isoToMoment(tplSord.mapKeys.NEXT_POSSIBLE_CONTRACT_END);

    if (!nextPossibleContractEnd) {
      me.addUpdate(tplSord, updates, { type: "GRP", key: "NEXT_POSSIBLE_TERMINATION", value: "" });
      return;
    }

    noticePeriod = sol.common.DateUtils.prepareDuration(tplSord.mapKeys.NOTICE_PERIOD, tplSord.mapKeys.NOTICE_PERIOD_UNIT);

    if (!noticePeriod) {
      return;
    }

    nextPossibleTermination = nextPossibleContractEnd.clone().subtract(noticePeriod);

    nextPossibleTermination = me.adjustToRealMonthEnd(nextPossibleTermination, nextPossibleContractEnd, [tplSord.mapKeys.NOTICE_PERIOD_UNIT, tplSord.mapKeys.TERMINATION_POINT]);

    nextPossibleTerminationIso = me.momentToIso(nextPossibleTermination);

    if (nextPossibleTermination.isBefore(moment().startOf("d"))) {
      nextPossibleTerminationIso = "";
    }

    me.addUpdate(tplSord, updates, { type: "GRP", key: "NEXT_POSSIBLE_TERMINATION", value: nextPossibleTerminationIso });

    return nextPossibleTermination;
  },

  /**
   * @private
   * @param {Object} tplSord Template Sord
   * @param {Object} updates Updates
   * @return {Moment} Next possible termination date
   */
  calcTerminationDateFixed: function (tplSord, updates) {
    var me = this,
        nextPossibleTerminationIso = "",
        contractEnd, noticePeriod, nextPossibleTermination;

    contractEnd = me.isoToMoment(tplSord.objKeys.CONTRACT_END);

    if (!contractEnd) {
      return;
    }

    contractEnd = me.isoToMoment(tplSord.objKeys.CONTRACT_END);

    noticePeriod = sol.common.DateUtils.prepareDuration(tplSord.mapKeys.NOTICE_PERIOD, tplSord.mapKeys.NOTICE_PERIOD_UNIT);

    if (noticePeriod) {
      nextPossibleTermination = contractEnd.clone().subtract(noticePeriod);

      if (nextPossibleTermination.isAfter(moment().startOf("d"))) {
        nextPossibleTermination = me.adjustToRealMonthEnd(nextPossibleTermination, contractEnd, [tplSord.mapKeys.NOTICE_PERIOD_UNIT, tplSord.mapKeys.TERMINATION_POINT]);
        nextPossibleTerminationIso = me.momentToIso(nextPossibleTermination);
      }
    }

    me.addUpdate(tplSord, updates, { type: "GRP", key: "NEXT_POSSIBLE_TERMINATION", value: nextPossibleTerminationIso });
  },

  /**
   * @private
   * @param {Moment} adjustMoment Adjust moment
   * @param {Moment} indicatorMoment Indicator moment
   * @param {String} indicatorUnits Indicator units
   */
  adjustToRealMonthEnd: function (adjustMoment, indicatorMoment, indicatorUnitStrings) {
    var me = this,
        endOfMonthUnits, i, indicatorUnitString, indicatorUnitKey;

    endOfMonthUnits = ["y", "hy", "Q", "M"];

    if (!adjustMoment || !indicatorMoment || !indicatorUnitStrings || (indicatorUnitStrings.length == 0)) {
      return adjustMoment;
    }

    if (!sol.common.DateUtils.isLastDayOfMonth(indicatorMoment, { toleranceDays: 1 })) {
      return adjustMoment;
    }

    for (i = 0; i < indicatorUnitStrings.length; i++) {
      indicatorUnitString = indicatorUnitStrings[i];
      indicatorUnitKey = me.getKwlKey(indicatorUnitString);
      if (endOfMonthUnits.indexOf(indicatorUnitKey) < 0) {
        return adjustMoment;
      }
    }

    adjustMoment = adjustMoment.clone().endOf("M");

    return adjustMoment;
  },

  /**
   * @private
   * @param {Object} tplSord Tempate Sord
   * @param {Object} updates Updates
   */
  calcReminderDate: function (tplSord, updates) {
    var me = this,
        startDateIso, startDate, reminderDate, reminderDateIso, reminderPeriod;

    startDateIso = tplSord.objKeys.NEXT_POSSIBLE_TERMINATION || tplSord.mapKeys.NEXT_POSSIBLE_CONTRACT_END || tplSord.objKeys.CONTRACT_END;

    startDate = me.isoToMoment(startDateIso);

    if (!startDate) {
      return;
    }

    reminderPeriod = sol.common.DateUtils.prepareDuration(tplSord.mapKeys.REMINDER_PERIOD, tplSord.mapKeys.REMINDER_PERIOD_UNIT);

    if (!reminderPeriod) {
      return;
    }

    reminderDate = startDate.subtract(reminderPeriod);

    reminderDateIso = me.momentToIso(reminderDate);

    me.addUpdate(tplSord, updates, { type: "GRP", key: "NEXT_REMINDER_DATE", value: reminderDateIso });
  }
});