#include "Server.h"

#include <AsynchronCall.h>
#include <Globals.h>
#include <HttpRequest.h>
#include <iostream>
#include <JSON.h>
#include <Klient.h>

// Inhalt der LoginServer Klasse aus LoginServer.h
// Konstruktor
FactoryCraftServer::FactoryCraftServer(InitDatei* zIni)
    : ReferenceCounter()
{
    Network::Start(100);
    runningThreads = 0;
    klients = new RCArray<FCKlient>();
    ini = dynamic_cast<InitDatei*>(zIni->getThis());
    id = (int)*zIni->zWert("ServerId");
    sslServer = new SSLServer();
    sslServer->setPrivateKeyPassword(zIni->zWert("SSLPasswort")->getText());
    sslServer->setCertificateFile(zIni->zWert("SSLCert")->getText());
    std::cout << "using cert file " << zIni->zWert("SSLCert")->getText()
              << "\n";
    sslServer->setPrivateKeyFile(zIni->zWert("SSLKey")->getText());
    std::cout << "using private key " << zIni->zWert("SSLKey")->getText()
              << "\n";
    server = new Server();
    std::cout << "Server Port: " << ini->zWert("Port")->getText() << "\n";
    if (!server->verbinde(
            (unsigned short)TextZuInt(ini->zWert("Port")->getText(), 10), 10))
    {
        std::cout << "Der Server konnte nicht gestartet werden.\n";
        exit(1);
    }
    std::cout << "SSL Server Port: " << ini->zWert("SSLPort")->getText()
              << "\n";
    if (!sslServer->verbinde(
            (unsigned short)TextZuInt(ini->zWert("SSLPort")->getText(), 10),
            10))
    {
        std::cout << "Der SSL Server konnte nicht gestartet werden.\n";
        exit(2);
    }
    Game::initialize(
        zIni->zWert("World")->getText(), zIni->zWert("SaveDir")->getText());
    new Framework::AsynchronCall("Server", [this]() {
        runningThreads++;
        while (server->isConnected())
        {
            SKlient* klient = server->getKlient();
            if (!klient) continue;
            unsigned short len;
            klient->setEmpfangTimeout(5000);
            klient->getNachricht((char*)&len, 2);
            char* key = new char[len];
            klient->getNachricht((char*)key, len);
            bool bg;
            klient->getNachricht((char*)&bg, 1);
            klient->setEmpfangTimeout(0);
            bool found = 0;
            EnterCriticalSection(&cs);
            for (FCKlient* client : *klients)
            {
                if (client->matchAuthKey(key, len))
                {
                    if (bg)
                    {
                        klient->sende("\1", 1);
                        client->setBackgroundClient(klient);
                    }
                    else
                    {
                        klient->sende("\1", 1);
                        client->setForegroundClient(klient);
                    }
                    found = 1;
                    break;
                }
            }
            LeaveCriticalSection(&cs);
            if (!found)
            {
                klient->sende("\0", 1);
                std::cout << "client failed to pass through authorisation\n";
                klient->release();
            }
        }
        runningThreads--;
    });
    InitializeCriticalSection(&cs);
}

// Destruktor
FactoryCraftServer::~FactoryCraftServer()
{
    sslServer->trenne();
    server->trenne();
    while (runningThreads > 0)
        Sleep(100);
    sslServer->release();
    server->release();
    if (klients) klients->release();
    ini->release();
    Game::INSTANCE->requestStop();
    Game::INSTANCE->release();
    DeleteCriticalSection(&cs);
}

// nicht constant
void FactoryCraftServer::run()
{
    runningThreads++;
    while (sslServer->isConnected())
    {
        SSLSKlient* klient = sslServer->getKlient();
        if (!klient) continue;
        Framework::getThreadRegister()->cleanUpClosedThreads();
        FCKlient* clHandle = new FCKlient(
            klient, dynamic_cast<FactoryCraftServer*>(getThis()));
        EnterCriticalSection(&cs);
        klients->add(clHandle);
        LeaveCriticalSection(&cs);
        clHandle->start();
    }
    runningThreads--;
}

void FactoryCraftServer::close()
{
    sslServer->trenne();
    EnterCriticalSection(&cs);
    for (int i = 0; i < klients->getEintragAnzahl(); i++)
        klients->z(i)->absturz();
    Game::INSTANCE->save();
    LeaveCriticalSection(&cs);
}

bool FactoryCraftServer::removeKlient(FCKlient* zKlient)
{
    bool gefunden = 0;
    EnterCriticalSection(&cs);
    for (int i = 0; i < klients->getEintragAnzahl(); i++)
    {
        if (klients->z(i) == zKlient)
        {
            klients->remove(i);
            gefunden = 1;
            break;
        }
    }
    LeaveCriticalSection(&cs);
    return gefunden;
}

bool FactoryCraftServer::hatClients() const
{
    return klients->hat(0);
}

int FactoryCraftServer::getUnencryptedPort() const
{
    return server->getPort();
}

// Inhalt der LSKlient aus LoginServer.h
// Konstruktor
FCKlient::FCKlient(SSLSKlient* klient, FactoryCraftServer* ls)
    : Thread()
{
    this->klient = klient;
    background = 0;
    foreground = 0;
    this->ls = ls;
    zGameClient = 0;
    backgroundReader = 0;
    foregroundReader = 0;
    backgroundWriter = 0;
    foregroundWriter = 0;
    authKey = randomSecret();
}

// Destruktor
FCKlient::~FCKlient()
{
    if (zGameClient)
    {
        zGameClient->logout();
        zGameClient = (GameClient*)zGameClient->release();
    }
    if (background) background->release();
    if (foreground) foreground->release();
    delete backgroundReader;
    delete foregroundReader;
    delete backgroundWriter;
    delete foregroundWriter;
    klient->release();
    ls->release();
}

// nicht constant
void FCKlient::setForegroundClient(SKlient* foreground)
{
    std::cout << "foreground client connected\n";
    this->foreground = foreground;
    foregroundReader = new NetworkReader(foreground);
    foregroundWriter = new NetworkWriter(foreground);
    if (foreground && background)
        zGameClient
            = Game::INSTANCE->addPlayer(dynamic_cast<FCKlient*>(getThis()), name);
    foregroundRunning = 1;
    new AsynchronCall([this]() {
        while (this->foreground->waitForNextMessage())
        {
            if (zGameClient) zGameClient->addMessage(foregroundReader);
            if (!zGameClient) Sleep(100);
        }
        cs.lock();
        foregroundRunning = 0;
        if (!backgroundRunning)
        {
            cs.unlock();
            if (zGameClient)
            {
                zGameClient->logout();
                zGameClient = (GameClient*)zGameClient->release();
            }
            ls->removeKlient(this);
        }
        else
        {
            cs.unlock();
        }
    });
}

void FCKlient::setBackgroundClient(SKlient* background)
{
    std::cout << "background client connected\n";
    this->background = background;
    backgroundReader = new NetworkReader(background);
    backgroundWriter = new NetworkWriter(background);
    if (foreground && background)
        zGameClient
            = Game::INSTANCE->addPlayer(dynamic_cast<FCKlient*>(getThis()),
                name);
    backgroundRunning = 1;
    new AsynchronCall([this]() {
        while (this->background->waitForNextMessage())
        {
            if (zGameClient) zGameClient->addMessage(backgroundReader);
            if (!zGameClient) Sleep(100);
        }
        cs.lock();
        backgroundRunning = 0;
        if (!foregroundRunning)
        {
            cs.unlock();
            if (zGameClient)
            {
                zGameClient->logout();
                zGameClient = (GameClient*)zGameClient->release();
            }
            ls->removeKlient(this);
        }
        else
        {
            cs.unlock();
        }
    });
}

void FCKlient::absturz()
{
    klient->trenne();
    if (background) background->trenne();
    if (foreground) foreground->trenne();
    warteAufThread(10000);
    ende();
}

void FCKlient::thread()
{
    bool identified = 0;
    while (1)
    {
        char c = 0;
        if (!klient->getNachricht(&c, 1))
            break;
        else
        {
            bool br = 0;
            switch (c)
            {
            case 1: // Klient identifikation
                {
                    char len;
                    klient->getNachricht(&len, 1);
                    char* name = new char[len + 1];
                    klient->getNachricht(name, len);
                    name[(int)len] = 0;
                    unsigned short sLen;
                    klient->getNachricht((char*)&sLen, 2);
                    char* secret = new char[sLen + 1];
                    klient->getNachricht(secret, sLen);
                    secret[sLen] = 0;
                    if (!Game::INSTANCE->checkPlayer(name, secret))
                    {
                        klient->sende("\0", 1);
                        delete[] name;
                        delete[] secret;
                        break;
                    }
                    if (!Game::INSTANCE->existsPlayer(name))
                    {
                        Text secret = Game::INSTANCE->createPlayer(name);
                        klient->sende("\2", 1);
                        short len = (short)secret.getLength();
                        klient->sende((char*)&len, 2);
                        klient->sende(secret.getText(), len);
                        identified = 1;
                    }
                    else
                    {
                        klient->sende("\1", 1);
                        identified = 1;
                    }
                    short keyLen = (short)authKey.getLength();
                    klient->sende((char*)&keyLen, 2);
                    klient->sende(authKey, keyLen);
                    this->name = name;
                    delete[] name;
                    delete[] secret;
                    break;
                }
            case 2: // Verbindungsende
                br = 1;
                if (zGameClient)
                {
                    zGameClient->logout();
                    zGameClient = (GameClient*)zGameClient->release();
                }
                klient->sende("\1", 1);
                break;
            case 3: // ping
                klient->sende("\1", 1);
                break;
            case 4: // check player name valid
                {
                    klient->sende("\1", 1);
                    char len;
                    klient->getNachricht(&len, 1);
                    char* name = new char[len + 1];
                    klient->getNachricht(name, len);
                    name[(int)len] = 0;
                    short sLen;
                    klient->getNachricht((char*)&sLen, 2);
                    char* secret = new char[sLen + 1];
                    klient->getNachricht(secret, sLen);
                    secret[sLen] = 0;
                    char res = 0;
                    if (Game::INSTANCE->checkPlayer(name, secret)) res = 1;
                    klient->sende(&res, 1);
                    delete[] name;
                    delete[] secret;
                    break;
                }
            default:
                br = 1;
                break;
            }
            if (br) break;
        }
    }
    if (!identified)
    {
        ls->removeKlient(this);
    }
}

NetworkWriter* FCKlient::zBackgroundWriter() const
{
    return backgroundWriter;
}

NetworkWriter* FCKlient::zForegroundWriter() const
{
    return foregroundWriter;
}

bool FCKlient::matchAuthKey(char* key, int len) const
{
    if (foreground && background) return 0;
    if (len != authKey.getLength()) return 0;
    for (int i = 0; i < len; i++)
    {
        if (key[i] != authKey.getText()[i]) return 0;
    }
    return 1;
}