#pragma once

#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <span>
#include <stdexcept>
#include <utility>

namespace radar::drivers::librevna::detail {

constexpr float kPi = 3.14159265358979323846F;

// Frame format:
// [0]=header, [1..2]=length_le, [3]=packet_type, [...payload...], [crc32_le]
constexpr std::uint8_t kFrameHeader = 0x5A;
constexpr std::size_t kFrameOverheadBytes = 8;
constexpr std::size_t kMaxFrameLengthBytes = 4096;

constexpr unsigned char kEndpointOut = 0x01;
constexpr unsigned char kEndpointIn = 0x81;
constexpr int kUsbInterface = 0;

constexpr std::uint8_t kPacketDeviceInfo = 5;
constexpr std::uint8_t kPacketAck = 7;
constexpr std::uint8_t kPacketNack = 10;
constexpr std::uint8_t kPacketRequestDeviceInfo = 15;
constexpr std::uint8_t kPacketVnaDatapoint = 27;
constexpr std::uint8_t kPacketInitiateSweep = 32;

constexpr std::size_t kDeviceInfoPayloadSize = 57;
constexpr std::size_t kDeviceInfoProtocolOffset = 0;
constexpr std::size_t kDeviceInfoNumPortsOffset = 54;
constexpr std::uint16_t kProtocolV14 = 14;

constexpr std::uint8_t kReferenceFlagMask = 0x10;
constexpr std::uint8_t kPort1Mask = 0x01;
constexpr std::uint8_t kPort2Mask = 0x02;

constexpr std::array<std::pair<std::uint16_t, std::uint16_t>, 3> kSupportedUsbIds = {
    std::pair<std::uint16_t, std::uint16_t>{0x0483, 0x564E},
    std::pair<std::uint16_t, std::uint16_t>{0x0483, 0x4121},
    std::pair<std::uint16_t, std::uint16_t>{0x1209, 0x4121},
};

constexpr int kUsbWriteTimeoutMs = 500;
constexpr int kAckTimeoutMs = 800;
constexpr int kDeviceInfoTimeoutSeconds = 2;
constexpr std::size_t kUsbReadChunkBytes = 16 * 1024;
constexpr int kUsbReadPollMinTimeoutMs = 1;
constexpr int kUsbReadPollMaxTimeoutMs = 50;

[[nodiscard]] inline auto read_u16_le(std::span<const std::uint8_t> data, std::size_t offset) -> std::uint16_t {
    if ((offset + sizeof(std::uint16_t)) > data.size()) {
        throw std::runtime_error("Failed to decode uint16 from payload");
    }
    return static_cast<std::uint16_t>(data[offset]) |
           (static_cast<std::uint16_t>(data[offset + 1]) << 8U);
}

[[nodiscard]] inline auto read_u32_le(std::span<const std::uint8_t> data, std::size_t offset) -> std::uint32_t {
    if ((offset + sizeof(std::uint32_t)) > data.size()) {
        throw std::runtime_error("Failed to decode uint32 from payload");
    }
    return static_cast<std::uint32_t>(data[offset]) |
           (static_cast<std::uint32_t>(data[offset + 1]) << 8U) |
           (static_cast<std::uint32_t>(data[offset + 2]) << 16U) |
           (static_cast<std::uint32_t>(data[offset + 3]) << 24U);
}

[[nodiscard]] inline auto read_u64_le(std::span<const std::uint8_t> data, std::size_t offset) -> std::uint64_t {
    if ((offset + sizeof(std::uint64_t)) > data.size()) {
        throw std::runtime_error("Failed to decode uint64 from payload");
    }

    std::uint64_t value = 0;
    for (std::size_t index = 0; index < sizeof(std::uint64_t); ++index) {
        value |= static_cast<std::uint64_t>(data[offset + index]) << (index * 8U);
    }
    return value;
}

[[nodiscard]] inline auto read_f32_le(std::span<const std::uint8_t> data, std::size_t offset) -> float {
    const auto raw = read_u32_le(data, offset);
    float out = 0.0F;
    std::memcpy(&out, &raw, sizeof(float));
    return out;
}

inline void write_u16_le(std::span<std::uint8_t> data, std::size_t offset, std::uint16_t value) {
    if ((offset + sizeof(std::uint16_t)) > data.size()) {
        throw std::runtime_error("Failed to encode uint16 into frame");
    }

    data[offset] = static_cast<std::uint8_t>(value & 0xFFU);
    data[offset + 1] = static_cast<std::uint8_t>((value >> 8U) & 0xFFU);
}

inline void write_u32_le(std::span<std::uint8_t> data, std::size_t offset, std::uint32_t value) {
    if ((offset + sizeof(std::uint32_t)) > data.size()) {
        throw std::runtime_error("Failed to encode uint32 into frame");
    }

    data[offset] = static_cast<std::uint8_t>(value & 0xFFU);
    data[offset + 1] = static_cast<std::uint8_t>((value >> 8U) & 0xFFU);
    data[offset + 2] = static_cast<std::uint8_t>((value >> 16U) & 0xFFU);
    data[offset + 3] = static_cast<std::uint8_t>((value >> 24U) & 0xFFU);
}

}  // namespace radar::drivers::librevna::detail

