#include "v4l2_control.h"

#ifdef __linux__

#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fstream>
#include <sstream>
#include <iostream>
#include <cstring>

V4L2Control::V4L2Control() : m_fd(-1), m_initialized(false) {
}

V4L2Control::~V4L2Control() {
    releaseDevice();
}

bool V4L2Control::openDevice(const std::string& devicePath) {
    m_fd = open(devicePath.c_str(), O_RDWR);
    if (m_fd == -1) {
        return false;
    }
    
    // Query device capabilities
    struct v4l2_capability cap;
    if (ioctl(m_fd, VIDIOC_QUERYCAP, &cap) == -1) {
        close(m_fd);
        m_fd = -1;
        return false;
    }
    
    // Check if device supports video capture
    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
        close(m_fd);
        m_fd = -1;
        return false;
    }
    
    m_devicePath = devicePath;
    return true;
}

void V4L2Control::closeDevice() {
    if (m_fd != -1) {
        close(m_fd);
        m_fd = -1;
    }
    m_devicePath.clear();
}

bool V4L2Control::queryControl(int controlId, struct v4l2_queryctrl& queryctrl) {
    if (m_fd == -1) return false;
    
    memset(&queryctrl, 0, sizeof(queryctrl));
    queryctrl.id = controlId;
    
    if (ioctl(m_fd, VIDIOC_QUERYCTRL, &queryctrl) == -1) {
        return false;
    }
    
    if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) {
        return false;
    }
    
    return true;
}

bool V4L2Control::setControlValue(int controlId, int value) {
    if (m_fd == -1) return false;
    
    struct v4l2_control control;
    control.id = controlId;
    control.value = value;
    
    return ioctl(m_fd, VIDIOC_S_CTRL, &control) == 0;
}

bool V4L2Control::getControlValue(int controlId, int& value) {
    if (m_fd == -1) return false;
    
    struct v4l2_control control;
    control.id = controlId;
    
    if (ioctl(m_fd, VIDIOC_G_CTRL, &control) == -1) {
        return false;
    }
    
    value = control.value;
    return true;
}

bool V4L2Control::isLogitechBrio(const std::string& devicePath) {
    int fd = open(devicePath.c_str(), O_RDWR);
    if (fd == -1) return false;
    
    struct v4l2_capability cap;
    bool isBrio = false;
    
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) {
        std::string driver(reinterpret_cast<const char*>(cap.driver));
        std::string card(reinterpret_cast<const char*>(cap.card));
        
        // Check for Logitech BRIO devices
        if (card.find("BRIO") != std::string::npos || 
            card.find("MX Brio") != std::string::npos ||
            (card.find("Logitech") != std::string::npos && driver == "uvcvideo")) {
            isBrio = true;
        }
    }
    
    close(fd);
    return isBrio;
}

std::vector<CameraDevice> V4L2Control::discoverDevices() {
    std::vector<CameraDevice> devices;
    
    // Scan /dev for video devices
    DIR* dir = opendir("/dev");
    if (!dir) return devices;
    
    struct dirent* entry;
    while ((entry = readdir(dir)) != nullptr) {
        std::string filename(entry->d_name);
        
        // Look for video devices
        if (filename.find("video") == 0) {
            std::string devicePath = "/dev/" + filename;
            
            if (isLogitechBrio(devicePath)) {
                int fd = open(devicePath.c_str(), O_RDWR);
                if (fd != -1) {
                    struct v4l2_capability cap;
                    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) {
                        CameraDevice device;
                        device.name = reinterpret_cast<const char*>(cap.card);
                        device.id = devicePath;
                        device.vendorId = 0x046d; // Logitech vendor ID
                        
                        // Determine product ID based on device name
                        if (device.name.find("MX Brio") != std::string::npos) {
                            device.productId = 0x085e;
                        } else if (device.name.find("BRIO") != std::string::npos) {
                            device.productId = 0x085b;
                        }
                        
                        devices.push_back(device);
                    }
                    close(fd);
                }
            }
        }
    }
    
    closedir(dir);
    return devices;
}

bool V4L2Control::initializeDevice(const std::string& deviceId) {
    if (m_initialized) {
        releaseDevice();
    }
    
    if (!openDevice(deviceId)) {
        return false;
    }
    
    // Verify this is a Logitech BRIO
    if (!isLogitechBrio(deviceId)) {
        closeDevice();
        return false;
    }
    
    m_initialized = true;
    return true;
}

void V4L2Control::releaseDevice() {
    if (m_initialized) {
        closeDevice();
        m_initialized = false;
    }
}

bool V4L2Control::getZoomCapabilities(ZoomCapabilities& caps) {
    if (!m_initialized) return false;
    
    struct v4l2_queryctrl queryctrl;
    
    // Try zoom absolute first
    if (queryControl(V4L2_CID_ZOOM_ABSOLUTE, queryctrl)) {
        caps.min = queryctrl.minimum;
        caps.max = queryctrl.maximum;
        caps.step = queryctrl.step;
        caps.supportsAbsolute = true;
        
        // Get current zoom value
        int current;
        if (getControlValue(V4L2_CID_ZOOM_ABSOLUTE, current)) {
            caps.current = current;
        }
        
        // Check if relative zoom is also supported
        caps.supportsRelative = queryControl(V4L2_CID_ZOOM_RELATIVE, queryctrl);
        
        return true;
    }
    
    // Try zoom continuous
    if (queryControl(V4L2_CID_ZOOM_CONTINUOUS, queryctrl)) {
        caps.min = queryctrl.minimum;
        caps.max = queryctrl.maximum;
        caps.step = queryctrl.step;
        caps.supportsAbsolute = true;
        caps.supportsRelative = false;
        
        // Get current zoom value
        int current;
        if (getControlValue(V4L2_CID_ZOOM_CONTINUOUS, current)) {
            caps.current = current;
        }
        
        return true;
    }
    
    return false;
}

bool V4L2Control::setZoomAbsolute(int value) {
    if (!m_initialized) return false;
    
    // Try V4L2_CID_ZOOM_ABSOLUTE first
    if (setControlValue(V4L2_CID_ZOOM_ABSOLUTE, value)) {
        return true;
    }
    
    // Fallback to continuous zoom
    return setControlValue(V4L2_CID_ZOOM_CONTINUOUS, value);
}

bool V4L2Control::setZoomRelative(int value) {
    if (!m_initialized) return false;
    
    // Try relative zoom control
    if (setControlValue(V4L2_CID_ZOOM_RELATIVE, value)) {
        return true;
    }
    
    // Fallback: get current absolute zoom and adjust
    int current;
    if (getControlValue(V4L2_CID_ZOOM_ABSOLUTE, current)) {
        return setZoomAbsolute(current + value);
    }
    
    // Fallback to continuous zoom
    if (getControlValue(V4L2_CID_ZOOM_CONTINUOUS, current)) {
        return setControlValue(V4L2_CID_ZOOM_CONTINUOUS, current + value);
    }
    
    return false;
}

bool V4L2Control::getZoomValue(int& value) {
    if (!m_initialized) return false;
    
    // Try absolute zoom first
    if (getControlValue(V4L2_CID_ZOOM_ABSOLUTE, value)) {
        return true;
    }
    
    // Fallback to continuous zoom
    return getControlValue(V4L2_CID_ZOOM_CONTINUOUS, value);
}

#endif // __linux__