////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                     /
// 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 <random>

#define T5_DURATION_MS 10000

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class CT6P7Sink: public CClTextSink
{
    size_t             m_szCount;
    std::mutex         m_cLock;
public:
    CT6P7Sink()
    {
        m_szCount = 0;
    }

    virtual ~CT6P7Sink()
    {
    }

    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 (    (i_rRawLog.pModuleName)
             && (0 == PStrCmp(i_rRawLog.pModuleName, TM("t6")))
           )
        {
            m_szCount++;
        }
        else
        {
            PRINTF(TM("%s\n"), i_rRawLog.pMessage);
        }
        m_cLock.unlock();

        return ECLIENT_STATUS_OK;
    }

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

    size_t GetCount()
    {
        m_cLock.lock();
        size_t l_szRet = m_szCount;
        m_cLock.unlock();

        return l_szRet;
    }

    void ClearCount()
    {
        m_cLock.lock();
        m_szCount = 0;
        m_cLock.unlock();
    }
};


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////uP7 callbacks/////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

enum eOp : int
{
    eOpNone = 0,
    eOpStart,

    eOpCut = eOpStart,
    eOpCoruptSid,
    eOpCoruptLenPlus,
    eOpCoruptLenZero,
    eOpCoruptType,

    eOpEnd = eOpCoruptType,

    eOpMax
};


static std::atomic<eOp>                 g_bCorrupt(eOpNone);
std::random_device                      g_cRd;
std::mt19937                            g_cGenS(g_cRd());
std::mt19937                            g_cGenO(g_cRd());
std::uniform_int_distribution<uint16_t> g_cSize(1, 7);
std::uniform_int_distribution<int>      g_cOp(eOpStart, eOpEnd);

uint64_t t6getTimerFrequency(void *i_pCtx)  { UNUSED_ARG(i_pCtx); return GetPerformanceFrequency(); }
uint64_t t6getTimerValue(void *i_pCtx)      { UNUSED_ARG(i_pCtx); return GetPerformanceCounter();   }
uint32_t t6getCurrentThreadId(void *i_pCtx) { UNUSED_ARG(i_pCtx); return CProc::Get_Thread_Id();    }
bool     t6sendPacket(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;
    }

    
    eOp l_eOp = g_bCorrupt.load();


    while (i_szChunks)
    {
        uint8_t *pData  = (uint8_t*)i_pChunks->pData;
        size_t   szData = i_pChunks->szData;


        if (l_eOp != eOpNone)
        {
            printf("Operation %d\n", l_eOp);
            stuP7baseHdr *pBase = (stuP7baseHdr*)pData;

            if (eOpCut == l_eOp)
            {
                pData++;
                szData--;
            }
            else if (eOpCoruptSid == l_eOp)
            {
                pBase->uSessionId = ~pBase->uSessionId;
            }
            else if (eOpCoruptLenPlus == l_eOp)
            {
                pBase->wSize += g_cSize(g_cGenS);
            }
            else if (eOpCoruptLenZero == l_eOp)
            {
                pBase->wSize = 0;
            }
            else if (eOpCoruptType == l_eOp)
            {
                pBase->bType = ~pBase->bType;
            }

            g_bCorrupt.store(eOpNone);
            l_eOp = eOpNone;
        }

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

    return (0 == i_szChunks);
}



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool t6Resync(const tXCHAR *i_pSessionFolder)
{
    CT6P7Sink   l_cP7Sink; 
    uint64_t    l_qwAdd = (uint64_t)(&l_cP7Sink);
    tXCHAR      l_pArgs[4096];
    bool        l_bReturn = true;

    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"), 0);

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

    if (!l_iProxy->RegisterCpu(0, false, GetPerformanceFrequency(), TM("T6"), 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             = NULL;                  /**< Context for timer functions */
    l_stConfig.cbTimerFrequency      = t6getTimerFrequency;   /**< callback to get system high-resolution timer frequency */
    l_stConfig.cbTimerValue          = t6getTimerValue;       /**< 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          = t6sendPacket;          /**< callback to send data packet to host */  
    l_stConfig.pCtxThread            = 0;                     /**< Context for cbGetCurrentThreadId function */
    l_stConfig.cbGetCurrentThreadId  = t6getCurrentThreadId;  /**< 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("t6 Counter", 0, 0, 1, 1, true, &l_hCounter1))
         || (!uP7TrcRegisterModule("t6", euP7Level_Trace, &l_hModule1))
       )
    {
        return false;
    }


    std::atomic<bool>   l_bExit(false);
    std::atomic<size_t> l_szMessages(0);

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

        const char *l_pText[10] = {
            "*",
            "*1",
            "*22",
            "*333",
            "*4444",
            "*55555",
            "*666666",
            "*7777777",
            "*88888888",
            "*999999999"
        };


        while (!l_bExit)
        {
            uP7ProcessIncomingData(&l_stReadCtx, 0);

            for (size_t l_szI = 0; l_szI < l_szMessages; l_szI++)
            {
                uP7ERR(50, l_hModule1, "T6 test message #%05zu {%s}", l_szI, l_pText[l_szI % 10]);

                if ((l_szI + 1) == l_szMessages)
                {
                    l_szMessages = 0;
                }
            }

            std::this_thread::sleep_for(std::chrono::milliseconds(25));
        }
    });

    size_t l_szCount = 1000;
    size_t l_szPrev  = 0;
    size_t l_szIter  = 20;
    for (size_t iI = 1; iI < l_szIter; iI++)
    {
        //enum eOp
        //{
        //    eOpNone = 0,
        //    eOpCut,
        //    eOpCoruptSid,
        //    eOpCoruptLen,
        //    eOpCoruptType,
        //    eOpCoruptCrc,
        //    eOpMax
        //};


        g_bCorrupt.store(eOpNone);

        l_cP7Sink.ClearCount();

        //Each cycle we will deliver 50, 100, 150, 200 ... 1000 messages
        l_szPrev = l_szCount * iI / l_szIter;
        l_szMessages = l_szPrev;

        //wait to deliver messages
        while ( 0!= l_szMessages)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }

        //wait for flushing ...
        std::this_thread::sleep_for(std::chrono::milliseconds(250));

        //set corruption flag
        g_bCorrupt.store((eOp)g_cOp(g_cGenO));

        //Each cycle we will deviver 950, 900, 850, ... 100 messages
        l_szMessages = l_szCount - l_szPrev;

        //wait to deliver messages
        while ( 0!= l_szMessages)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }

        //wait for flushing ...
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    
        //checking how much were recieved
        if (l_cP7Sink.GetCount() != l_szCount - 1)
        {
            printf("ERROR: iteration %zu, received: %zu\n", iI, l_cP7Sink.GetCount());
            l_bReturn = false;
            break;
        }
    }


    l_bExit = true;
    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;
    }

    return l_bReturn;
}
