/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

var count = output.length;

var itemTotals = {};
itemTotals.length = count;

var total = 0;
var categoryTotals = {};
var testTotalsByCategory = {};

var mean = 0;
var categoryMeans = {};
var testMeansByCategory = {};

var stdDev = 0;
var categoryStdDevs = {};
var testStdDevsByCategory = {};

var stdErr = 0;
var categoryStdErrs = {};
var testStdErrsByCategory = {};

function initialize()
{
    itemTotals = {total: []};

    for (var i = 0; i < categories.length; i++) {
        var category = categories[i];
        itemTotals[category] = [];
        categoryTotals[category] = 0;
        testTotalsByCategory[category] = {};
        categoryMeans[category] = 0;
        testMeansByCategory[category] = {};
        categoryStdDevs[category] = 0;
        testStdDevsByCategory[category] = {};
        categoryStdErrs[category] = 0;
        testStdErrsByCategory[category] = {};
    }

    for (var i = 0; i < tests.length; i++) {
        var test = tests[i];
        itemTotals[test] = [];
        var category = test.replace(/-.*/, "");
        testTotalsByCategory[category][test] = 0;
        testMeansByCategory[category][test] = 0;
        testStdDevsByCategory[category][test] = 0;
        testStdErrsByCategory[category][test] = 0;
    }

    for (var i = 0; i < count; i++) {
        itemTotals["total"][i] = 0;
        for (var category in categoryTotals) {
            itemTotals[category][i] = 0;
            for (var test in testTotalsByCategory[category]) {
                itemTotals[test][i] = 0;
            }
        }
    }
}

function computeItemTotals()
{
    for (var i = 0; i < output.length; i++) {
        var result = output[i];
        for (var test in result) {
            var time = result[test];
            var category = test.replace(/-.*/, "");
            itemTotals["total"][i] += time;
            itemTotals[category][i] += time;
            itemTotals[test][i] += time;
        }
    }
}

function computeTotals()
{
    for (var i = 0; i < output.length; i++) {
        var result = output[i];
        for (var test in result) {
            var time = result[test];
            var category = test.replace(/-.*/, "");
            total += time;
            categoryTotals[category] += time;
            testTotalsByCategory[category][test] += time;
        }
    }
}

function computeMeans()
{
    mean = total / count;
    for (var category in categoryTotals) {
        categoryMeans[category] = categoryTotals[category] / count;
        for (var test in testTotalsByCategory[category]) {
            testMeansByCategory[category][test] = testTotalsByCategory[category][test] / count;
        }
    }
}

function standardDeviation(mean, items)
{
    var deltaSquaredSum = 0;
    for (var i = 0; i < items.length; i++) {
        var delta = items[i] - mean;
        deltaSquaredSum += delta * delta;
    }
    variance = deltaSquaredSum / (items.length - 1);
    return Math.sqrt(variance);
}

function computeStdDevs()
{
    stdDev = standardDeviation(mean, itemTotals["total"]);
    for (var category in categoryStdDevs) {
        categoryStdDevs[category] = standardDeviation(categoryMeans[category], itemTotals[category]);
    }
    for (var category in categoryStdDevs) {
        for (var test in testStdDevsByCategory[category]) {
            testStdDevsByCategory[category][test] = standardDeviation(testMeansByCategory[category][test], itemTotals[test]);
        }
    }
}

function computeStdErrors()
{
    var sqrtCount = Math.sqrt(count);

    stdErr = stdDev / sqrtCount;
    for (var category in categoryStdErrs) {
        categoryStdErrs[category] = categoryStdDevs[category] / sqrtCount;
    }
    for (var category in categoryStdDevs) {
        for (var test in testStdErrsByCategory[category]) {
            testStdErrsByCategory[category][test] = testStdDevsByCategory[category][test] / sqrtCount;
        }
    }

}

var tDistribution = [NaN, NaN, 12.71, 4.30, 3.18, 2.78, 2.57, 2.45, 2.36, 2.31, 2.26, 2.23, 2.20, 2.18, 2.16, 2.14, 2.13, 2.12, 2.11, 2.10, 2.09, 2.09, 2.08, 2.07, 2.07, 2.06, 2.06, 2.06, 2.05, 2.05, 2.05, 2.04, 2.04, 2.04, 2.03, 2.03, 2.03, 2.03, 2.03, 2.02, 2.02, 2.02, 2.02, 2.02, 2.02, 2.02, 2.01, 2.01, 2.01, 2.01, 2.01, 2.01, 2.01, 2.01, 2.01, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 2.00, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.99, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.98, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.97, 1.96];
var tMax = tDistribution.length;
var tLimit = 1.96;

function tDist(n)
{
    if (n > tMax)
        return tLimit;
    return tDistribution[n];
}


function formatResult(meanWidth, mean, stdErr, n, mode)
{
    // NaN mean means that the test did not run correctly.
    if (mean != mean) {
        var result = "";
        for (var i = 0; i < meanWidth - 3; ++i)
            result += " ";
        if (mode == "test")
            result += "ERROR: Invalid test run.";
        else
            result += "ERROR: Some tests failed.";
        return result;
    }
    
    var meanString = mean.toFixed(1).toString();
    while (meanString.length < meanWidth) {
        meanString = " " + meanString;
    }

    if (n == 1)
        return meanString + "ms";

    return meanString + "ms +/- " + ((tDist(n) * stdErr / mean) * 100).toFixed(1) + "%";
}

function computeLabelWidth()
{
    var width = "Total".length;
    for (var category in categoryMeans) {
        if (category.length + 2 > width)
            width = category.length + 2;
    }
    for (var i = 0; i < tests.length; i++) {
        var shortName = tests[i].replace(/^[^-]*-/, "");
        if (shortName.length + 4 > width)
            width = shortName.length + 4;
    }

    return width;
}

function computeMeanWidth()
{
    var width = mean.toFixed(1).toString().length;
    for (var category in categoryMeans) {
        var candidate = categoryMeans[category].toFixed(2).toString().length;
        if (candidate > width)
            width = candidate;
        for (var test in testMeansByCategory[category]) {
            var candidate = testMeansByCategory[category][test].toFixed(2).toString().length;
            if (candidate > width)
                width = candidate;
        }
    }

    return width;
}

function resultLine(labelWidth, indent, label, meanWidth, mean, stdErr, mode)
{
    var result = "";
    for (i = 0; i < indent; i++) {
        result += " ";
    }
    
    result += label + ": ";

    for (i = 0; i < (labelWidth - (label.length + indent)); i++) {
        result += " ";
    }
    
    return result + formatResult(meanWidth, mean, stdErr, count, mode);
}

function printOutput()
{
    var labelWidth = computeLabelWidth();
    var meanWidth = computeMeanWidth();

    print("\n");
    print("============================================");
    if (count == 1)
        print("RESULTS");
    else
        print("RESULTS (means and 95% confidence intervals)");
    print("--------------------------------------------");
    print(resultLine(labelWidth, 0, "Total", meanWidth, mean, stdErr, "total"));
    print("--------------------------------------------");
    for (var category in categoryMeans) {
        print("");
        print(resultLine(labelWidth, 2, category, meanWidth, categoryMeans[category], categoryStdErrs[category], "category"));
        for (var test in testMeansByCategory[category]) {
            var shortName = test.replace(/^[^-]*-/, "");
            print(resultLine(labelWidth, 4, shortName, meanWidth, testMeansByCategory[category][test], testStdErrsByCategory[category][test], "test"));
        }
    }
}

initialize();
computeItemTotals();
computeTotals();
computeMeans();
computeStdDevs();
computeStdErrors();
printOutput();
