//-------------------------------------------------------------------------
// Copyright (c) 2018-2020 Qualcomm Technologies, Inc.
// All Rights Reserved.
// Confidential and Proprietary - Qualcomm Technologies, Inc.
//------------------------------------------------------------------------

#pragma once

#if defined(_WIN32) || defined(_WIN64)
#define _CRT_SECURE_NO_WARNINGS
#endif /* _WIN32 || _WIN64 */

#include <cstdint>
#include <cstring>
#include <cstdio>
#include "ringbuffer.h"
#include "is_dbg_util.h"
#include "eis_util.h"

/* If not defined explicit timer type, use default values according to the OS */
#if !defined(IS_DBG_WIN_PERFORMANCE_TIMER) && !defined(IS_DBG_ANDROID_MONOTONIC_TIMER)
#if defined(_WIN32) || defined(_WIN64)
#define IS_DBG_WIN_PERFORMANCE_TIMER
#elif defined (ANDROID)
#define IS_DBG_ANDROID_MONOTONIC_TIMER
#endif
#endif /* !IS_DBG_WIN_PERFORMANCE_TIMER && !IS_DBG_ANDROID_MONOTONIC_TIMER */

/* According to the defined timer type - add relevant includes */
#if defined(IS_DBG_WIN_PERFORMANCE_TIMER)
#include <windows.h>
#elif defined(IS_DBG_ANDROID_MONOTONIC_TIMER)
#include <time.h>
#else
#error "timer not supported"
#endif

class IsMeasureTimeBase
{
protected:
    static const uint32_t       _MAX_ID_LEN = 255;

    char                        _dir[_MAX_ID_LEN + 1];

private:
    static const uint32_t       _MAX_RING_SIZE      = (40 * 5) * (1024 * 5);

    char                        _id[_MAX_ID_LEN + 1];
    RingBuffer<uint64_t>        _ts;
    RingBuffer<uint64_t>        _results;
    RingBuffer<uint32_t>        _start_id;
    uint64_t                    _start_time_ns;
    uint32_t                    _last_start;

    virtual uint64_t  get_time_ns() = 0;

    static int open_file(const char* folder, const char* name, FILE **f)
    {
        static uint32_t idx = 0;
        char filename_[255] = { 0 };
        (void)snprintf(filename_, sizeof(filename_), "%s%05u_%s.csv", folder, idx, name);
        *f = fopen(filename_, "w");
        if (*f == NULL)
        {
            IS_ERR(IS_ERR_GENERAL_ERROR, "Could not open file: %s", filename_);
            return -1;
        }

        idx++;
        return 0;
    }

public:

    IsMeasureTimeBase(const char* id) :
        _ts(_MAX_RING_SIZE),
        _results(_MAX_RING_SIZE),
        _start_id(_MAX_RING_SIZE),
        _start_time_ns(0),
        _last_start(0)
    {
        strlcpy(_id, id, _MAX_ID_LEN);
    }

    virtual ~IsMeasureTimeBase() {} // clang whines without it

    void StartMeasure()
    {
        _start_time_ns = get_time_ns();
        _last_start++;
    }

    void SaveMeasure()
    {
        const uint64_t end_time_ns = get_time_ns();
        const uint64_t delta_ns = end_time_ns - _start_time_ns;
        _start_time_ns = end_time_ns;

        _ts.push(end_time_ns);
        _results.push(delta_ns);
        _start_id.push(_last_start);
    }

    void SaveResults(FILE* file)
    {
        FILE * t_file = file;

        if (NULL == t_file)
        {
            if (0 != open_file(_dir,_id, &t_file))
            {
                return;
            }
        }

        IS_INFO("Saving %s timer\n" ,_id);

        IS_ASSERT(_results.size() == _start_id.size());
        IS_ASSERT(_results.size() == _ts.size());

        for (uint32_t idx = 0; idx < _results.size(); idx++)
        {
            fprintf(t_file, "%" PRIu64 ", %u , %" PRIu64 "\n",_ts[idx], _start_id[idx], _results[idx]);
        }

        _results.clear();
        _start_id.clear();
        _last_start = 0;

        if (NULL == file)
        {
            fclose(t_file);
        }
    }
};

#if defined(IS_DBG_WIN_PERFORMANCE_TIMER)
#include <windows.h>

class IsMeasureTime : public IsMeasureTimeBase
{
private:
    uint32_t                    _freq_hz;

    uint64_t get_time_ns()
    {
        LARGE_INTEGER currMeasurement;
        (void)QueryPerformanceCounter(&currMeasurement);

        const uint64_t low_part_ns = ((uint32_t)currMeasurement.LowPart * (uint64_t)1000000000LLU / _freq_hz);
        const int32_t high_part_ns = (int32_t)((uint32_t)currMeasurement.HighPart * (uint64_t)1000000000LLU / _freq_hz);

        const uint64_t ret = (uint64_t)low_part_ns + (((int64_t)high_part_ns << 31) << 1);

        return ret;
    }

public:

    IsMeasureTime(const char *id, const char* dir = NULL, bool set_affinity_mask = true) : IsMeasureTimeBase(id), _freq_hz(0)
    {
        if (NULL != dir)
        {
            strlcpy(_dir, dir, _MAX_ID_LEN);
        }
        else
        {
            strlcpy(_dir, IS_DBG_UTILS_DEFAULT_DIR, _MAX_ID_LEN);
        }

        if (!SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS))
        {
            const DWORD dwError = GetLastError();
            IS_ERR(IS_ERR_GENERAL_ERROR, "SetPriorityClass() failed - %u", dwError);
        }

        if (!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL))
        {
            const DWORD dwError = GetLastError();
            IS_ERR(IS_ERR_GENERAL_ERROR, "SetThreadPriority() failed - %u", dwError);
        }

        if (true == set_affinity_mask)
        {
            if (!SetProcessAffinityMask(GetCurrentProcess(), 0x3))
            {
                const DWORD dwError = GetLastError();
                IS_ERR(IS_ERR_GENERAL_ERROR, "SetProcessAffinityMask() failed - %u", dwError);
            }
        }

        LARGE_INTEGER tickFrequency;
        (void)QueryPerformanceFrequency(&tickFrequency);

        _freq_hz = (uint32_t)tickFrequency.QuadPart;
        IS_ASSERT(_freq_hz < 0xFFFFFFFF); /* Sanity check */
    }

    virtual ~IsMeasureTime() {} // clang whines without it
};


#elif defined(IS_DBG_ANDROID_MONOTONIC_TIMER)
class IsMeasureTime : public IsMeasureTimeBase
{
    uint64_t get_time_ns()
    {
        timespec currMeasurement;
        clock_gettime(CLOCK_MONOTONIC, &currMeasurement);

        return currMeasurement.tv_sec * 1000000000UL + currMeasurement.tv_nsec;
    }

public:
    IsMeasureTime(const char *id) : IsMeasureTimeBase(id)
    {
        strlcpy(_dir, IS_DBG_UTILS_DEFAULT_DIR, _MAX_ID_LEN);
    }

    IsMeasureTime(const char *id, const char* dir) : IsMeasureTimeBase(id)
    {
        if (NULL != dir)
        {
            strlcpy(_dir, dir, _MAX_ID_LEN);
        }
        else
        {
            strlcpy(_dir, IS_DBG_UTILS_DEFAULT_DIR, _MAX_ID_LEN);
        }
    }
};

#endif /* IS_DBG_WIN_PERFORMANCE_TIMER */
