#include <Chunk/Chunk.hpp>
#include <Messenger/Messenger.hpp>
#include <Protocol/Protocol.hpp>
#include <Serialize/Base.hpp>
#include <Serialize/Serialize.hpp>
#include <Entity/Entity.hpp>
#include <Log/Log.hpp>
#include <Utilities/Extern.hpp>
#include <BeltView/BeltView.hpp>
#include <Utilities/Utilities.hpp>

void Messenger::threadMain()
{
    int dummy = 0;

    while (m_ok && m_queue.isActive())
    {
        m_queue.processAllCommands(dummy);
        m_queue.waitForCommands();
    }
}

Messenger::Messenger(SOCKET socket) :
    m_socket(socket),
    m_ok(true),
    m_thread{}
{
    m_thread = std::thread{&Messenger::threadMain, this};
}

Messenger::~Messenger()
{
    if (m_queue.isActive())
    {
        crash("Messenger deleted while still connected! This is a resource leak!");
    }
}

void Messenger::disconnect()
{
    disconnecting = true;
    m_queue.terminate();
    m_thread.join();

    closesocket(m_socket);
    m_socket = SOCKET_ERROR;
}

void Messenger::sendRaw(Protocol::MessageType type, const void *payload, uint32_t size)
{
    std::vector<uint8_t> vpayload((const uint8_t*) payload, (const uint8_t*) payload + size);

    if (size > 0xFFFF)
    {
        crash(formatString("sendRaw: message too big ($+ bytes)", size));
    }

    uint8_t typeAndFlags = static_cast<uint8_t>(type);
    if (size > 256)
    {
        std::vector<uint8_t> compressed;
        compressBytes(vpayload, compressed, 6);

        if (compressed.size() < vpayload.size())
        {
            vpayload = std::move(compressed);
            typeAndFlags |= Protocol::MessageHeader::compressed;
        }
    }

    m_queue.add([this, typeAndFlags, vpayload](int&)
    {
        if (disconnecting)
        {
            return;
        }

        uint16_t size = vpayload.size();
        Protocol::MessageHeader header;
        header.typeAndFlags = typeAndFlags;
        std::memcpy(header.payloadSize, &size, 2);

        m_ok = m_ok
            && writeSocket(m_socket, (char*) &header, sizeof(header))
            && (vpayload.size() == 0 || writeSocket(m_socket, (char*) vpayload.data(), vpayload.size()));
    });
}

void Messenger::setVisibleChunkBox(const BoundingBox<cint32> &visibleBox)
{
    if (currentVisibleChunkBox == visibleBox)
    {
        return;
    }

    currentVisibleChunkBox = visibleBox;

    sendSerial(Protocol::MessageType::SetVisibleChunkBox, visibleBox);
}

void Messenger::sendChunk(Chunk &chunk)
{
    BoundingBox<cint32> chunkBox{
        chunk.base.x, chunk.base.y, chunk.base.z,
        chunk.base.x + 16, chunk.base.y + 16, chunk.base.z + 16,
    };

    if (currentVisibleChunkBox.getVolume() == 0 || !chunkBox.overlaps(currentVisibleChunkBox))
    {
        // Chunk not visible to this client, don't send.
        return;
    }

    std::vector<uint8_t> chunkMessage;

    // Chunk header:
    // Encode the chunk coordinates relative to the visible box. Note that these will always fit
    // in a single byte.
    chunkMessage.push_back(((uint32_t) chunk.base.x - (uint32_t) currentVisibleChunkBox.x0) >> 4);
    chunkMessage.push_back(((uint32_t) chunk.base.y - (uint32_t) currentVisibleChunkBox.y0) >> 4);
    chunkMessage.push_back(((uint32_t) chunk.base.z - (uint32_t) currentVisibleChunkBox.z0) >> 4);

    // Now the chunk payload.
    chunk.toNetworkPayload(chunkMessage);

    sendRaw(Protocol::MessageType::ChunkData, chunkMessage.data(), chunkMessage.size());
}

void Messenger::sendBlockUpdates(const Protocol::BlockUpdate *updates, size_t numUpdates)
{
    while (numUpdates != 0)
    {
        size_t batchSize = std::min(numUpdates, maxBlockUpdatesPerMessage);

        sendRaw(Protocol::MessageType::BlockUpdateList, updates, sizeof(Protocol::BlockUpdate) * batchSize);

        updates += batchSize;
        numUpdates -= batchSize;
    }
}

void Messenger::sendEmpty(Protocol::MessageType type)
{
    sendRaw(type, nullptr, 0);
}

void Messenger::sendClientKeepAlive(bool isPlayerTyping)
{
    sendSerial(Protocol::MessageType::KeepAlive, isPlayerTyping);
}

void Messenger::sendProtocolError(std::string message)
{
    sendRaw(Protocol::MessageType::ProtocolError, message.c_str(), message.size());
}

void Messenger::sendEntityMessage(uint64_t entityID, const void *msg, size_t size)
{
    Protocol::EntityMessage *message = (Protocol::EntityMessage*) malloc(sizeof(Protocol::EntityMessage) + size);
    message->entityID = entityID;
    memcpy(message->payload, msg, size);
    sendRaw(Protocol::MessageType::EntityMessage, message, sizeof(Protocol::EntityMessage) + size);
    free(message);
}

void Messenger::sendCreateEntityMessage(std::shared_ptr<Entity> entity)
{
    std::vector<uint8_t> data;
    serializeInto(data, entity->id, entity->typeId);
    entity->serializeState(data);

    sendRaw(Protocol::MessageType::CreateEntity, data.data(), data.size());
}

void Messenger::sendChat(const std::string &text, glm::vec3 color)
{
    size_t payloadSize = text.size() + 3;
    uint8_t *payload = new uint8_t[payloadSize];
    
    int i;
    for (i=0; i<3; i++)
    {
        payload[i] = (uint8_t) (color[i] * 255.0f);
    }

    std::memcpy(payload + 3, text.c_str(), text.size());

    sendRaw(Protocol::MessageType::Chat, payload, payloadSize);

    delete[] payload;
}

void Messenger::sendPlaySoundMessage(const std::string &name, glm::ivec3 blockPos, glm::vec3 vel)
{
    sendSerial(Protocol::MessageType::PlaySound, name, blockPos, vel);
}

void Messenger::sendBeltViewUpdatedMessage(const BeltView &beltView)
{
    sendSerial(Protocol::MessageType::BeltViewUpdated, beltView.state.id, beltView);
}

void Messenger::sendBeltViewRemovedMessage(uint64_t id)
{
    sendSerial(Protocol::MessageType::BeltViewRemoved, id);
}

void Messenger::sendKickedFromServerMessage(std::string reason)
{
    sendRaw(Protocol::MessageType::KickedFromServer, reason.data(), reason.size());
}

void Messenger::sendSetClientPrefs(const Protocol::ClientPrefs &prefs)
{
    sendRaw(Protocol::MessageType::SetClientPrefs, &prefs, sizeof(prefs));
}