////////////////////////////////////////////////////////////////////////////////
//                                                                             /
// 2012-2019 (c) Baical                                                        /
//                                                                             /
// This library is free software; you can redistribute it and/or               /
// modify it under the terms of the GNU Lesser General Public                  /
// License as published by the Free Software Foundation; either                /
// version 3.0 of the License, or (at your option) any later version.          /
//                                                                             /
// 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 GNU Lesser General Public            /
// License along with this library.                                            /
//                                                                             /
////////////////////////////////////////////////////////////////////////////////
//https://stackoverflow.com/questions/9637551/relocation-r-x86-64-32s-against-rodata-while-compiling-on-64-bit-platform

#include "common.h"

#define P7PROVIDER_PLUGIN_GUID {0x4CA06FED, 0xF30A, 0x4607, {0xA5, 0xB7, 0x3D, 0xB1, 0xFA, 0x7E, 0x77, 0x5A}}

#if defined(_WIN32) || defined(_WIN64)
     #pragma comment(lib, "Psapi.lib")
     #pragma comment(lib, "Pdh.lib")
#endif


//{0x4CA06FED, 0xF30A, 0x4607, { 0xA5, 0xB7, 0x3D, 0xB1, 0xFA, 0x7E, 0x77, 0x5A }};
static const GUID g_sPlugin_GUID = P7PROVIDER_PLUGIN_GUID;

extern "C" 
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Get_Plugin_Info
P7_EXPORT Bk::eResult __cdecl Get_Plugin_Info(Bk::stPluginInfo *o_pInfo)
{
    Bk::eResult l_eResult = Bk::eOk;

    if (NULL == o_pInfo)
    {
        return Bk::eErrorWrongInput;
    }

    memcpy(&o_pInfo->sGUID, &g_sPlugin_GUID, sizeof(GUID));
    //memcpy_s(&o_pInfo->sGUID, sizeof(GUID), &g_sPlugin_GUID, sizeof(GUID));

    o_pInfo->eType         = Bk::ePluginProvider;
    o_pInfo->dwAPI_Version = BK_PLUGIN_API_VERSION;
    o_pInfo->qwVersion     = 0; //will be filled outside;
    o_pInfo->pFormats      = NULL;

    PStrCpy(o_pInfo->pName, LENGTH(o_pInfo->pName), TM("Provider: P7 Udp"));

    return l_eResult;
}//Get_Plugin_Info

//compiler check than function match the prototype
static const fnBkGetPluginInfo g_pGetPluginInfo = Get_Plugin_Info;


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Create_Provider
P7_EXPORT Bk::eResult __cdecl Create_Provider(Bk::IUnknown   *i_pCore, 
                                              Bk::IBNode     *i_pNode, 
                                              CProperty      *i_pProp, 
                                              Bk::IProvider **o_pProvider)
{
    Bk::eResult l_eReturn = Bk::eOk;

    if (NULL == o_pProvider)
    {
        return Bk::eErrorWrongInput;
    }

    CProviderP7 *l_pP7Shell = new CProviderP7(i_pCore, i_pNode, i_pProp);

    if (    (l_pP7Shell)
         && (FALSE == l_pP7Shell->Get_Initialized())
       )
    {
        delete l_pP7Shell;
        l_pP7Shell = NULL;
    }

    *o_pProvider = l_pP7Shell; 

    return l_eReturn;
}//Create_Provider

//compiler check than function match the prototype
static const fnBkCreateProvider g_pBkCreateProvider = Create_Provider;
}//extern "C"


GASSERT(CProviderP7::eThreadMainCountSignal == 1);
//"m_cExit_Event wrong initialization");
//Update m_cExit_Event.Init((tUINT8)eThreadMainCountSignal, EMEVENT_SINGLE_AUTO))

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Create_Provider
CProviderP7::CProviderP7(Bk::IUnknown *i_pCore, Bk::IBNode *i_pNode, CProperty *i_pProp)
    : m_lReference(1)
    , m_pServer(NULL)
    , m_pDns_Resolver(NULL)

    , m_iP7_Trace(NULL)
    , m_hP7_TraceModule(NULL)
    , m_iP7_Tel(NULL)
    , m_pJlTrProxy(NULL)

    , m_bIsThread(FALSE)

    , m_pPropRoot(i_pProp)

    , m_bInitialized(FALSE)

    , m_pCore(NULL)
{
    UNUSED_ARG(i_pNode);

    m_iP7_Trace = P7_Get_Shared_Trace(BK_SHARED_TRACE);
    m_iP7_Tel   = P7_Get_Shared_Telemetry(BK_SHARED_TELEMETRY);

    if (m_iP7_Trace)
    {
        m_iP7_Trace->Register_Module(TM("Prv/Net"), &m_hP7_TraceModule);

        m_pJlTrProxy = new CJrTrProxy(m_iP7_Trace);
    }

    if (m_pPropRoot)
    {
        m_pPropRoot->Add_Ref();
    }

    if (i_pCore)
    {
        if (Bk::eOk == i_pCore->Query_Interface(Bk::eInterfaceCore, (void*&)m_pCore))
        {
            m_bInitialized = TRUE;
        }
    }

    if (!m_bInitialized)
    {
        LOG_ERROR(TM("[0x%08p] Can't query Bk::eInterfaceCore interface"), this);
    }

    if (m_bInitialized)
    {
        m_pServer = P7_Create_Server(m_pJlTrProxy, m_pPropRoot);
        if (NULL == m_pServer)
        {
            m_bInitialized = FALSE;
            LOG_ERROR(TM("[0x%08p] P7_Create_Server fail"), this);
        }
    }

    if (m_bInitialized)
    {
        m_pDns_Resolver = new CDNS_Resolver();
    }

    if (m_bInitialized)
    {
        if (FALSE == m_cExit_Event.Init((tUINT8)eThreadMainCountSignal, EMEVENT_SINGLE_AUTO))
        {
            m_bInitialized = FALSE;
            LOG_ERROR(TM("[0x%08p] Exit event wasn't created !"), this);
        }
    }

    if (m_bInitialized)
    {
        if (FALSE == CThShell::Create(&Static_Routine, 
                                      this,
                                      &m_hThread,
                                      TM("Prov/Mon")               
                                     )
           )
        {
            m_bInitialized = FALSE;
            LOG_ERROR(TM("[0x%08p] Thread wasn't created !"), this);
        }
        else
        {
            m_bIsThread = TRUE;
        }
    }
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//~CProviderP7
CProviderP7::~CProviderP7()
{
    if (m_bIsThread)
    {
        m_cExit_Event.Set(eThreadMainExit);
        if (TRUE == CThShell::Close(m_hThread, 15000))
        {
            m_hThread   = 0;//NULL;
            m_bIsThread = FALSE;
        }
        else
        {
            LOG_ERROR(TM("[0x%08p] stat thread timeout expire !"), this);
        }
    }

    m_cConnections.Clear(TRUE);

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

    if (m_pDns_Resolver)
    {
        delete m_pDns_Resolver;
        m_pDns_Resolver = NULL;
    }

    if (m_pCore)
    {
        m_pCore->Release();
    }

    if (m_pJlTrProxy)
    {
        delete m_pJlTrProxy;
        m_pJlTrProxy = NULL;
    }

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

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

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

} //~CProviderP7


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Query_Interface
Bk::eResult CProviderP7::Query_Interface(Bk::eInterface  i_eId, void *&o_rUnknown)
{
    UNUSED_ARG(i_eId);
    UNUSED_ARG(o_rUnknown);
    return Bk::eErrorNotImplemented;
}//Query_Interface


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Add_Source
Bk::eResult CProviderP7::Add_Source(const tXCHAR *i_pSource, void *&o_rToken)
{
    UNUSED_ARG(i_pSource);
    UNUSED_ARG(o_rToken);
    return Bk::eErrorNotSupported;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Del_Source
Bk::eResult CProviderP7::Del_Source(const void *i_pToken)
{
    UNUSED_ARG(i_pToken);
    return Bk::eErrorNotSupported;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Get_Progress
Bk::eResult CProviderP7::Get_Progress(tUINT32 &o_rProgress)
{
    UNUSED_ARG(o_rProgress);
    return Bk::eErrorNotSupported;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Exceptional_Shutdown
Bk::eResult CProviderP7::Exceptional_Shutdown()
{
    if (m_bIsThread)
    {
        m_cExit_Event.Set(eThreadMainExit);
        if (TRUE == CThShell::Close(m_hThread, 15000))
        {
            m_hThread = 0;//NULL;
            m_bIsThread = FALSE;
        }
        else
        {
            LOG_ERROR(TM("[0x%08p] stat thread timeout expire !"), this);
        }
    }

    return Bk::eOk;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Release
tINT32 CProviderP7::Add_Ref()
{
    return ATOMIC_INC(&m_lReference);
}
    

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Release
tINT32 CProviderP7::Release()
{
    tINT32 l_lResult = ATOMIC_DEC(&m_lReference);
    if (0 >= l_lResult)
    {
        delete this;
    }

    return l_lResult;
} //Release






////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//RoutineMain
void CProviderP7::RoutineMain()
{
    CBList<stStat*>   l_cNetStat;
    CPerformanceInfo  l_cPerfMon(TM("GBaical"));
    eThreadMain       l_eWait        = eThreadMainCountSignal;
    tBOOL             l_bExit        = FALSE;
    IP7S_Address     *l_pAddress     = NULL;
    tUINT32           l_uTickConn    = GetTickCount();

    tUINT16           l_bTidMemBusy  = P7TELEMETRY_INVALID_ID_V2;
    tUINT16           l_bTidMemAlloc = P7TELEMETRY_INVALID_ID_V2;
    tUINT16           l_bTidCpuBk    = P7TELEMETRY_INVALID_ID_V2;
    tUINT16           l_bTidCpuSys   = P7TELEMETRY_INVALID_ID_V2;
    tUINT16           l_bTidHandles  = P7TELEMETRY_INVALID_ID_V2;
    tUINT16           l_bTidThreads  = P7TELEMETRY_INVALID_ID_V2;
    tUINT16           l_bTidMemory   = P7TELEMETRY_INVALID_ID_V2;
    tUINT16           l_bTidConn     = P7TELEMETRY_INVALID_ID_V2;

    size_t            l_szMemBusy    = 0;
    size_t            l_szMemAlloc   = 0;
                                     
    tUINT32           l_dwMilSecs    = 0;
    CTickHi           l_cTicksTel;      

    if (m_iP7_Trace)
    {
        m_iP7_Trace->Register_Thread(TM("Core"), 0);
    }

    LOG_INFO(TM("Start P7 provider thread"), 0);

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //create network counters
    if (m_iP7_Tel)
    {
        while ((l_pAddress = m_pServer->Get_Next_Address(l_pAddress)))
        {
            sockaddr_storage *l_pSockStorage = l_pAddress->Get_Socket_Address();
            tXCHAR l_pIp[128];

            if (Print_SAddr((sockaddr*)&l_pSockStorage, l_pIp, LENGTH(l_pIp)))
            {
                stStat *l_pStat = new stStat(l_pAddress);

                tXCHAR l_pName[256];
                PSPrint(l_pName, LENGTH(l_pName), TM("Provider/Network/%s/In (Mb\\s)"), l_pIp);
                m_iP7_Tel->Create(l_pName, 0.0, 0.0, 150.0, 120.0, TRUE, &l_pStat->bTidNetIn);

                PSPrint(l_pName, LENGTH(l_pName), TM("Provider/Network/%s/Out (Kb\\s)"), l_pIp);
                m_iP7_Tel->Create(l_pName, 0.0, 0.0, 1024.0, 768.0, TRUE, &l_pStat->bTidNetOut);

                l_cNetStat.Add_After(l_cNetStat.Get_Last(), l_pStat);
            }
        }

        m_iP7_Tel->Create(TM("Provider/Buffers/Busy (Mb)"), 0.0, 0.0, 132.0, 132.0, TRUE, &l_bTidMemBusy);
        m_iP7_Tel->Create(TM("Provider/Buffers/Allocated (Mb)"), 0.0, 0.0, 132.0, 132.0, TRUE, &l_bTidMemAlloc);

        m_iP7_Tel->Create(TM("Provider/Connections"), 0.0, 0.0, 16.0, 132.0, TRUE, &l_bTidConn);

        m_iP7_Tel->Create(TM("Process/CPU Total (%)"), 0.0, 0.0, 100.0, 80.0, TRUE, &l_bTidCpuSys);

        m_iP7_Tel->Create(TM("Process/CPU Baical (%)"), 0.0, 0.0, 100.0, 80.0, TRUE, &l_bTidCpuBk);
        m_iP7_Tel->Create(TM("Process/Handles"), 0.0, 0.0, 1024.0, 768.0, TRUE, &l_bTidHandles);
        m_iP7_Tel->Create(TM("Process/Threads"), 0.0, 0.0, 32.0, 24.0, TRUE, &l_bTidThreads);
        m_iP7_Tel->Create(TM("Process/Memory (Kb)"), 0.0, 0.0, 756.0*1024.0, 512.0*1024.0, TRUE, &l_bTidMemory);
    }

    l_cTicksTel.Start();

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //main loop
    while (!l_bExit)
    {
        l_eWait = (eThreadMain)m_cExit_Event.Wait(10);

        if (eThreadMainExit == l_eWait)
        {
            l_bExit = TRUE;
        }
        else
        {
            ////////////////////////////////////////////////////////////////////////////////////////////////////////////
            //Create new connections
            l_pAddress = NULL;
            while ((l_pAddress = m_pServer->Get_Next_Address(l_pAddress)) != NULL)
            {
                IP7S_Connnection *l_pConnection = l_pAddress->Get_New_Conn();
                if (l_pConnection)
                {
                    m_cConnections.Add_After(m_cConnections.Get_Last(), 
                                              new CConnectionP7(m_pCore, 
                                                                m_pServer, 
                                                                l_pAddress, 
                                                                l_pConnection, 
                                                                m_pPropRoot, 
                                                                m_pDns_Resolver, 
                                                                m_iP7_Trace, 
                                                                m_iP7_Tel
                                                               )
                                             );
                }
            }
            
            ////////////////////////////////////////////////////////////////////////////////////////////////////////////
            //Cleanup old connections
            if (CTicks::Difference(GetTickCount(), l_uTickConn) > 1000)
            {
                pAList_Cell l_pEl = NULL;
                while ((l_pEl = m_cConnections.Get_Next(l_pEl)))
                {
                    CConnectionP7 *l_pConnection = m_cConnections.Get_Data(l_pEl);
                    if (    (l_pConnection)
                         && (!l_pConnection->Get_Active())
                       )
                    {
                        pAList_Cell l_pPrev = m_cConnections.Get_Prev(l_pEl);
                        m_cConnections.Del(l_pEl, TRUE);
                        l_pEl = l_pPrev;
                    }
                }

                l_uTickConn = GetTickCount();
            }
                
            ////////////////////////////////////////////////////////////////////////////////////////////////////////////
            //providing statistics
            if (m_iP7_Tel)
            {
                l_cTicksTel.Reset();
                l_cTicksTel.Stop();
                l_dwMilSecs += l_cTicksTel.Get() / 100;
                l_cTicksTel.Start();

                if (l_dwMilSecs > 100)
                {
                    pAList_Cell l_pEl = NULL;
                    while ((l_pEl = l_cNetStat.Get_Next(l_pEl)))
                    {
                        stStat *l_pStat = l_cNetStat.Get_Data(l_pEl);
                        if (    (l_pStat)
                             && (l_pStat->pAddress)
                           )
                        {
                            tUINT32 l_dwIn  = 0;
                            tUINT32 l_dwOut = 0;
                            l_pStat->pAddress->Get_Stat(l_dwIn, l_dwOut);

                            m_iP7_Tel->Add(l_pStat->bTidNetIn, (tDOUBLE)l_dwIn * (1000.0 / (tDOUBLE)l_dwMilSecs) / (1024.0*1024.0));
                            m_iP7_Tel->Add(l_pStat->bTidNetOut, (tDOUBLE)l_dwOut * (1000.0 / (tDOUBLE)l_dwMilSecs) / 1024.0);
                        }
                    }

                    m_pServer->Get_MemStat(l_szMemBusy, l_szMemAlloc);
                    m_iP7_Tel->Add(l_bTidMemBusy, (tDOUBLE)l_szMemBusy / (1024.0*1024.0));
                    m_iP7_Tel->Add(l_bTidMemAlloc, (tDOUBLE)l_szMemAlloc / (1024.0*1024.0));

                    m_iP7_Tel->Add(l_bTidConn, m_cConnections.Count());

                    if (l_cPerfMon.Refresh())
                    {
                        m_iP7_Tel->Add(l_bTidCpuBk,   (tDOUBLE)l_cPerfMon.Get(IPerformanceInfo::eCounterProcessCpu));
                        m_iP7_Tel->Add(l_bTidCpuSys,  (tDOUBLE)l_cPerfMon.Get(IPerformanceInfo::eCounterSystemCpu));
                        m_iP7_Tel->Add(l_bTidHandles, (tDOUBLE)l_cPerfMon.Get(IPerformanceInfo::eCounterProcessHandles));
                        m_iP7_Tel->Add(l_bTidThreads, (tDOUBLE)l_cPerfMon.Get(IPerformanceInfo::eCounterProcessThreads));
                        m_iP7_Tel->Add(l_bTidMemory,  (tDOUBLE)l_cPerfMon.Get(IPerformanceInfo::eCounterProcessMemory)/ 1024ll);
                    }
                }
            }//if (m_iP7_Tel)
        }
    }//while (WAIT_TIMEOUT == WaitForSingleObject(m_hEvExit, 250))

    l_cNetStat.Clear(TRUE);
    m_cConnections.Clear(TRUE);

    LOG_INFO(TM("Stop P7 provider thread"), 0);
    if (m_iP7_Trace)
    {
        m_iP7_Trace->Unregister_Thread(0);
    }
}//RoutineMain




