if (typeof moment === "undefined" && typeof require === 'function') {
    var moment = require('moment');
}

(function (moment) {
    var STRINGS = {
        nodiff: '',
        year: 'year',
        years: 'years',
        month: 'month',
        months: 'months',
        day: 'day',
        days: 'days',
        hour: 'hour',
        hours: 'hours',
        minute: 'minute',
        minutes: 'minutes',
        second: 'second',
        seconds: 'seconds',
        delimiter: ' '
    };

    function pluralize(num, word) {
        return num + ' ' + STRINGS[word + (num === 1 ? '' : 's')];
    }

    function buildStringFromValues(yDiff, mDiff, dDiff, hourDiff, minDiff, secDiff) {
        var result = [];

        if (yDiff) {
            result.push(pluralize(yDiff, 'year'));
        }
        if (mDiff) {
            result.push(pluralize(mDiff, 'month'));
        }
        if (dDiff) {
            result.push(pluralize(dDiff, 'day'));
        }
        if (hourDiff) {
            result.push(pluralize(hourDiff, 'hour'));
        }
        if (minDiff) {
            result.push(pluralize(minDiff, 'minute'));
        }
        if (secDiff) {
            result.push(pluralize(secDiff, 'second'));
        }

        return result.join(STRINGS.delimiter);
    }

    function buildValueObject(yDiff, mDiff, dDiff, hourDiff, minDiff, secDiff, msDiff, firstDateWasLater) {
        return {
            "years": yDiff,
            "months": mDiff,
            "days": dDiff,
            "hours": hourDiff,
            "minutes": minDiff,
            "seconds": secDiff,
            "ms": msDiff,
            "firstDateWasLater": firstDateWasLater
        }
    }
    moment.fn.preciseDiff = function (d2, returnValueObject) {
        return moment.preciseDiff(this, d2, returnValueObject);
    };

    moment.preciseDiff = function (d1, d2, returnValueObject) {
        var m1 = moment(d1), m2 = moment(d2), firstDateWasLater;

        m1.add(m2.utcOffset() - m1.utcOffset(), 'minutes'); // shift timezone of m1 to m2

        if (m1.isSame(m2)) {
            if (returnValueObject) {
                return buildValueObject(0, 0, 0, 0, 0, 0, 0, false);
            } else {
                return STRINGS.nodiff;
            }
        }
        if (m1.isAfter(m2)) {
            var tmp = m1;
            m1 = m2;
            m2 = tmp;
            firstDateWasLater = true;
        } else {
            firstDateWasLater = false;
        }

        var yDiff = m2.year() - m1.year();
        var mDiff = m2.month() - m1.month();
        var dDiff = m2.date() - m1.date();
        var hourDiff = m2.hour() - m1.hour();
        var minDiff = m2.minute() - m1.minute();
        var secDiff = m2.second() - m1.second();
        var msDiff = m2.millisecond() - m1.millisecond();

        if (msDiff < 0) {
            msDiff = 1000 + msDiff;
            secDiff--;
        }
        if (secDiff < 0) {
            secDiff = 60 + secDiff;
            minDiff--;
        }
        if (minDiff < 0) {
            minDiff = 60 + minDiff;
            hourDiff--;
        }
        if (hourDiff < 0) {
            hourDiff = 24 + hourDiff;
            dDiff--;
        }
        if (dDiff < 0) {
            var daysInLastFullMonth = moment(m2.year() + '-' + (m2.month() + 1), "YYYY-MM").subtract(1, 'M').daysInMonth();
            if (daysInLastFullMonth < m1.date()) { // 31/01 -> 2/03
                dDiff = daysInLastFullMonth + dDiff + (m1.date() - daysInLastFullMonth);
            } else {
                dDiff = daysInLastFullMonth + dDiff;
            }
            mDiff--;
        }
        if (mDiff < 0) {
            mDiff = 12 + mDiff;
            yDiff--;
        }

        if (returnValueObject) {
            return buildValueObject(yDiff, mDiff, dDiff, hourDiff, minDiff, secDiff, msDiff, firstDateWasLater);
        } else {
            return buildStringFromValues(yDiff, mDiff, dDiff, hourDiff, minDiff, secDiff);
        }


    };
}(moment));