#pragma once

#include "json.hpp"
#include <arpa/inet.h>
#include <netdb.h>
#include <stdlib.h>
#include <string>
#include <unistd.h>

namespace sliger::rpc {

struct Err {
    std::string msg;
};

template <typename T> class Res {
public:
    Res(T value)
    {
        this->value = value;
        this->holds_value = true;
    }
    Res(Err error)
    {
        this->error = error;
        this->holds_value = false;
    }
    auto is_ok() -> bool { return this->holds_value; }
    auto ok() -> T { return this->value; }
    auto err() -> Err { return this->error; }

private:
    bool holds_value;
    T value;
    Err error;
};

class BracketFinder {
public:
    BracketFinder()
        : layers(0)
    {
    }
    auto feed(int8_t c)
    {
        if (c == '{') {
            this->layers += 1;
        } else if (c == '}') {
            this->layers -= 1;
        }
    }
    auto bracket_closed() -> bool { return this->layers == 0; }

private:
    int layers;
};

struct Unit { };

class BufferedWriter {
public:
    BufferedWriter(int fd)
        : fd(fd)
    {
    }
    BufferedWriter(const BufferedWriter&) = delete;
    BufferedWriter operator=(const BufferedWriter&) = delete;
    BufferedWriter(BufferedWriter&&) = delete;
    BufferedWriter operator=(BufferedWriter&&) = delete;
    ~BufferedWriter() { close(fd); }

    auto flush() -> Res<size_t>;
    auto write(uint8_t byte) -> Res<Unit>;
    auto write(std::string message) -> Res<Unit>;

private:
    static const size_t length = 1024;
    size_t occupied = 0;
    uint8_t buffer[length];
    int fd;
};

namespace {
    static inline auto create_address(uint16_t port) -> sockaddr_in
    {
        return {
            .sin_family = AF_INET,
            .sin_port = htons(port),
            .sin_addr = { .s_addr = inet_addr("127.0.0.1") },
            .sin_zero = { 0 },
        };
    }
}

/// - load code
///     - program input
/// - run
/// - run debug
///     - fwamegwaph option
///     - code covewage option
/// - fetch fwamegwaph
///     - json string
/// - fetch code covewage
///     - json string
/// - fetch stack
///     - json string
template <typename Functor> class RpcServer {
public:
    RpcServer(Functor&& functor)
        : functor(functor) {};

    auto listen() -> Res<Unit>
    {
        int socket;
        {
            socket = ::socket(AF_INET, SOCK_STREAM, 0);
            if (socket < 0) {
                return Err { .msg
                    = std::format("could not get socket ({})", socket) };
            };
        }
        {
            auto address = create_address(13370);
            auto err
                = ::bind(socket, (struct sockaddr*)&address, sizeof(address));
            if (err < 0) {
                return Err { .msg = std::format("could not bind ({})", err) };
            };
            return Unit {};
        }
        {
            auto err = ::listen(socket, 0);
            if (err < 0) {
                return Err { .msg = std::format("could not listen ({})", err) };
            }
        }
        while (true) {

            auto client_address = create_address(13370);
            socklen_t address_size = sizeof(client_address);
            int client = ::accept(
                socket, (struct sockaddr*)&client_address, &address_size);
            if (client < 0) {
                return Err { .msg
                    = std::format("could not accept ({})", client) };
            }
            const size_t buf_len = 1024;
            int8_t buffer[buf_len] = {};
            auto bracket_finder = BracketFinder();
            std::string message = {};
            while (true) {
                ssize_t bytes_read = read(client, buffer, buf_len);
                if (bytes_read < 0) {
                    return Err { .msg
                        = std::format("could not read ({})", bytes_read) };
                } else if (bytes_read == 0) {
                    break;
                }
                for (size_t i = 0; i < (size_t)bytes_read; ++i) {
                    message += buffer[i];
                    bracket_finder.feed(buffer[i]);
                    if (!bracket_finder.bracket_closed()) {
                        continue;
                    }
                    auto req = sliger::json::parse_json(message);
                    if (!req.ok()) {
                        auto err = req.err();
                        auto msg = std::format(
                            "error parsing rpc message: '{}' @ {}:{}\n",
                            err.msg, err.pos.line, err.pos.col);
                        return Err {
                            .msg = msg,
                        };
                    }
                    auto writer = std::make_unique<BufferedWriter>(client);
                    this->functor(std::move(req.val()), std::move(writer));
                    message.clear();
                    break;
                }
            }
        }
        std::unreachable();
    };

private:
    Functor functor;
};

};