////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                     /
// This library is free software; you can redistribute it and/or modify it under the terms of the provided License.    /
//                                                                                                                     /
// This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even  the  implied/
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more/
// details.                                                                                                            /
// You should have received a copy of the the License along with this library.                                         /
//                                                                                                                     /
// 2012-2024 (c) Baical                                                                                                /
//                                                                                                                     /
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "common.h"
#include <atomic>
#include <inttypes.h>

#define T5_DURATION_MS 60000

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class C5TimeShift : public IuP7Time
{
    uint64_t m_qwFreq;
    uint64_t m_qwTimeStart;
    uint64_t m_qwMsPerSecondShift;
    std::atomic<int> m_iRc;
public:
    C5TimeShift(uint64_t i_qwMsPerSecondShift)
        : m_qwFreq(GetPerformanceFrequency())
        , m_qwTimeStart(GetPerformanceCounter())
        , m_qwMsPerSecondShift(i_qwMsPerSecondShift)
        , m_iRc(1)
    {
    }

    uint64_t GetClockValue() //artificially shift time
    {
        return GetTime();
    }

    virtual uint64_t GetFrequency()
    {
        return m_qwFreq;
    }

    virtual uint64_t GetTime()
    {
        return GetPerformanceCounter() + (uint64_t)(((GetPerformanceCounter() - m_qwTimeStart) * m_qwMsPerSecondShift) / 1000ull);
    }

    virtual int32_t Add_Ref() 
    {
        return ++m_iRc;
    }

    virtual int32_t Release()
    {
        if (0 == --m_iRc)
        {
            delete this;
            return 0;
        }
        return m_iRc;
    }
private:
    virtual ~C5TimeShift()
    {

    }
};


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class CT5P7Sink: public CClTextSink
{
    size_t      m_szErrors;
    std::mutex  m_cLock;
    uint64_t    m_qwCpuStartTime;
    uint64_t    m_qwCpuEndtTime;
    uint64_t    m_qwHostStartTime;
    uint64_t    m_qwHostEndtTime;
public:
    CT5P7Sink()
    {
        m_szErrors        = 0;
        m_qwCpuStartTime  = 0ull;
        m_qwCpuEndtTime   = 0ull;
        m_qwHostStartTime = 0ull;
        m_qwHostEndtTime  = 0ull;
    }

    virtual ~CT5P7Sink()
    {
    }

    virtual eClient_Status Initialize(tXCHAR **i_pArgs, tINT32 i_iCount) 
    {
        UNUSED_ARG(i_pArgs);
        UNUSED_ARG(i_iCount);
        return ECLIENT_STATUS_OK;
    }

    virtual eClient_Status Log(const CClTextSink::sLog &i_rRawLog, 
                               const tXCHAR            *i_pFmtLog, 
                               size_t                   i_szFmtLog
                              )
    {
        UNUSED_ARG(i_pFmtLog);
        UNUSED_ARG(i_szFmtLog);
        m_cLock.lock();
        if (0 != PStrICmp(TM("MicroP7"), i_rRawLog.pChannel))
        {
            if (!m_qwCpuStartTime)
            {
                m_qwCpuStartTime  = i_rRawLog.qwRawTime;
            }

            m_qwCpuEndtTime  = i_rRawLog.qwRawTime;
        }
        else 
        {
            if (i_rRawLog.eLevel > EP7TRACE_LEVEL_ERROR)
            {
                m_szErrors ++;
            }
        }
        m_cLock.unlock();

        return ECLIENT_STATUS_OK;
    }

    virtual eClient_Status TryRoll() 
    {
        return ECLIENT_STATUS_OK; 
    }
    virtual eClient_Status DumpBuffers() 
    {
        return ECLIENT_STATUS_OK;
    }

    void updateHostTime()
    {
        if (!m_qwHostStartTime)
        {
            m_qwHostStartTime = GetPerformanceCounter();
        }

        m_qwHostEndtTime = GetPerformanceCounter();
    }

    int64_t GetDriftMilliseconds()
    {
        m_cLock.lock();
        int64_t dbHostDuration = (int64_t)((m_qwHostEndtTime - m_qwHostStartTime)*1000ull/GetPerformanceFrequency());
        int64_t dbCpuDuration = (int64_t)((m_qwCpuEndtTime - m_qwCpuStartTime) / 10000ull);
        int64_t l_qwReturn = dbHostDuration - dbCpuDuration;
        m_cLock.unlock();

        return l_qwReturn;
    }

    size_t GetErrorsCount()
    {
        m_cLock.lock();
        size_t l_szReturn = m_szErrors;
        m_cLock.unlock();
        return l_szReturn;
    }
};


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////uP7 callbacks/////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
uint64_t t5getTimerFrequency(void *i_pCtx)      { UNUSED_ARG(i_pCtx); return GetPerformanceFrequency();     }
uint64_t t5getTimerValue(void *i_pCtx)          { return static_cast<C5TimeShift*>(i_pCtx)->GetClockValue();}
uint32_t t5getCurrentThreadId(void *i_pCtx)     { UNUSED_ARG(i_pCtx); return CProc::Get_Thread_Id();        }
bool     t5sendPacket(void *i_pCtx, enum euP7packetRank i_eRank, const struct stP7packetChunk *i_pChunks,
                      size_t  i_szChunks, size_t i_szData)
{
    UNUSED_ARG(i_eRank);
    IuP7Fifo *l_pFifo = static_cast<IuP7Fifo*>(i_pCtx);

    if (l_pFifo->GetFreeBytes() < i_szData)
    {
        return false;
    }

    while (i_szChunks)
    {
        if (!l_pFifo->Write(i_pChunks->pData, i_pChunks->szData))
        {
            break;
        }
        i_pChunks++;
        i_szChunks--;
    }

    return (0 == i_szChunks);
}



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool t5Time(const tXCHAR *i_pSessionFolder)
{
    uint64_t    l_qwDriftMs  = 1;
    C5TimeShift*l_pTimeShift = new C5TimeShift(l_qwDriftMs);
    CT5P7Sink   l_cP7Sink; 
    uint64_t    l_qwAdd = (uint64_t)(&l_cP7Sink);
    tXCHAR      l_pArgs[4096];

    PSPrint(l_pArgs, LENGTH(l_pArgs), TM("/P7.Verb=4 /P7.Sink=ExternalSinc /P7.ExtAddr=%llX /P7.Pool=16384"), l_qwAdd);
    //PSPrint(l_pArgs, LENGTH(l_pArgs), TM("/P7.Verb=4 /P7.Sink=Baical /P7.Pool=16384"), l_qwAdd);

    IuP7proxy *l_iProxy = uP7createProxy(l_pArgs, i_pSessionFolder);
    IuP7Fifo  *l_iFifo  = NULL; 

    if (!l_iProxy->RegisterCpu(0, false, l_pTimeShift, TM("T5"), 128*1024, true, l_iFifo))
    {
        return false;
    }

    struct stuP7config l_stConfig; 

    l_stConfig.bSessionIdCrc7        = g_bCrc7;               /**< Session ID crc */ 
    l_stConfig.uSessionId            = g_uSessionId;          /**< Session ID*/  
    l_stConfig.pCtxTimer             = l_pTimeShift;          /**< Context for timer functions */
    l_stConfig.cbTimerFrequency      = t5getTimerFrequency;   /**< callback to get system high-resolution timer frequency */
    l_stConfig.cbTimerValue          = t5getTimerValue;       /**< callback to get system high-resolution timer value */
    l_stConfig.pCtxLock              = NULL;                  /**< Context for lock functions, set it to NULL if there is no RTOS*/
    l_stConfig.cbLock                = NULL;                  /**< callback to call OS lock function, set it to NULL if there is no RTOS */
    l_stConfig.cbUnLock              = NULL;                  /**< callback to call OS unlock function, set it to NULL if there is no RTOS */
    l_stConfig.pCtxPacket            = l_iFifo;               /**< Context for data sending */
    l_stConfig.cbSendPacket          = t5sendPacket;          /**< callback to send data packet to host */  
    l_stConfig.pCtxThread            = 0;                     /**< Context for cbGetCurrentThreadId function */
    l_stConfig.cbGetCurrentThreadId  = t5getCurrentThreadId;  /**< callback get current thread ID, set it to NULL if there is no RTOS*/  
    l_stConfig.eDefaultVerbosity     = euP7Level_Trace;       /**< Default verbosity*/  

    //Generated descriptors, located in uP7preprocessed.h
    l_stConfig.pModules              = g_pModules;            /**< Trace modules descriptors, generated by uP7 preprocessor*/
    l_stConfig.szModules             = g_szModules;           /**< Trace modules descriptors count*/  
    l_stConfig.pTraces               = g_pTraces;             /**< Trace descriptors, generated by uP7 preprocessor*/  
    l_stConfig.szTraces              = g_szTraces;            /**< Trace descriptors count*/  
    l_stConfig.pTelemetry            = g_pTelemetry;          /**< Telemetry descriptors, generated by uP7 preprocessor*/  
    l_stConfig.szTelemetry           = g_szTelemetry;         /**< Telemetry descriptors count*/  
    l_stConfig.eAlignment            = euP7alignment4;

    if (!uP7initialize(&l_stConfig))
    {
        return false; //we don't care about memory leak! test failed application will be closed
    }


    huP7Module l_hModule1 = uP7_MODULE_INVALID_ID;
    huP7TelId  l_hCounter1 = uP7_TELEMETRY_INVALID_ID;
    if (    (!uP7TelCreateCounter("t5 Counter", 0, 0, 1, 1, true, &l_hCounter1))
         || (!uP7TrcRegisterModule("t5", euP7Level_Trace, &l_hModule1))
       )
    {
        return false;
    }

    std::thread *l_pThread = new std::thread([&]
    {
        stReadContext l_stReadCtx = {0};
        l_stReadCtx.pFifo = l_iFifo;
        uint32_t l_uTimeStart = GetTickCount();
        uint32_t uit = 0;

        while (CTicks::Difference(GetTickCount(), l_uTimeStart) < T5_DURATION_MS)
        {
            uP7TRC(49, l_hModule1, "T5 test message", 0);
            l_cP7Sink.updateHostTime();
            uP7ProcessIncomingData(&l_stReadCtx, 5);
            if (uit++ % 100 == 0)
            {
                printf("%d%% ", (CTicks::Difference(GetTickCount(), l_uTimeStart) * 100) / T5_DURATION_MS);
            }
        }
        printf("\n");
    });


    l_pThread->join();
    delete l_pThread;
    l_pThread = NULL;

    uP7unInitialize();

    if (l_iFifo)
    {
        l_iProxy->UnRegisterCpu(0);
        l_iFifo->Release();
    }

    if (l_iProxy)
    {
        l_iProxy->Release();
        l_iProxy = NULL;
    }


    if (l_cP7Sink.GetErrorsCount())
    {
        return false;
    }

    int64_t qwDrift = l_cP7Sink.GetDriftMilliseconds();

    printf("t5Time: drift: %" PRId64 "ms for duration %ds, with artificially created drift %dms\n", 
           qwDrift,
           T5_DURATION_MS / 1000,
           (uint32_t)(T5_DURATION_MS * l_qwDriftMs / 1000ull)
        );

    if (abs(qwDrift) > 3)
    {
        return false;
    }

    l_pTimeShift->Release();
    l_pTimeShift = NULL;

    return true;
}
