#pragma once
#include <string>
#include <optional>
#include "../utils/logger.hpp"
#include <picoquic/picosocks.h>

/// Network protocol
namespace net {

/// Application Protocol Negotiation
constexpr auto ALPN = "univerxel_quic";

/// Reasons for client server connection to break
enum class disconnect_reason: uint16_t {
    UNEXPECTED = 0,
    /// Client quit
    QUIT = 1,
    /// Server close
    CLOSE = 2,
    /// Server full
    FULL = 3,
    /// Bad authentication
    UNAUTHORIZED = 4,
    PROTOCOL_VIOLATION = 15,
};

/// Packets from server to client
enum class server_packet_type: uint8_t {
    //MAYBE: HELLO = 0,

    /// Must close connection
    /// reason(uint16_t)
    QUIT = UINT8_MAX,

    /// Set client position and instance id
    /// node_id(entityId) relative_transform
    TELEPORT = 1,

    /// Average of preloaded chunks in region
    /// {area_region_ref, {region_chunk_pos, Voxel}[]}
    /// Voxel swap bit indicate a chunk full of Voxel without flag bit
    REGION = 17,
    /// Full chunk update
    /// {node_chunk_ref, zstd<chunk rle>}
    /// empty: all sent
    CHUNK = 18,
    /// Uncompressed chunk changes
    /// {node_ref, {chunk_pos, uint16_t(count), Chunk::Edit[]}[]}
    RAW_EDITS = 19,
    /// Chunk changes instruction
    /// action::FillShape
    EDITS = 20,

    /// Update entities instances hierarchy, position, velocity and content
    /// {node_id(id), ...}[]
    /// if Has<Added>: {start_point, ...}
    /// --  if Is<Instance>: model_id
    /// --  if Is<Part>: usvec3(size)
    /// --  if Is<Area>: Area::params
    /// --  else {}
    /// if Has<Moved>: start_point
    /// if Has<Removed>: {}
    ENTITIES = 33,

    /// Model data
    /// model_id, char(data)[] (not null terminated)
    MODEL = 42,

    /// World compression dictionary
    /// zstd dict
    COMPRESSION = 64,
    /// Server capabilities
    /// uint16_t(loadDistance), bool(predictable), (if predictable) size_t(floodfillLimit)
    //MAYBE: use uint8_t flags
    CAPABILITIES = 65,

    /// Provide authentication token
    /// char[] (not null terminated)
    REGISTERED = 127,

    /// Public chat message
    /// char[] (not null terminated)
    MESSAGE = 129,
};
/// Packets from client to server
enum class client_packet_type: uint8_t {
    /// Authenticate
    /// uint8_t(type), payload
    /// Register: 0, char(name)[]
    /// Login: 64, char(name)[] char[] (not null terminated)
    HELLO = 0,

    /// Position update (unreliable)
    /// relative_pos
    MOVE = 16,

    /// Request missing regions
    /// area_id, region_pos[max: MAX_PENDING_CHUNK_COUNT]
    REQUEST_REGIONS = 17,
    /// Request missing chunks
    /// node_id, chunk_pos[max: MAX_PENDING_CHUNK_COUNT]
    REQUEST_CHUNKS = 18,

    /// Interact with voxels
    /// actions::FillShape
    FILL_SHAPE = 20,

    /// Request entity hierarchy
    /// node_id(root)
    REQUEST_ENTITIES = 33,

    /// Request model data
    /// model_id
    REQUEST_MODEL = 42,

    /// Client capabilities
    /// bool(edit_handling)
    //MAYBE: use uint8_t flags
    CAPABILITIES = 65,

    /// Send public text message
    /// char[] (not null terminated)
    MESSAGE = 129,
};

constexpr auto MAX_PENDING_CHUNK_COUNT = 256;

/// Server address as name(ip or dns name) and port
struct address {
    std::string host;
    int port;

    /// Return address_storage(ip+port) and isName
    std::optional<std::pair<sockaddr_storage, bool>> toAddress() const {
        std::pair<sockaddr_storage, bool> res;
        int isName = 0;
        if (picoquic_get_server_address(host.c_str(), port, &res.first, &isName) != 0)
            return {};

        res.second = isName;
        return res;
    }

    friend inline std::ostream& operator<<(std::ostream& os, const address& ct) {
        return os << ct.host << ':' << ct.port;
    }
};
/// Server properties
struct exposure: address {
    uint32_t max_connections;
    /// TLS private key
    std::string key;
    /// TLS public key
    std::string cert;
};

}