#include "Network/UDPServer.h"

UDPServer::UDPServer()
{
    m_Socket = std::unique_ptr<boost::asio::ip::udp::socket>(new boost::asio::ip::udp::socket(m_IOService, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 27666)));
}

UDPServer::UDPServer(int port)
{
    m_Socket = std::unique_ptr<boost::asio::ip::udp::socket>(new boost::asio::ip::udp::socket(m_IOService, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port)));
}

UDPServer::~UDPServer()
{ }
// TODO: Fix correct groups
void UDPServer::Send(Packet& packet, PlayerDefinition& playerDefinition)
{
    packet.UpdateSize();
    try {
        // Remove header from packet.
        packet.ReadData(packet.HeaderSize());
        int totalBytesSent = 0;
        int groupIndex = 1;
        int groupSize = std::ceil((float)packet.Size() / MAXPACKETSIZE);
        int packetDataSent = 0;
        int packetDataSize = packet.Size() - packet.HeaderSize();

        while (packetDataSize > packetDataSent) {
            Packet splitPacket(packet.GetMessageType(), playerDefinition.PacketID);
            splitPacket.ChangeGroupIndex(groupIndex);
            splitPacket.ChangeGroupSize(groupSize);
            splitPacket.ChangeGroup(playerDefinition.PacketGroup);
            int amountToSend = packetDataSize - packetDataSent;
            if (amountToSend > MAXPACKETSIZE) {
                amountToSend = MAXPACKETSIZE;
            }
            splitPacket.WriteData(packet.ReadData(amountToSend), amountToSend);
            splitPacket.UpdateSize();
            // Remove header size from bytes sent soo that we only
            // count data in the packet
            int bytesSent = 0;
            bytesSent = m_Socket->send_to(
                boost::asio::buffer(splitPacket.Data(), splitPacket.Size()),
                playerDefinition.Endpoint,
                0);
            packetDataSent += bytesSent - splitPacket.HeaderSize();
            totalBytesSent += bytesSent;
            //LOG_INFO("bytesSent size %i, groupIndex: %i. Number of packets: %i", bytesSent, sequenceNumber, totalMessages);
            ++groupIndex;
        }
        playerDefinition.PacketGroup++;
    } catch (const boost::system::system_error& e) {
        LOG_INFO(e.what());
        // TODO: Clean up invalid endpoints out of m_ConnectedPlayers later
        playerDefinition.Endpoint = boost::asio::ip::udp::endpoint();
    }
}

void UDPServer::SendToConnectedPlayers(Packet& packet, std::map<PlayerID, PlayerDefinition>& playersTosendTo)
{
    packet.UpdateSize();
    // Remove header from packet.
    packet.ReadData(packet.HeaderSize());
    int totalBytesSent = 0;
    int groupIndex = 1;
    int groupSize = std::ceil((float)packet.Size() / MAXPACKETSIZE);
    int packetDataSent = 0;
    int packetDataSize = packet.Size() - packet.HeaderSize();

    while (packetDataSize > packetDataSent) {
        Packet splitPacket(packet.GetMessageType());
        splitPacket.ChangeGroupIndex(groupIndex);
        splitPacket.ChangeGroupSize(groupSize);
        int amountToSend = packetDataSize - packetDataSent;
        if (amountToSend > MAXPACKETSIZE) {
            amountToSend = MAXPACKETSIZE;
        }
        splitPacket.WriteData(packet.ReadData(amountToSend), amountToSend);
        splitPacket.UpdateSize();
        // Remove header size from bytes sent soo that we only
        // count data in the packet
        int bytesSent = 0;
        for (auto& kv : playersTosendTo) {
            try {
                splitPacket.ChangeGroup(kv.second.PacketGroup);
                bytesSent = m_Socket->send_to(
                    boost::asio::buffer(splitPacket.Data(), splitPacket.Size()),
                    kv.second.Endpoint,
                    0);
                // LOG_INFO("bytesSent: %i", bytesSent);
            } catch (const boost::system::system_error& e) {
                LOG_INFO("UDPServer::SendToConnectedPlayers: Disconnected client. %s", e.what());
                // TODO: Clean up invalid endpoints out of m_ConnectedPlayers later
                kv.second.Endpoint = boost::asio::ip::udp::endpoint();
            }
        }
        packetDataSent += splitPacket.Size() - splitPacket.HeaderSize();
        totalBytesSent += splitPacket.Size();

        //LOG_INFO("bytesSent size %i, groupIndex: %i. Number of packets: %i", bytesSent, sequenceNumber, totalMessages);
        ++groupIndex;
    }
    for (auto& kv : playersTosendTo) {
        kv.second.PacketGroup++;
    }
}

// Send back to endpoint of received packet
void UDPServer::Send(Packet & packet)
{
    packet.UpdateSize();
    size_t bytesSent = m_Socket->send_to(
        boost::asio::buffer(
            packet.Data(),
            packet.Size()),
        m_ReceiverEndpoint,
        0);
    //LOG_INFO("Size of packet is %i", bytesSent);
}

// Broadcasting respond specific logic
void UDPServer::Send(Packet & packet, boost::asio::ip::udp::endpoint endpoint)
{
    packet.UpdateSize();
    size_t bytesSent = m_Socket->send_to(
        boost::asio::buffer(
            packet.Data(),
            packet.Size()),
        endpoint,
        0);
    //LOG_INFO("Size of packet is %i", bytesSent);
}

// Broadcasting
void UDPServer::Broadcast(Packet & packet, int port)
{
    packet.UpdateSize();
    m_Socket->set_option(boost::asio::socket_base::broadcast(true));
    size_t bytesSent = m_Socket->send_to(
        boost::asio::buffer(
            packet.Data(),
            packet.Size()),
        boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4().broadcast(), port),
        0);
    m_Socket->set_option(boost::asio::socket_base::broadcast(false));
}

void UDPServer::Receive(Packet & packet, PlayerDefinition & playerDefinition)
{
    int bytesRead = readBuffer();
    if (bytesRead > 0) {
        packet.ReconstructFromData(m_ReadBuffer, bytesRead);
    }
    playerDefinition.Endpoint = m_ReceiverEndpoint;
}

bool UDPServer::IsSocketAvailable()
{
    return m_Socket->available();
}

int UDPServer::readBuffer()
{
    if (!m_Socket) {
        return 0;
    }
    int addasdasd = m_Socket->available();
    boost::system::error_code error;
    // Read size of packet
    m_Socket->receive_from(boost
        ::asio::buffer((void*)m_ReadBuffer, sizeof(int)),
        m_ReceiverEndpoint, boost::asio::ip::udp::socket::message_peek, error);
    unsigned int sizeOfPacket = 0;
    memcpy(&sizeOfPacket, m_ReadBuffer, sizeof(int));

    if (sizeOfPacket > m_Socket->available()) {
        LOG_WARNING("UDPServer::readBuffer(): We haven't got the whole packet yet.");
        //return 0;
    }

    // if the buffer is to small increase the size of it
    if (sizeOfPacket > m_BufferSize) {
        delete[] m_ReadBuffer;
        m_ReadBuffer = new char[sizeOfPacket];
        m_BufferSize = sizeOfPacket;
    }

    // Read the rest of the message
    size_t bytesReceived = m_Socket->receive_from(boost
        ::asio::buffer((void*)(m_ReadBuffer),
            sizeOfPacket),
        m_ReceiverEndpoint, 0, error);
    if (error) {
        //LOG_ERROR("receive: %s", error.message().c_str());
    }
    if (sizeOfPacket > 1000000)
        LOG_WARNING("The packets received are bigger than 1MB");

    return bytesReceived;
}

void UDPServer::AcceptNewConnections(int& nextPlayerID, std::map<PlayerID, PlayerDefinition>& connectedPlayers)
{ }