#include "utils.h"

#include <QCoreApplication>
#include <QApplication>
#include <QFile>
#include <QTemporaryFile>
#include <QDebug>
#include <QTextStream>

#include "interfaces.h"
#include "DCPlugin/include/hwapi.h"

//#include <unistd.h>
#include <thread>
#include <memory>
#include <QSharedMemory>
#include <QScopedPointer>
#include <QProcess>

#define COLUMN_STATUS       (0)
#define COLUMN_NAME         (1)
#define COLUMN_DATE_TIME    (2)
#define COLUMN_RESULT       (3)

Utils::Utils(QString update_ctrl_file,
             QObject *parent,
             char const *serialInterface,
             char const *baudrate)
    : QObject(parent)
    , m_hw(new hwapi())
    , m_serialInterface(serialInterface)
    , m_baudrate(baudrate)
    , m_update_ctrl_file(update_ctrl_file)
    , m_update_ctrl_file_copy(update_ctrl_file + ".copy")
    , m_in(&m_update_ctrl_file)
    , m_out(&m_update_ctrl_file_copy)
    , m_init(true) {

    if (!m_update_ctrl_file.exists()) {
        qCritical() << "Update-file" << m_update_ctrl_file.fileName()
                    << "does not exist";
        m_init = false;
    }
    if (!m_update_ctrl_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qCritical() << "can not open " << m_update_ctrl_file.fileName()
                    << "for reading";
        m_init = false;
    }
    if (!m_update_ctrl_file_copy.open(QIODevice::WriteOnly | QIODevice::Text)) {
        qCritical() << "can not open " << m_update_ctrl_file_copy.fileName()
                    << "for writing";
        m_init = false;
    }
}

Utils::~Utils() {

}

void Utils::updateBinary(char const *fileToSendToDC) {
    qDebug() << "file to send to DC ..." << fileToSendToDC;
    qDebug() << "baudrate ............." << m_baudrate;
    qDebug() << "serial interface ....." << m_serialInterface;
    m_hw->dc_updateDC(fileToSendToDC, m_baudrate, m_serialInterface);
    std::this_thread::sleep_for(std::chrono::milliseconds(3000));
    QCoreApplication::quit();
}

void Utils::updatePrinterConf(int nrOfTemplate, char const *fileToSendToDC) {
    QVector<int> printTemplates{ nrOfTemplate };
    QVector<QString> filesToSend{ fileToSendToDC };

    m_hw->dc_updatePrinterTemplate(hwapi::FileTypeJson::PRINTER,
                                   printTemplates, filesToSend,
                                   QString(m_baudrate),
                                   QString(m_serialInterface));
    std::this_thread::sleep_for(std::chrono::milliseconds(3000));
    QCoreApplication::quit();
}

QStringList Utils::getOpenLines() {
    QStringList openLines;

    while (!m_in.atEnd()) {
        QString line = m_in.readLine().trimmed();
        // QString.split() is defined >= 5.14
        if (!line.startsWith("OPEN")) {
            m_out << line;
        } else {
            openLines << line;
        }
    }
    return openLines;
}

bool Utils::doUpdate() {
    /*
        The file referred to by 'update_data' has the following structure for
        each line:

        # ======================================================================
        # STATUS    |  NAME  |   DATE  |  RESULT
        # ======================================================================
        # where
        #
        # STATUS: OPEN or CLOSED
        # NAME  : If starting with 'opkg' it is an opkg-command to be executed.
        #         Otherwise its the name of a file which has to be updated.
        # DATE  : 0000-00-00T00:00:00
        # RESULT: SUCCESS or ERROR (possibly with description)
        #
     */
    if (!m_init) {
        return false;
    }

    QStringList openLines = getOpenLines();

    bool res = false;
    QList<QString>::const_iterator it;
    for (it = openLines.cbegin(); it != openLines.cend(); ++it) {
        int start = 0, end;
        int column = 0;
        QString status, name, datetime, result;
        QString line = *it;
        while ((end = line.indexOf(QChar(','), start)) != -1) {
            QString next = line.mid(start, end).trimmed();
            switch (column) {
            case COLUMN_STATUS:
                status = next;
                break;
            case COLUMN_NAME:
                name = next;
                break;
            case COLUMN_DATE_TIME:
                datetime = next;
                break;
            case COLUMN_RESULT:
                result = next;
                break;
            }
            ++column;
            start = end + 1;
        }

        if (!status.contains("OPEN")) {
            qCritical() << "Parsing error for" << m_update_ctrl_file.fileName();
            return false;
        }
        if (name.contains("dc2c") && name.endsWith(".bin")) {
            updateBinary(name.toStdString().c_str());
            res = true;
        } else
        if (name.contains("DC2C_print") && name.endsWith(".json")) {
            int i = name.indexOf("DC2C_print");
            int templateIdx = name.mid(i).midRef(10, 2).toInt();
            updatePrinterConf(templateIdx, name.toStdString().c_str());
            res = true;
        } else
        if (name.contains("opkg")) {
            int i = name.indexOf("opkg ");
            QString rest = name.mid(i).trimmed();
            QScopedPointer<QProcess> p(new QProcess(this));
            p->setProcessChannelMode(QProcess::MergedChannels);
            p->start("opkg", QStringList() << rest);
            if (p->waitForStarted(1000)) {
                if (p->state() == QProcess::ProcessState::Running) {
                    if (p->waitForFinished(10000)) {
                        QByteArray output = p->readAllStandardOutput();
                        qCritical() << output;
                        res = true;
                    }
                }
            }
        } else {
            // TODO
        }
        QString resultLine = "CLOSED";
        resultLine += ", " + name;
        resultLine += ", " + QDateTime::currentDateTime().toString(Qt::ISODate);
        resultLine += ", " + (res == true) ? "SUCCESS" : "ERROR";
        m_out << resultLine;
    } // for (it = openLines.cbegin(); it != openLines.end(); ++it) {

    return finishUpdate(openLines.size() > 0);
}

bool Utils::finishUpdate(bool replaceCtrlFile) {
    if (replaceCtrlFile) {
        if (!m_update_ctrl_file_copy.exists()) {
            return false;
        }
        if (!m_update_ctrl_file.remove()) {
            return false;
        }
        if (!m_update_ctrl_file_copy.rename(m_update_ctrl_file.fileName())) {
            return false;
        }
    }
    return true;
}
