#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/types.h>
#include <linux/netlink.h>
#include <errno.h>
#include <unistd.h>
#include <regex>
#include <iostream>

#include <sys/inotify.h>

#include <cups/cups.h>

#include <QDebug>
#include <QString>
#include <QProcess>
#include <QTimer>
#include <QFile>
#include <QDir>
#include <QThread>
#include <QObject>
#include <QMetaType>
#include <QUrl>
#include <QDataStream>

#include "device_monitor.h"
#include "daemonipcdbus.h"
#include "common.h"
#include "lpinfo_browser.h"
#include "udev_devices.h"
#include "information_collector.h"

#define UEVENT_BUFFER_SIZE 2048

cups_dest_t *getDestWithURI(const char *uri);
DeviceInformation getDeviceInformationFromDest(cups_dest_t *dest);
bool guessInfoFromUri(DeviceInformation &deviceInfo, const QString &uri);
QString formatPrinterURI(QString originalStr);

static int init_hotplug_sock(void)
{
    struct sockaddr_nl snl;
    const int buffersize = 16 * 1024 * 1024;
    int retval;
    memset(&snl, 0x00, sizeof(struct sockaddr_nl));
    snl.nl_family = AF_NETLINK;
    snl.nl_pid = getpid();
    snl.nl_groups = 1;
    int hotplug_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
    if (hotplug_sock == -1)
    {
        qDebug("error getting socket: %s", strerror(errno));
        return -1;
    }
    /* set receive buffersize */
    setsockopt(hotplug_sock, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));
    retval = bind(hotplug_sock, (struct sockaddr *) &snl, sizeof(struct sockaddr_nl));
    if (retval < 0) {
        qDebug("bind failed: %s", strerror(errno));
        close(hotplug_sock);
        hotplug_sock = -1;
        return -1;
    }
    return hotplug_sock;
}




QString getDeviceTypeFromPath(const QString &path)
{
    QString res;
    QString bInterfaceClass;
    QStringList bInterfaceClassPathList = getRetFromCommand(QStringList{"find", path ,"-name", "bInterfaceClass"}).split("\n");
    for (int i = 0; i < bInterfaceClassPathList.size(); i++) {
        bInterfaceClass = getRetFromCommand(QStringList{"cat", bInterfaceClassPathList.at(i)});
        if (bInterfaceClass == "00" || bInterfaceClass == "ff"){
            continue;
        }
        res = bInterfaceClass;
    }
    return res;
}


// DeviceMonitor start

DeviceMonitor *DeviceMonitor::getInstance()
{   

    static DeviceMonitor *instance = nullptr;
    static QThread *thread;
    if (nullptr == instance) {
        instance = new DeviceMonitor();
        thread = new QThread();
        instance->moveToThread(thread);
        connect(DaemonIpcDbus::getInstance(), &DaemonIpcDbus::sendarg,
                instance,                     &DeviceMonitor::getarg);
        thread->start();
    }
    return instance;
}

DeviceMonitor::DeviceMonitor()
{
}

DeviceMonitor::~DeviceMonitor()
{
}

void DeviceMonitor::getarg(QStringList arg)
{
    static int sendflag = 1;
    // qDebug() << arg;
    if (checkCommand(arg) == CheckRes::CHECK_FALSE) {
        return ;
    }
    if (checkCommand(arg) == CheckRes::MAINWINDOW) {
        sendflag = 0;
    }

    if (sendflag == 0) {
        return;
    }
    if (arg.size() != 2)
        return;
    if (arg[0] == "add")
        this->usbDeviceAdd(arg[1]);
    else if (arg[0] == "remove") {
        this->usbDeviceRemove(arg[1]);
    }
    else if (arg[0] == "add_uri") {
        DeviceInformation device;
        if ( guessInfoFromUri(device, arg[1]) == true) {
            device.connectType = ConnectType::InfoFrom_USB;
            emit this->findUsbConnect(device);
        }
    }
}

bool DeviceMonitor::usbDeviceAdd(const QString &qstr)
{
    QString path = QString("/sys") + qstr.right(qstr.size() - 1 - qstr.indexOf('@'));
    QString deviceType = getDeviceTypeFromPath(path);

    // 打印机设备类型为07
    if (setFindPrinterOnly == true) {
        if (deviceType != "07")
            return false;
    }
    // 判断是否重复
    if (this->m_mpDeviceInformationMap.contains(path)) {
        return false;
    }

    DeviceInformation newDevice;
    
    newDevice.connectType  = ConnectType::InfoFrom_USB;
    newDevice.sysPath      = path;
    newDevice.devicePath   = "/dev/" + getRetFromCommand(QStringList{"cat", path + "/uevent", "|" , "grep", "DEVNAME"}).remove("DEVNAME=");
    newDevice.deviceType   = deviceType;
    newDevice.busNumber    = getRetFromCommand(QStringList{"cat", path + "/busnum"});
    newDevice.deviceNumber = getRetFromCommand(QStringList{"cat", path + "/devnum"});
    newDevice.VID          = getRetFromCommand(QStringList{"cat", path + "/idVendor"});
    newDevice.PID          = getRetFromCommand(QStringList{"cat", path + "/idProduct"});

    if (do_add(nullptr, qstringTochar(path.remove("/sys")), newDevice) != 0) {
        qDebug() << "do_add failed!" << qstringTochar(path.remove("/sys"));
    }

    // qDebug () << newDevice.uri;
    if (!newDevice.uri.contains("usb")) {
        newDevice.uri.clear();
    }

    this->m_mpDeviceInformationMap.insert(path, newDevice);
    qDebug() << "find a new painter!";
    // qDebug() << newDevice;
    InformationCollector::getInstance().addMessage(QString("usb printer device add: ") + devInfoPrint(newDevice));
    emit this->findUsbConnect(newDevice);
    return true;
}

bool DeviceMonitor::usbDeviceRemove(const QString &qstr)
{
    // qDebug() << "remove a painter!";
    QString path = QString("/sys") + qstr.right(qstr.size() - 1 - qstr.indexOf('@'));
    // qDebug() << path;
    InformationCollector::getInstance().addMessage(QString("usb printer device remove: ") + path);
    emit this->findUsbDisconnect(path);
    if (this->m_mpDeviceInformationMap.contains(path)) {
        this->m_mpDeviceInformationMap.remove(path);
    }
    return true;
}




QString getUriHead(const QString &uri)
{
    if (!uri.size())
        return "";
    QString head = uri.left(uri.indexOf(":"));
    if (head.size())
        return head;
    return "";
}

cups_dest_t *getDestWithURI(const char *uri)
{
    // qDebug() << "im in getDestWithURI";
    // qDebug() << uri;
    int i;
    cups_dest_t *dests, *dest;
    const char *value;
    int num_dests = cupsGetDests(&dests);

    for (i = num_dests, dest = dests; i > 0; i --, dest ++) {
        // for (size_t j = 0; j < dest->num_options; j++) {
        //     qDebug("%s %s\n",dest->options[j].name, dest->options[j].value);
        // }
        
        if (dest->num_options) {
            value = cupsGetOption("device-uri", dest->num_options, dest->options);
            if ( value != NULL && strcmp(uri, value) == 0) {
                return dest;
            }
        }
    }
    cupsFreeDests(num_dests, dests);
    return NULL;
}

DeviceInformation getDeviceInformationFromDest(cups_dest_t *dest)
{
    DeviceInformation deviceInfo;
    if (dest->name != NULL)
        deviceInfo.name = dest->name;
    if (dest->num_options) {
        deviceInfo.makeAndModel = cupsGetOption("printer-make-and-model", dest->num_options, dest->options);
        deviceInfo.uri = cupsGetOption("device-uri", dest->num_options, dest->options);
        // qDebug() << deviceInfo.uri;
    }
    QString tempUri = deviceInfo.uri;
    if (tempUri.contains("ipp") && tempUri.contains("%20")) {
        tempUri.replace(tempUri.indexOf("%20"),3, "/");
        // tempUri[tempUri.indexOf("%20")] = "/";
    }
    tempUri = formatPrinterURI(tempUri);
    QString head = getUriHead(tempUri);

    if (head == "usb") {
        tempUri.remove("usb://");
        deviceInfo.vendor = tempUri.left(tempUri.indexOf("/"));
        tempUri.remove( tempUri.left( tempUri.indexOf("/") + 1 ) );

        //TODO: model series 字段删掉
        deviceInfo.model = tempUri.left(tempUri.indexOf("?"));
        if (deviceInfo.model.contains(" series")) {
            deviceInfo.model.remove(" series");
        }

        tempUri.remove( tempUri.left( tempUri.indexOf("?") + 1 ) );
        tempUri.remove("serial=");
        if (tempUri.contains("&"))
            deviceInfo.serial = tempUri.left(tempUri.indexOf("&"));
        else
            deviceInfo.serial = tempUri;
    }
    else if (head == "ipp" || head == "ipps") {
        tempUri.remove("ipp://");
        deviceInfo.vendor = tempUri.left(tempUri.indexOf("/"));
        tempUri.remove( tempUri.left(tempUri.indexOf("/") + 1) );
        tempUri = tempUri.left(tempUri.indexOf("."));
        deviceInfo.model = tempUri;
        if (deviceInfo.model.contains(" series")) {
            deviceInfo.model.remove(" series");
        }
    }
    return deviceInfo;
}

QList<DeviceInformation> DeviceMonitor::getAllPrinterWithPDD(const bool usbConnectOnly)
{
    QList<DeviceInformation> res;
    int i;
    cups_dest_t *dests, *dest;
    int num_dests = cupsGetDests(&dests);
    // qDebug() << num_dests;
    for (i = num_dests, dest = dests; i > 0; i --, dest ++)
    {
        if (dest->num_options) {
            QString uri = cupsGetOption("device-uri", dest->num_options, dest->options);
            if (usbConnectOnly == true && !uri.contains("usb://")) {
                continue;
            }
            QString value = cupsGetOption("printer-make-and-model", dest->num_options, dest->options);
            if (value.size() && value != "Remote Printer") {
                DeviceInformation device = getDeviceInformationFromDest(dest);
                res.append(device);
            }
        }
    }
    cupsFreeDests(num_dests, dests);
    return res;
}

bool guessInfoFromUri(DeviceInformation &deviceInfo, const QString &uri)
{
    deviceInfo.uri = uri;
    if (deviceInfo.uri.isEmpty() ) {
        return false;
    }
    QString tempUri = deviceInfo.uri;
    if (tempUri.contains("ipp") && tempUri.contains("%20")) {
        tempUri.replace(tempUri.indexOf("%20"),3, "/");
        // tempUri[tempUri.indexOf("%20")] = "/";
    }
    tempUri = formatPrinterURI(tempUri);
    QString head = getUriHead(tempUri);

    if (head == "usb") {
        tempUri.remove("usb://");
        deviceInfo.vendor = tempUri.left(tempUri.indexOf("/"));
        tempUri.remove( tempUri.left( tempUri.indexOf("/") + 1 ) );

        //TODO: model series 字段删掉
        deviceInfo.model = tempUri.left(tempUri.indexOf("?"));
        if (deviceInfo.model.contains(" series")) {
            deviceInfo.model.remove(" series");
        }

        tempUri.remove( tempUri.left( tempUri.indexOf("?") + 1 ) );
        tempUri.remove("serial=");
        if (tempUri.contains("&"))
            deviceInfo.serial = tempUri.left(tempUri.indexOf("&"));
        else
            deviceInfo.serial = tempUri;
        specialDeviceCheck(deviceInfo.vendor, deviceInfo.model);
        return true;
    }
    return false;
//        else if (head == "ipp" || head == "ipps") {
//            tempUri.remove("ipp://");
//            deviceInfo.vendor = tempUri.left(tempUri.indexOf("/"));
//            tempUri.remove( tempUri.left(tempUri.indexOf("/") + 1) );
//            tempUri = tempUri.left(tempUri.indexOf("."));
//            deviceInfo.model = tempUri.split(" ").at(0);
//        }
}

QList<DeviceInformation> DeviceMonitor::getAllPrinterConnected()
{   
    QList<DeviceInformation> res;
    QStringList uriList;
    uriList.append( getRetFromCommand( QStringList{"lpinfo", "-v", "|" , "grep", "-e", "\'usb://\'"})
                    .remove("direct ")
//                    .remove("network ")
                    .split("\n") 
                  );

    for (int i = 0; i < uriList.size(); i++) {
        // qDebug () << i;
        // qDebug() << uriList.at(i).toStdString().c_str();
        cups_dest_t *dest = getDestWithURI(uriList.at(i).toStdString().c_str());

        if (dest != NULL) {
            DeviceInformation device = getDeviceInformationFromDest(dest);
            res.append(device);
            continue;
        }
        // qDebug() << i << "is not find in dests!";

        // 如果没找到 代表是新连入还没有驱动的打印机
        DeviceInformation deviceInfo;
        deviceInfo.uri = uriList.at(i);
        if (deviceInfo.uri.isEmpty() ) {
            // qDebug() << i << "is not find uri!";
            continue;
        }
        QString tempUri = deviceInfo.uri;
        if (tempUri.contains("ipp") && tempUri.contains("%20")) {
            tempUri.replace(tempUri.indexOf("%20"),3, "/");
            // tempUri[tempUri.indexOf("%20")] = "/";
        }
        tempUri = formatPrinterURI(tempUri);
        QString head = getUriHead(tempUri);

        if (head == "usb") {
            tempUri.remove("usb://");
            deviceInfo.vendor = tempUri.left(tempUri.indexOf("/"));
            tempUri.remove( tempUri.left( tempUri.indexOf("/") + 1 ) );

            //TODO: model series 字段删掉
            deviceInfo.model = tempUri.left(tempUri.indexOf("?"));
            if (deviceInfo.model.contains(" series")) {
                deviceInfo.model.remove(" series");
            }

            tempUri.remove( tempUri.left( tempUri.indexOf("?") + 1 ) );
            tempUri.remove("serial=");
            if (tempUri.contains("&"))
                deviceInfo.serial = tempUri.left(tempUri.indexOf("&"));
            else
                deviceInfo.serial = tempUri;
            specialDeviceCheck(deviceInfo.vendor, deviceInfo.model);
            deviceInfo.connectType = ConnectType::InfoFrom_USB;
            res.append(deviceInfo);
        }
//        else if (head == "ipp" || head == "ipps") {
//            tempUri.remove("ipp://");
//            deviceInfo.vendor = tempUri.left(tempUri.indexOf("/"));
//            tempUri.remove( tempUri.left(tempUri.indexOf("/") + 1) );
//            tempUri = tempUri.left(tempUri.indexOf("."));
//            deviceInfo.model = tempUri.split(" ").at(0);
//        }


    }
    return res;
}
//将输入字符串中的“%20”转变为空格
QString formatPrinterURI(QString originalStr)
{
    if(!originalStr.contains("usb"))
    {
        return originalStr;
    }
    while (originalStr.contains("%20"))
    {
        originalStr.replace(originalStr.indexOf("%20"),3, " ");
    }

    return originalStr;
}



// DeviceMonitorBackend start

DeviceMonitorBackend *DeviceMonitorBackend::getInstance()
{
    static DeviceMonitorBackend *instance = nullptr;
    static QThread *thread;
    if (nullptr == instance) {
        instance = new DeviceMonitorBackend();
        thread = new QThread();
        instance->moveToThread(thread);
        connect(instance, &DeviceMonitorBackend::start,
                instance, &DeviceMonitorBackend::detectStart);
        thread->start();
    }
    return instance;
}


DeviceMonitorBackend::DeviceMonitorBackend()
{
}

DeviceMonitorBackend::~DeviceMonitorBackend()
{
}

void DeviceMonitorBackend::detectStart()
{
    // qDebug() << "DeviceMonitor::detectStart";
    // qDebug() << QThread::currentThreadId();
    // usbDeviceAdd("bind@/devices/pci0000:00/0000:00:16.0/0000:0b:00.0/usb3/3-2");
    int hotplug_sock = init_hotplug_sock();
    while(1)
    {
        char buf[UEVENT_BUFFER_SIZE*2] = {0};
        // recv 会等待usb信号连入
        recv(hotplug_sock, &buf, sizeof(buf), 0);
        this->usbDeviceIdentify(QString(buf));
    }
}

bool DeviceMonitorBackend::usbDeviceIdentify(const QString &qstr)
{
    // qDebug() << qstr;
    // qDebug() << "DeviceMonitor::usbDeviceIdentify";
    // bind@/devices/pci0000:00/0000:00:11.0/0000:02:01.0/usb1/1-1
    if (  qstr.indexOf("bind") == 0
      &&  qstr.contains("pci") 
      && !qstr.right( qstr.size() -1 - qstr.lastIndexOf('/') ).contains(":") 
    ) {
        InformationCollector::getInstance().addMessage(QString("usb device add: ") + qstr);
        emit this->usbDeviceAdd(qstr);
        return true;
    }
    // unbind@/devices/pci0000:00/0000:00:11.0/0000:02:01.0/usb1/1-1
    if (  qstr.contains("unbind")
      &&  qstr.contains("pci") 
      && !qstr.right( qstr.size() -1 - qstr.lastIndexOf('/') ).contains(":")
    ) {
        InformationCollector::getInstance().addMessage(QString("usb device remove: ") + qstr);
        emit this->usbDeviceRemove(qstr);
        return true;
    }
    return false;
}

