/*
 * Copyright 2012 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "TimerData.h"

#include "Timer.h"
#include <limits>

TimerData::TimerData(int maxNumTimings)
    : fMaxNumTimings(maxNumTimings)
    , fCurrTiming(0)
    , fWallTimes(maxNumTimings)
    , fTruncatedWallTimes(maxNumTimings)
    , fCpuTimes(maxNumTimings)
    , fTruncatedCpuTimes(maxNumTimings)
    , fGpuTimes(maxNumTimings) {}

bool TimerData::appendTimes(Timer* timer) {
    SkASSERT(timer != nullptr);
    if (fCurrTiming >= fMaxNumTimings) {
        return false;
    }

    fWallTimes[fCurrTiming] = timer->fWall;
    fTruncatedWallTimes[fCurrTiming] = timer->fTruncatedWall;
    fCpuTimes[fCurrTiming] = timer->fCpu;
    fTruncatedCpuTimes[fCurrTiming] = timer->fTruncatedCpu;
    fGpuTimes[fCurrTiming] = timer->fGpu;

    ++fCurrTiming;

    return true;
}

SkString TimerData::getResult(const char* doubleFormat,
                              Result result,
                              const char *configName,
                              uint32_t timerFlags,
                              int itersPerTiming) {
    SkASSERT(itersPerTiming >= 1);

    if (!fCurrTiming) {
        return SkString("");
    }

    int numTimings = fCurrTiming;

    SkString wallStr(" msecs = ");
    SkString truncWallStr(" Wmsecs = ");
    SkString cpuStr(" cmsecs = ");
    SkString truncCpuStr(" Cmsecs = ");
    SkString gpuStr(" gmsecs = ");

    double wallMin = std::numeric_limits<double>::max();
    double truncWallMin = std::numeric_limits<double>::max();
    double cpuMin = std::numeric_limits<double>::max();
    double truncCpuMin = std::numeric_limits<double>::max();
    double gpuMin = std::numeric_limits<double>::max();

    double wallSum = 0;
    double truncWallSum = 0;
    double cpuSum = 0;
    double truncCpuSum = 0;
    double gpuSum = 0;

    for (int i = 0; i < numTimings; ++i) {
        if (kPerIter_Result == result) {
            wallStr.appendf(doubleFormat, fWallTimes[i] / itersPerTiming);
            truncWallStr.appendf(doubleFormat, fTruncatedWallTimes[i] / itersPerTiming);
            cpuStr.appendf(doubleFormat, fCpuTimes[i] / itersPerTiming);
            truncCpuStr.appendf(doubleFormat, fTruncatedCpuTimes[i] / itersPerTiming);
            gpuStr.appendf(doubleFormat, fGpuTimes[i] / itersPerTiming);

            if (i != numTimings - 1) {
                static const char kSep[] = ", ";
                wallStr.append(kSep);
                truncWallStr.append(kSep);
                cpuStr.append(kSep);
                truncCpuStr.append(kSep);
                gpuStr.append(kSep);
            }
        } else if (kMin_Result == result) {
            wallMin = SkTMin(wallMin, fWallTimes[i]);
            truncWallMin = SkTMin(truncWallMin, fTruncatedWallTimes[i]);
            cpuMin = SkTMin(cpuMin, fCpuTimes[i]);
            truncCpuMin = SkTMin(truncCpuMin, fTruncatedCpuTimes[i]);
            gpuMin = SkTMin(gpuMin, fGpuTimes[i]);
        } else {
            SkASSERT(kAvg_Result == result);
            wallSum += fWallTimes[i];
            truncWallSum += fTruncatedWallTimes[i];
            cpuSum += fCpuTimes[i];
            truncCpuSum += fTruncatedCpuTimes[i];
        }

        // We always track the GPU sum because whether it is non-zero indicates if valid gpu times
        // were recorded at all.
        gpuSum += fGpuTimes[i];
    }

    if (kMin_Result == result) {
        wallStr.appendf(doubleFormat, wallMin / itersPerTiming);
        truncWallStr.appendf(doubleFormat, truncWallMin / itersPerTiming);
        cpuStr.appendf(doubleFormat, cpuMin / itersPerTiming);
        truncCpuStr.appendf(doubleFormat, truncCpuMin / itersPerTiming);
        gpuStr.appendf(doubleFormat, gpuMin / itersPerTiming);
    } else if (kAvg_Result == result) {
        int divisor = numTimings * itersPerTiming;
        wallStr.appendf(doubleFormat, wallSum / divisor);
        truncWallStr.appendf(doubleFormat, truncWallSum / divisor);
        cpuStr.appendf(doubleFormat, cpuSum / divisor);
        truncCpuStr.appendf(doubleFormat, truncCpuSum / divisor);
        gpuStr.appendf(doubleFormat, gpuSum / divisor);
    }

    SkString str;
    str.printf("  %4s:", configName);
    if (timerFlags & kWall_Flag) {
        str += wallStr;
    }
    if (timerFlags & kTruncatedWall_Flag) {
        str += truncWallStr;
    }
    if (timerFlags & kCpu_Flag) {
        str += cpuStr;
    }
    if (timerFlags & kTruncatedCpu_Flag) {
        str += truncCpuStr;
    }
    if ((timerFlags & kGpu_Flag) && gpuSum > 0) {
        str += gpuStr;
    }
    return str;
}

Json::Value TimerData::getJSON(uint32_t timerFlags,
                               Result result,
                               int itersPerTiming) {
    SkASSERT(itersPerTiming >= 1);
    Json::Value dataNode;
    Json::Value wallNode, truncWall, cpuNode, truncCpu, gpuNode;
    if (!fCurrTiming) {
        return dataNode;
    }

    int numTimings = fCurrTiming;

    double wallMin = std::numeric_limits<double>::max();
    double truncWallMin = std::numeric_limits<double>::max();
    double cpuMin = std::numeric_limits<double>::max();
    double truncCpuMin = std::numeric_limits<double>::max();
    double gpuMin = std::numeric_limits<double>::max();

    double wallSum = 0;
    double truncWallSum = 0;
    double cpuSum = 0;
    double truncCpuSum = 0;
    double gpuSum = 0;

    for (int i = 0; i < numTimings; ++i) {
        if (kPerIter_Result == result) {
            wallNode.append(fWallTimes[i] / itersPerTiming);
            truncWall.append(fTruncatedWallTimes[i] / itersPerTiming);
            cpuNode.append(fCpuTimes[i] / itersPerTiming);
            truncCpu.append(fTruncatedCpuTimes[i] / itersPerTiming);
            gpuNode.append(fGpuTimes[i] / itersPerTiming);
        } else if (kMin_Result == result) {
            wallMin = SkTMin(wallMin, fWallTimes[i]);
            truncWallMin = SkTMin(truncWallMin, fTruncatedWallTimes[i]);
            cpuMin = SkTMin(cpuMin, fCpuTimes[i]);
            truncCpuMin = SkTMin(truncCpuMin, fTruncatedCpuTimes[i]);
            gpuMin = SkTMin(gpuMin, fGpuTimes[i]);
        } else {
            SkASSERT(kAvg_Result == result);
            wallSum += fWallTimes[i];
            truncWallSum += fTruncatedWallTimes[i];
            cpuSum += fCpuTimes[i];
            truncCpuSum += fTruncatedCpuTimes[i];
        }

        // We always track the GPU sum because whether it is non-zero indicates if valid gpu times
        // were recorded at all.
        gpuSum += fGpuTimes[i];
    }

    if (kMin_Result == result) {
        wallNode.append(wallMin / itersPerTiming);
        truncWall.append(truncWallMin / itersPerTiming);
        cpuNode.append(cpuMin / itersPerTiming);
        truncCpu.append(truncCpuMin / itersPerTiming);
        gpuNode.append(gpuMin / itersPerTiming);
    } else if (kAvg_Result == result) {
        int divisor = numTimings * itersPerTiming;
        wallNode.append(wallSum / divisor);
        truncWall.append(truncWallSum / divisor);
        cpuNode.append(cpuSum / divisor);
        truncCpu.append(truncCpuSum / divisor);
        gpuNode.append(gpuSum / divisor);
    }

    if (timerFlags & kWall_Flag) {
        dataNode["wall"] = wallNode;
    }
    if (timerFlags & kTruncatedWall_Flag) {
        dataNode["truncWall"] = truncWall;
    }
    if (timerFlags & kCpu_Flag) {
        dataNode["cpu"] = cpuNode;
    }
    if (timerFlags & kTruncatedCpu_Flag) {
        dataNode["trucCpu"] = truncCpu;
    }
    if ((timerFlags & kGpu_Flag) && gpuSum > 0) {
        dataNode["gpu"] = gpuNode;
    }
    return dataNode;
}
