////////////////////////////////////////////////////////////////////////////////
//                                                                             /
// 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.                                            /
//                                                                             /
////////////////////////////////////////////////////////////////////////////////
#ifndef BK_REMOTE_SERVER_H
#define BK_REMOTE_SERVER_H

#include "RemoteBase.h"

#define  RM_THREAD_EXIT_SIGNAL                                (MEVENT_SIGNAL_0)
#define  RM_THREAD_EVENT_SIGNAL                               (MEVENT_SIGNAL_0)

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class CRemoteServer
{
public:
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    struct stCommand
    {
        eRmCommand  eCommand;
        tUINT8     *pData;
        size_t      szData;

        stCommand(eRmCommand i_eCommand, tUINT8 *i_pData, size_t i_szData)
            : eCommand(i_eCommand)
            , pData(NULL)
            , szData(i_szData)
        {
            if (    (i_pData)
                 && (i_szData)
               )
            {
                pData = (tUINT8*)malloc(szData);
                if (pData)
                {
                    memcpy(pData, i_pData, szData); 
                }
            }
        }

        ~stCommand()
        {
            if (pData)
            {
                free(pData);
                pData = NULL;
            }

            szData = 0;
        }
    };

protected:
    tBOOL              m_bWSA; 
    struct sockaddr_in m_stAddr;
    CUDP_Socket       *m_pSocket;
    CLock              m_cLock;

    CMEvent            m_cComm_Event;
    tBOOL              m_bComm_Thread;
    CThShell::tTHREAD  m_hComm_Thread;
    CMEvent            m_cEvent;
    CBList<stCommand*> m_cEvents;


public:
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    CRemoteServer(tINT32 i_iTimeoutms, tBOOL &o_rError)
        : m_bWSA(FALSE)
        , m_pSocket(NULL)
        , m_bComm_Thread(FALSE)
    {
        CLock l_cLock(&m_cLock);

        o_rError = FALSE;

        m_bWSA = WSA_Init();
        
        if (!m_bWSA)
        {
            o_rError = TRUE;
            return;
        }

        memset(&m_stAddr, 0, sizeof(m_stAddr));

        //N.B.: Code is DESIGNED to use loopback interface! Only loopback for security reasons.
        //Because of loopback packet size, protocol and all other propereties were selected!
        m_stAddr.sin_family      = AF_INET;
        m_stAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
        m_stAddr.sin_port        = htons(RM_UDP_PORT);   

        tUINT32 l_uiTick = GetTickCount();
        do 
        {
            m_pSocket = new CUDP_Socket(NULL, (sockaddr*)&m_stAddr, TRUE);

            if (!m_pSocket->Initialized())
            {
                delete m_pSocket;
                m_pSocket = NULL;
            }
            else
            {
                break;
            }

            CThShell::Sleep(250);

            i_iTimeoutms -= CTicks::Difference(GetTickCount(), l_uiTick);
            l_uiTick      = GetTickCount();

            if (i_iTimeoutms <= 0)
            {
                break;
            }
        } while (1);

        if (!m_pSocket)
        {
            o_rError = TRUE;
            return;
        }

        
        if (FALSE == m_cEvent.Init(1, EMEVENT_MULTI))
        {
            o_rError = TRUE;
            return;
        }


        if (FALSE == m_cComm_Event.Init(1, EMEVENT_SINGLE_AUTO))
        {
            o_rError = TRUE;
            return;
        }

        if (FALSE == CThShell::Create(&Static_Comm_Routine, this, &m_hComm_Thread, TM("P7:Comm")))
        {
            o_rError = TRUE;
            return;
        }
        else
        {
            m_bComm_Thread = TRUE;
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    virtual ~CRemoteServer()
    {
        m_cComm_Event.Set(RM_THREAD_EXIT_SIGNAL);

        if (m_bComm_Thread)
        {
            if (TRUE == CThShell::Close(m_hComm_Thread, 60000))
            {
                m_hComm_Thread = 0;//NULL;
                m_bComm_Thread = FALSE;
            }
        }

        m_cLock.Lock();
        if (m_pSocket)
        {
            delete m_pSocket;
            m_pSocket = NULL;
        }

        if (m_bWSA)
        {
            WSA_UnInit();
        }

        m_cEvents.Clear(TRUE);

        m_cLock.Unlock();
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //do not forget to delete event
    CRemoteServer::stCommand *PullCommand(tUINT32 i_iTimeoutms)
    {
        if (RM_THREAD_EVENT_SIGNAL == m_cEvent.Wait(i_iTimeoutms))
        {
            CLock l_cLock(&m_cLock);
            CRemoteServer::stCommand *l_pReturn = m_cEvents.Get_Data(m_cEvents.Get_First());
            m_cEvents.Del(m_cEvents.Get_First(), FALSE);
            return l_pReturn;
        }

        return NULL;
    }

private:
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    static THSHELL_RET_TYPE THSHELL_CALL_TYPE Static_Comm_Routine(void *i_pContext)
    {
        CRemoteServer *l_pRoutine = static_cast<CRemoteServer *>(i_pContext);
        if (l_pRoutine)
        {
            l_pRoutine->Comm_Routine();
        }

        CThShell::Cleanup();
        return THSHELL_RET_OK;
    } 

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    void Comm_Routine()
    {
        tBOOL             l_bExit              = FALSE;
        tUINT32           l_dwSignal           = MEVENT_TIME_OUT;
        tUINT32           l_dwWait_TimeOut     = 0;
        tUINT32           l_dwReceived         = 0;
        char              l_pData[RM_MAX_PACKET_SIZE];
        sRmCommon        *l_pHeader = (sRmCommon*)l_pData;
        sockaddr_storage  l_tAddress;

        memset(&l_tAddress, 0, sizeof(l_tAddress));

        while (FALSE == l_bExit)
        {
            l_dwSignal = m_cComm_Event.Wait(l_dwWait_TimeOut);

            l_dwWait_TimeOut = 10;

            if (RM_THREAD_EXIT_SIGNAL == l_dwSignal)
            {
                l_bExit = TRUE;
            }
            else if (MEVENT_TIME_OUT == l_dwSignal)
            {
                CLock l_cLock(&m_cLock);

                l_dwReceived = 0;
                if (    (UDP_SOCKET_OK == m_pSocket->Recv(&l_tAddress, (char*)l_pData, RM_MAX_PACKET_SIZE, &l_dwReceived, 0))
                     && (l_dwReceived >= sizeof(sRmCommon))
                   )
                {
                    l_dwWait_TimeOut = 0;

                    if (    (l_pHeader->uiFlags & RM_FLAG_NO_CRC)
                         || (l_pHeader->uiCrc32 == Get_CRC32((tUINT8*)l_pData + sizeof(l_pHeader->uiCrc32), 
                                                             l_pHeader->uiSize - sizeof(l_pHeader->uiCrc32)
                                                            )
                            )
                       )
                    {
                        if (    (eRmCmdExit         == l_pHeader->eCmd)
                             || (eRmCmdBringToFront == l_pHeader->eCmd)
                             || (eRmCmdOpenFile     == l_pHeader->eCmd)
                           )
                        {
                            m_cEvents.Add_After(m_cEvents.Get_Last(), 
                                                new stCommand(l_pHeader->eCmd,
                                                              (tUINT8*)l_pData,
                                                              (size_t)l_pHeader->uiSize
                                                             )
                                               );
                            m_cEvent.Set(RM_THREAD_EVENT_SIGNAL);
                        }

                        SendAck(l_tAddress, eRmAckOk, 25);
                    }
                    else
                    {
                        SendAck(l_tAddress, eRmAckError, 25);
                    }
                }
            }
        }

    } //Comm_Routine()


    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    void SendAck(sockaddr_storage &i_rAddr, eRmAck i_eCode, tINT32 i_iTimeoutms)
    {
        sRmAck  l_cAck      = {};
        tUINT32 l_uiAddSize = (AF_INET6 == i_rAddr.ss_family) ? sizeof(sockaddr_in6) : sizeof(sockaddr_in);

        l_cAck.eCode         = i_eCode;
        l_cAck.stHdr.eCmd    = eRmCmdAck;
        l_cAck.stHdr.uiFlags = 0;
        l_cAck.stHdr.uiSize  = sizeof(sRmAck);
        l_cAck.stHdr.uiCrc32 = Get_CRC32((tUINT8*)&l_cAck + sizeof(l_cAck.stHdr.uiCrc32), 
                                         l_cAck.stHdr.uiSize - sizeof(l_cAck.stHdr.uiCrc32));

        tUINT32 l_uiTick = GetTickCount();

        while (UDP_SOCKET_OK != m_pSocket->Send((sockaddr*)&i_rAddr, l_uiAddSize, (char*)&l_cAck, l_cAck.stHdr.uiSize))
        {
            CThShell::Sleep(5);

            i_iTimeoutms -= CTicks::Difference(GetTickCount(), l_uiTick);
            l_uiTick      = GetTickCount();

            if (i_iTimeoutms <= 0)
            {
                break;
            }
        }
    }
};


#endif //BK_REMOTE_SERVER_H