/*
 * Directory services routines for the CUPS scheduler.
 *
 * Copyright © 2007-2018 by Apple Inc.
 * Copyright © 1997-2007 by Easy Software Products, all rights reserved.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more
 * information.
 */

#include <cups/cups.h>
#include <cups/adminutil.h>
#include <cups/http.h>
#include <cups/cups.h>
#include <cups/ipp.h>
#include <QVariant>
#include <QDebug>
#include "lpinfo_browser.h"
#include "common.h"

QList<usb_device_info> usbList;
bool is_usb_check;

LpinfoBrowser *LpinfoBrowser::getInstance()
{
    static LpinfoBrowser *instance = nullptr;
    if (nullptr == instance) {
        instance = new LpinfoBrowser();
    }
    return instance;
}

LpinfoBrowser::LpinfoBrowser(/* args */)
{
    connect(this, &LpinfoBrowser::browse, [=](){
        this->show_devices(1,3000,nullptr,nullptr);
    });
}

LpinfoBrowser::~LpinfoBrowser()
{
}

void
device_cb(
    const char *device_class,		/* I - device-class string */
    const char *device_id,		/* I - device-id string */
    const char *device_info,		/* I - device-info string */
    const char *device_make_and_model,	/* I - device-make-and-model string */
    const char *device_uri,		/* I - device-uri string */
    const char *device_location,	/* I - device-location string */
    void       *user_data)		/* I - User data */
{
    int	*long_status;			/* Show verbose info? */

// 0x5555556507a4 "MFG:Hewlett-Packard;MDL:HP LaserJet Professional P1106;CMD:ZJS,PJL,ACL,HTTP;CLS:PRINTER;DES:HP LaserJet Professional P1106;FWVER:20120814;"
/*
* Display the device...
*/

    long_status = (int *)user_data;

    if (*long_status && strstr(device_uri, "://") != nullptr){
        // qDebug("device_uri is %s\n", device_uri);
        // qDebug("device_class is %s\n", device_class);
        // qDebug("device_info is %s\n", device_info);
        // qDebug("device_make_and_model is %s\n", device_make_and_model);
        // qDebug("device_id is %s\n", device_id);
        // qDebug("device_location is %s\n", device_location);
        // qDebug("\n");
        if (::is_usb_check) {
            if (QString(device_uri).contains("usb://")) {
                usb_device_info info;
                info.uri = device_uri;
                info.device_id = device_id;
                ::usbList.append(info);
            }
        }
        else
            LpinfoBrowser::getInstance()->findLpinfoDevice(QString(device_uri));
    }
    // else
    // qDebug("%s %s\n", device_class, device_uri);
}


int				/* O - 0 on success, 1 on failure */
LpinfoBrowser::show_devices(
    int        long_status,		/* I - Long status report? */
    int        timeout,			/* I - Timeout */
    const char *include_schemes,	/* I - List of schemes to include */
    const char *exclude_schemes)	/* I - List of schemes to exclude */
{

    if (cupsGetDevices(CUPS_HTTP_DEFAULT, timeout, include_schemes,
                       exclude_schemes, device_cb, &long_status) != IPP_OK) {
    
        qDebug("lpinfo: %s", cupsLastErrorString());
        return (1);
    }

    return (0);
}

QList<usb_device_info> LpinfoBrowser::listUsbDevices()
{
    ::usbList.clear();
    ::is_usb_check = true;
    this->show_devices(1,3000,nullptr,nullptr);
    return ::usbList;

}

// find remote printer
struct printerAttr
{
    const char *name;
    ipp_tag_e tag;
};

printerAttr attrList[] =
{
    {"printer-name",              IPP_TAG_NAME},
    {"printer-type",              IPP_TAG_ENUM},
    {"printer-location",          IPP_TAG_TEXT},
    {"printer-info",              IPP_TAG_TEXT},
    {"printer-make-and-model",    IPP_TAG_TEXT},
    {"printer-state",             IPP_TAG_ENUM},
    {"printer-state-message",     IPP_TAG_TEXT},
    {"printer-state-reasons",     IPP_TAG_KEYWORD},
    {"printer-uri-supported",     IPP_TAG_URI},
    {"device-uri",                IPP_TAG_URI},
    {"printer-is-shared",         IPP_TAG_BOOLEAN},
    {"printer-is-accepting-jobs", IPP_TAG_BOOLEAN},
    {"printer-up-time",           IPP_TAG_INTEGER},
    {"queued-job-count",          IPP_TAG_INTEGER},
};

enum printerAttrCnt
{
    ALL = -1,
    PRINTER_NAME,
    PRINTER_TYPE,
    PRINTER_LOCATION,
    PRINTER_INFO,
    PRINTER_MAKE_AND_MODEL,
    PRINTER_STATE,
    PRINTER_STATE_MESSAGE,
    PRINTER_STATE_REASONS,
    PRINTER_URI_SUPPORTED,
    DEVICE_URI,
    PRINTER_IS_SHARED,
    PRINTER_IS_ACCEPTING_JOBS,
    PRINTER_UP_TIME,
    QUEUED_JOB_COUNT
};

QVariant getValueFromAttr(ipp_attribute_t *attr, int i)
{
    QVariant var;
    switch (ippGetValueTag(attr))
    {
    case IPP_TAG_NAME:
    case IPP_TAG_TEXT:
    case IPP_TAG_KEYWORD:
    case IPP_TAG_URI:
    case IPP_TAG_CHARSET:
    case IPP_TAG_MIMETYPE:
    case IPP_TAG_LANGUAGE:
        var = ippGetString (attr, i, NULL);
        break;
    case IPP_TAG_INTEGER:
    case IPP_TAG_ENUM:
        var = ippGetInteger (attr, i);
        break;
    case IPP_TAG_BOOLEAN:
        var = ippGetBoolean (attr, i);
        break;
    case IPP_TAG_RANGE:
        // lower = ippGetRange (attr, i, &upper);
        // val = Py_BuildValue ("(ii)",
		// 	 lower,
		// 	 upper);
        break;
    
    case IPP_TAG_NOVALUE:
        ;
        break;
    case IPP_TAG_DATE:
        ;
        break;
    case IPP_TAG_RESOLUTION:
    // xres = ippGetResolution(attr, i, &yres, &units);
    // val = Py_BuildValue ("(iii)",
	// 		 xres,
	// 		 yres,
	// 		 units);
        break;
    default:
        printf ("(unknown IPP value tag 0x%x)", ippGetValueTag(attr));
        break;
    }
    if (var.isNull()) {
        qDebug() << "the variant is empty!";
    }
    return var;
}

QVariant getQVarFromAttr(ipp_attribute_t *attr, printerAttrCnt i)
{
    QVariant var;
    if (i == -1
     || !strcmp(ippGetName(attr), attrList[i].name)
     && ippGetValueTag(attr) == attrList[i].tag) {

        var = getValueFromAttr(attr, 0);
        if (var.isNull()) {
            qDebug() << "the variant is empty!";
        }
    }
    return var;
}

QStringList LpinfoBrowser::findRemotePrinter(const QString &host, const int port)
{
    QStringList resList;
    int encryption = (http_encryption_t) cupsEncryption ();
    http_t *http = httpConnectEncrypt (QTC(host), port, (http_encryption_t) encryption);
    if (!http) {
        qDebug ("failed to connect to remote: %s:%d\n", QTC(host), port);
        return resList;
    }
    ipp_t *request = ippNewRequest(CUPS_GET_PRINTERS), *answer;
    ipp_attribute_t *attr;
    const char *attributes[] = {
        attrList[PRINTER_NAME].name,
        attrList[PRINTER_TYPE].name,
        attrList[PRINTER_LOCATION].name,
        attrList[PRINTER_INFO].name,
        attrList[PRINTER_MAKE_AND_MODEL].name,
        attrList[PRINTER_STATE].name,
        attrList[PRINTER_STATE_MESSAGE].name,
        attrList[PRINTER_STATE_REASONS].name,
        attrList[PRINTER_URI_SUPPORTED].name,
        attrList[DEVICE_URI].name,
        attrList[PRINTER_IS_SHARED].name,
    };
    ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
		           "requested-attributes",
		           sizeof (attributes) / sizeof (attributes[0]),
		           NULL, attributes);
    answer = cupsDoRequest (http, request, "/");
    if (!answer || ippGetStatusCode (answer) > IPP_OK_CONFLICT) {
        if (answer && ippGetStatusCode (answer) == IPP_NOT_FOUND) {
        // No printers.
            qDebug ("no printers in %s:%d\n",  QTC(host), port);
            ippDelete (answer);
        }
        else 
            qDebug("error: %d, %s\n", answer ? ippGetStatusCode (answer) : cupsLastError (), answer ? NULL : cupsLastErrorString ());
        if (answer)
            ippDelete (answer);
        return resList;
    }
    for (attr = ippFirstAttribute(answer); attr; attr = ippNextAttribute(answer)) {
        char *printer = NULL;
        if (attr && ippGetGroupTag (attr) != IPP_TAG_PRINTER)
            continue;
        if (!attr)
            break;
        QVariant var = getQVarFromAttr(attr, PRINTER_NAME);
        if (!var.isNull())
            resList.append(var.toString());
        var = getQVarFromAttr(attr, PRINTER_URI_SUPPORTED);
        if (!var.isNull())
            resList.append(var.toString());
    }
    ippDelete (answer);
    return resList;
}