import { Duration, isDuration } from './constructor';

import isNumber from '../utils/is-number';

import toInt from '../utils/to-int';

import absRound from '../utils/abs-round';

import hasOwnProp from '../utils/has-own-prop';

import { DATE, HOUR, MINUTE, SECOND, MILLISECOND } from '../units/constants';

import { cloneWithOffset } from '../units/offset';

import { createLocal } from '../create/local';

import { createInvalid as invalid } from './valid';



// 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

var 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)) {

        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;

    }



    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;

}

