#include <Material/Material.hpp>
#include <Protocol/Protocol.hpp>
#include <thread>
#include <string>
#include <Display/WidgetDisplay/RegisteringDisplay/RegisteringDisplay.hpp>
#include <Widget/Label/Label.hpp>
#include <Widget/Layout/Center/Center.hpp>
#include <Widget/Layout/Column/Column.hpp>
#include <Widget/Button/Button.hpp>
#include <Display/WidgetDisplay/ServerSelection/ServerSelection.hpp>
#include <Widget/TextField/TextField.hpp>
#include <Display/WidgetDisplay/WorldDisplay/WorldDisplay.hpp>
#include <Display/WidgetDisplay/MessageDisplay/MessageDisplay.hpp>
#include <Utilities/ServerSessionTable.hpp>
#include <World/RemoteWorld/RemoteWorld.hpp>
#include <Utilities/Socket.hpp>
#include <Display/WidgetDisplay/WorldDisplay/WorldDisplay.hpp>
#include <Display/WidgetDisplay/LoginDisplay/LoginDisplay.hpp>

void RegisteringDisplay::finish(Material &icon, const std::string &caption, const std::string &msg)
{
    Display::setCurrent<MessageDisplay>(icon, caption, msg);
};

RegisteringDisplay::RegisteringDisplay(std::string userName, std::string password, std::string server)
{
    setWidget(new Center(new Label("Registering...")));

    std::thread([this, server, userName, password]()
    {
        std::string error;
        SOCKET sock = connectSocket(server, error);

        if (sock == INVALID_SOCKET)
        {
            this->m_cmdqueue.add([error](RegisteringDisplay &display)
            {
                display.finish(Material::alert, "Could not connect", error);
            });
            
            return;
        }

        // Send the register greeting.
        Protocol::ClientGreetingHeader clientHeader(Protocol::ClientGreetingCode::Register);
        if (!writeSocket(sock, (char*) &clientHeader, sizeof(clientHeader)))
        {
            closesocket(sock);

            this->m_cmdqueue.add([](RegisteringDisplay &display)
            {
                display.finish(Material::alert, "Could not connect", "Server disconnected.");
            });
            
            return;
        };

        // Send the register header.
        Protocol::RegisterHeader registerHeader;
        registerHeader.userNameSize = userName.size();
        registerHeader.passwordSize = password.size();
        if (!writeSocket(sock, (char*) &registerHeader, sizeof(registerHeader)))
        {
            closesocket(sock);

            this->m_cmdqueue.add([](RegisteringDisplay &display)
            {
                display.finish(Material::alert, "Could not connect", "Server disconnected.");
            });
            
            return;
        };

        // Send the username.
        if (!writeSocket(sock, userName.c_str(), userName.size()))
        {
            closesocket(sock);

            this->m_cmdqueue.add([](RegisteringDisplay &display)
            {
                display.finish(Material::alert, "Could not connect", "Server disconnected.");
            });
            
            return;
        };

        // Send the password.
        if (!writeSocket(sock, password.c_str(), password.size()))
        {
            closesocket(sock);

            this->m_cmdqueue.add([](RegisteringDisplay &display)
            {
                display.finish(Material::alert, "Could not connect", "Server disconnected.");
            });
            
            return;
        };

        // Read the server greeting.
        Protocol::ServerGreetingHeader serverHeader;
        if (!readSocket(sock, (char*) &serverHeader, sizeof(serverHeader)))
        {
            closesocket(sock);

            this->m_cmdqueue.add([](RegisteringDisplay &display)
            {
                display.finish(Material::alert, "Could not connect", "Server disconnected.");
            });
            
            return;
        };

        // Check that the greeting is valid.
        if (!serverHeader.isValid())
        {
            closesocket(sock);

            this->m_cmdqueue.add([](RegisteringDisplay &display)
            {
                display.finish(Material::alert, "Server error", "Invalid server greeting.");
            });
            
            return;
        };

        // Check the greeting code.
        switch (serverHeader.code)
        {
        case Protocol::ServerGreetingCode::AccountCreated:
        case Protocol::ServerGreetingCode::AccountNotCreated:
            break;
        case Protocol::ServerGreetingCode::IncompatibleVersion:
            closesocket(sock);

            this->m_cmdqueue.add([serverHeader](RegisteringDisplay &display)
            {
                display.finish(
                    Material::alert,
                    "Incompatible server",
                    formatString(
                        "Your version: $+\nServer version: $+",
                        Version::ofThisBuild,
                        serverHeader.version
                    )
                );
            });
            
            return;
        default:
            closesocket(sock);

            this->m_cmdqueue.add([serverHeader](RegisteringDisplay &display)
            {
                display.finish(
                    Material::alert,
                    "Server error",
                    formatString("Unrecognized greeting code: $+", static_cast<int>(serverHeader.code))
                );
            });
            
            return;
        };

        // Receive the message size.
        uint8_t messageSize;
        if (!readSocket(sock, (char*) &messageSize, 1))
        {
            closesocket(sock);

            this->m_cmdqueue.add([](RegisteringDisplay &display)
            {
                display.finish(Material::alert, "Could not connect", "Server disconnected.");
            });
            
            return;
        };

        // Receive the message.
        char *messageBuffer = new char[messageSize+1];
        if (!readSocket(sock, messageBuffer, messageSize))
        {
            delete[] messageBuffer;
            closesocket(sock);

            this->m_cmdqueue.add([](RegisteringDisplay &display)
            {
                display.finish(Material::alert, "Could not connect", "Server disconnected.");
            });
            
            return;
        };
        
        messageBuffer[messageSize] = 0;
        std::string message(messageBuffer);
        delete[] messageBuffer;

        if (serverHeader.code == Protocol::ServerGreetingCode::AccountCreated)
        {
            UserDB::SessionID sid{};
            setServerSession(server, userName, sid);
        }

        // Done, close the socket and display the message.
        Protocol::ServerGreetingCode greetingCode = serverHeader.code;
        closesocket(sock);
        this->m_cmdqueue.add([greetingCode, message](RegisteringDisplay &display)
        {
            Material &icon = greetingCode == Protocol::ServerGreetingCode::AccountCreated ?
                Material::accept :
                Material::alert;
            std::string caption = greetingCode == Protocol::ServerGreetingCode::AccountCreated ?
                "Account created" :
                "Could not register";
            display.finish(icon, caption, message);
        });
    }).detach();
};

void RegisteringDisplay::update()
{
    WidgetDisplay::update();
    m_cmdqueue.processAllCommands(*this);
};