/*
 * MICROUTILS.US (MODULE)
 *
 * Version:     1.3.0
 * Author:      VideoPlayerCode
 * URL:         https://github.com/VideoPlayerCode/mpv-tools
 * License:     Apache License, Version 2.0
 */

/* jshint -W097 */
/* global mp, module, require */

'use strict';

var Utils = {};

// NOTE: This is an implementation of a non-recursive quicksort, which doesn't
// risk any stack overflows. This function is necessary because of a MuJS <=
// 1.0.1 bug which causes a stack overflow when running its built-in sort() on
// any large array. See: https://github.com/ccxvii/mujs/issues/55
// Furthermore, this performs optimized case-insensitive sorting.
Utils.quickSort = function(arr, options)
{
    options = options || {};

    var i, sortRef,
        caseInsensitive = !!options.caseInsensitive;

    if (caseInsensitive) {
        sortRef = arr.slice(0);
        for (i = sortRef.length - 1; i >= 0; --i)
            if (typeof sortRef[i] === 'string')
                sortRef[i] = sortRef[i].toLowerCase();

        return Utils.quickSort_Run(arr, sortRef);
    }

    return Utils.quickSort_Run(arr);
};

Utils.quickSort_Run = function(arr, sortRef)
{
    if (arr.length <= 1)
        return arr;

    var hasSortRef = !!sortRef;
    if (!hasSortRef)
        sortRef = arr; // Use arr instead. Makes a direct reference (no copy).

    if (arr.length !== sortRef.length)
        throw 'Array and sort-reference length must be identical';

    // Adapted from a great, public-domain C algorithm by Darel Rex Finley.
    // Original implementation: http://alienryderflex.com/quicksort/
    // Ported by SteveJobzniak and extended to sort via a 2nd reference array,
    // to allow sorting the main array by _any_ criteria via the 2nd array.
    var refPiv, arrPiv, beg = [], end = [], stackMax = -1, stackPtr = 0, L, R;

    beg.push(0); end.push(sortRef.length);
    ++stackMax; // Tracks highest available stack index.
    while (stackPtr >= 0) {
        L = beg[stackPtr]; R = end[stackPtr] - 1;
        if (L < R) {
            if (hasSortRef) // If we have a SEPARATE sort-ref, mirror actions!
                arrPiv = arr[L];
            refPiv = sortRef[L]; // Left-pivot is fastest, no MuJS math needed!

            while (L < R) {
                while (sortRef[R] >= refPiv && L < R) R--;
                if (L < R) {
                    if (hasSortRef)
                        arr[L] = arr[R];
                    sortRef[L++] = sortRef[R];
                }
                while (sortRef[L] <= refPiv && L < R) L++;
                if (L < R) {
                    if (hasSortRef)
                        arr[R] = arr[L];
                    sortRef[R--] = sortRef[L];
                }
            }

            if (hasSortRef)
                arr[L] = arrPiv;
            sortRef[L] = refPiv;

            if (stackPtr === stackMax) {
                beg.push(0); end.push(0); // Grow stacks to fit next elem.
                ++stackMax;
            }

            beg[stackPtr + 1] = L + 1;
            end[stackPtr + 1] = end[stackPtr];
            end[stackPtr++] = L;
        } else {
            stackPtr--;
            // NOTE: No need to shrink stack here. Size-reqs GROW until sorted!
            // (Anyway, MuJS is slow at splice() and wastes time if we shrink.)
        }
    }

    return arr;
};

Utils.isInt = function(value)
{
    // Verify that the input is an integer (whole number).
    return (typeof value !== 'number' || isNaN(value)) ?
        false :
        (value | 0) === value;
};

Utils._hexSymbols = [
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
];

Utils.toHex = function(num, outputLength)
{
    // Generates a fixed-length output, and handles negative numbers properly.
    var result = '';
    while (outputLength--) {
        result = Utils._hexSymbols[num & 0xF] + result;
        num >>= 4;
    }
    return result;
};

Utils.shuffle = function(arr)
{
    var m = arr.length, tmp, i;

    while (m) { // While items remain to shuffle...
        // Pick a remaining element...
        i = Math.floor(Math.random() * m--);

        // And swap it with the current element.
        tmp = arr[m];
        arr[m] = arr[i];
        arr[i] = tmp;
    }

    return arr;
};

Utils.trim = function(str)
{
    return str.replace(/(?:^\s+|\s+$)/g, ''); // Trim left and right whitespace.
};

Utils.ltrim = function(str)
{
    return str.replace(/^\s+/, ''); // Trim left whitespace.
};

Utils.rtrim = function(str)
{
    return str.replace(/\s+$/, ''); // Trim right whitespace.
};

Utils.dump = function(value)
{
    mp.msg.error(JSON.stringify(value));
};

Utils.benchmarkStart = function(textLabel)
{
    Utils.benchmarkTimestamp = mp.get_time();
    Utils.benchmarkTextLabel = textLabel;
};

Utils.benchmarkEnd = function()
{
    var now = mp.get_time(),
        start = Utils.benchmarkTimestamp ? Utils.benchmarkTimestamp : now,
        elapsed = now - start,
        label = typeof Utils.benchmarkTextLabel === 'string' ? Utils.benchmarkTextLabel : '';
    mp.msg.info('Time Elapsed (Benchmark'+(label.length ? ': '+label : '')+'): '+elapsed+' seconds.');
};

module.exports = Utils;
