#include "DeviceInfoReceiver.h"

#include <QtEndian>
#include <chrono>
#include <cstring>
#include <arpa/inet.h>

DeviceInfoReceiver::DeviceInfoReceiver(QObject* parent, uint16_t port)
    : QObject(parent), listen_port_(port) {
}

DeviceInfoReceiver::~DeviceInfoReceiver() {
    stop();
}

bool DeviceInfoReceiver::start() {
    udp_socket_ = std::make_unique<QUdpSocket>(this);
    if (!udp_socket_->bind(QHostAddress::AnyIPv4, listen_port_, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) {
        emit error(QString("无法绑定端口 %1: %2").arg(listen_port_).arg(udp_socket_->errorString()));
        return false;
    }
    connect(udp_socket_.get(), &QUdpSocket::readyRead, this, &DeviceInfoReceiver::on_udp_read_ready);
    return true;
}

void DeviceInfoReceiver::stop() {
    if (udp_socket_) {
        udp_socket_->close();
        udp_socket_.reset();
    }
}

void DeviceInfoReceiver::request_info(const QHostAddress& target) {
    if (!udp_socket_) return;
    uint16_t magic = qToBigEndian<uint16_t>(DEVICE_INFO_REQUEST);
    udp_socket_->writeDatagram(reinterpret_cast<const char*>(&magic), sizeof(magic), target, listen_port_);
}

void DeviceInfoReceiver::on_udp_read_ready() {
    while (udp_socket_ && udp_socket_->hasPendingDatagrams()) {
        QByteArray datagram;
        datagram.resize(int(udp_socket_->pendingDatagramSize()));
        QHostAddress sender;
        quint16 sender_port;
        if (udp_socket_->readDatagram(datagram.data(), datagram.size(), &sender, &sender_port) <= 0) {
            continue;
        }

        DeviceInfo info;
        if (parse_packet(datagram, info)) {
            emit deviceInfoReceived(info);
        }
    }
}

bool DeviceInfoReceiver::parse_packet(const QByteArray& datagram, DeviceInfo& out) {
    if (datagram.size() < int(sizeof(DeviceInfoPacket))) {
        return false;
    }

    const uint8_t* data = reinterpret_cast<const uint8_t*>(datagram.constData());
    const DeviceInfoPacket* pkt = reinterpret_cast<const DeviceInfoPacket*>(data);

    if (ntohs(pkt->magic) != DEVICE_INFO_MAGIC) {
        return false;
    }

    out.width = ntohs(pkt->width);
    out.height = ntohs(pkt->height);

    // framerate
    uint32_t fps_bits = ntohl(*reinterpret_cast<const uint32_t*>(&pkt->framerate));
    float fps;
    std::memcpy(&fps, &fps_bits, sizeof(fps));
    out.framerate = fps;

    uint32_t fmt = ntohl(pkt->pixel_format);
    out.pixelFormat = fourcc_to_string(fmt);

    out.devicePath = QString::fromLatin1(pkt->device_path, strnlen(pkt->device_path, sizeof(pkt->device_path)));
    out.deviceName = QString::fromLatin1(pkt->device_name, strnlen(pkt->device_name, sizeof(pkt->device_name)));

    uint32_t cur_fps_bits = ntohl(*reinterpret_cast<const uint32_t*>(&pkt->current_fps));
    float cur_fps;
    std::memcpy(&cur_fps, &cur_fps_bits, sizeof(cur_fps));
    out.currentFps = cur_fps;

    out.totalFrames = ntohl(pkt->total_frames);
    uint64_t ts = ntohll(pkt->timestamp);
    out.timestamp = QDateTime::fromMSecsSinceEpoch(qint64(ts));
    return true;
}

uint64_t DeviceInfoReceiver::ntohll(uint64_t v) const {
    // big endian check
    static const int num = 1;
    if (*reinterpret_cast<const char*>(&num) == 0) {
        return v;
    }
    uint32_t high = ntohl(static_cast<uint32_t>(v >> 32));
    uint32_t low = ntohl(static_cast<uint32_t>(v & 0xFFFFFFFFULL));
    return (static_cast<uint64_t>(low) << 32) | high;
}

QString DeviceInfoReceiver::fourcc_to_string(uint32_t fourcc) const {
    char c[5] = {0};
    c[0] = static_cast<char>(fourcc & 0xFF);
    c[1] = static_cast<char>((fourcc >> 8) & 0xFF);
    c[2] = static_cast<char>((fourcc >> 16) & 0xFF);
    c[3] = static_cast<char>((fourcc >> 24) & 0xFF);
    return QString::fromLatin1(c);
}
