import { Duration, isDuration } from "./constructor.js";
import isNumber from "../utils/is-number.js";
import toInt from "../utils/to-int.js";
import absRound from "../utils/abs-round.js";
import hasOwnProp from "../utils/has-own-prop.js";
import { DATE, HOUR, MINUTE, SECOND, MILLISECOND } from "../units/constants.js";
import { cloneWithOffset } from "../units/offset.js";
import { createLocal } from "../create/local.js";
import { createInvalid as invalid } from "./valid.js"; // ASP.NET json date format regex

var aspNetRegex = /^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/,
    // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
// somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
// and further modified to allow for strings containing both week and day
isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;
export function createDuration(input, key) {
  var duration = input,
      // matching against regexp is expensive, do it on demand
  match = null,
      sign,
      ret,
      diffRes;

  if (isDuration(input)) {
    duration = {
      ms: input._milliseconds,
      d: input._days,
      M: input._months
    };
  } else if (isNumber(input) || !isNaN(+input)) {
    duration = {};

    if (key) {
      duration[key] = +input;
    } else {
      duration.milliseconds = +input;
    }
  } else if (match = aspNetRegex.exec(input)) {
    sign = match[1] === '-' ? -1 : 1;
    duration = {
      y: 0,
      d: toInt(match[DATE]) * sign,
      h: toInt(match[HOUR]) * sign,
      m: toInt(match[MINUTE]) * sign,
      s: toInt(match[SECOND]) * sign,
      ms: toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match

    };
  } else if (match = isoRegex.exec(input)) {
    sign = match[1] === '-' ? -1 : 1;
    duration = {
      y: parseIso(match[2], sign),
      M: parseIso(match[3], sign),
      w: parseIso(match[4], sign),
      d: parseIso(match[5], sign),
      h: parseIso(match[6], sign),
      m: parseIso(match[7], sign),
      s: parseIso(match[8], sign)
    };
  } else if (duration == null) {
    // checks for null or undefined
    duration = {};
  } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) {
    diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to));
    duration = {};
    duration.ms = diffRes.milliseconds;
    duration.M = diffRes.months;
  }

  ret = new Duration(duration);

  if (isDuration(input) && hasOwnProp(input, '_locale')) {
    ret._locale = input._locale;
  }

  if (isDuration(input) && hasOwnProp(input, '_isValid')) {
    ret._isValid = input._isValid;
  }

  return ret;
}
createDuration.fn = Duration.prototype;
createDuration.invalid = invalid;

function parseIso(inp, sign) {
  // We'd normally use ~~inp for this, but unfortunately it also
  // converts floats to ints.
  // inp may be undefined, so careful calling replace on it.
  var res = inp && parseFloat(inp.replace(',', '.')); // apply sign while we're at it

  return (isNaN(res) ? 0 : res) * sign;
}

function positiveMomentsDifference(base, other) {
  var res = {};
  res.months = other.month() - base.month() + (other.year() - base.year()) * 12;

  if (base.clone().add(res.months, 'M').isAfter(other)) {
    --res.months;
  }

  res.milliseconds = +other - +base.clone().add(res.months, 'M');
  return res;
}

function momentsDifference(base, other) {
  var res;

  if (!(base.isValid() && other.isValid())) {
    return {
      milliseconds: 0,
      months: 0
    };
  }

  other = cloneWithOffset(other, base);

  if (base.isBefore(other)) {
    res = positiveMomentsDifference(base, other);
  } else {
    res = positiveMomentsDifference(other, base);
    res.milliseconds = -res.milliseconds;
    res.months = -res.months;
  }

  return res;
}