#include <cups/cups.h>
#include <cups/http.h>
#include <errno.h>
#include <fcntl.h>
#include <libudev.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <ctype.h>
#include <syslog.h>
#include <unistd.h>
#include <libusb-1.0/libusb.h>
#include <glib.h>
#include <dirent.h>
#include <libudev.h>

#include <QString>
#include <QStringList>
#include <QDebug>

#include "udev_devices.h"
#include "device_information.h"
#include "common.h"



/* Device URI schemes in decreasing order of preference. */
const QStringList device_uri_types = {
    // "hp",
    "usb",
};
const char *exclude_schemes[] = {
    "beh",
    "cups-pdf",
    "bluetooth",
    "dnssd",
    "http",
    "https",
    "ipp",
    "lpd",
    "ncp",
    "parallel",
    "scsi",
    "smb",
    "snmp",
    "socket",
};

// usb-001-067
static char *
devpath_from_usb_devaddr (struct udev *udev, const char *devaddr)
{
    char *devname_ending = g_strdup (devaddr);
    char *devname;
    const char *devpath;
    struct udev_enumerate *udev_enum;
    struct udev_list_entry *first = NULL;
    struct udev_device *device;

    g_strdelimit (devname_ending, "-", '/');
    devname = g_strdup_printf("/dev/bus/%s", devname_ending);
    g_free (devname_ending);

    udev_enum = udev_enumerate_new (udev);
    if (udev_enum == NULL) {
        qDebug() << "udev_enumerate_new failed";
        exit (1);
    }

    if (udev_enumerate_add_match_property (udev_enum, "DEVNAME", devname) < 0) {
        udev_enumerate_unref (udev_enum);
        qDebug() << "udev_enumerate_add_match_property failed";
        exit (1);
    }

    if (udev_enumerate_scan_devices (udev_enum) < 0) {
        udev_enumerate_unref (udev_enum);
        qDebug() << "udev_enumerate_scan_devices failed";
        exit (1);
    }

    first = udev_enumerate_get_list_entry (udev_enum);
    if (first == NULL) {
        udev_enumerate_unref (udev_enum);
        qDebug ("no device named %s found", devname);
        exit (1);
    }

    device = udev_device_new_from_syspath (udev,
                                           udev_list_entry_get_name (first));
    if (device == NULL) {
        udev_enumerate_unref (udev_enum);
        qDebug ("unable to examine device");
        exit (1);
    }

    devpath = udev_device_get_devpath (device);
    udev_enumerate_unref (udev_enum);
    if (!devpath) {
        qDebug ("no devpath for device");
        exit (1);
    }

    g_free (devname);
    return g_strdup (devpath);
}

// 将device_id中的信息转成id
void
parse_device_id (const char *device_id,
		         struct device_id_s *id)
{
    QString s = QString(device_id);

    // len = strlen (device_id);
    if (s.size() == 0)
        return;

    if (s[s.size() - 1] == '\n')
        s.chop(1);
    

    id->full_device_id = s;
    
    QStringList ls = s.split(";");
    
    for (auto ss : ls) {
        if (id->mfg.isEmpty()
         && (ss.indexOf("MANUFACTURER") == 0 || ss.indexOf("MFG") == 0)
         && (ss.contains(":"))) {
        
            id->mfg = ss.split(":").at(1);
        }

        else if (id->mdl.isEmpty()
         && (ss.indexOf("MODEL") == 0 || ss.indexOf("MDL") == 0)
         && (ss.contains(":"))) {
        
            id->mdl = ss.split(":").at(1);
        }

        else if (id->sern.isEmpty()
         && (ss.indexOf("SERIALNUMBER") == 0 || ss.indexOf("SERN") == 0 || ss.indexOf("SN") == 0)
         && (ss.contains(":"))) {
        
            id->sern = ss.split(":").at(1);
        }
    }
    // 特殊打印机处理
    specialDeviceCheck(id->mfg, id->mdl);
    return ;
}

static char *
get_ieee1284_id_from_child (struct udev *udev, struct udev_device *parent)
{
    struct udev_enumerate *udev_enum;
    struct udev_list_entry *item, *first = NULL;
    char *device_id = NULL;

    udev_enum = udev_enumerate_new (udev);
    if (!udev_enum) {
        qDebug ("udev_enumerate_new failed");
        exit (1);
    }

    if (udev_enumerate_add_match_parent (udev_enum, parent) < 0) {
        udev_enumerate_unref (udev_enum);
        qDebug ("uname to add parent match");
        exit (1);
    }

    if (udev_enumerate_scan_devices (udev_enum) < 0) {
        udev_enumerate_unref (udev_enum);
        qDebug ("udev_enumerate_scan_devices failed");
        exit (1);
    }

    first = udev_enumerate_get_list_entry (udev_enum);
    udev_list_entry_foreach (item, first) {
    const char *ieee1284_id = NULL;
    struct udev_device *dev;
    dev = udev_device_new_from_syspath (udev,
                    udev_list_entry_get_name (item));
    if (dev == NULL)
        continue;

    ieee1284_id = udev_device_get_sysattr_value (dev, "ieee1284_id");
    if (ieee1284_id)
        device_id = g_strdup (ieee1284_id);

    udev_device_unref (dev);
    if (device_id)
        break;
    }

    udev_enumerate_unref (udev_enum);
    return device_id;
}

static char *
get_ieee1284_id_using_libusb (struct udev_device *dev,
			      const char *usbserial)
{
    const char *idVendorStr, *idProductStr;
    unsigned long idVendor, idProduct;
    char *end;
    int conf = 0, iface = 0, altset = 0, numdevs = 0, i, n, m;
    libusb_device **list;
    struct libusb_device *device;
    struct libusb_device_handle *handle = NULL;
    struct libusb_device_descriptor devdesc;
    struct libusb_config_descriptor *confptr = NULL;
    const struct libusb_interface *ifaceptr = NULL;
    const struct libusb_interface_descriptor *altptr = NULL;
    char libusbserial[1024];
    char ieee1284_id[1024];
    int got = 0;

    idVendorStr = udev_device_get_sysattr_value (dev, "idVendor");
    idProductStr = udev_device_get_sysattr_value (dev, "idProduct");

    if (!idVendorStr || !idProductStr) {
    qDebug ("Missing sysattr %s",
        idVendorStr ?
        (idProductStr ? "serial" : "idProduct") : "idVendor");
    return NULL;
    }

    idVendor = strtoul (idVendorStr, &end, 16);
    if (end == idVendorStr) {
        qDebug ("Invalid idVendor: %s", idVendorStr);
        return NULL;
    }

    idProduct = strtoul (idProductStr, &end, 16);
    if (end == idProductStr) {
        qDebug ("Invalid idProduct: %s", idProductStr);
        return NULL;
    }

    // qDebug ("Device vendor/product is %04zX:%04zX",
	//         idVendor, idProduct);

    libusb_init(NULL);
    numdevs = libusb_get_device_list(NULL, &list);
  if (numdevs > 0)
    for (i = 0; i < numdevs; i++) {
        device = list[i];

        if (libusb_get_device_descriptor(device, &devdesc) < 0)
            continue;

        if (!devdesc.bNumConfigurations || !devdesc.idVendor ||
            !devdesc.idProduct) {
            
            continue;
        }

        if (devdesc.idVendor != idVendor || devdesc.idProduct != idProduct)
            continue;

        for (conf = 0; conf < devdesc.bNumConfigurations; conf++) {
            if (libusb_get_config_descriptor(device, conf, &confptr) < 0)
                continue;
            for (iface = 0, ifaceptr = confptr->interface;
            iface < confptr->bNumInterfaces;
            iface ++, ifaceptr ++) {
                for (altset = 0, altptr = ifaceptr->altsetting; altset < ifaceptr->num_altsetting; altset ++, altptr ++) {
                    if (altptr->bInterfaceClass != LIBUSB_CLASS_PRINTER ||
                    altptr->bInterfaceSubClass != 1)
                        continue;
                    
                    if (libusb_open(device, &handle) < 0) {
                        qDebug ("failed to open device");
                        continue;
                    }

                    if (usbserial[0] != '\0'
                    && (libusb_get_string_descriptor_ascii(handle,
                                                           devdesc.iSerialNumber,
                                                           (unsigned char *)libusbserial,
                                                           sizeof(libusbserial))) > 0 
                    && strcmp(usbserial, libusbserial) != 0) {
                        libusb_close (handle);
                        handle = NULL;
                        continue;
                    }
                    
                    n = altptr->bInterfaceNumber;
                    if (libusb_claim_interface(handle, n) < 0) {
                        libusb_close (handle);
                        handle = NULL;
                        qDebug ("failed to claim interface");
                        continue;
                    }
                    if (n != 0 && libusb_claim_interface(handle, 0) < 0) {
                        qDebug ("failed to claim interface 0");
                    }
    
                    m = altptr->bAlternateSetting;
                    if (libusb_set_interface_alt_setting(handle, n, m) < 0) {
                        libusb_close (handle);
                        handle = NULL;
                        qDebug ("failed set altinterface");
                        continue;
                    }

                    memset (ieee1284_id, '\0', sizeof (ieee1284_id));
                    if (libusb_control_transfer(handle,
                                                LIBUSB_REQUEST_TYPE_CLASS |
                                                LIBUSB_ENDPOINT_IN |
                                                LIBUSB_RECIPIENT_INTERFACE,
                                                0, conf,
                                                (n << 8) | m,
                                                (unsigned char *)ieee1284_id,
                                                sizeof (ieee1284_id),
                                                5000) < 0) {

                        libusb_close (handle);
                        handle = NULL;
                        qDebug ("Failed to fetch Device ID");
                        continue;
                    }

                    got = 1;
                    libusb_close (handle);
                    break;
                }
            }
	    }
    }

    libusb_free_device_list(list, 1);
    libusb_exit(NULL);

    if (got)
        return g_strdup (ieee1284_id + 2);
    return NULL;
}

static ipp_t *
cupsDoRequestOrDie (http_t *http,
		    ipp_t *request,
		    const char *resource)
{
    ipp_t *answer = cupsDoRequest (http, request, resource);
    if (answer == NULL) {
        qDebug ("failed to send IPP request %d", ippGetOperation (request));
        exit (1);
    }

    if (ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
        qDebug ("IPP request %d failed (%d)", ippGetOperation (request), ippGetStatusCode (answer));
        exit (1);
    }

    return answer;
}

static int
find_matching_device_uris (struct device_id_s *id,
                           QStringList &uris,
                           const char *devpath)
{
    http_t *cups;
    ipp_t *request, *answer;
    ipp_attribute_t *attr;
    QStringList uris_noserial, all_uris;

    uris.clear();
    uris_noserial.clear();
    all_uris.clear();

    /* Leave the bus to settle (see e.g. bug #1206808). */
    // https://bugzilla.redhat.com/show_bug.cgi?id=1206808
    // 暂时禁用，出现问题再添加延时
    // sleep (5);

    cups = httpConnectEncrypt (cupsServer (), ippPort(), cupsEncryption ());
    if (cups == NULL) {
        /* 
        Don't bother retrying here.  We've probably been run from
        udev before the cups.socket systemd unit is running.  We'll
        get run again, as the systemd service
        udev-configure-printer.service, after cups.socket.  For more
        information:
        http://0pointer.de/blog/projects/socket-activation2.html
        */

        qDebug () << "failed to connect to CUPS server; giving up";
        return -1;
    }

    request = ippNewRequest (CUPS_GET_DEVICES);
    ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_NAME, "exclude-schemes",
                   sizeof (exclude_schemes) / sizeof(exclude_schemes[0]),
                   NULL, exclude_schemes);

    ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "timeout",
                   2);

    answer = cupsDoRequestOrDie (cups, request, "/");
    httpClose (cups);

    for (attr = ippFirstAttribute (answer); attr; attr = ippNextAttribute (answer)) {
        while (attr && ippGetGroupTag (attr) != IPP_TAG_PRINTER)
	        attr = ippNextAttribute (answer);

        if (!attr)
            break;

        QString device_uri;
        struct device_id_s this_id;

        for (; attr && ippGetGroupTag (attr) == IPP_TAG_PRINTER; attr = ippNextAttribute (answer)) {
            if (ippGetValueTag (attr) == IPP_TAG_URI &&
                !strcmp (ippGetName (attr), "device-uri")) {

                // qDebug() << ippGetString (attr, 0, NULL);
                device_uri = QString(ippGetString (attr, 0, NULL));
            }
            else if (ippGetValueTag (attr) == IPP_TAG_TEXT &&
                     !strcmp (ippGetName (attr), "device-id")) {
                
                // qDebug() << ippGetString (attr, 0, NULL);
                parse_device_id (ippGetString (attr, 0, NULL), &this_id);
            }
        }

        /* Only use device schemes in our preference order for matching
        * against the IEEE 1284 Device ID. */
        if (device_uri.size()) {
            int ok = 0;
            for (size_t i = 0; i < device_uri_types.size(); i++) {
                if (device_uri.indexOf(device_uri_types[i]) == 0
                 && device_uri[device_uri_types[i].size()] == ':') {
                    
                    ok = 1;
                    break;
                }
            }
            if (ok)
                // add_device_uri (&all_uris, device_uri);
                all_uris.append(device_uri);
            else
                device_uri.clear();
        }


        /* Now check the manufacturer and model names. */
        if (device_uri.size() && !this_id.mfg.isEmpty() && !this_id.mdl.isEmpty() 
            && this_id.mfg == id->mfg 
            && this_id.mdl == id->mdl) {
            //  We've checked everything except the serial numbers.  This
            //  is more complicated.  Some devices include a serial
            //  number (SERN) field in their IEEE 1284 Device ID.  Others
            //  don't -- this was not a mandatory field in the
            //  specification.
            
            //  If the device includes SERN field in its, it must match
            //  what the device-id attribute has.
            
            //  Otherwise, the only means we have of knowing which device
            //  is meant is the USB serial number.
            
            //  CUPS backends may choose to insert the USB serial number
            //  into the SERN field when reporting a device-id attribute.
            //  HPLIP does this, and it seems not to stray too far from
            //  the intent of that field.  We accommodate this.
            
            //  Alternatively, CUPS backends may include the USB serial
            //  number somewhere in their reported device-uri attributes.
            //  For instance, the CUPS 1.4 usb backend, when compiled
            //  with libusb support, gives device URIs containing the USB
            //  serial number for devices without a SERN field, like
            //  this: usb://HP/DESKJET%20990C?serial=US05M1D20CIJ
            
            //  To accommodate this we examine tokens between '?', '='
            //  and '&' delimiters to check for USB serial number
            //  matches.
            
            //  CUPS 1.3, and CUPS 1.4 without libusb support, doesn't do this.
            //  As a result we also need to deal with devices that don't report a
            //  SERN field where the backends that don't add a SERN field from
            //  the USB serial number and also don't include the USB serial
            //  number in the URI.
            

            int match = 0;

            // 序列号也ok 匹配成功
            if (!id->sern.isEmpty() && !this_id.sern.isEmpty()
                && id->sern == this_id.sern) {
                // qDebug ("SERN fields match");
                match = 1;
            }

            if (!match && id->sern.size()) {
                if (id->sern.isEmpty()) {
                    if (!this_id.sern.isEmpty() && id->sern == this_id.sern) {
                        // qDebug ("SERN field matches USB serial number");
                        match = 1;
                    }
                }

                if (!match) {
                    QRegExp sep("(\\?|\\=|\\&|\\/)");//?=$/
                    QStringList uriList = device_uri.split(sep);
                    for (auto token : uriList) {
                        if (QString(token) == id->sern) {
                            // qDebug ("URI contains USB serial number");
                            match = 1;
                            break;
                        }
                    }
                }
            }

            if (match) {
                // qDebug() << "URI match: " << device_uri;
                uris.append(device_uri);
            }
            else if (id->sern.isEmpty()) {
                qDebug() << "URI matches without serial number: ", device_uri;
                uris_noserial.append(device_uri);
            }
        }

        if (!attr)
            break;
    }

    ippDelete (answer);

    //  Decide what to do about device URIs that did not match a serial
    //  number.  The device had no SERN field, and the USB serial number
    //  was nowhere to be found from the device URI or device-id field.
    
    //  Device URIs with no reference to serial number can only each ever
    //  work when only one printer of that model is connected.
    //  Accordingly, it is safe to disable queues using such URIs, as we
    //  know the removed/added device is that lone printer.
    
    //  When adding queues it is best to avoid URIs that don't
    //  distinguish serial numbers.
    
    //  What we'll do, then, is concatenate the list of "non-serial" URIs
    //  onto the end of the list of "serial" URIs.

    // 将uris_noserial插到uris中
    if (uris.size() == 0 && uris_noserial.size() > 0) {
        qDebug ("No serial number URI matches so using those without");
    }
    for (size_t i = 0; i < uris_noserial.size(); i++) {
        uris.append(uris_noserial.at(i));
    }


    // Having decided which device URIs match based on IEEE 1284 Device
    // ID, we now need to look for "paired" URIs for other functions of
    // a multi-function device.  This are the same except for the
    // scheme.
    // TODO:
    // all_uris 中如果有和 uris中只有scheme不同的uri，将其插入到uris中
    size_t I = uris.size();
    size_t J = all_uris.size();
    for (size_t i = 0; i < I; i++) {
        for (size_t j = 0; j < J; j++) {
            QString si = uris.at(i).mid(uris.at(i).indexOf(":"), -1);
            QString sj = all_uris.at(j).mid(all_uris.at(j).indexOf(":"), -1);
            if (si == sj) {
                uris.append(all_uris.at(j));
            }
        }
    }

    return uris.size();
}

static char *
device_id_from_devpath (struct udev *udev, const char *devpath,
                        struct device_id_s *id)
{
    struct udev_device *dev;
    const char *serial;
    size_t syslen, devpathlen;
    char *syspath;
    char usbserial[256];
    const char *device_id = NULL;
    char *usb_device_devpath;
    int num_names;

    syslen = strlen ("/sys");
    devpathlen = strlen (devpath);
    syspath = (char *) malloc (syslen + devpathlen + 1);
    if (syspath == NULL) {
        qDebug ("out of memory");
        exit (1);
    }
    memcpy (syspath, "/sys", syslen);
    memcpy (syspath + syslen, devpath, devpathlen);
    syspath[syslen + devpathlen] = '\0';


    dev = udev_device_new_from_syspath (udev, syspath);
    if (dev == NULL) {
        udev_device_unref (dev);
        qDebug ("unable to access %s", syspath);
        return NULL;
    }

    usb_device_devpath = strdup (udev_device_get_devpath (dev));
//   qDebug ("device devpath is %s", usb_device_devpath);

    serial = udev_device_get_sysattr_value (dev, "serial");
    if (serial) {
        strncpy (usbserial, serial, sizeof(usbserial));
        usbserial[sizeof(usbserial) - 1] = '\0';
    }
    else
        usbserial[0] = '\0';

    device_id = get_ieee1284_id_from_child (udev, dev);
    if (!device_id)
        /* Use libusb to fetch the Device ID. */
        device_id = get_ieee1284_id_using_libusb (dev, usbserial);

    if (device_id)
        parse_device_id (device_id, id);
    if (id->sern.isEmpty() && usbserial) {
        id->sern = QString(usbserial);
    }
    udev_device_unref (dev);
    return usb_device_devpath;
}

int
do_add (const char *devaddr, const char *path, DeviceInformation &devInfo)
{
    struct device_id_s id;
    QStringList device_uris;
    struct udev *udev;
    char *devpath = NULL;
    char *usb_device_devpath = NULL;
    udev = udev_new ();

    if (!devaddr && !path) {
        return 1;
    }
    // TODO : system-config-printer 中加入了判断是否是蓝牙连入的
    //        判断，暂时不知道是为什么，故而没有加入
    // is_bluetooth(devaddr)

    // 通过 udev 信息获取到设备路径
    // devaddr:usb-001-067
    // devpath:/devices/pci0000:00/0000:00:14.0/usb1/1-2
    if (!path)
        devpath = devpath_from_usb_devaddr (udev, devaddr);
    else
        devpath = g_strdup(path);

    // printf("%s\n", devpath);

    // 从devpath中获取device_id
    usb_device_devpath = device_id_from_devpath (udev, devpath, &id);
    // printf("%s\n", usb_device_devpath);
    // qDebug() << id.full_device_id;
    // qDebug() << id.mfg;
    // qDebug() << id.mdl;
    // qDebug() << id.sern;
    g_free (devpath);
    udev_unref (udev);

    if (id.mfg.isEmpty() || id.mdl.isEmpty()) {
        free(usb_device_devpath);
        return 1;
    }
    find_matching_device_uris (&id, device_uris, usb_device_devpath);
    // qDebug() << device_uris;
    auto tt = device_uris.toSet();
    device_uris = tt.toList();
    if (device_uris.isEmpty())
        return 1;
    // qDebug() << device_uris;
    devInfo.vendor = id.mfg;
    devInfo.model = id.mdl;
    devInfo.serial = id.sern;
    devInfo.uri = device_uris.at(0);
    return 0;
}

QStringList list_printers_dev_path()
{
    struct udev *udev;
    struct udev_enumerate *enumerate;
    struct udev_list_entry *devices, *dev_list_entry;
    struct udev_device *dev;
    QStringList res;

    udev = udev_new();
    if (!udev) {
        qDebug("Can't create udev\n");
        return res;
    }

    enumerate = udev_enumerate_new(udev);
    udev_enumerate_add_match_subsystem(enumerate, "usb");
    udev_enumerate_add_match_sysattr(enumerate, "bInterfaceClass", "07");
    udev_enumerate_scan_devices(enumerate);
    devices = udev_enumerate_get_list_entry(enumerate);

    udev_list_entry_foreach(dev_list_entry, devices)
    {
        const char *path;

        path = udev_list_entry_get_name(dev_list_entry);
        dev = udev_device_new_from_syspath(udev, path);
        dev = udev_device_get_parent_with_subsystem_devtype(
            dev,
            "usb",
            "usb_device");
        if (!dev)
        {
            // printf("Unable to find parent usb device.");
            continue;
        }
        // printf(" VID/PID: %s %s\n",
        //        udev_device_get_sysattr_value(dev, "idVendor"),
        //        udev_device_get_sysattr_value(dev, "idProduct"));
        // printf(" %s\n %s\n",
        //        udev_device_get_sysattr_value(dev, "manufacturer"),
        //        udev_device_get_sysattr_value(dev, "product"));
        // printf(" serial: %s\n",
        //        udev_device_get_sysattr_value(dev, "serial"));
        // printf("%s\n", udev_device_get_devpath(dev));
        // printf(" serial: %s\n",
        //        udev_device_get_sysattr_value(dev, "interface"));
        res.append(udev_device_get_devpath(dev));
        udev_device_unref(dev);
    }
    /* Free the enumerator object */
    udev_enumerate_unref(enumerate);

    udev_unref(udev);
    res = res.toSet().toList();
    return res;
}

// int main(int argc, char **argv)
// {
//     do_add(argv[0], argv[2]);
// }

QString get_device_id_from_path(const QString &path)
{
    struct device_id_s id;
    struct udev *udev;
    char *usb_device_devpath = NULL;
    QString res = QString();

    if (path.isEmpty()) {
        return res;
    }

    udev = udev_new ();

    usb_device_devpath = device_id_from_devpath (udev, qstringTochar(path), &id);
    res = id.full_device_id;
    free(usb_device_devpath);

    return res;
}