#ifndef GAMINGCORE_NETWORK_HPP
#define GAMINGCORE_NETWORK_HPP

#include <core/Buffer.hpp>
#include <core/Types.hpp>
#include <core/UniquePointer.hpp>

namespace Core {
    enum class PacketSendMode { RELIABLE, SEQUENCED, UNSEQUENCED };

    class InPacket {
        const char* data;
        size_t size;
        size_t index;

    public:
        InPacket(const void* data, size_t n);
        InPacket(const InPacket&) = delete;
        InPacket(InPacket&&) = delete;
        InPacket& operator=(const InPacket&) = delete;
        InPacket& operator=(InPacket&&) = delete;

        bool readU8(u8& u);
        bool readU16(u16& u);
        bool readU32(u32& u);
        bool readI8(i8& i);
        bool readI16(i16& i);
        bool readI32(i32& i);
        bool readFloat(float& f);
        size_t readString(char* buffer, size_t n);
        bool read(void* buffer, size_t n);
    };

    struct OutPacket {
        Buffer data{};
        void writeU8(u8 u);
        void writeU16(u16 u);
        void writeU32(u32 u);
        void writeI8(i8 i);
        void writeI16(i16 i);
        void writeI32(i32 i);
        void writeFloat(float f);
        void writeString(const char* buffer);
        void write(const void* buffer, size_t n);
    };

    using Port = u16;
    using OnServerConnect = void (*)();
    using OnServerDisconnect = void (*)();
    using OnServerPacket = void (*)(InPacket&);

    struct Client final {
        Client();
        ~Client();
        bool start();
        void stop();
        bool connect(const char* server, Port port, int timeoutTicks);
        void setTimeout(u32 timeout, u32 timeoutMin, u32 timeoutMax);
        void disconnect(int timeoutTicks);
        void sendPacket(const OutPacket& p, PacketSendMode mode);
        void tick();
        void setConnectHandler(OnServerConnect oc);
        void setDisconnectHandler(OnServerDisconnect od);
        void setPacketHandler(OnServerPacket op);
        void resetHandler();
        bool isConnecting();
        bool isConnected();

        struct Data;

    private:
        UniquePointer<Data> data;
    };

    struct Server;

    using ClientHandle = int;
    using OnClientConnect = void (*)(Server&, ClientHandle);
    using OnClientDisconnect = void (*)(Server&, ClientHandle);
    using OnClientPacket = void (*)(Server&, ClientHandle, InPacket&);

    struct Server final {
        Server();
        ~Server();
        bool start(Port port, size_t maxClients);
        void stop();
        void tick();
        void sendPacketBroadcast(const OutPacket& p, PacketSendMode mode);
        void sendPacket(
            ClientHandle client, const OutPacket& p, PacketSendMode mode);
        void setTimeout(
            ClientHandle client, u32 timeout, u32 timeoutMin, u32 timeoutMax);
        void disconnectClient(ClientHandle client);
        void setConnectHandler(OnClientConnect oc);
        void setDisconnectHandler(OnClientDisconnect od);
        void setPacketHandler(OnClientPacket op);
        void resetHandler();

        struct Data;

    private:
        UniquePointer<Data> data;
    };

}

#endif