#include <atomic>
#include <thread>

#include "network/Client.h"
#include "network/Server.h"
#include "tests/NetworkTests.h"
#include "tests/Test.h"

static void sleep(int millis) {
    std::this_thread::sleep_for(std::chrono::milliseconds(millis));
}

static int packageCounter = 0;

struct ServerConsumer {
    bool connected = false;
    bool disconnect = false;

    uint8 data1 = 0;
    uint16 data2 = 0;
    uint32 data3 = 0;
    int8 data4 = 0;
    int16 data5 = 0;
    int32 data6 = 0;
    int8 data7 = 0;
    int16 data8 = 0;
    int32 data9 = 0;
    StringBuffer<20> data10;
    float data11 = 0.0f;

    void onConnect(Server::Client& client) {
        (void)client;
        connected = true;
    }

    void onDisconnect(Server::Client& client) {
        (void)client;
        disconnect = true;
    }

    void onPacket(Server::Client& client, InPacket& in) {
        (void)client;
        in.readU8(data1);
        in.readU16(data2);
        in.readU32(data3);
        in.readS8(data4);
        in.readS16(data5);
        in.readS32(data6);
        in.readS8(data7);
        in.readS16(data8);
        in.readS32(data9);
        in.readString(data10);
        in.readFloat(data11);

        if(packageCounter == 0) {
            OutPacket out = OutPacket::reliable(0);
            client.send(out);
        } else if(packageCounter == 1) {
            OutPacket out = OutPacket::sequenced(0);
            client.send(out);
        } else if(packageCounter == 2) {
            OutPacket out = OutPacket::unsequenced(0);
            client.send(out);
        }
        packageCounter++;
    }
};

struct ClientConsumer {
    bool package = false;

    void onDisconnect() {
    }

    void onPacket(InPacket& in) {
        (void)in;
        package = true;
    }
};

static void testConnect(Test& test, OutPacket out) {
    Server server;
    if(server.start(54321, 5).has()) {
        test.checkEqual(false, true, "server can initialize");
        return;
    }
    Client client;
    if(client.start().has()) {
        test.checkEqual(false, true, "client can initialize");
        return;
    }

    std::atomic_bool running(true);
    ServerConsumer serverConsumer;
    std::thread listen([&running, &server, &serverConsumer]() {
        while(running) {
            server.consumeEvents(serverConsumer);
        }
    });

    test.checkEqual(false, client.connect("127.0.0.1", 54321, 5).has(),
                    "connection failed");

    ClientConsumer clientConsumer;
    for(int i = 0; i < 100; i++) {
        client.consumeEvents(clientConsumer);
    }

    out.writeU8(0xF1);
    out.writeU16(0xF123);
    out.writeU32(0xF1234567);
    out.writeS8(-0x71);
    out.writeS16(-0x7123);
    out.writeS32(-0x71234567);
    out.writeS8(0x71);
    out.writeS16(0x7123);
    out.writeS32(0x71234567);
    StringBuffer<20> s("Hi there");
    out.writeString(s);
    out.writeFloat(252345.983f);
    client.send(out);

    for(int i = 0; i < 100; i++) {
        client.consumeEvents(clientConsumer);
    }

    test.checkEqual(true, clientConsumer.package, "client has received data");
    test.checkEqual(true, serverConsumer.connected, "server has connection");

    test.checkEqual(static_cast<uint8>(0xF1), serverConsumer.data1,
                    "correct value is sent 1");
    test.checkEqual(static_cast<uint16>(0xF123), serverConsumer.data2,
                    "correct value is sent 2");
    test.checkEqual(0xF1234567u, serverConsumer.data3,
                    "correct value is sent 3");
    test.checkEqual(static_cast<int8>(-0x71), serverConsumer.data4,
                    "correct value is sent 4");
    test.checkEqual(static_cast<int16>(-0x7123), serverConsumer.data5,
                    "correct value is sent 5");
    test.checkEqual(-0x71234567, serverConsumer.data6,
                    "correct value is sent 6");
    test.checkEqual(static_cast<int8>(0x71), serverConsumer.data7,
                    "correct value is sent 7");
    test.checkEqual(static_cast<int16>(0x7123), serverConsumer.data8,
                    "correct value is sent 8");
    test.checkEqual(0x71234567, serverConsumer.data9,
                    "correct value is sent 9");
    test.checkEqual(s, serverConsumer.data10, "correct value is sent 10");
    test.checkFloat(252345.983f, serverConsumer.data11, 0.01f,
                    "correct value is sent 11");

    client.disconnect();
    sleep(100);

    test.checkEqual(true, serverConsumer.disconnect, "client has disconnected");

    running = false;
    listen.join();
}

void NetworkTests::test() {
    Test test("Network");
    testConnect(test, OutPacket::unsequenced(50));
    testConnect(test, OutPacket::reliable(50));
    testConnect(test, OutPacket::sequenced(50));
    test.finalize();
}