#include "common/NetworkPackets.h"
#include "data/Array.h"
#include "data/HashMap.h"
#include "math/Vector.h"
#include "network/Server.h"
#include "raw-terminal/Console.h"
#include "server/snuviscript/Snuvi.h"
#include "utils/Clock.h"
#include "utils/Random.h"
#include "utils/SplitString.h"

static constexpr int WORLD_SIZE = 16;

static bool running = true;
static Array<bool, WORLD_SIZE * WORLD_SIZE * WORLD_SIZE> world(false);

static HashMap<Server::Client, int> players;

static void handleCommands() {
    const ConsoleLine* line = readConsoleLine("> ");
    if(line != nullptr) {
        StringBuffer<50> buffer;
        for(int i = 0; i < line->length; i++) {
            buffer.appendUnicode(line->data[i]);
        }
        buffer.printLine();
        if(buffer == "stop") {
            running = false;
        }
    }
}

static void handleCommand(List<uint32>& s) {
    StringBuffer<256> buffer;
    for(uint32 u : s) {
        buffer.appendUnicode(u);
    }
    SplitString<256> split(buffer);
    if(split.getLength() >= 1) {
        if(strcmp(split[0], "/script") == 0) {
            if(split.getLength() >= 2) {
                Snuvi::start(split[1]);
            } else {
                puts("/script <script>");
            }
        }
    }
}

static void set(int x, int y, int z, bool b) {
    if(x < 0 || x >= WORLD_SIZE || y < 0 || y >= WORLD_SIZE || z < 0 ||
       z >= WORLD_SIZE) {
        return;
    }
    world[x * WORLD_SIZE * WORLD_SIZE + y * WORLD_SIZE + z] = b;

    OutPacket out(100);
    out.writeU8(static_cast<uint8>(ToClientPacket::SET_BLOCK));
    out.writeS32(x);
    out.writeS32(y);
    out.writeS32(z);
    out.writeU8(b);
    Server::send(out, PacketSendMode::RELIABLE);
}

static void onConnect(Server::Client client) {
    OutPacket out(100);
    out.writeU8(static_cast<uint8>(ToClientPacket::WORLD));
    for(bool b : world) {
        out.writeU8(b);
    }
    Server::send(client, out, PacketSendMode::RELIABLE);
    players.add(client, 0);
}

static void onDisconnect(Server::Client client) {
    players.remove(client);
}

static void onPacket(Server::Client client, InPacket& in) {
    uint8 type = 0;
    if(in.read(type)) {
        puts("no data");
        return;
    }
    switch(static_cast<ToServerPacket>(type)) {
        case ToServerPacket::SET_BLOCK: {
            IntVector3 pos;
            in.read(pos);
            uint8 type;
            in.read(type);
            set(pos[0], pos[1], pos[2], type);
            break;
        }
        case ToServerPacket::PLAYER: {
            Vector3 pos;
            in.read(pos[0]);
            in.read(pos[1]);
            in.read(pos[2]);

            OutPacket out(100);
            out.writeU8(static_cast<uint8>(ToClientPacket::PLAYER));
            out.writeFloat(pos[0]);
            out.writeFloat(pos[1]);
            out.writeFloat(pos[2]);
            out.writeS32(client);
            for(Server::Client c : players.keys()) {
                if(c != client) {
                    Server::send(c, out, PacketSendMode::RELIABLE);
                }
            }
            break;
        }
        case ToServerPacket::CHAT: {
            List<uint32> s;
            uint32 u;
            while(!in.read(u)) {
                s.add(u);
            }

            if(s.getLength() > 0 && s[0] == '/') {
                handleCommand(s);
                return;
            }

            OutPacket out(100);
            out.writeU8(static_cast<uint8>(ToClientPacket::CHAT));
            for(uint32 u : s) {
                out.writeU32(u);
            }
            Server::send(out, PacketSendMode::RELIABLE);
            break;
        }
        default: printf("invalid package type %d\n", static_cast<int>(type));
    }
}

static void init() {
    Snuvi::init();
    Server::setPacketHandler(onPacket);
    Server::setConnectHandler(onConnect);
    Server::setDisconnectHandler(onDisconnect);
    Random r(0);
    for(bool& b : world) {
        b = r.next() & 1;
    }
}

static void tick() {
    handleCommands();
    Server::tick();
}

static void loop() {
    init();
    Clock clock;
    constexpr Clock::Nanos NANOS_PER_TICK = 50000000;
    Clock::Nanos lag = 0;
    while(running) {
        lag += clock.update();
        while(lag >= NANOS_PER_TICK) {
            lag -= NANOS_PER_TICK;
            tick();
        }
        Clock::Nanos waitNanos = NANOS_PER_TICK - lag;
        if(waitNanos > 300000) {
            clock.wait(waitNanos);
        }
    }
}

int main() {
    if(initConsole()) {
        return 0;
    }
    Error e = Server::start(11196, 50);
    if(e.has()) {
        puts(e.message);
        return 0;
    }
    loop();
    Server::stop();
    return 0;
}