/*
 * Copyright The OpenTelemetry Authors
 * SPDX-License-Identifier: Apache-2.0
 */

#ifndef OTEL_CLR_PROFILER_STATS_H_
#define OTEL_CLR_PROFILER_STATS_H_

#include <chrono>

#include "util.h"

namespace trace
{

class SWStat
{
    std::atomic_ullong* _value;
    std::chrono::steady_clock::time_point _startTime;

public:
    SWStat(std::atomic_ullong* value)
    {
        _value = value;
        _startTime = std::chrono::steady_clock::now();
    }
    ~SWStat()
    {
        auto increment = (std::chrono::steady_clock::now() - _startTime).count();
        _value->fetch_add(increment);
    }
};

class Stats : public Singleton<Stats>
{
    friend class Singleton<Stats>;

private:
    std::atomic_ullong jitCachedFunctionSearchStarted = {0};
    std::atomic_ullong callTargetRequestRejit = {0};
    std::atomic_ullong callTargetRewriter = {0};
    std::atomic_ullong jitInlining = {0};
    std::atomic_ullong jitCompilationStarted = {0};
    std::atomic_ullong moduleUnloadStarted = {0};
    std::atomic_ullong moduleLoadFinished = {0};
    std::atomic_ullong assemblyLoadFinished = {0};
    std::atomic_ullong initialize = {0};

    //
    std::atomic_uint jitCachedFunctionSearchStartedCount = {0};
    std::atomic_uint callTargetRequestRejitCount = {0};
    std::atomic_uint callTargetRewriterCount = {0};
    std::atomic_uint jitInliningCount = {0};
    std::atomic_uint jitCompilationStartedCount = {0};
    std::atomic_uint moduleUnloadStartedCount = {0};
    std::atomic_uint moduleLoadFinishedCount = {0};
    std::atomic_uint assemblyLoadFinishedCount = {0};

public:
    Stats()
    {
        jitCachedFunctionSearchStarted = 0;
        callTargetRequestRejit = 0;
        jitInlining = 0;
        jitCompilationStarted = 0;
        moduleUnloadStarted = 0;
        moduleLoadFinished = 0;
        assemblyLoadFinished = 0;
        initialize = 0;

        jitCachedFunctionSearchStartedCount = 0;
        callTargetRequestRejitCount = 0;
        jitInliningCount = 0;
        jitCompilationStartedCount = 0;
        moduleUnloadStartedCount = 0;
        moduleLoadFinishedCount = 0;
        assemblyLoadFinishedCount = 0;
    }
    SWStat JITCachedFunctionSearchStartedMeasure()
    {
        jitCachedFunctionSearchStartedCount++;
        return SWStat(&jitCachedFunctionSearchStarted);
    }
    SWStat CallTargetRequestRejitMeasure()
    {
        callTargetRequestRejitCount++;
        return SWStat(&callTargetRequestRejit);
    }
    SWStat CallTargetRewriterCallbackMeasure()
    {
        callTargetRewriterCount++;
        return SWStat(&callTargetRewriter);
    }
    SWStat JITInliningMeasure()
    {
        jitInliningCount++;
        return SWStat(&jitInlining);
    }
    SWStat JITCompilationStartedMeasure()
    {
        jitCompilationStartedCount++;
        return SWStat(&jitCompilationStarted);
    }
    SWStat ModuleUnloadStartedMeasure()
    {
        moduleUnloadStartedCount++;
        return SWStat(&moduleUnloadStarted);
    }
    SWStat ModuleLoadFinishedMeasure()
    {
        moduleLoadFinishedCount++;
        return SWStat(&moduleLoadFinished);
    }
    SWStat AssemblyLoadFinishedMeasure()
    {
        assemblyLoadFinishedCount++;
        return SWStat(&assemblyLoadFinished);
    }
    SWStat InitializeMeasure()
    {
        return SWStat(&initialize);
    }
    std::string ToString()
    {
        const auto ns_initialize = initialize.load();
        const auto ns_moduleLoadFinished = moduleLoadFinished.load();
        const auto ns_callTargetRequestRejit = callTargetRequestRejit.load();
        const auto ns_callTargetRewriter = callTargetRewriter.load();
        const auto ns_assemblyLoadFinished = assemblyLoadFinished.load();
        const auto ns_moduleUnloadStarted = moduleUnloadStarted.load();
        const auto ns_jitCompilationStarted = jitCompilationStarted.load();
        const auto ns_jitInlining = jitInlining.load();
        const auto ns_jitCachedFunctionSearchStarted = jitCachedFunctionSearchStarted.load();

        const auto count_moduleLoadFinishedCount = moduleLoadFinishedCount.load();
        const auto count_callTargetRequestRejitCount = callTargetRequestRejitCount.load();
        const auto count_callTargetRewriterCount = callTargetRewriterCount.load();
        const auto count_assemblyLoadFinishedCount = assemblyLoadFinishedCount.load();
        const auto count_moduleUnloadStartedCount = moduleUnloadStartedCount.load();
        const auto count_jitCompilationStartedCount = jitCompilationStartedCount.load();
        const auto count_jitInliningCount = jitInliningCount.load();
        const auto count_jitCachedFunctionSearchStartedCount = jitCachedFunctionSearchStartedCount.load();

        const auto ns_total = ns_initialize + ns_moduleLoadFinished + ns_callTargetRequestRejit +
                              ns_callTargetRewriter + ns_assemblyLoadFinished + ns_moduleUnloadStarted +
                              ns_jitCompilationStarted + ns_jitInlining + ns_jitCachedFunctionSearchStarted;

        std::stringstream ss;
        ss << "Total ";
        ss << ns_total / 1000000 << "ms ";
        ss << "[Initialize=";
        ss << ns_initialize / 1000000 << "ms";
        ss << ", ModuleLoadFinished=";
        ss << ns_moduleLoadFinished / 1000000 << "ms"
           << "/" << count_moduleLoadFinishedCount;
        ss << ", CallTargetRequestRejit=";
        ss << ns_callTargetRequestRejit / 1000000 << "ms"
           << "/" << count_callTargetRequestRejitCount;
        ss << ", CallTargetRewriter=";
        ss << ns_callTargetRewriter / 1000000 << "ms"
           << "/" << count_callTargetRewriterCount;
        ss << ", AssemblyLoadFinished=";
        ss << ns_assemblyLoadFinished / 1000000 << "ms"
           << "/" << count_assemblyLoadFinishedCount;
        ss << ", ModuleUnloadStarted=";
        ss << ns_moduleUnloadStarted / 1000000 << "ms"
           << "/" << count_moduleUnloadStartedCount;
        ss << ", JitCompilationStarted=";
        ss << ns_jitCompilationStarted / 1000000 << "ms"
           << "/" << count_jitCompilationStartedCount;
        ss << ", JitInlining=";
        ss << ns_jitInlining / 1000000 << "ms"
           << "/" << count_jitInliningCount;
        ss << ", JitCacheFunctionSearchStarted=";
        ss << ns_jitCachedFunctionSearchStarted / 1000000 << "ms"
           << "/" << count_jitCachedFunctionSearchStartedCount;
        ss << "]";
        return ss.str();
    }
};

} // namespace trace

#endif // OTEL_CLR_PROFILER_STATS_H_