#include "zmq.hpp"
#include <DirectCommunicationServer.hpp>
#include <memory>
#include <string>
#include <sys/types.h>
#include <thread>
#include <loguru.hpp>


namespace DirectCommunication 
{
    DirectCommunicationServer::DirectCommunicationServer(ushort port):port_(port)
    {

        context_ = zmq::context_t();
        socket_ = zmq::socket_t(context_,zmq::socket_type::server);

        socket_.bind("tcp://*:"+std::to_string(port_));
        socket_.set(zmq::sockopt::rcvtimeo,100);

        Worker_ = std::thread(&DirectCommunicationServer::workerFunc_,this);
        stopWorker_ = false;


    }


    DirectCommunicationServer::~DirectCommunicationServer()
    {
        sendMessage("CLOSE");
        stopWorker_ = true;

        Worker_.join();

        socket_.close();
    }

    void DirectCommunicationServer::registerMessageCallback(std::function<void(std::string)> MessageHandle)
    {
        MessageHandle_ = MessageHandle;
    }


    void  DirectCommunicationServer::sendMessage(std::string msg)
    {
        if (connectedClients_.size() > 0) 
        {
            for (auto it = connectedClients_.begin(); it != connectedClients_.end(); ++it) {
                zmq::message_t zmqMsg(msg.begin(),msg.end());
                zmqMsg.set_routing_id(*it);
                try {
                socket_.send(zmqMsg,zmq::send_flags::dontwait);
                
                } catch (const std::exception& e) {
                    LOG_S(ERROR)<<e.what(); 
                    LOG_S(ERROR)<< "Routing id: " << *it;
                    deleteClient(*it);
                }
            }
        }
    }

    std::string  DirectCommunicationServer::getLatestMessage()
    {
        if (receivedMessages_.size() > 0) 
        {   
            std::string msg; 
            receivedMessages_.get(msg);
            return msg;
        }
        return "NULL";
    }

    int DirectCommunicationServer::countClients()
    {
        return connectedClients_.size();
    }


    void DirectCommunicationServer::workerFunc_()
    {
        while (stopWorker_ == false) 
        {   
            zmq::message_t msg;
            auto recv = socket_.recv(msg,zmq::recv_flags::none);

            if (recv > 0) 
            {
                if (!hasClient(msg.routing_id())) 
                {
                    connectedClients_.push_back(msg.routing_id());
                    LOG_S(INFO)<< "new Client connected ID: " <<msg.routing_id();
                }
                if (msg.to_string() == "CLOSE") 
                {
                    LOG_S(INFO)<<"CLOSE received from: "<< msg.routing_id();
                    deleteClient(msg.routing_id());
                    LOG_S(INFO)<<"remaining connected clients: " <<connectedClients_.size();
                }else 
                {   
                    if (MessageHandle_ != nullptr) 
                    {
                        MessageHandle_(msg.to_string());
                    }
                    else 
                    {
                        receivedMessages_.addElement(msg.to_string());
                    }
                }
            }
        }

    }


    bool DirectCommunicationServer::hasClient(std::uint32_t clientId)
    {
        if ( std::find(connectedClients_.begin(), connectedClients_.end(), clientId) != connectedClients_.end() )
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    void DirectCommunicationServer::deleteClient(std::uint32_t clientId)
    {
        auto it =  std::find(connectedClients_.begin(), connectedClients_.end(), clientId);
        if (it != connectedClients_.end() ) {
            connectedClients_.erase(it);
        }
    }




}