void MainWindow::startPatching()
{
    Q_D(MainWindow);

    d->bytes = 0;
    d->maxBytes = 0;
    d->files = 0;
    d->maxFiles = 0;

    d->progressBar->setMaximum(0);
    d->progressBar->setValue(0);
    d->detailsLbl->clear();

    if (!d->supported) {
        if (d->presetSel->currentIndex() == 0) {
            d->patchInfo = new mbp::PatchInfo(); // TODO: Memory leak here!

            d->patchInfo->addAutoPatcher("StandardPatcher",
                                         mbp::PatchInfo::AutoPatcherArgs());
            d->patchInfo->setHasBootImage(d->hasBootImageCb->isChecked());
            if (d->patchInfo->hasBootImage()) {
                d->patchInfo->setRamdisk(d->device->id() + "/default");
                QString text = d->bootImageLe->text().trimmed();
                if (!text.isEmpty()) {
                    QStringList split = text.split(QStringLiteral(","));
                    std::vector<std::string> bootImages;
                    for (const QString &bootImage : split) {
                        bootImages.push_back(bootImage.toUtf8().constData());
                    }
                    d->patchInfo->setBootImages(std::move(bootImages));
                }
            }

            d->patchInfo->setDeviceCheck(!d->deviceCheckCb->isChecked());
        } else {
            d->patchInfo = d->patchInfos[d->presetSel->currentIndex() - 1];
        }
    }

    d->state = MainWindowPrivate::Patching;
    updateWidgetsVisibility();

    FileInfoPtr fileInfo = new mbp::FileInfo();
    fileInfo->setFilename(d->fileName.toUtf8().constData());
    fileInfo->setDevice(d->device);
    fileInfo->setPatchInfo(d->patchInfo);
    QString romId;
    if (d->instLocSel->currentIndex() >= d->instLocs.size()) {
        romId = QStringLiteral("data-slot-%1").arg(d->instLocLe->text());
    } else {
        romId = d->instLocs[d->instLocSel->currentIndex()].id;
    }
    fileInfo->setRomId(romId.toUtf8().constData());

    emit runThread(d->patcher, fileInfo);
}
MainWindow::MainWindow(mbp::PatcherConfig *pc, QWidget *parent)
    : QWidget(parent), d_ptr(new MainWindowPrivate())
{
    Q_D(MainWindow);

    setWindowIcon(QIcon(QStringLiteral(":/icons/icon.png")));
    setWindowTitle(qApp->applicationName());

    // If we're passed an argument, switch to automatic mode
    if (qApp->arguments().size() > 2) {
        d->autoMode = true;
        d->fileName = qApp->arguments().at(1);
    } else {
        d->autoMode = false;
        d->fileName.clear();
    }

    d->pc = pc;

    addWidgets();
    setWidgetActions();
    populateDevices();
    populateInstallationLocations();
    updateWidgetsVisibility();

    QString lastDeviceId = d->settings.value(
            QStringLiteral("last_device"), QString()).toString();
    for (size_t i = 0; i < d->devices.size(); ++i) {
        if (strcmp(mb_device_id(d->devices[i].get()),
                   lastDeviceId.toUtf8().data()) == 0) {
            d->deviceSel->setCurrentIndex(i);
            break;
        }
    }

    // Create thread
    d->thread = new QThread(this);
    d->task = new PatcherTask();
    d->task->moveToThread(d->thread);

    connect(d->thread, &QThread::finished,
            d->task, &QObject::deleteLater);
    connect(this, &MainWindow::runThread,
            d->task, &PatcherTask::patch);
    connect(d->task, &PatcherTask::finished,
            this, &MainWindow::onPatchingFinished);
    connect(d->task, &PatcherTask::progressUpdated,
            this, &MainWindow::onProgressUpdated);
    connect(d->task, &PatcherTask::filesUpdated,
            this, &MainWindow::onFilesUpdated);
    connect(d->task, &PatcherTask::detailsUpdated,
            this, &MainWindow::onDetailsUpdated);

    d->thread->start();
}
void MainWindow::onDeviceSelected(int index)
{
    Q_D(MainWindow);
    d->device = d->pc->devices()[index];

    if (d->state == MainWindowPrivate::FinishedPatching) {
        d->state = MainWindowPrivate::ChoseFile;
    }

    updateWidgetsVisibility();
}
void MainWindow::onPatchingFinished(const QString &newFile, bool failed,
                                    const QString &errorMessage)
{
    Q_D(MainWindow);

    d->patcherNewFile = newFile;
    d->patcherFailed = failed;
    d->patcherError = errorMessage;

    d->state = MainWindowPrivate::FinishedPatching;
    updateWidgetsVisibility();
}
void MainWindow::onDeviceSelected(int index)
{
    Q_D(MainWindow);
    if (index < d->devices.size()) {
        d->device = d->devices[index].get();
    }

    if (d->state == MainWindowPrivate::FinishedPatching) {
        d->state = MainWindowPrivate::ChoseFile;
    }

    updateWidgetsVisibility();
}
MainWindow::MainWindow(mbp::PatcherConfig *pc, QWidget *parent)
    : QWidget(parent), d_ptr(new MainWindowPrivate())
{
    Q_D(MainWindow);

    setWindowIcon(QIcon(QStringLiteral(":/icons/icon.png")));
    setWindowTitle(qApp->applicationName());

    // If we're passed an argument, switch to automatic mode
    if (qApp->arguments().size() > 2) {
        d->autoMode = true;
        d->fileName = qApp->arguments().at(1);
    } else {
        d->autoMode = false;
        d->fileName.clear();
    }

    d->pc = pc;

    d->patcher = pc->createPatcher("MultiBootPatcher");

    addWidgets();
    setWidgetActions();
    populateWidgets();
    setWidgetDefaults();
    refreshInstallationLocations();
    updateWidgetsVisibility();

    // Create thread
    d->thread = new QThread(this);
    d->task = new PatcherTask();
    d->task->moveToThread(d->thread);

    connect(d->thread, &QThread::finished,
            d->task, &QObject::deleteLater);
    connect(this, &MainWindow::runThread,
            d->task, &PatcherTask::patch);
    connect(d->task, &PatcherTask::finished,
            this, &MainWindow::onPatchingFinished);
    connect(d->task, &PatcherTask::progressUpdated,
            this, &MainWindow::onProgressUpdated);
    connect(d->task, &PatcherTask::filesUpdated,
            this, &MainWindow::onFilesUpdated);
    connect(d->task, &PatcherTask::detailsUpdated,
            this, &MainWindow::onDetailsUpdated);

    d->thread->start();
}
void MainWindow::chooseFile()
{
    Q_D(MainWindow);

    QString fileName = QFileDialog::getOpenFileName(this, QString(),
            d->settings.value(QStringLiteral("last_dir")).toString(),
            tr("Zip files and Odin tarballs (*.zip *.tar.md5 *.tar.md5.gz *.tar.md5.xz)"));
    if (fileName.isNull()) {
        return;
    }

    d->settings.setValue(QStringLiteral("last_dir"),
                         QFileInfo(fileName).dir().absolutePath());

    d->state = MainWindowPrivate::ChoseFile;

    d->fileName = fileName;

    updateWidgetsVisibility();
}
void MainWindow::startPatching()
{
    Q_D(MainWindow);

    d->bytes = 0;
    d->maxBytes = 0;
    d->files = 0;
    d->maxFiles = 0;

    d->progressBar->setMaximum(0);
    d->progressBar->setValue(0);
    d->detailsLbl->clear();

    d->state = MainWindowPrivate::Patching;
    updateWidgetsVisibility();

    QString romId;
    if (d->instLocSel->currentIndex() == d->instLocs.size()) {
        romId = QStringLiteral("data-slot-%1").arg(d->instLocLe->text());
    } else if (d->instLocSel->currentIndex() == d->instLocs.size() + 1) {
        romId = QStringLiteral("extsd-slot-%1").arg(d->instLocLe->text());
    } else {
        romId = d->instLocs[d->instLocSel->currentIndex()].id;
    }


    QStringList suffixes;
    suffixes << QStringLiteral(".tar.md5");
    suffixes << QStringLiteral(".tar.md5.gz");
    suffixes << QStringLiteral(".tar.md5.xz");
    suffixes << QStringLiteral(".zip");

    QFileInfo qFileInfo(d->fileName);
    QString suffix;
    QString outputName;
    bool isOdin = false;

    for (const QString &suffix : suffixes) {
        if (d->fileName.endsWith(suffix)) {
            // Input name: <parent path>/<base name>.<suffix>
            // Output name: <parent path>/<base name>_<rom id>.zip
            outputName = d->fileName.left(d->fileName.size() - suffix.size())
                    % QStringLiteral("_")
                    % romId
                    % QStringLiteral(".zip");
            isOdin = suffix.contains(QStringLiteral(".tar.md5"));
            break;
        }
    }
    if (outputName.isEmpty()) {
        outputName = qFileInfo.completeBaseName()
                % QStringLiteral("_")
                % romId
                % QStringLiteral(".")
                % qFileInfo.suffix();
    }

    QString inputPath(QDir::toNativeSeparators(qFileInfo.filePath()));
    QString outputPath(QDir::toNativeSeparators(
            qFileInfo.dir().filePath(outputName)));

    FileInfoPtr fileInfo = new mbp::FileInfo();
    fileInfo->setInputPath(inputPath.toUtf8().constData());
    fileInfo->setOutputPath(outputPath.toUtf8().constData());
    fileInfo->setDevice(d->device);
    fileInfo->setRomId(romId.toUtf8().constData());

    if (isOdin) {
        d->patcher = d->pc->createPatcher("OdinPatcher");
    } else {
        d->patcher = d->pc->createPatcher("MultiBootPatcher");
    }

    emit runThread(d->patcher, fileInfo);
}