#include "FactoryClient.h"

#include <Bild.h>
#include <Textur.h>

#include "Globals.h"

using namespace Network;
using namespace Framework;

FactoryClient::FactoryClient()
{
    client = 0;
    background = 0;
    foreground = 0;
    backgroundReader = 0;
    foregroundReader = 0;
    bgReaderUsage = 0;
    fgReaderUsage = 0;
}

FactoryClient::~FactoryClient()
{
    if (client) disconnect();
}

void FactoryClient::loadServerInfo()
{
    std::cout << "downloading server type information\n";
    // receive type information
    for (int i = 0; i < blockTypeCount; i++)
        blockTypes[i]->release();
    delete[] blockTypes;
    for (int i = 0; i < itemTypeCount; i++)
        itemTypes[i]->release();
    delete[] itemTypes;
    for (int i = 0; i < entityTypeCount; i++)
        entityTypes[i]->release();
    delete[] entityTypes;
    foregroundReader->lese((char*)&blockTypeCount, 4);
    blockTypes = new BlockType*[blockTypeCount];
    for (int i = 0; i < blockTypeCount; i++)
    {
        int id;
        foregroundReader->lese((char*)&id, 4);
        bool needsInstance;
        foregroundReader->lese((char*)&needsInstance, 1);
        bool needsSubscription;
        foregroundReader->lese((char*)&needsSubscription, 1);
        int maxHp;
        foregroundReader->lese((char*)&maxHp, 4);
        blockTypes[i] = new BlockType(id,
            needsInstance,
            ModelInfo(foregroundReader),
            maxHp,
            needsSubscription);
    }
    foregroundReader->lese((char*)&itemTypeCount, 4);
    itemTypes = new ItemType*[itemTypeCount];
    for (int i = 0; i < itemTypeCount; i++)
    {
        int id;
        foregroundReader->lese((char*)&id, 4);
        char len;
        foregroundReader->lese((char*)&len, 1);
        char* name = new char[len + 1];
        foregroundReader->lese(name, len);
        name[len] = 0;
        short tlen;
        foregroundReader->lese((char*)&tlen, 2);
        char* tooltipUIML = new char[tlen + 1];
        foregroundReader->lese(tooltipUIML, tlen);
        tooltipUIML[tlen] = 0;
        itemTypes[i] = new ItemType(
            id, ModelInfo(foregroundReader), Text(name), Text(tooltipUIML));
        delete[] name;
        delete[] tooltipUIML;
    }
    foregroundReader->lese((char*)&entityTypeCount, 4);
    entityTypes = new EntityType*[entityTypeCount];
    for (int i = 0; i < entityTypeCount; i++)
    {
        int id;
        foregroundReader->lese((char*)&id, 4);
        entityTypes[i] = new EntityType(id, ModelInfo(foregroundReader));
    }
    // pre rendering item models
    Kam3D* kam = new Kam3D();
    Welt3D* w = new Welt3D();
    w->addDiffuseLight(DiffuseLight{
        Vec3<float>(0.5f, 0.5f, -1.f), Vec3<float>(1.f, 1.f, 1.f)});
    kam->setWelt(w);
    kam->setBildschirmPosition(0, 0);
    kam->setBildschirmSize(50, 50);
    kam->setPosition(Vec3<float>(0, 0, 0));
    kam->setRotation(
        {(float)PI / 2.f, 0.f, std::atan2(0.f, -1.f) + (float)PI / 2});
    Bild* b = new Bild();
    b->neuBild(50, 50, 0);
    for (int i = 0; i < itemTypeCount; i++)
    {
        Model3D* mdl = new Model3D();
        Model3DData* data = itemTypes[i]->getItemModel();
        if (data)
        {
            Vec3<float> min = data->getMinPos();
            Vec3<float> max = data->getMaxPos();
            float maxX = MAX(
                MAX(MAX(abs(min.x), abs(max.x)), MAX(abs(min.y), abs(max.y))),
                MAX(abs(min.z), abs(max.z)));
            kam->setPosition(Vec3<float>(maxX * 5, 0.f, 0.f));
        }
        mdl->setModelDaten(data);
        mdl->setModelTextur(itemTypes[i]->getItemTextur());
        mdl->setPosition(Vec3<float>(0.f, 0.f, 0.f));
        mdl->setDrehung(0.25f, 0.25f, 0.55f);
        w->addZeichnung(mdl);
        w->tick(0);
        window->zBildschirm()->lock();
        DX11Textur* t = (DX11Textur*)window->zBildschirm()
                            ->zGraphicsApi()
                            ->createOrGetTextur(
                                Text("rendered/items/") + itemTypes[i]->getId(),
                                dynamic_cast<Bild*>(b->getThis()));
        window->zBildschirm()->zGraphicsApi()->renderKamera(kam, t);
        Bild* result = new Bild();
        t->copyToImage(result);
        itemTypes[i]->setBild(result);
        t->release();
        window->zBildschirm()->unlock();
        w->removeZeichnung(mdl);
    }
    b->release();
    kam->release();
}

bool FactoryClient::connect(Text ip, unsigned short sslPort)
{
    if (client) disconnect();
    client = new SSLKlient();
    if (!client->verbinde(sslPort, ip)) return false;
    char c;
    while (client->hatNachricht(1))
        client->getNachricht(&c, 1);
    this->ip = ip;
    return 1;
}

int FactoryClient::ping()
{
    ZeitMesser zm;
    zm.messungStart();
    if (!client->sende("\3", 1)) return -1;
    char c;
    client->getNachricht(&c, 1);
    zm.messungEnde();
    return (int)(zm.getSekunden() * 1000);
}

int FactoryClient::status(Framework::Text name, Framework::Text secret)
{
    if (!client->sende("\4", 1)) return 404;
    char c;
    client->getNachricht(&c, 1);
    if (c == 1)
    {
        char len = (char)name.getLength();
        client->sende(&len, 1);
        client->sende(name, len);
        short sLen = (short)secret.getLength();
        client->sende((char*)&sLen, 2);
        client->sende(secret, sLen);
        char res;
        client->getNachricht(&res, 1);
        if (res == 1) return 200;
        if (res == 0) return 403;
    }
    return 404;
}

int FactoryClient::join(
    Framework::Text name, Framework::Text& secret, unsigned short port)
{
    client->sende("\1", 1);
    char len = (char)name.getLength();
    client->sende(&len, 1);
    client->sende(name, len);
    short sLen = (short)secret.getLength();
    client->sende((char*)&sLen, 2);
    client->sende(secret, sLen);
    char res;
    client->getNachricht(&res, 1);
    if (res == 1 || res == 2)
    {
        if (res == 2)
        {
            client->getNachricht((char*)&sLen, 2);
            char* buffer = new char[sLen + 1];
            client->getNachricht(buffer, sLen);
            buffer[sLen] = 0;
            secret = buffer;
            delete[] buffer;
        }
        short keyLen;
        client->getNachricht((char*)&keyLen, 2);
        char* key = new char[keyLen];
        client->getNachricht(key, keyLen);
        foreground = new Klient();
        if (!foreground->verbinde(port, ip))
        {
            delete[] key;
            return false;
        }
        if (!foreground->sende((char*)&keyLen, 2))
        {
            delete[] key;
            return false;
        }
        if (!foreground->sende(key, keyLen))
        {
            delete[] key;
            return false;
        }
        background = new Klient();
        if (!background->verbinde(port, ip))
        {
            delete[] key;
            foreground->release();
            foreground = 0;
            background->release();
            background = 0;
            return false;
        }
        if (!background->sende((char*)&keyLen, 2))
        {
            delete[] key;
            foreground->release();
            foreground = 0;
            background->release();
            background = 0;
            return false;
        }
        if (!background->sende(key, keyLen))
        {
            delete[] key;
            foreground->release();
            foreground = 0;
            background->release();
            background = 0;
            return false;
        }
        delete[] key;
        bool bg = 0;
        if (!foreground->sende((char*)&bg, 1))
        {
            delete[] key;
            return 201;
        }
        foregroundReader = new NetworkReader(foreground);
        bg = 1;
        if (!background->sende((char*)&bg, 1)) return 201;
        backgroundReader = new NetworkReader(background);
        char res;
        foregroundReader->lese(&res, 1);
        if (res != 1) return 403;
        backgroundReader->lese(&res, 1);
        if (res != 1) return 403;
        client->trenne();
        loadServerInfo();
        return 200;
    }
    if (res == 0) return 403;
    return 500;
}

void FactoryClient::disconnect()
{
    if (client)
    {
        NetworkReader* fgReader = foregroundReader;
        NetworkReader* bgReader = backgroundReader;
        backgroundReader = 0;
        foregroundReader = 0;
        if (foreground) foreground->trenne();
        if (background) background->trenne();
        while (fgReaderUsage > 0 || bgReaderUsage > 0)
            Sleep(100);
        delete fgReader;
        delete bgReader;
        client->release();
        client = 0;
        if (foreground) foreground->release();
        foreground = 0;
        if (background) background->release();
        background = 0;
    }
}

NetworkReader* FactoryClient::getNextForegroundMessage()
{
    fgReaderUsage++;
    if (!foreground) return 0;
    if (!foreground->isConnected()) return 0;
    if (!foreground->hatNachricht(0)) return 0;
    return foregroundReader;
}

NetworkReader* FactoryClient::getNextBackgroundMessage()
{
    bgReaderUsage++;
    if (!background) return 0;
    if (!background->isConnected()) return 0;
    if (!background->hatNachricht(0)) return 0;
    return backgroundReader;
}

void FactoryClient::endMessageReading(bool bg)
{
    if (bg)
        bgReaderUsage--;
    else
        fgReaderUsage--;
}

void FactoryClient::sendPlayerAction(char* data, unsigned short length)
{
    if (!foreground) return;
    cs.lock();
    length += 1;
    foreground->sende((char*)&length, 2);
    char msgId = 2;
    foreground->sende(&msgId, 1);
    foreground->sende((char*)data, length - 1);
    cs.unlock();
}

void FactoryClient::sendPlayerMovement(MovementFrame& frame)
{
    if (!foreground) return;
    cs.lock();
    short length = 38;
    foreground->sende((char*)&length, 2);
    char msgId = 2; // player message
    foreground->sende(&msgId, 1);
    foreground->sende(&msgId, 1); // set movement
    foreground->sende((char*)&frame.direction.x, 4);
    foreground->sende((char*)&frame.direction.y, 4);
    foreground->sende((char*)&frame.direction.z, 4);
    foreground->sende((char*)&frame.targetPosition.x, 4);
    foreground->sende((char*)&frame.targetPosition.y, 4);
    foreground->sende((char*)&frame.targetPosition.z, 4);
    foreground->sende((char*)&frame.movementFlags, 4);
    foreground->sende((char*)&frame.duration, 8);
    cs.unlock();
}

void FactoryClient::entityAPIRequest(
    int entityId, char* message, unsigned short length)
{
    if (!foreground) return;
    cs.lock();
    length += 5;
    foreground->sende((char*)&length, 2);
    char msgId = 3;
    foreground->sende(&msgId, 1);
    foreground->sende((char*)&entityId, 4);
    foreground->sende(message, length - 5);
    cs.unlock();
}

void FactoryClient::blockAPIRequest(
    Vec3<int> pos, char* message, unsigned short length)
{
    if (!foreground) return;
    cs.lock();
    length += 14;
    foreground->sende((char*)&length, 2);
    char msgId = 1;
    foreground->sende(&msgId, 1);
    foreground->sende(&msgId, 1);
    foreground->sende((char*)&pos.x, 4);
    foreground->sende((char*)&pos.y, 4);
    foreground->sende((char*)&pos.z, 4);
    foreground->sende(message, length - 14);
    cs.unlock();
}

void FactoryClient::blockAPIRequest(
    int dimensionId, Vec3<int> pos, char* message, unsigned short length)
{
    if (!foreground) return;
    cs.lock();
    length += 18;
    foreground->sende((char*)&length, 2);
    char msgId = 7;
    foreground->sende(&msgId, 1);
    foreground->sende((char*)&dimensionId, 4);
    msgId = 1;
    foreground->sende(&msgId, 1);
    foreground->sende((char*)&pos.x, 4);
    foreground->sende((char*)&pos.y, 4);
    foreground->sende((char*)&pos.z, 4);
    foreground->sende(message, length - 18);
    cs.unlock();
}

void FactoryClient::chunkAPIRequest(
    Punkt center, char* message, unsigned short length)
{
    if (!foreground) return;
    length += 10;
    cs.lock();
    foreground->sende((char*)&length, 2);
    char type = 1;
    foreground->sende(&type, 1);
    type = 0;
    foreground->sende(&type, 1);
    foreground->sende((char*)&center.x, 4);
    foreground->sende((char*)&center.y, 4);
    foreground->sende(message, length - 10);
    cs.unlock();
}

void FactoryClient::dimensionAPIRequest(char* message, unsigned short length)
{
    if (!foreground) return;
    length += 1;
    cs.lock();
    foreground->sende((char*)&length, 2);
    char type = 1;
    foreground->sende(&type, 1);
    foreground->sende(message, length - 1);
    cs.unlock();
}

void FactoryClient::inventoryAPIRequest(
    Framework::Either<int, Framework::VecN<int, 4>> target,
    char* message,
    unsigned short length)
{
    if (!foreground) return;
    cs.lock();
    length += target.isA() ? 6 : 18;
    foreground->sende((char*)&length, 2);
    char msgId = 4;
    foreground->sende(&msgId, 1);
    bool isEntity = target.isA();
    foreground->sende((char*)&isEntity, 1);
    if (target.isA())
    {
        int id = target.getA();
        foreground->sende((char*)&id, 4);
    }
    else
    {
        for (int i = 0; i < 4; i++)
        {
            int v = target.getB()[i];
            foreground->sende((char*)&v, 4);
        }
    }
    foreground->sende(message, length - (target.isA() ? 6 : 18));
    cs.unlock();
}

void FactoryClient::craftingUIMLRequest(int itemTypeId)
{
    if (!foreground) return;
    cs.lock();
    short length = 5;
    foreground->sende((char*)&length, 2);
    char msgId = 5;
    foreground->sende(&msgId, 1);
    foreground->sende((char*)&itemTypeId, 4);
    cs.unlock();
}

void FactoryClient::sendChatMessage(Framework::Text message)
{
    if (!background) return;
    cs.lock();
    short length = message.getLength() + 4;
    background->sende((char*)&length, 2);
    char msgId = 6;
    background->sende(&msgId, 1);
    msgId = 0;
    background->sende(&msgId, 1);
    length = message.getLength();
    background->sende((char*)&length, 2);
    background->sende(message.getText(), message.getLength());
    cs.unlock();
}

void FactoryClient::chatAPIRequest(char* data, unsigned short length)
{
    if (!background) return;
    cs.lock();
    short totalLength = length + 1;
    background->sende((char*)&totalLength, 2);
    char msgId = 6;
    background->sende(&msgId, 1);
    background->sende(data, length);
    cs.unlock();
}

bool FactoryClient::isConnected()
{
    return foreground && background && foreground->isConnected()
        && background->isConnected();
}

void FactoryClient::leaveGame()
{
    cs.lock();
    disconnect();
    cs.unlock();
}