// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qv4l2cameradevices_p.h"
#include "qv4l2filedescriptor_p.h"
#include "qv4l2camera_p.h"

#include <private/qcameradevice_p.h>
#include <private/qcore_unix_p.h>

#include <qdir.h>
#include <qfile.h>
#include <qdebug.h>
#include <qloggingcategory.h>

#include <linux/videodev2.h>

QT_BEGIN_NAMESPACE

static Q_LOGGING_CATEGORY(qLcV4L2CameraDevices, "qt.multimedia.ffmpeg.v4l2cameradevices");

static bool areCamerasEqual(QList<QCameraDevice> a, QList<QCameraDevice> b)
{
    auto areCamerasDataEqual = [](const QCameraDevice &a, const QCameraDevice &b) {
        Q_ASSERT(QCameraDevicePrivate::handle(a));
        Q_ASSERT(QCameraDevicePrivate::handle(b));
        return *QCameraDevicePrivate::handle(a) == *QCameraDevicePrivate::handle(b);
    };

    return std::equal(a.cbegin(), a.cend(), b.cbegin(), b.cend(), areCamerasDataEqual);
}

QV4L2CameraDevices::QV4L2CameraDevices(QPlatformMediaIntegration *integration)
    : QPlatformVideoDevices(integration)
{
    m_deviceWatcher.addPath(QLatin1String("/dev"));
    connect(&m_deviceWatcher, &QFileSystemWatcher::directoryChanged, this,
            &QV4L2CameraDevices::checkCameras);
    doCheckCameras();
}

QList<QCameraDevice> QV4L2CameraDevices::videoDevices() const
{
    return m_cameras;
}

void QV4L2CameraDevices::checkCameras()
{
    if (doCheckCameras())
        emit videoInputsChanged();
}

bool QV4L2CameraDevices::doCheckCameras()
{
    QList<QCameraDevice> newCameras;

    QDir dir(QLatin1String("/dev"));
    const auto devices = dir.entryList(QDir::System);

    bool first = true;

    for (auto device : devices) {
        //        qCDebug(qLcV4L2Camera) << "device:" << device;
        if (!device.startsWith(QLatin1String("video")))
            continue;

        QByteArray file = QFile::encodeName(dir.filePath(device));
        const int fd = open(file.constData(), O_RDONLY);
        if (fd < 0)
            continue;

        auto fileCloseGuard = qScopeGuard([fd]() { close(fd); });

        v4l2_fmtdesc formatDesc = {};

        struct v4l2_capability cap;
        if (xioctl(fd, VIDIOC_QUERYCAP, &cap) < 0)
            continue;

        if (cap.device_caps & V4L2_CAP_META_CAPTURE)
            continue;
        if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
            continue;
        if (!(cap.capabilities & V4L2_CAP_STREAMING))
            continue;

        auto camera = std::make_unique<QCameraDevicePrivate>();

        camera->id = file;
        camera->description = QString::fromUtf8((const char *)cap.card);
        qCDebug(qLcV4L2CameraDevices) << "found camera" << camera->id << camera->description;

        formatDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

        while (!xioctl(fd, VIDIOC_ENUM_FMT, &formatDesc)) {
            auto pixelFmt = formatForV4L2Format(formatDesc.pixelformat);
            qCDebug(qLcV4L2CameraDevices) << "    " << pixelFmt;

            if (pixelFmt == QVideoFrameFormat::Format_Invalid) {
                ++formatDesc.index;
                continue;
            }

            qCDebug(qLcV4L2CameraDevices) << "frame sizes:";
            v4l2_frmsizeenum frameSize = {};
            frameSize.pixel_format = formatDesc.pixelformat;

            while (!xioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frameSize)) {
                ++frameSize.index;
                if (frameSize.type != V4L2_FRMSIZE_TYPE_DISCRETE)
                    continue;

                QSize resolution(frameSize.discrete.width, frameSize.discrete.height);
                float min = 1e10;
                float max = 0;

                v4l2_frmivalenum frameInterval = {};
                frameInterval.pixel_format = formatDesc.pixelformat;
                frameInterval.width = frameSize.discrete.width;
                frameInterval.height = frameSize.discrete.height;

                while (!xioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frameInterval)) {
                    ++frameInterval.index;
                    if (frameInterval.type != V4L2_FRMIVAL_TYPE_DISCRETE)
                        continue;
                    float rate = float(frameInterval.discrete.denominator)
                            / float(frameInterval.discrete.numerator);
                    if (rate > max)
                        max = rate;
                    if (rate < min)
                        min = rate;
                }

                qCDebug(qLcV4L2CameraDevices) << "    " << resolution << min << max;

                if (min <= max) {
                    auto fmt = std::make_unique<QCameraFormatPrivate>();
                    fmt->pixelFormat = pixelFmt;
                    fmt->resolution = resolution;
                    fmt->minFrameRate = min;
                    fmt->maxFrameRate = max;
                    camera->videoFormats.append(fmt.release()->create());
                    camera->photoResolutions.append(resolution);
                }
            }

            ++formatDesc.index;
        }

        if (camera->videoFormats.empty())
            continue;

        // first camera is default
        camera->isDefault = std::exchange(first, false);

        newCameras.append(camera.release()->create());
    }

    if (areCamerasEqual(m_cameras, newCameras))
        return false;

    m_cameras = std::move(newCameras);
    return true;
}

QT_END_NAMESPACE

#include "moc_qv4l2cameradevices_p.cpp"
