#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QTimer>
#include <QMessageBox>
#include <QProcess>


QString suffix;
struct libusb_context *usbctx = NULL;
struct libusb_device_handle *usbd = NULL;
int screen_width, screen_height;

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    connect(ui->pbDo, SIGNAL(clicked(bool)), SLOT(onMonitor()));
    connect(ui->pbRefresh, SIGNAL(clicked(bool)), SLOT(onRefresh()));

    QTimer::singleShot(0, this, SLOT(onRefresh()));
#ifdef Q_OS_WIN
    suffix = ".exe";
#else
    suffix = "";
#endif

    libusb_init(&usbctx);
}

MainWindow::~MainWindow()
{
    delete ui;
}

QString MainWindow::requestUSBScreenID(struct libusb_device_handle *u)
{
    unsigned char cmd[] = {0x51, 0x02, 0x08, 0x1f, 0xf0};
    unsigned char buf[9] = {0};

    libusb_control_transfer(u, 0x40, 0xb5, 0, 0, cmd, 5, 100);
    libusb_control_transfer(u, 0xc0, 0xb6, 0, 0, cmd, 1, 100);
    libusb_control_transfer(u, 0xc0, 0xb7, 0, 0, buf, 9, 100);
    return QString().asprintf("%02x%02x-%02x%02x-%02x%02x%02x%02x",
                              buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8]);
}

QString MainWindow::requestUSBScreenInfo(struct libusb_device_handle *usbd)
{
    uint8_t cmd[128] = {0};
    libusb_control_transfer(usbd, 0xc0, 0xa0, 0, 0, cmd, 64, 200);
    QString version = QString().asprintf("%s", cmd);
    QStringList ver_list = version.split(",");
    if (ver_list.count() >= 2) {
        QStringList res_list = ver_list.at(1).split("x");
        if (res_list.count() >= 2) {
            screen_width = res_list.at(0).toInt();
            screen_height = res_list.at(1).toInt();
        }
    }
    return version;
}

void MainWindow::clearScreen(libusb_device_handle *usbd)
{
    uint32_t size = screen_width * screen_height * 2;
    uint8_t *buf = (uint8_t *)malloc(size);
    memset(buf, 0, size);

    // send current frame data to screen.
    uint8_t cmd2[] = {0x00, 0x2c, 0x00, 0x00, 0x00, 0x00};
    cmd2[2] = (uint8_t)(size >> 0);
    cmd2[3] = (uint8_t)(size >> 8);
    cmd2[4] = (uint8_t)(size >> 16);
    cmd2[5] = (uint8_t)(size >> 24);
    libusb_control_transfer(usbd, 0x40, 0xb0, 0, 0, cmd2, 6, 100);
    libusb_bulk_transfer(usbd, 0x02, buf, size, NULL, 100);
    free(buf);
}

void MainWindow::onRefresh()
{
    struct libusb_device **list = NULL;
    ui->cbList->clear();

    int count = libusb_get_device_list(usbctx, &list);
    for (int i = 0; i < count; i++) {
        struct libusb_device_descriptor desc;
        memset(&desc, 0, sizeof(struct libusb_device_descriptor));
        if (libusb_get_device_descriptor(list[i], &desc))
            continue;
        if (desc.idVendor != 0xc872 || desc.idProduct != 0x1004)
            continue;

        struct libusb_device_handle *u = NULL;
        libusb_open(list[i], &u);
        if (u == NULL)
            continue;

        libusb_claim_interface(u, 0);
        libusb_set_interface_alt_setting(u, 0, 0);
        ui->cbList->addItem(requestUSBScreenID(u));
        libusb_close(u);
    }
    libusb_free_device_list(list, 0);
}

void MainWindow::onMonitor()
{
    if (ui->pbDo->text() == "Stop") {
        ui->pbRefresh->setDisabled(false);
        ui->pbDo->setText("Start");
        monitor_thread.stop();

        clearScreen(usbd);
        libusb_close(usbd);
        usbd = NULL;
        return;
    }

    if (ui->cbList->currentText().isEmpty()) {
        QMessageBox::information(this, "Error",  "No valid screen.");
        return;
    }

    struct libusb_device **list = NULL;
    int count = libusb_get_device_list(usbctx, &list);
    for (int i = 0; i < count; i++) {
        struct libusb_device_descriptor desc;
        memset(&desc, 0, sizeof(struct libusb_device_descriptor));
        if (libusb_get_device_descriptor(list[i], &desc))
            continue;
        if (desc.idVendor != 0xc872 || desc.idProduct != 0x1004)
            continue;

        struct libusb_device_handle *u = NULL;
        libusb_open(list[i], &u);
        if (u == NULL)
            continue;

        libusb_claim_interface(u, 0);
        libusb_set_interface_alt_setting(u, 0, 0);
        if (requestUSBScreenID(u) == ui->cbList->currentText()) {
            usbd = u;
            break;
        }
        libusb_close(u);
    }
    libusb_free_device_list(list, 0);

    if (usbd == NULL) {
        QMessageBox::information(this, "Error",  "Screen is not connected.");
        return;
    }

    ui->pbRefresh->setDisabled(true);
    ui->pbDo->setText("Stop");
    requestUSBScreenInfo(usbd);
    monitor_thread.start();
}

void MProMonitorThread::stop()
{
    runtag = false;
#ifdef Q_OS_WIN
    // kill other process created mprodrv process.
    WinExec("taskkill /im mprodrv.exe /t /f", SW_HIDE);
#endif
}

#ifdef Q_OS_WIN
#include "mprodrv.h"

void MProMonitorThread::run()
{
    if (usbd == NULL) {
        qDebug() << "No screen connected.";
        return;
    }

    stop();
    Sleep(800);

    struct mpro_frame *mf = mpro_open_frame();
    if (mf == NULL) {
        qDebug() << "Can not create frame, need to be admin.";
        return;
    }

    int vsync = 800 * 480 * 36 / screen_width / screen_height;
    mf->frame->width = screen_width;
    mf->frame->height = screen_height;
    mf->frame->vsync = vsync < 24 ? 24 : vsync;  // < 24fps OS do not work.

    QProcess process;
    process.start(qApp->applicationDirPath() + QString("/mprodrv") + suffix);
    process.waitForStarted();

    int fsize = mf->frame->width * mf->frame->height * 2;
    uint8_t *frame = (uint8_t *)malloc(fsize);

    runtag = true;
    uint8_t cmd[] = {0x00, 0x2c, (uint8_t)fsize, (uint8_t)(fsize >> 8),
                     (uint8_t)(fsize >> 16), 0x00};
    while (runtag) {
        ARGBToRGB565Dither(mpro_get_frame(mf), mf->frame->stride, frame,
                           mf->frame->width * 2, NULL, mf->frame->width, mf->frame->height);
        if (libusb_control_transfer(usbd, 0x40, 0xb0, 0, 0, cmd, 6, 100) < 0)
            break;
        if (libusb_bulk_transfer(usbd, 0x02, frame, fsize, NULL, 100) < 0)
            break;
    }

    // disconnect, set screen to black.
    memset(frame, 0, fsize);
    libusb_bulk_transfer(usbd, 0x02, frame, fsize, NULL, 100);
    free(frame);

    // force termiate the application for screen.
    process.terminate();
    mpro_close_frame(mf);
}
#else
void MProMonitorThread::run()
{

}
#endif
