#include "ResultDatabase.h"

#include <cfloat>
#include <algorithm>
#include <cmath>
#include <iomanip>

using namespace std;

bool ResultDatabase::Result::operator<(const Result& rhs) const {
    if (test < rhs.test) return true;
    if (test > rhs.test) return false;
    if (atts < rhs.atts) return true;
    if (atts > rhs.atts) return false;
    return false;  // less-operator returns false on equal
}

double ResultDatabase::Result::GetMin() const {
    double r = FLT_MAX;
    for (int i = 0; i < value.size(); i++) {
        r = min(r, value[i]);
    }
    return r;
}

double ResultDatabase::Result::GetMax() const {
    double r = -FLT_MAX;
    for (int i = 0; i < value.size(); i++) {
        r = max(r, value[i]);
    }
    return r;
}

double ResultDatabase::Result::GetMedian() const { return GetPercentile(50); }

double ResultDatabase::Result::GetPercentile(double q) const {
    int n = value.size();
    if (n == 0) return FLT_MAX;
    if (n == 1) return value[0];

    if (q <= 0) return value[0];
    if (q >= 100) return value[n - 1];

    double index = ((n + 1.) * q / 100.) - 1;

    vector<double> sorted = value;
    sort(sorted.begin(), sorted.end());

    if (n == 2) return (sorted[0] * (1 - q / 100.) + sorted[1] * (q / 100.));

    int index_lo = int(index);
    double frac = index - index_lo;
    if (frac == 0) return sorted[index_lo];

    double lo = sorted[index_lo];
    double hi = sorted[index_lo + 1];
    return lo + (hi - lo) * frac;
}

double ResultDatabase::Result::GetMean() const {
    double r = 0;
    for (int i = 0; i < value.size(); i++) {
        r += value[i];
    }
    return r / double(value.size());
}

double ResultDatabase::Result::GetStdDev() const {
    double r = 0;
    double u = GetMean();
    if (u == FLT_MAX) return FLT_MAX;
    for (int i = 0; i < value.size(); i++) {
        r += (value[i] - u) * (value[i] - u);
    }
    r = sqrt(r / value.size());
    return r;
}


void ResultDatabase::AddResults(const string& test, const string& atts, const string& unit,
                                const vector<double>& values) {
    for (int i = 0; i < values.size(); i++) {
        AddResult(test, atts, unit, values[i]);
    }
}

static string RemoveAllButLeadingSpaces(const string& a) {
    string b;
    int n = a.length();
    int i = 0;
    while (i < n && a[i] == ' ') {
        b += a[i];
        ++i;
    }
    for (; i < n; i++) {
        if (a[i] != ' ' && a[i] != '\t') b += a[i];
    }
    return b;
}

void ResultDatabase::AddResult(const string& test_orig, const string& atts_orig,
                               const string& unit_orig, double value) {
    string test = RemoveAllButLeadingSpaces(test_orig);
    string atts = RemoveAllButLeadingSpaces(atts_orig);
    string unit = RemoveAllButLeadingSpaces(unit_orig);
    int index;
    for (index = 0; index < results.size(); index++) {
        if (results[index].test == test && results[index].atts == atts) {
            if (results[index].unit != unit) throw "Internal error: mixed units";

            break;
        }
    }

    if (index >= results.size()) {
        Result r;
        r.test = test;
        r.atts = atts;
        r.unit = unit;
        results.push_back(r);
    }

    results[index].value.push_back(value);
}

// ****************************************************************************
//  Method:  ResultDatabase::DumpDetailed
//
//  Purpose:
//    Writes the full results, including all trials.
//
//  Arguments:
//    out        where to print
//
//  Programmer:  Jeremy Meredith
//  Creation:    August 14, 2009
//
//  Modifications:
//    Jeremy Meredith, Wed Nov 10 14:25:17 EST 2010
//    Renamed to DumpDetailed to make room for a DumpSummary.
//
//    Jeremy Meredith, Thu Nov 11 11:39:57 EST 2010
//    Added note about (*) missing value tag.
//
//    Jeremy Meredith, Tue Nov 23 13:57:02 EST 2010
//    Changed note about missing values to be worded a little better.
//
// ****************************************************************************
void ResultDatabase::DumpDetailed(ostream& out) {
    vector<Result> sorted(results);
    sort(sorted.begin(), sorted.end());

    const int testNameW = 24;
    const int attW = 12;
    const int fieldW = 11;
    out << std::fixed << right << std::setprecision(4);

    int maxtrials = 1;
    for (int i = 0; i < sorted.size(); i++) {
        if (sorted[i].value.size() > maxtrials) maxtrials = sorted[i].value.size();
    }

    // TODO: in big parallel runs, the "trials" are the procs
    // and we really don't want to print them all out....
    out << setw(testNameW) << "test\t" << setw(attW) << "atts\t" << setw(fieldW) << "median\t"
        << "mean\t"
        << "stddev\t"
        << "min\t"
        << "max\t";
    for (int i = 0; i < maxtrials; i++) out << "trial" << i << "\t";
    out << endl;

    for (int i = 0; i < sorted.size(); i++) {
        Result& r = sorted[i];
        out << setw(testNameW) << r.test + "\t";
        out << setw(attW) << r.atts + "\t";
        out << setw(fieldW) << r.unit + "\t";
        if (r.GetMedian() == FLT_MAX)
            out << "N/A\t";
        else
            out << r.GetMedian() << "\t";
        if (r.GetMean() == FLT_MAX)
            out << "N/A\t";
        else
            out << r.GetMean() << "\t";
        if (r.GetStdDev() == FLT_MAX)
            out << "N/A\t";
        else
            out << r.GetStdDev() << "\t";
        if (r.GetMin() == FLT_MAX)
            out << "N/A\t";
        else
            out << r.GetMin() << "\t";
        if (r.GetMax() == FLT_MAX)
            out << "N/A\t";
        else
            out << r.GetMax() << "\t";
        for (int j = 0; j < r.value.size(); j++) {
            if (r.value[j] == FLT_MAX)
                out << "N/A\t";
            else
                out << r.value[j] << "\t";
        }

        out << endl;
    }
    out << endl
        << "Note: Any results marked with (*) had missing values." << endl
        << "      This can occur on systems with a mixture of" << endl
        << "      device types or architectural capabilities." << endl;
}


// ****************************************************************************
//  Method:  ResultDatabase::DumpDetailed
//
//  Purpose:
//    Writes the summary results (min/max/stddev/med/mean), but not
//    every individual trial.
//
//  Arguments:
//    out        where to print
//
//  Programmer:  Jeremy Meredith
//  Creation:    November 10, 2010
//
//  Modifications:
//    Jeremy Meredith, Thu Nov 11 11:39:57 EST 2010
//    Added note about (*) missing value tag.
//
// ****************************************************************************
void ResultDatabase::DumpSummary(ostream& out) {
    vector<Result> sorted(results);
    sort(sorted.begin(), sorted.end());

    const int testNameW = 24;
    const int attW = 12;
    const int fieldW = 9;
    out << std::fixed << right << std::setprecision(4);

    // TODO: in big parallel runs, the "trials" are the procs
    // and we really don't want to print them all out....
    out << setw(testNameW) << "test\t" << setw(attW) << "atts\t" << setw(fieldW) << "units\t"
        << "median\t"
        << "mean\t"
        << "stddev\t"
        << "min\t"
        << "max\t";
    out << endl;

    for (int i = 0; i < sorted.size(); i++) {
        Result& r = sorted[i];
        out << setw(testNameW) << r.test + "\t";
        out << setw(attW) << r.atts + "\t";
        out << setw(fieldW) << r.unit + "\t";
        if (r.GetMedian() == FLT_MAX)
            out << "N/A\t";
        else
            out << r.GetMedian() << "\t";
        if (r.GetMean() == FLT_MAX)
            out << "N/A\t";
        else
            out << r.GetMean() << "\t";
        if (r.GetStdDev() == FLT_MAX)
            out << "N/A\t";
        else
            out << r.GetStdDev() << "\t";
        if (r.GetMin() == FLT_MAX)
            out << "N/A\t";
        else
            out << r.GetMin() << "\t";
        if (r.GetMax() == FLT_MAX)
            out << "N/A\t";
        else
            out << r.GetMax() << "\t";

        out << endl;
    }
    out << endl
        << "Note: results marked with (*) had missing values such as" << endl
        << "might occur with a mixture of architectural capabilities." << endl;
}

// ****************************************************************************
//  Method:  ResultDatabase::ClearAllResults
//
//  Purpose:
//    Clears all existing results from the ResultDatabase; used for multiple passes
//    of the same test or multiple tests.
//
//  Arguments:
//
//  Programmer:  Jeffrey Young
//  Creation:    September 10th, 2014
//
//  Modifications:
//
//
// ****************************************************************************
void ResultDatabase::ClearAllResults() { results.clear(); }

// ****************************************************************************
//  Method:  ResultDatabase::DumpCsv
//
//  Purpose:
//    Writes either detailed or summary results (min/max/stddev/med/mean), but not
//    every individual trial.
//
//  Arguments:
//    out        file to print CSV results
//
//  Programmer:  Jeffrey Young
//  Creation:    August 28th, 2014
//
//  Modifications:
//
// ****************************************************************************
void ResultDatabase::DumpCsv(string fileName) {
    bool emptyFile;
    vector<Result> sorted(results);

    sort(sorted.begin(), sorted.end());

    // Check to see if the file is empty - if so, add the headers
    emptyFile = this->IsFileEmpty(fileName);

    // Open file and append by default
    ofstream out;
    out.open(fileName.c_str(), std::ofstream::out | std::ofstream::app);

    // Add headers only for empty files
    if (emptyFile) {
        // TODO: in big parallel runs, the "trials" are the procs
        // and we really don't want to print them all out....
        out << "test, "
            << "atts, "
            << "units, "
            << "median, "
            << "mean, "
            << "stddev, "
            << "min, "
            << "max, ";
        out << endl;
    }

    for (int i = 0; i < sorted.size(); i++) {
        Result& r = sorted[i];
        out << r.test << ", ";
        out << r.atts << ", ";
        out << r.unit << ", ";
        if (r.GetMedian() == FLT_MAX)
            out << "N/A, ";
        else
            out << r.GetMedian() << ", ";
        if (r.GetMean() == FLT_MAX)
            out << "N/A, ";
        else
            out << r.GetMean() << ", ";
        if (r.GetStdDev() == FLT_MAX)
            out << "N/A, ";
        else
            out << r.GetStdDev() << ", ";
        if (r.GetMin() == FLT_MAX)
            out << "N/A, ";
        else
            out << r.GetMin() << ", ";
        if (r.GetMax() == FLT_MAX)
            out << "N/A, ";
        else
            out << r.GetMax() << ", ";

        out << endl;
    }
    out << endl;

    out.close();
}

// ****************************************************************************
//  Method:  ResultDatabase::IsFileEmpty
//
//  Purpose:
//    Returns whether a file is empty - used as a helper for CSV printing
//
//  Arguments:
//    file  The input file to check for emptiness
//
//  Programmer:  Jeffrey Young
//  Creation:    August 28th, 2014
//
//  Modifications:
//
// ****************************************************************************

bool ResultDatabase::IsFileEmpty(string fileName) {
    bool fileEmpty;

    ifstream file(fileName.c_str());

    // If the file doesn't exist it is by definition empty
    if (!file.good()) {
        return true;
    } else {
        fileEmpty = (bool)(file.peek() == ifstream::traits_type::eof());
        file.close();

        return fileEmpty;
    }

    // Otherwise, return false
    return false;
}


// ****************************************************************************
//  Method:  ResultDatabase::GetResultsForTest
//
//  Purpose:
//    Returns a vector of results for just one test name.
//
//  Arguments:
//    test       the name of the test results to search for
//
//  Programmer:  Jeremy Meredith
//  Creation:    December  3, 2010
//
//  Modifications:
//
// ****************************************************************************
vector<ResultDatabase::Result> ResultDatabase::GetResultsForTest(const string& test) {
    // get only the given test results
    vector<Result> retval;
    for (int i = 0; i < results.size(); i++) {
        Result& r = results[i];
        if (r.test == test) retval.push_back(r);
    }
    return retval;
}

// ****************************************************************************
//  Method:  ResultDatabase::GetResults
//
//  Purpose:
//    Returns all the results.
//
//  Arguments:
//
//  Programmer:  Jeremy Meredith
//  Creation:    December  3, 2010
//
//  Modifications:
//
// ****************************************************************************
const vector<ResultDatabase::Result>& ResultDatabase::GetResults() const { return results; }
