/*
 * Shared and maintained by [Nezasa](http://www.nezasa.com)
 * Published under [Apache 2.0 license](http://www.apache.org/licenses/LICENSE-2.0.html)
 * © Nezasa, 2012-2013
 *
 * ---
 *
 * Javascript library for parsing of ISO 8601 durations. Supported are durations of
 * the form P3Y6M4DT12H30M17S or PT1S or P1Y4DT1H3S etc.
 *
 * @author Nezasa AG -- https://github.com/nezasa
 * @contributor Jason "Palamedes" Ellis -- https://github.com/palamedes
 * @contributor mdartic -- https://github.com/mdartic
 * @contributor murb -- https://github.com/murb
 */


// if the module has no dependencies, the above pattern can be simplified to
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory();
    } else {
        // Browser globals (root is window)
        // maintaining nezasa global
        if (!root.nezasa) root.nezasa = {};
        if (!root.nezasa.iso8601) root.nezasa.iso8601 = factory();
    }
}(this, function () {
    var iso8601 = {
        Period: {}
    };

    // create sub packages

    //---- public properties

    /**
     * version of the ISO8601 version
     */
    iso8601.version = '0.2';

    //---- public methods

    /**
     * Returns an array of the duration per unit. The normalized sum of all array elements
     * represents the total duration.
     *
     * - array[0]: years
     * - array[1]: months
     * - array[2]: weeks
     * - array[3]: days
     * - array[4]: hours
     * - array[5]: minutes
     * - array[6]: seconds
     *
     * @param period iso8601 period string
     * @param distributeOverflow if 'true', the unit overflows are merge into the next higher units. Defaults to 'false'.
     */
    iso8601.Period.parse = function(period, distributeOverflow) {
        return parsePeriodString(period, distributeOverflow);
    };

    /**
     * Returns the total duration of the period in seconds.
     */
    iso8601.Period.parseToTotalSeconds = function(period) {

        var multiplicators = [31104000 /* year   (360*24*60*60) */,
            2592000  /* month  (30*24*60*60) */,
            604800   /* week   (24*60*60*7) */,
            86400    /* day    (24*60*60) */,
            3600     /* hour   (60*60) */,
            60       /* minute (60) */,
            1        /* second (1) */];
        var durationPerUnit = parsePeriodString(period);
        var durationInSeconds = 0;

        for (var i = 0; i < durationPerUnit.length; i++) {
            durationInSeconds += durationPerUnit[i] * multiplicators[i];
        }

        return durationInSeconds;
    };

    /**
     * Return boolean based on validity of period
     * @param period
     * @return {Boolean}
     */
    iso8601.Period.isValid = function(period) {
        try {
            parsePeriodString(period);
            return true;
        } catch(e) {
            return false;
        }
    }

    /**
     * Returns a more readable string representation of the ISO8601 period.
     * @param period the ISO8601 period string
     * @param unitName the names of the time units if there is only one (such as hour or minute).
     *        Defaults to ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'].
     * @param unitNamePlural thenames of the time units if there are several (such as hours or minutes).
     *        Defaults to ['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds'].
     * @param distributeOverflow if 'true', the unit overflows are merge into the next higher units. Defaults to 'false'.
     */
    iso8601.Period.parseToString = function(period, unitNames, unitNamesPlural, distributeOverflow) {

        var result = ['', '', '', '', '', '', ''];
        var durationPerUnit = parsePeriodString(period, distributeOverflow);

        // input validation (use english as default)
        if (!unitNames)       unitNames       = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'];
        if (!unitNamesPlural) unitNamesPlural = ['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds'];

        // assemble string per unit
        for (var i = 0; i < durationPerUnit.length; i++) {
            if (durationPerUnit[i] > 0) {
                if   (durationPerUnit[i] == 1) result[i] = durationPerUnit[i] + " " + unitNames[i];
                else                           result[i] = durationPerUnit[i] + " " + unitNamesPlural[i];
            }
        }

        // trim because of space at very end and because of join(" ")
        // replace double spaces because of join(" ") and empty strings
        // Its actually possible to get more than 2 spaces in a row,
        //   so lets get 2+ spaces and remove them
        return result.join(' ').trim().replace(/[ ]{2,}/g,' ');
    };

    //---- private methods

    /**
     * Parses a ISO8601 period string.
     * @param period iso8601 period string
     * @param _distributeOverflow if 'true', the unit overflows are merge into the next higher units.
     */
    function parsePeriodString(period, _distributeOverflow) {

        // regex splits as follows
        // grp0 omitted as it is equal to the sample
        //
        // | sample            | grp1   | grp2 | grp3 | grp4 | grp5 | grp6       | grp7 | grp8 | grp9 |
        // --------------------------------------------------------------------------------------------
        // | P1Y2M3W           | 1Y2M3W | 1Y   | 2M   | 3W   | 4D   | T12H30M17S | 12H  | 30M  | 17S  |
        // | P3Y6M4DT12H30M17S | 3Y6M4D | 3Y   | 6M   |      | 4D   | T12H30M17S | 12H  | 30M  | 17S  |
        // | P1M               | 1M     |      | 1M   |      |      |            |      |      |      |
        // | PT1M              | 3Y6M4D |      |      |      |      | T1M        |      | 1M   |      |
        // --------------------------------------------------------------------------------------------

        var distributeOverflow = (_distributeOverflow) ? _distributeOverflow : false;
        var valueIndexes       = [2, 3, 4, 5, 7, 8, 9];
        var duration           = [0, 0, 0, 0, 0, 0, 0];
        var overflowLimits     = [0, 12, 4, 7, 24, 60, 60];
        var struct;

        // upcase the string just in case people don't follow the letter of the law
        period = period.toUpperCase();

        // input validation
        if (!period)                         return duration;
        else if (typeof period !== "string") throw new Error("Invalid iso8601 period string '" + period + "'");

        // parse the string
        if (struct = /^P((\d+Y)?(\d+M)?(\d+W)?(\d+D)?)?(T(\d+H)?(\d+M)?(\d+S)?)?$/.exec(period)) {

            // remove letters, replace by 0 if not defined
            for (var i = 0; i < valueIndexes.length; i++) {
                var structIndex = valueIndexes[i];
                duration[i] = struct[structIndex] ? +struct[structIndex].replace(/[A-Za-z]+/g, '') : 0;
            }
        }
        else {
            throw new Error("String '" + period + "' is not a valid ISO8601 period.");
        }

        if (distributeOverflow) {
            // note: stop at 1 to ignore overflow of years
            for (var i = duration.length - 1; i > 0; i--) {
                if (duration[i] >= overflowLimits[i]) {
                    duration[i-1] = duration[i-1] + Math.floor(duration[i]/overflowLimits[i]);
                    duration[i] = duration[i] % overflowLimits[i];
                }
            }
        }

        return duration;
    };

    return iso8601;

}));


