////////////////////////////////////////////////////////////////////////////////
//                                                                             /
// 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_CLIENT_H
#define BK_REMOTE_CLIENT_H

#include "RemoteBase.h"
#include "UTF.h"

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class CRemoteClient
{
    tBOOL              m_bWSA; 
    struct sockaddr_in m_stAddr;
    CUDP_Socket       *m_pSocket;
    tUINT8             m_pData[RM_MAX_PACKET_SIZE];
    sRmCommon         *m_pHeader;

public:
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    CRemoteClient(tBOOL &o_rError)
        : m_bWSA(FALSE)
        , m_pSocket(NULL)
    {
        o_rError = FALSE;
        m_pHeader = (sRmCommon*)m_pData;

        //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);   

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

        m_pSocket = new CUDP_Socket(NULL, (sockaddr*)&m_stAddr, FALSE);

        if (!m_pSocket->Initialized())
        {
            delete m_pSocket;
            m_pSocket = NULL;
            o_rError = TRUE;
            return;
        }

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

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    virtual ~CRemoteClient()
    {
        if (m_pSocket)
        {
            delete m_pSocket;
            m_pSocket = NULL;
        }

        if (m_bWSA)
        {
            WSA_UnInit();
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    tBOOL Ping()
    {
        m_pHeader->eCmd    = eRmCmdPing;
        m_pHeader->uiFlags = 0;
        m_pHeader->uiSize  = sizeof(sRmCommon);
        m_pHeader->uiCrc32 = Get_CRC32((tUINT8*)m_pHeader + sizeof(m_pHeader->uiCrc32), 
                                       m_pHeader->uiSize - sizeof(m_pHeader->uiCrc32));
        
        return (Send(100) && GetAck(400));
    }


    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    tBOOL Exit()
    {
        m_pHeader->eCmd    = eRmCmdExit;
        m_pHeader->uiFlags = 0;
        m_pHeader->uiSize  = sizeof(sRmCommon);
        m_pHeader->uiCrc32 = Get_CRC32((tUINT8*)m_pHeader + sizeof(m_pHeader->uiCrc32), 
                                       m_pHeader->uiSize - sizeof(m_pHeader->uiCrc32));
        
        return (Send(100) && GetAck(400));
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    tBOOL OpenFile(const tXCHAR *i_pFile)
    {
        if (!i_pFile)
        {
            return FALSE;
        }

        sRmOpenFile *l_pFile = (sRmOpenFile *)m_pData;
        l_pFile->stHdr.eCmd    = eRmCmdOpenFile;
        l_pFile->stHdr.uiFlags = 0;
        l_pFile->stHdr.uiSize  = sizeof(sRmCommon);

     #if defined(UTF8_ENCODING)
        strcpy(l_pFile->pUtf8Name, i_pFile);
        l_pFile->stHdr.uiSize += strlen(i_pFile) + 1;
     #else
        tINT32 l_iLen = Convert_UTF16_To_UTF8(i_pFile,
                                              (tACHAR*)l_pFile->pUtf8Name,
                                              RM_MAX_PACKET_SIZE - sizeof(sRmCommon)
                                             );

        if (0 > l_iLen)
        {
            return FALSE;
        }

        l_pFile->stHdr.uiSize += l_iLen + 1;
     #endif

        l_pFile->stHdr.uiCrc32 = Get_CRC32((tUINT8*)l_pFile + sizeof(l_pFile->stHdr.uiCrc32), 
                                           l_pFile->stHdr.uiSize - sizeof(l_pFile->stHdr.uiCrc32));


        return (Send(100) && GetAck(400));
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    tBOOL BringToFront()
    {
        m_pHeader->eCmd    = eRmCmdBringToFront;
        m_pHeader->uiFlags = 0;
        m_pHeader->uiSize  = sizeof(sRmCommon);
        m_pHeader->uiCrc32 = Get_CRC32((tUINT8*)m_pHeader + sizeof(m_pHeader->uiCrc32), 
                                       m_pHeader->uiSize - sizeof(m_pHeader->uiCrc32));
        return (Send(100) && GetAck(400));
    }

private:
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    tBOOL Send(tINT32 i_iTimeoutms)
    {
        tBOOL l_bReturn = TRUE;
        m_pHeader->uiCrc32 = Get_CRC32((tUINT8*)m_pData + sizeof(m_pHeader->uiCrc32), 
                                       m_pHeader->uiSize - sizeof(m_pHeader->uiCrc32));

        tUINT32 l_uiTick = GetTickCount();

        while (UDP_SOCKET_OK != m_pSocket->Send((sockaddr*)&m_stAddr, sizeof(sockaddr_in), (const char*)m_pData, m_pHeader->uiSize))
        {
            CThShell::Sleep(5);

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

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

        return l_bReturn;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    tBOOL GetAck(tUINT32 i_iTimeoutms)
    {
        tUINT32           l_dwReceived = 0;
        sockaddr_storage  l_tAddress;

        if (UDP_SOCKET_OK == m_pSocket->Recv(&l_tAddress, (char*)m_pData, RM_MAX_PACKET_SIZE, &l_dwReceived, i_iTimeoutms))
        {
            if (    (m_pHeader->uiFlags & RM_FLAG_NO_CRC)
                 || (m_pHeader->uiCrc32 == Get_CRC32(m_pData + sizeof(m_pHeader->uiCrc32), 
                                                     m_pHeader->uiSize - sizeof(m_pHeader->uiCrc32)
                                                    )
                    )
                )
            {
                if (    (eRmCmdAck == m_pHeader->eCmd)
                     && (l_dwReceived == sizeof(sRmAck))
                   )
                {
                    sRmAck *l_pAck = (sRmAck *)m_pData;
                    return (l_pAck->eCode == eRmAckOk);
                }
            }
        }
        return FALSE;
    }

};

#endif //BK_REMOTE_CLIENT_H