
var arg, i, max, method, methodIndex, decimalPlaces,
    reps, rounding, start, timesEqual, Xs, Ys,
    bdM, bdMT, bdOT, bdRs, bdXs, bdYs,
    bnM, bnMT, bnOT, bnRs, bnXs, bnYs,
    memoryUsage, showMemory, bnR, bdR,
    prevRss, prevHeapUsed, prevHeapTotal,
    args = process.argv.splice(2),
    BigDecimal = require('./lib/bigdecimal_GWT/bigdecimal').BigDecimal,
    BigNumber = require('../bignumber'),
    bdMs = ['add', 'subtract', 'multiply', 'divide', 'remainder', 'compareTo', 'pow'],
    bnMs1 = ['plus', 'minus', 'times', 'dividedBy', 'modulo', 'comparedTo', 'toPower'],
    bnMs2 = ['', '', '', 'div', 'mod', 'cmp', ''],
    Ms = [bdMs, bnMs1, bnMs2],
    allMs = [].concat.apply([], Ms),
    expTotal = 0,
    total = 0,

    ALWAYS_SHOW_MEMORY = false,
    DEFAULT_MAX_DIGITS = 20,
    DEFAULT_POW_MAX_DIGITS = 20,
    DEFAULT_REPS = 1e4,
    DEFAULT_POW_REPS = 1e2,
    DEFAULT_PLACES = 20,
    MAX_POWER = 50,

    getRandom = function (maxDigits) {
        var i = 0, z,
            // number of digits - 1
            n = Math.random() * ( maxDigits || 1 ) | 0,
            r = ( Math.random() * 10 | 0 ) + '';

        if ( n ) {
            if ( z = r === '0' ) {
                r += '.';
            }
            
            for ( ; i++ < n; r += Math.random() * 10 | 0 ){}

            // 20% chance of integer
            if ( !z && Math.random() > 0.2 )
                r = r.slice( 0, i = ( Math.random() * n | 0 ) + 1 ) + '.' + r.slice(i);
        }

        // Avoid 'division by zero' error with division and modulo.
        if ((bdM == 'divide' || bdM == 'remainder') && parseFloat(r) === 0)
            r = ( ( Math.random() * 9 | 0 ) + 1 ) + '';
            
        total += n + 1;

        // 50% chance of negative
        return Math.random() > 0.5 ? r : '-' + r;
    },
    
    pad = function (str) {
        str += '... ';
        while (str.length < 26) str += ' ';
        return str;
    },

    getFastest = function (bn, bd) {
        var r;
        if (Math.abs(bn - bd) > 2) {
            r = 'Big' + ((bn < bd)
            ? 'Number ' + (bn ? parseFloat((bd / bn).toFixed(1)) : bd)
            : 'Decimal ' + (bd ? parseFloat((bn / bd).toFixed(1)) : bn)) +
                ' times faster';
        } else {
            timesEqual = 1;
            r = 'Times approximately equal';
        }
        return r;
    },

    showMemoryChange = function () {
        if (showMemory) {
            memoryUsage = process.memoryUsage();

            var rss = memoryUsage.rss,
                heapUsed = memoryUsage.heapUsed,
                heapTotal = memoryUsage.heapTotal;

            console.log(' Change in memory usage: ' +
                ' rss: ' +  toKB(rss - prevRss) +
                ', hU: ' + toKB(heapUsed - prevHeapUsed) +
                ', hT: ' + toKB(heapTotal - prevHeapTotal));
            prevRss = rss; prevHeapUsed = heapUsed; prevHeapTotal = heapTotal;
        }
    },

    toKB = function (m) {
        return parseFloat((m / 1024).toFixed(1)) + ' KB';
    };


// PARSE COMMAND LINE AND SHOW HELP

if (arg = args[0], typeof arg != 'undefined' && !isFinite(arg) &&
    allMs.indexOf(arg) == -1 && !/^-*m$/i.test(arg)) {
    console.log(
    '\n node bigtime-OOM [METHOD] [METHOD CALLS [MAX DIGITS [DECIMAL PLACES]]]\n' +
    '\n METHOD: The method to be timed and compared with the automatically' +
    '\n         chosen corresponding method from BigDecimal or BigNumber\n' +
    '\n BigDecimal: add  subtract multiply divide remainder compareTo pow' +
    '\n BigNumber:  plus minus times dividedBy modulo comparedTo toPower' +
    '\n             (div mod cmp pow)' +
    '\n\n METHOD CALLS: The number of method calls to be timed' +
    '\n\n MAX DIGITS: The maximum number of digits of the random ' +
    '\n             numbers used in the method calls' +
    '\n\n DECIMAL PLACES: The number of decimal places used in division' +
    '\n                 (The rounding mode is randomly chosen)' +
    '\n\n Default values: METHOD: randomly chosen' +
    '\n                 METHOD CALLS: ' + DEFAULT_REPS +
    '  (pow: ' + DEFAULT_POW_REPS + ')' +
    '\n                 MAX DIGITS: ' + DEFAULT_MAX_DIGITS +
    '  (pow: ' + DEFAULT_POW_MAX_DIGITS + ')' +
    '\n                 DECIMAL PLACES: ' + DEFAULT_PLACES + '\n' +
    '\n E.g.s node bigtime-OOM\n       node bigtime-OOM minus' +
    '\n       node bigtime-OOM add 100000' +
    '\n       node bigtime-OOM times 20000 100' +
    '\n       node bigtime-OOM div 100000 50 20' +
    '\n       node bigtime-OOM 9000' +
    '\n       node bigtime-OOM 1000000 20\n' +
    '\n To show memory usage include an argument m or -m' +
    '\n E.g.  node bigtime-OOM m add');
} else {
    BigNumber.config({EXPONENTIAL_AT : 1E9, RANGE : 1E9});
    Number.prototype.toPlainString = Number.prototype.toString;

    for (i = 0; i < args.length; i++) {
        arg = args[i];

        if (isFinite(arg)) {
            arg = Math.abs(parseInt(arg));
            if (reps == null) {
                reps = arg <= 1e10 ? arg : 0;
            } else if (max == null) {
                max = arg <= 1e6 ? arg : 0;
            } else if (decimalPlaces == null) {
                decimalPlaces = arg <= 1e6 ? arg : DEFAULT_PLACES;
            }
        } else if (/^-*m$/i.test(arg)) {
            showMemory = true;
        } else if (method == null) {
            method = arg;
        }
    }

    for (i = 0;
         i < Ms.length && (methodIndex = Ms[i].indexOf(method)) == -1;
         i++) {}

    bnM = methodIndex == -1
        ? bnMs1[methodIndex = Math.floor(Math.random() * bdMs.length)]
        : (Ms[i][0] == 'add' ? bnMs1 : Ms[i])[methodIndex];

    bdM = bdMs[methodIndex];

    if (!reps)
        reps = bdM == 'pow' ? DEFAULT_POW_REPS : DEFAULT_REPS;
    if (!max)
        max = bdM == 'pow' ? DEFAULT_POW_MAX_DIGITS : DEFAULT_MAX_DIGITS;
    if (decimalPlaces == null)
        decimalPlaces = DEFAULT_PLACES;

    Xs = [reps], Ys = [reps];
    bdXs = [reps], bdYs = [reps], bdRs = [reps];
    bnXs = [reps], bnYs = [reps], bnRs = [reps];
    showMemory = showMemory || ALWAYS_SHOW_MEMORY;

    console.log('\n BigNumber %s vs BigDecimal %s', bnM, bdM);
    console.log('\n Method calls: %d', reps);

    if (bdM == 'divide') {
        rounding = Math.floor(Math.random() * 7);
        console.log('\n Decimal places: %d\n Rounding mode: %d', decimalPlaces, rounding);
        BigNumber.config(decimalPlaces, rounding);
    }

    if (showMemory) {
        memoryUsage = process.memoryUsage();
        console.log(' Memory usage:            rss: ' +
            toKB(prevRss = memoryUsage.rss) + ', hU: ' +
            toKB(prevHeapUsed = memoryUsage.heapUsed) + ', hT: ' +
            toKB(prevHeapTotal = memoryUsage.heapTotal));
    }


    // CREATE RANDOM NUMBERS

    // POW: BigDecimal requires JS Number type for exponent argument
    if (bdM == 'pow') {

        process.stdout.write('\n Creating ' + reps +
            ' random numbers (max. digits: ' + max + ')... ');

        for (i = 0; i < reps; i++) {
            Xs[i] = getRandom(max);
        }
        console.log('done\n Average number of digits: %d',
            ((total / reps) | 0));

        process.stdout.write(' Creating ' + reps +
            ' random integer exponents (max. value: ' + MAX_POWER + ')... ');

        for (i = 0; i < reps; i++) {
            bdYs[i] = bnYs[i] = Math.floor(Math.random() * (MAX_POWER + 1));
            expTotal += bdYs[i];
        }
        console.log('done\n Average value: %d', ((expTotal / reps) | 0));

        showMemoryChange();


        // POW: TIME CREATION OF BIGDECIMALS

        process.stdout.write('\n Creating BigDecimals...  ');

        start = +new Date();
            for (i = 0; i < reps; i++) {
                bdXs[i] = new BigDecimal(Xs[i]);
            }
        bdOT = +new Date() - start;

        console.log('done. Time taken: %s ms', bdOT || '<1');

        showMemoryChange();


        // POW: TIME CREATION OF BIGNUMBERS

        process.stdout.write(' Creating BigNumbers...      ');

        start = +new Date();
            for (i = 0; i < reps; i++) {
                bnXs[i] = new BigNumber(Xs[i]);
            }
        bnOT = +new Date() - start;

        console.log('done. Time taken: %s ms', bnOT || '<1');


    // NOT POW
    } else {

        process.stdout.write('\n Creating ' + (reps * 2) +
            ' random numbers (max. digits: ' + max + ')... ');


        for (i = 0; i < reps; i++) {
            Xs[i] = getRandom(max);
            Ys[i] = getRandom(max);
        }
        console.log('done\n Average number of digits: %d',
            ( total / (reps * 2) ) | 0);

        showMemoryChange();


        // TIME CREATION OF BIGDECIMALS

        process.stdout.write('\n Creating BigDecimals...  ');

        start = +new Date();
            for (i = 0; i < reps; i++) {
                bdXs[i] = new BigDecimal(Xs[i]);
                bdYs[i] = new BigDecimal(Ys[i]);
            }
        bdOT = +new Date() - start;

        console.log('done. Time taken: %s ms', bdOT || '<1');

        showMemoryChange();


        // TIME CREATION OF BIGNUMBERS

        process.stdout.write(' Creating BigNumbers...   ');

        start = +new Date();
            for (i = 0; i < reps; i++) {
                bnXs[i] = new BigNumber(Xs[i]);
                bnYs[i] = new BigNumber(Ys[i]);
            }
        bnOT = +new Date() - start;

        console.log('done. Time taken: %s ms', bnOT || '<1');
    }

    showMemoryChange();

    console.log('\n Object creation: %s\n', getFastest(bnOT, bdOT));


    // TIME BIGDECIMAL METHOD CALLS

    process.stdout.write(pad(' BigDecimal ' + bdM));

    if (bdM == 'divide') {
        start = +new Date();
            while (i--) bdRs[i] = bdXs[i][bdM](bdYs[i], decimalPlaces, rounding);
        bdMT = +new Date() - start;
    } else {
        start = +new Date();
            while (i--) bdRs[i] = bdXs[i][bdM](bdYs[i]);
        bdMT = +new Date() - start;
    }

    console.log('done. Time taken: %s ms', bdMT || '<1');


    // TIME BIGNUMBER METHOD CALLS

    i = reps;
    process.stdout.write(pad(' BigNumber  ' + bnM));

    start = +new Date();
        while (i--) bnRs[i] = bnXs[i][bnM](bnYs[i]);
    bnMT = +new Date() - start;

    console.log('done. Time taken: %s ms', bnMT || '<1');


    // TIMINGS SUMMARY

    console.log('\n Method calls:    %s', getFastest(bnMT, bdMT));

    if (!timesEqual) {
        console.log('\n Overall:         ' +
            getFastest((bnOT || 1) + (bnMT || 1), (bdOT || 1) + (bdMT || 1)));
    }



    // CHECK FOR MISMATCHES

    process.stdout.write('\n Checking for mismatches... ');

    for (i = 0; i < reps; i++) {

        bnR = bnRs[i].toString();
        bdR = bdRs[i].toPlainString();

        // Strip any trailing zeros from non-integer BigDecimals
        if (bdR.indexOf('.') != -1) {
            bdR = bdR.replace(/\.?0+$/, '');
        }

        if (bdR !== bnR) {
            console.log('breaking on first mismatch (result number %d):' +
                '\n\n BigDecimal: %s\n BigNumber:  %s', i, bdR, bnR);
            console.log('\n x: %s\n y: %s', Xs[i], Ys[i]);

            if (bdM == 'divide') {
                console.log('\n dp: %d\n r: %d',decimalPlaces, rounding);
            }
            break;
        }
    }
    if (i == reps) {
        console.log('done. None found.\n');
    }
}



