/*++

Program name:

 dm

Module Name:

  WebSocketClient.cpp

Notices:

  WebSocket Client

Author:

  Copyright (c) Prepodobny Alen

  mailto: alienufo@inbox.ru
  mailto: ufocomp@gmail.com

--*/

#include "Core.hpp"
#include "WebSocketClient.hpp"
//----------------------------------------------------------------------------------------------------------------------

extern "C++" {

namespace Apostol {

    namespace Client {

        CString GenUniqueId() {
            return GetUID(32).Lower();
        }

        //--------------------------------------------------------------------------------------------------------------

        //-- CWebSocketClient ------------------------------------------------------------------------------------------

        //--------------------------------------------------------------------------------------------------------------

        CWebSocketClient::CWebSocketClient(): CCustomWebSocketClient() {
            m_pContext = nullptr;
            m_SendCount = 0;
            m_PingDateTime = 0;
            m_PongDateTime = 0;
            m_HeartbeatDateTime = 0;
            m_RegistrationDateTime = 0;
            m_HeartbeatInterval = 600;
            m_Authorized = false;
            m_OnAuthorizeEvent = nullptr;
            m_OnSubscribeEvent = nullptr;
            m_OnKeyEvent = nullptr;
            m_OnWebSocketEvent = nullptr;
        }
        //--------------------------------------------------------------------------------------------------------------

        CWebSocketClient::CWebSocketClient(CContext *Context, const CLocation &URI): CCustomWebSocketClient(URI) {
            m_pContext = Context;
            m_SendCount = 0;
            m_PingDateTime = 0;
            m_PongDateTime = 0;
            m_HeartbeatDateTime = 0;
            m_RegistrationDateTime = 0;
            m_HeartbeatInterval = 600;
            m_Authorized = false;
            m_OnAuthorizeEvent = nullptr;
            m_OnSubscribeEvent = nullptr;
            m_OnKeyEvent = nullptr;
            m_OnWebSocketEvent = nullptr;
        }
        //--------------------------------------------------------------------------------------------------------------

        void CWebSocketClient::Reload() {
            m_Authorized = false;
            m_SendCount = 0;
            m_PongDateTime = 0;
            m_HeartbeatDateTime = 0;
            m_RegistrationDateTime = 0;
        }
        //--------------------------------------------------------------------------------------------------------------

        void CWebSocketClient::CheckCallError(const CWSMessage &Error, const CWSMessage &Message) {
            if (Error.ErrorCode == 401) {
                m_Authorized = false;
                m_RegistrationDateTime = 0;
                m_MessageList.Add(Message);
            }
        }
        //--------------------------------------------------------------------------------------------------------------

        bool CWebSocketClient::Authorized() const {
            return m_Authorized;
        }
        //--------------------------------------------------------------------------------------------------------------

        void CWebSocketClient::Authorize() {

            auto OnRequest = [this](CWebSocketMessageHandler *AHandler, CWebSocketConnection *AWSConnection) {
                const auto &wsMessage = RequestToMessage(AWSConnection);
                if (wsMessage.MessageTypeId == mtCallResult) {
                    if (wsMessage.Payload.HasOwnProperty("authorized")) {
                        m_Authorized = wsMessage.Payload["authorized"].AsBoolean();
                        DoAuthorizeEvent(AHandler->Message(), wsMessage);
                    }

                    if (m_Authorized) {
                        Subscribe();
                    }
                } else if (wsMessage.MessageTypeId == mtCallError) {
                    DoError(wsMessage.ErrorCode, wsMessage.ErrorMessage);
                }
            };

            CWSMessage Message;

            Message.MessageTypeId = WSProtocol::mtOpen;
            Message.UniqueId = GenUniqueId();
            Message.Action = "/authorize";
            Message.Payload = CString().Format(R"({"secret": "%s"})", m_pContext->Secret().c_str());

            SendMessage(Message, OnRequest);
        }
        //--------------------------------------------------------------------------------------------------------------

        void CWebSocketClient::Subscribe() {

            auto OnRequest = [this](CWebSocketMessageHandler *AHandler, CWebSocketConnection *AWSConnection) {
                const auto &wsMessage = RequestToMessage(AWSConnection);
                if (wsMessage.MessageTypeId == mtCallResult) {
                    DoSubscribeEvent(AHandler->Message(), wsMessage);
                    UpdateKey();
                } else if (wsMessage.MessageTypeId == mtCallError) {
                    DoError(wsMessage.ErrorCode, wsMessage.ErrorMessage);
                }
            };

            CWSMessage Message;

            Message.MessageTypeId = WSProtocol::mtCall;
            Message.UniqueId = GenUniqueId();
            Message.Action = "/observer/subscribe";
            Message.Payload = CString().Format(R"({"publisher":"notify","filter": {"classes": ["key"]},"params":{"type":"object"}})");

            SendMessage(Message, OnRequest);
        }
        //--------------------------------------------------------------------------------------------------------------

        void CWebSocketClient::UpdateKey() {

            auto OnRequest = [this](CWebSocketMessageHandler *AHandler, CWebSocketConnection *AWSConnection) {
                const auto &wsMessage = RequestToMessage(AWSConnection);
                if (wsMessage.MessageTypeId == mtCallResult) {
                    DoKeyEvent(AHandler->Message(), wsMessage);
                } else if (wsMessage.MessageTypeId == mtCallError) {
                    DoError(wsMessage.ErrorCode, wsMessage.ErrorMessage);
                }
            };

            const auto &Keys = m_pContext->PGP();

            for (int i = 0; i < Keys.Count(); i++ ) {
                const auto &Key = Keys[i];

                CWSMessage Message;

                Message.MessageTypeId = WSProtocol::mtCall;
                Message.UniqueId = GenUniqueId();
                Message.Action = "/key/public";
                Message.Payload = CString().Format(
                        R"({"type": "pgp", "account": "%s", "code": "%s", "fields": ["code", "data"]})",
                        m_pContext->Name().c_str(), Key.Name.c_str());

                SendMessage(Message, OnRequest);
            }
        }
        //--------------------------------------------------------------------------------------------------------------

        void CWebSocketClient::Send(const CString &Action, const CJSON &Payload, COnMessageHandlerEvent &&Handler) {

            auto OnRequest = [this](CWebSocketMessageHandler *AHandler, CWebSocketConnection *AWSConnection) {
                const auto &wsMessage = RequestToMessage(AWSConnection);
                if (wsMessage.MessageTypeId == mtCallResult) {
                    DoWebSocketEvent(AHandler->Message(), wsMessage);
                } else if (wsMessage.MessageTypeId == mtCallError) {
                    DoError(wsMessage.ErrorCode, wsMessage.ErrorMessage);
                }
            };

            CWSMessage Message;

            Message.MessageTypeId = WSProtocol::mtCall;
            Message.UniqueId = GenUniqueId();
            Message.Action = Action;
            Message.Payload = Payload;

            m_SendCount++;
            SendMessage(Message, Handler == nullptr ? OnRequest : std::move(Handler));
        }
        //--------------------------------------------------------------------------------------------------------------

        void CWebSocketClient::PushMessageList() {
            for (int i = 0; i < m_MessageList.Count(); ++i) {
                SendMessage(m_MessageList[i]);
            }
            m_MessageList.Clear();
        }
        //--------------------------------------------------------------------------------------------------------------

        void CWebSocketClient::Heartbeat(CDateTime Now) {
            if (Active() && Connected() && !Connection()->ClosedGracefully() && Connection()->Protocol() == pWebSocket) {
                if (m_PongDateTime == 0)
                    m_PongDateTime = Now;

                if (Now - m_PongDateTime >= (CDateTime) 90 / SecsPerDay) {
                    //DoTimeOut();
                    return;
                }

                if (Now >= m_PingDateTime) {
                    m_PingDateTime = Now + (CDateTime) 60 / SecsPerDay; // 60 sec
                    Ping();
                } else if (!m_Authorized) {
                    if (Now >= m_RegistrationDateTime) {
                        m_RegistrationDateTime = Now + (CDateTime) 30 / SecsPerDay; // 30 sec
                        Authorize();
                    }
                } else {
                    if (Now >= m_HeartbeatDateTime) {
                        m_HeartbeatDateTime = Now + (CDateTime) m_HeartbeatInterval / SecsPerDay;
                        DoHeartbeat();
                    }
                }
            }
        }
        //--------------------------------------------------------------------------------------------------------------

        void CWebSocketClient::DoPing(CObject *Sender) {

        }
        //--------------------------------------------------------------------------------------------------------------

        void CWebSocketClient::DoPong(CObject *Sender) {
            m_PongDateTime = Now();
        }
        //--------------------------------------------------------------------------------------------------------------

        void CWebSocketClient::DoAuthorizeEvent(const CWSMessage &Request, const CWSMessage &Response) {
            if (m_OnAuthorizeEvent != nullptr) {
                m_OnAuthorizeEvent(this, Request, Response);
            }
        }
        //--------------------------------------------------------------------------------------------------------------

        void CWebSocketClient::DoSubscribeEvent(const CWSMessage &Request, const CWSMessage &Response) {
            if (m_OnSubscribeEvent != nullptr) {
                m_OnSubscribeEvent(this, Request, Response);
            }
        }
        //--------------------------------------------------------------------------------------------------------------

        void CWebSocketClient::DoKeyEvent(const CWSMessage &Request, const CWSMessage &Response) {
            if (m_OnKeyEvent != nullptr) {
                m_OnKeyEvent(this, Request, Response);
            }
        }
        //--------------------------------------------------------------------------------------------------------------

        void CWebSocketClient::DoWebSocketEvent(const CWSMessage &Request, const CWSMessage &Response) {
            if (m_OnWebSocketEvent != nullptr) {
                m_OnWebSocketEvent(this, Request, Response);
            }
        }

        //--------------------------------------------------------------------------------------------------------------

        //-- CWebSocketClientItem --------------------------------------------------------------------------------------

        //--------------------------------------------------------------------------------------------------------------

        CWebSocketClientItem::CWebSocketClientItem(CWebSocketClientManager *AManager): CCollectionItem(AManager), CWebSocketClient() {

        }
        //--------------------------------------------------------------------------------------------------------------

        CWebSocketClientItem::CWebSocketClientItem(CWebSocketClientManager *AManager, CContext *Context, const CLocation &URI):
                CCollectionItem(AManager), CWebSocketClient(Context, URI) {

        }

        //--------------------------------------------------------------------------------------------------------------

        //-- CWebSocketClientManager -----------------------------------------------------------------------------------

        //--------------------------------------------------------------------------------------------------------------

        CWebSocketClientItem *CWebSocketClientManager::GetItem(int Index) const {
            return (CWebSocketClientItem *) inherited::GetItem(Index);
        }
        //--------------------------------------------------------------------------------------------------------------

        CWebSocketClientItem *CWebSocketClientManager::Add(CContext *Context, const CLocation &URI) {
            return new CWebSocketClientItem(this, Context, URI);
        }

    }
}

}
