#include "net_msg.hpp"

#include <array>

#include "q_stdinc.hpp"
#include "quakedef.hpp"
#include "common.hpp"
#include "mathlib.hpp"

namespace net {
    void msg::write_lores_coord(const float f) {
        write_short(static_cast<short>(std::round(f * 8)));
    }

    void msg::write_hires_coord(const float f) {
        write_short(static_cast<short>(f));
        write_byte(static_cast<int>(f * 255) % 255);
    }

    std::optional<float> msg::read_lores_coord() {
        const auto c = read_short();

        if (!c.has_value()) {
            return std::nullopt;
        }

        return c.value() * (1.0 / 8);
    }

    std::optional<float> msg::read_hires_coord() {
        const auto c = read_short();
        const auto d = read_byte();

        if (!c.has_value() || !d.has_value()) {
            return std::nullopt;
        }

        return c.value() + (d.value() * (1.0 / 255));
    }

    void msg::clear() {
        _data.clear();
        _eos = false;
        _offset = 0;
    }

    void msg::write_char(const char c) {
        _data.push_back(c);
    }

    void msg::write_byte(const std::uint8_t b) {
        _data.push_back(b);
    }

    void msg::write_short(const short s) {
        _data.push_back(s & 0xFF);
        _data.push_back((s >> 8) & 0xFF);
    }

    void msg::write_long(const int l) {
        _data.push_back(l & 0xFF);
        _data.push_back((l >> 8) & 0xFF);
        _data.push_back((l >> 16) & 0xFF);
        _data.push_back((l >> 24) & 0xFF);
    }

    void msg::write_float(const float f) {
        write_long(std::bit_cast<std::int32_t>(f));
    }

    void msg::write_string(const std::string_view s) {
        if (!s.empty()) {
            for (const auto &c: s) {
                _data.push_back(c);
            }
        }
        _data.push_back('\0');
    }

    static rmq_flags operator&(rmq_flags lhs, rmq_flags rhs) {
        return static_cast<rmq_flags>(static_cast<int>(lhs) & static_cast<int>(rhs));
    }

    void msg::write_coord(const float f, const rmq_flags flags) {
        if ((flags & rmq_flags::float_coord) != rmq_flags::none) {
            write_float(f);
        } else if ((flags & rmq_flags::int_coord) != rmq_flags::none) {
            write_long(static_cast<int>(std::round(f * 16)));
        } else if ((flags & rmq_flags::hires_coord) != rmq_flags::none) {
            write_hires_coord(f);
        } else {
            write_lores_coord(f);
        }
    }

    void msg::write_angle(const float f, const rmq_flags flags) {
        if ((flags & rmq_flags::float_angle) != rmq_flags::none) {
            write_float(f);
        } else if ((flags & rmq_flags::short_angle) != rmq_flags::none) {
            const auto rounded = static_cast<int>(std::round(f * 65536.0 / 360.0));
            write_short(static_cast<short>(rounded & 65535));
        } else {
            const auto rounded = static_cast<int>(std::round(f * 256.0 / 360.0));
            write_byte(static_cast<std::uint8_t>(rounded & 255));
        }
    }

    void msg::write_angle_16(const float f, const rmq_flags flags) {
        if ((flags & rmq_flags::float_angle) != rmq_flags::none) {
            write_float(f);
        } else {
            const auto rounded = static_cast<int>(std::round(f * 65536.0 / 360.0));
            write_short(static_cast<short>(rounded & 65535));
        }
    }

    void msg::begin_reading() {
        _offset = 0;
        _eos = false;
    }

    std::optional<char> msg::read_char() {
        if (_eos || _offset + 1 > _data.size()) {
            _eos = true;
            return std::nullopt;
        }

        return static_cast<signed char>(_data[_offset++]);
    }

    std::optional<std::uint8_t> msg::read_byte() {
        if (_eos || _offset + 1 > _data.size()) {
            _eos = true;
            return std::nullopt;
        }

        return _data[_offset++];
    }

    std::optional<short> msg::read_short() {
        if (_eos || _offset + 2 > _data.size()) {
            _eos = true;
            return std::nullopt;
        }

        auto res = static_cast<short>(_data[_offset] + (_data[_offset + 1] << 8));

        _offset += 2;

        return res;
    }

    std::optional<int> msg::read_long() {
        if (_eos || _offset + 4 > _data.size()) {
            _eos = true;
            return std::nullopt;
        }

        auto res = _data[_offset] + (_data[_offset + 1] << 8) + (_data[_offset + 2] << 16) + (
                       _data[_offset + 3] << 24);

        _offset += 4;

        return res;
    }


    std::optional<float> msg::read_float() {
        if (_eos || _offset + 4 > _data.size()) {
            _eos = true;
            return std::nullopt;
        }

        auto res = std::bit_cast<float>(read_long().value());

        return res;
    }

    std::optional<std::string> msg::read_string() {
        std::string res{};

        if (_eos) {
            return std::nullopt;
        }

        do {
            auto c = read_char();
            if (!c.has_value() || c.value() == 0) {
                break;
            }
            res += c.value();
        } while (!_eos);

        return res;
    }

    std::optional<float> msg::read_coord(const rmq_flags flags) {
        if ((flags & rmq_flags::float_coord) != rmq_flags::none) {
            return read_float();
        }
        if ((flags & rmq_flags::int_coord) != rmq_flags::none) {
            const auto i = read_long();
            if (!i.has_value()) {
                return std::nullopt;
            }
            return i.value() * (1.0 / 16.0);
        }
        if ((flags & rmq_flags::hires_coord) != rmq_flags::none) {
            return read_hires_coord();
        }
        return read_lores_coord();
    }

    std::optional<float> msg::read_angle(const rmq_flags flags) {
        if ((flags & rmq_flags::float_angle) != rmq_flags::none) {
            return read_float();
        }
        if ((flags & rmq_flags::short_angle) != rmq_flags::none) {
            auto a = read_short();
            if (!a.has_value()) {
                return std::nullopt;
            }
            return a.value() * (360.0 / 65536);
        }
        const auto c = read_char();
        if (!c.has_value()) {
            return std::nullopt;
        }
        return c.value() * (360.0 / 256);
    }

    std::optional<float> msg::read_angle_16(rmq_flags flags) {
        if ((flags & rmq_flags::float_angle) != rmq_flags::none) {
            return read_float();
        }
        const auto s = read_short();
        if (!s.has_value()) {
            return std::nullopt;
        }
        return s.value() * (360.0 / 65536);
    }

    void msg::write(const std::uint8_t *data, std::size_t size) {
        for (auto i = 0; i < size; i++) {
            _data.push_back(data[i]);
        }
    }
}
