#include "PortLoader.h"
#include "PortLoaderErrors.h"
#include "ProgressLog.h"
#include "protocol/BurnProtocol.h"
#include <QFile>
#include <QDateTime>
#include "sha.h"
#include "tedswriter_fw_suffix.h"

PortLoader::PortLoader() {

}

void PortLoader::init(BurnProtocol &proto, LQError &err, ProgressLog &log) {
    m_proto = &proto;

    log.printDbgLine(tr("Start device detection"));
    proto.detect(err);
    if (err.isSuccess()) {
        log.printDbgLine(tr("Device detected!"));
    } else {
        log.printError(tr("Device detection failed!"), err);
    }

    if (err.isSuccess() && proto.supportDevInfo()) {
        log.printDbgLine(tr("Start read device information"));
        proto.getDevInfo(m_devinfo, err);
        if (err.isSuccess()) {
            log.printDbgLine(tr("Read device information done:"));
            log.printDbgLine(tr("    Bootloader version:    %1").arg(m_devinfo.bootloaderVersion()));
            log.printDbgLine(tr("    Device type:           %1").arg(m_devinfo.devTypeInfo().typeName()));

            m_boot_addr = m_devinfo.devTypeInfo().defaultBootAddr();
        } else {
            log.printError(tr("Read device information failed"), err);
        }
    }
}

void PortLoader::cleanup(LQError &err, ProgressLog &log) {
    checkDev(err, log);
    if (err.isSuccess()) {
        log.printDbgLine(tr("Start device cleanup..."));
        m_proto->devCleanup(m_boot_addr, err);
        if (err.isSuccess()) {
            log.printDbgLine(tr("Device cleanup done successfully"));
        } else {
            log.printError(tr("Device cleanup error"), err);
        }
    }
}

void PortLoader::burn(const QString &filename, LQError &err, ProgressLog &log) {
    checkDev(err, log);
    if (err.isSuccess()) {
        burn_mem_addr_t boot_addr {m_boot_addr};
        QFile file{filename};
        if (file.open(QFile::OpenModeFlag::ReadOnly)) {
            log.printDbgLine(tr("Start write firmware file (%1)").arg(filename));
            bool eof {false};
            burn_mem_addr_t addr {boot_addr};



            SHA256Context sha;
            SHA256Reset(&sha);

            quint32 fw_size {0};

            while (err.isSuccess() && !eof) {
                const QByteArray &data {file.read(m_proto->maxWriteBlockSize())};
                if (data.size() > 0) {
                    m_proto->writeMemBlock(addr, data, err);
                    if (err.isSuccess()) {
                        addr += data.size();

                        SHA256Input(&sha, reinterpret_cast<const uint8_t *>(data.data()), data.size());
                        fw_size += data.size();
                    }
                    log.printDbg(".");
                } else {
                    eof = file.atEnd();
                    if (file.error() != QFileDevice::FileError::NoError) {
                        log.printError(tr("Firmware file read error"), err = PortLoaderErrors::fileError(file.error()));
                    }
                }
            }
            log.printDbgLine(QString{});
            file.close();

            if (err.isSuccess()) {
                t_tedswrtr_fw_suffix suffix;
                memset(&suffix, 0, sizeof(suffix));
                suffix.suffix_size = sizeof(suffix);
                suffix.sign = TEDSWRTR_FW_SUFFIX_SIGN;
                suffix.fw_size = fw_size;
                suffix.burn_date = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
                SHA256Result(&sha, suffix.hash);
                m_proto->writeMemBlock(m_boot_addr + TEDSWRTR_FW_SUFFIX_OFFSET,
                                       QByteArray::fromRawData(reinterpret_cast<char *>(&suffix), suffix.suffix_size), err);
            }

            if (err.isSuccess()) {
                log.printDbgLine(tr("Firmware file was written successfully"));
            } else {
                log.printError(tr("Firmware file write error"), err);
            }
        }


    }
}

void PortLoader::checkDev(LQError &err, ProgressLog &log) {

}

void PortLoader::startApp(LQError &err, ProgressLog &log) {
    checkDev(err, log);
    if (err.isSuccess()) {
        log.printDbgLine(tr("Start application"));
        m_proto->startApp(m_boot_addr, err);
        if (err.isSuccess()) {
            log.printDbgLine(tr("Appliclication start done"));
        } else {
            log.printError(tr("Application start error"), err);
        }
    }
}

PortLoader::~PortLoader() {

}
