/*
 * 86Box	A hypervisor and IBM PC system emulator that specializes in
 *		running old operating systems and software designed for IBM
 *		PC systems and compatibles from 1981 through fairly recent
 *		system designs based on the PCI bus.
 *
 *		This file is part of the 86Box distribution.
 *
 *		Hard disk dialog code.
 *
 *
 *
 * Authors:	Joakim L. Gilje <jgilje@jgilje.net>
 *          Cacodemon345
 *
 *		Copyright 2021 Joakim L. Gilje
 *      Copyright 2022 Cacodemon345
 */
#include "qt_harddiskdialog.hpp"
#include "ui_qt_harddiskdialog.h"

extern "C" {
#include <86box/86box.h>
#include <86box/hdd.h>
#include "../disk/minivhd/minivhd.h"
#include "../disk/minivhd/minivhd_util.h"
}

#include <thread>

#include <QMessageBox>
#include <QDebug>

#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QFileDialog>
#include <QProgressDialog>
#include <QPushButton>
#include <QStringBuilder>
#include <QStringList>

#include "qt_harddrive_common.hpp"
#include "qt_settings_bus_tracking.hpp"
#include "qt_models_common.hpp"
#include "qt_util.hpp"

HarddiskDialog::HarddiskDialog(bool existing, QWidget *parent) :
    QDialog(parent),
    ui(new Ui::HarddiskDialog)
{
    ui->setupUi(this);

    if (existing) {
        ui->fileField->setFilter(tr("Hard disk images") % util::DlgFilter({ "hd?", "im?", "vhd" }) % tr("All files") % util::DlgFilter({ "*" }, true));

        setWindowTitle(tr("Add Existing Hard Disk"));
        ui->lineEditCylinders->setEnabled(false);
        ui->lineEditHeads->setEnabled(false);
        ui->lineEditSectors->setEnabled(false);
        ui->lineEditSize->setEnabled(false);
        ui->comboBoxType->setEnabled(false);

        ui->comboBoxFormat->hide();
        ui->labelFormat->hide();

        connect(ui->fileField, &FileField::fileSelected, this, &HarddiskDialog::onExistingFileSelected);
    } else {
        QStringList filters({ tr("Raw image") % util::DlgFilter({ "img" }, true),
                              tr("HDI image") % util::DlgFilter({ "hdi" }, true),
                              tr("HDX image") % util::DlgFilter({ "hdx" }, true),
                              tr("Fixed-size VHD") % util::DlgFilter({ "vhd" }, true),
                              tr("Dynamic-size VHD") % util::DlgFilter({ "vhd" }, true),
                              tr("Differencing VHD") % util::DlgFilter({ "vhd" }, true) });

        ui->fileField->setFilter(filters.join(";;"));

        setWindowTitle(tr("Add New Hard Disk"));
        ui->fileField->setCreateFile(true);

        connect(ui->fileField, &FileField::fileSelected, this, [this, filters] {
            int filter = filters.indexOf(ui->fileField->selectedFilter());
            if (filter > -1)
                ui->comboBoxFormat->setCurrentIndex(filter);
            ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
        });
    }

    auto* model = ui->comboBoxFormat->model();
    model->insertRows(0, 6);
    model->setData(model->index(0, 0), tr("Raw image (.img)"));
    model->setData(model->index(1, 0), tr("HDI image (.hdi)"));
    model->setData(model->index(2, 0), tr("HDX image (.hdx)"));
    model->setData(model->index(3, 0), tr("Fixed-size VHD (.vhd)"));
    model->setData(model->index(4, 0), tr("Dynamic-size VHD (.vhd)"));
    model->setData(model->index(5, 0), tr("Differencing VHD (.vhd)"));

    model = ui->comboBoxBlockSize->model();
    model->insertRows(0, 2);
    model->setData(model->index(0, 0), tr("Large blocks (2 MB)"));
    model->setData(model->index(1, 0), tr("Small blocks (512 KB)"));

    ui->comboBoxBlockSize->hide();
    ui->labelBlockSize->hide();

    Harddrives::populateBuses(ui->comboBoxBus->model());
    ui->comboBoxBus->setCurrentIndex(3);

    model = ui->comboBoxType->model();
    for (int i = 0; i < 127; i++) {
        uint64_t size = ((uint64_t) hdd_table[i][0]) * hdd_table[i][1] * hdd_table[i][2];
        uint32_t size_mb = size >> 11LL;
        //QString text = QString("%1 MiB (CHS: %2, %3, %4)").arg(size_mb).arg(hdd_table[i][0]).arg(hdd_table[i][1]).arg(hdd_table[i][2]);
        QString text = QString::asprintf(tr("%u MB (CHS: %i, %i, %i)").toUtf8().constData(), (size_mb), (hdd_table[i][0]), (hdd_table[i][1]), (hdd_table[i][2]));
        Models::AddEntry(model, text, i);
    }
    Models::AddEntry(model, tr("Custom..."), 127);
    Models::AddEntry(model, tr("Custom (large)..."), 128);

    ui->lineEditSize->setValidator(new QIntValidator());
    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
}

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

uint8_t HarddiskDialog::bus() const {
    return static_cast<uint8_t>(ui->comboBoxBus->currentData().toUInt());
}

uint8_t HarddiskDialog::channel() const {
    return static_cast<uint8_t>(ui->comboBoxChannel->currentData().toUInt());
}

QString HarddiskDialog::fileName() const {
    return ui->fileField->fileName();
}

void HarddiskDialog::on_comboBoxFormat_currentIndexChanged(int index) {
    bool enabled;
    if (index == 5) { /* They switched to a diff VHD; disable the geometry fields. */
        enabled = false;
        ui->lineEditCylinders->setText(tr("(N/A)"));
        ui->lineEditHeads->setText(tr("(N/A)"));
        ui->lineEditSectors->setText(tr("(N/A)"));
        ui->lineEditSize->setText(tr("(N/A)"));
    } else {
        enabled = true;
        ui->lineEditCylinders->setText(QString::number(cylinders_));
        ui->lineEditHeads->setText(QString::number(heads_));
        ui->lineEditSectors->setText(QString::number(sectors_));
        recalcSize();
    }
    ui->lineEditCylinders->setEnabled(enabled);
    ui->lineEditHeads->setEnabled(enabled);
    ui->lineEditSectors->setEnabled(enabled);
    ui->lineEditSize->setEnabled(enabled);
    ui->comboBoxType->setEnabled(enabled);

    if (index < 4) {
        ui->comboBoxBlockSize->hide();
        ui->labelBlockSize->hide();
    } else {
        ui->comboBoxBlockSize->show();
        ui->labelBlockSize->show();
    }
}

/* If the disk geometry requested in the 86Box GUI is not compatible with the internal VHD geometry,
 * we adjust it to the next-largest size that is compatible. On average, this will be a difference
 * of about 21 MB, and should only be necessary for VHDs larger than 31.5 GB, so should never be more
 * than a tenth of a percent change in size.
 */
static void adjust_86box_geometry_for_vhd(MVHDGeom *_86box_geometry, MVHDGeom *vhd_geometry)
{
    if (_86box_geometry->cyl <= 65535) {
        vhd_geometry->cyl = _86box_geometry->cyl;
        vhd_geometry->heads = _86box_geometry->heads;
        vhd_geometry->spt = _86box_geometry->spt;
        return;
    }

    int desired_sectors = _86box_geometry->cyl * _86box_geometry->heads * _86box_geometry->spt;
    if (desired_sectors > 267321600)
        desired_sectors = 267321600;

    int remainder = desired_sectors % 85680; /* 8560 is the LCM of 1008 (63*16) and 4080 (255*16) */
    if (remainder > 0)
        desired_sectors += (85680 - remainder);

    _86box_geometry->cyl = desired_sectors / (16 * 63);
    _86box_geometry->heads = 16;
    _86box_geometry->spt = 63;

    vhd_geometry->cyl = desired_sectors / (16 * 255);
    vhd_geometry->heads = 16;
    vhd_geometry->spt = 255;
}

static HarddiskDialog* callbackPtr = nullptr;
static MVHDGeom create_drive_vhd_fixed(const QString& fileName, HarddiskDialog* p, uint16_t cyl, uint8_t heads, uint8_t spt) {
    MVHDGeom _86box_geometry = { .cyl = cyl, .heads = heads, .spt = spt };
    MVHDGeom vhd_geometry;
    adjust_86box_geometry_for_vhd(&_86box_geometry, &vhd_geometry);

    int vhd_error = 0;
    QByteArray filenameBytes = fileName.toUtf8();
    callbackPtr = p;
    MVHDMeta *vhd = mvhd_create_fixed(filenameBytes.data(), vhd_geometry, &vhd_error, [](uint32_t current_sector, uint32_t total_sectors) {
        callbackPtr->fileProgress((current_sector * 100) / total_sectors);
    });
    callbackPtr = nullptr;

    if (vhd == NULL) {
        _86box_geometry.cyl = 0;
        _86box_geometry.heads = 0;
        _86box_geometry.spt = 0;
    } else {
        mvhd_close(vhd);
    }

    return _86box_geometry;
}

static MVHDGeom create_drive_vhd_dynamic(const QString& fileName, uint16_t cyl, uint8_t heads, uint8_t spt, int blocksize) {
    MVHDGeom _86box_geometry = { .cyl = cyl, .heads = heads, .spt = spt };
    MVHDGeom vhd_geometry;
    adjust_86box_geometry_for_vhd(&_86box_geometry, &vhd_geometry);
    int vhd_error = 0;
    QByteArray filenameBytes = fileName.toUtf8();
    MVHDCreationOptions options;
    options.block_size_in_sectors = blocksize;
    options.path = filenameBytes.data();
    options.size_in_bytes = 0;
    options.geometry = vhd_geometry;
    options.type = MVHD_TYPE_DYNAMIC;

    MVHDMeta *vhd = mvhd_create_ex(options, &vhd_error);
    if (vhd == NULL) {
        _86box_geometry.cyl = 0;
        _86box_geometry.heads = 0;
        _86box_geometry.spt = 0;
    } else {
        mvhd_close(vhd);
    }

    return _86box_geometry;
}

static MVHDGeom create_drive_vhd_diff(const QString& fileName, const QString& parentFileName, int blocksize) {
    int vhd_error = 0;
    QByteArray filenameBytes = fileName.toUtf8();
    QByteArray parentFilenameBytes = fileName.toUtf8();
    MVHDCreationOptions options;
    options.block_size_in_sectors = blocksize;
    options.path = filenameBytes.data();
    options.parent_path = parentFilenameBytes.data();
    options.type = MVHD_TYPE_DIFF;

    MVHDMeta *vhd = mvhd_create_ex(options, &vhd_error);
    MVHDGeom vhd_geometry;
    if (vhd == NULL) {
        vhd_geometry.cyl = 0;
        vhd_geometry.heads = 0;
        vhd_geometry.spt = 0;
    } else {
        vhd_geometry = mvhd_get_geometry(vhd);

        if (vhd_geometry.spt > 63) {
            vhd_geometry.cyl = mvhd_calc_size_sectors(&vhd_geometry) / (16 * 63);
            vhd_geometry.heads = 16;
            vhd_geometry.spt = 63;
        }

        mvhd_close(vhd);
    }

    return vhd_geometry;
}

void HarddiskDialog::onCreateNewFile() {

    for (auto& curObject : children())
    {
        if (qobject_cast<QWidget*>(curObject)) qobject_cast<QWidget*>(curObject)->setDisabled(true);
    }

    ui->progressBar->setEnabled(true);
    setResult(QDialog::Rejected);
    qint64 size = ui->lineEditSize->text().toUInt() << 20U;
    if (size > 0x1FFFFFFE00ll) {
        QMessageBox::critical(this, tr("Disk image too large"), tr("Disk images cannot be larger than 127 GB."));
        return;
    }

    int img_format = ui->comboBoxFormat->currentIndex();
    uint32_t zero = 0;
    uint32_t base = 0x1000;
    uint32_t sector_size = 512;

    auto fileName = ui->fileField->fileName();
    QString expectedSuffix;
    switch (img_format) {
    case 1:
        expectedSuffix = "hdi";
        break;
    case 2:
        expectedSuffix = "hdx";
        break;
    case 3:
    case 4:
    case 5:
        expectedSuffix = "vhd";
        break;
    }
    if (! expectedSuffix.isEmpty()) {
        QFileInfo fileInfo(fileName);
        if (fileInfo.suffix().compare(expectedSuffix, Qt::CaseInsensitive) != 0) {
            fileName = QString("%1.%2").arg(fileName, expectedSuffix);
            ui->fileField->setFileName(fileName);
        }
    }

    QFile file(fileName);
    if (! file.open(QIODevice::WriteOnly)) {
        QMessageBox::critical(this, tr("Unable to write file"), tr("Make sure the file is being saved to a writable directory."));
        return;
    }

    if (img_format == 1) { /* HDI file */
        QDataStream stream(&file);
        stream.setByteOrder(QDataStream::LittleEndian);
        if (size >= 0x100000000ll) {
            QMessageBox::critical(this, tr("Disk image too large"), tr("HDI disk images cannot be larger than 4 GB."));
            return;
        }
        uint32_t s = static_cast<uint32_t>(size);
        stream << zero;			/* 00000000: Zero/unknown */
        stream << zero;			/* 00000004: Zero/unknown */
        stream << base;         /* 00000008: Offset at which data starts */
        stream << s;            /* 0000000C: Full size of the data (32-bit) */
        stream << sector_size;  /* 00000010: Sector size in bytes */
        stream << sectors_;		/* 00000014: Sectors per cylinder */
        stream << heads_;		/* 00000018: Heads per cylinder */
        stream << cylinders_;	/* 0000001C: Cylinders */

        for (int i = 0; i < 0x3f8; i++) {
            stream << zero;
        }
    } else if (img_format == 2) { /* HDX file */
        QDataStream stream(&file);
        stream.setByteOrder(QDataStream::LittleEndian);
        quint64 signature = 0xD778A82044445459;
        stream << signature;	/* 00000000: Signature */
        stream << size;			/* 00000008: Full size of the data (64-bit) */
        stream << sector_size;	/* 00000010: Sector size in bytes */
        stream << sectors_;		/* 00000014: Sectors per cylinder */
        stream << heads_;		/* 00000018: Heads per cylinder */
        stream << cylinders_;	/* 0000001C: Cylinders */
        stream << zero;			/* 00000020: [Translation] Sectors per cylinder */
        stream << zero;			/* 00000004: [Translation] Heads per cylinder */
    } else if (img_format >= 3) { /* VHD file */
        file.close();

        MVHDGeom _86box_geometry{};
        int block_size = ui->comboBoxBlockSize->currentIndex() == 0 ? MVHD_BLOCK_LARGE : MVHD_BLOCK_SMALL;
        switch (img_format) {
        case 3:
        {
            connect(this, &HarddiskDialog::fileProgress, this, [this] (int value) { ui->progressBar->setValue(value); QApplication::processEvents(); } );
            ui->progressBar->setVisible(true);
            [&_86box_geometry, fileName, this] {
                _86box_geometry = create_drive_vhd_fixed(fileName, this, cylinders_, heads_, sectors_);
            }();
        }
            break;
        case 4:
            _86box_geometry = create_drive_vhd_dynamic(fileName, cylinders_, heads_, sectors_, block_size);
            break;
        case 5:
            QString vhdParent = QFileDialog::getOpenFileName(
                this,
                tr("Select the parent VHD"),
                QString(),
                tr("VHD files") %
                util::DlgFilter({ "vhd" }) %
                tr("All files") %
                util::DlgFilter({ "*" }, true));

            if (vhdParent.isEmpty()) {
                return;
            }
            _86box_geometry = create_drive_vhd_diff(fileName, vhdParent, block_size);
            break;
        }

        if (_86box_geometry.cyl == 0 &&
            _86box_geometry.heads == 0 &&
            _86box_geometry.spt == 0)
        {
            QMessageBox::critical(this, tr("Unable to write file"), tr("Make sure the file is being saved to a writable directory."));
            return;
        }
        else if (img_format != 5) {
            QMessageBox::information(this, tr("Disk image created"), tr("Remember to partition and format the newly-created drive."));
        }

        ui->lineEditCylinders->setText(QString::number(_86box_geometry.cyl));
        ui->lineEditHeads->setText(QString::number(_86box_geometry.heads));
        ui->lineEditSectors->setText(QString::number(_86box_geometry.spt));
        cylinders_ = _86box_geometry.cyl;
        heads_ = _86box_geometry.heads;
        sectors_ = _86box_geometry.spt;
        setResult(QDialog::Accepted);

        return;
    }

    // formats 0, 1 and 2
    connect(this, &HarddiskDialog::fileProgress, this, [this] (int value) { ui->progressBar->setValue(value); QApplication::processEvents(); } );
    ui->progressBar->setVisible(true);
    [size, &file, this] {
        QDataStream stream(&file);
        stream.setByteOrder(QDataStream::LittleEndian);

        QByteArray buf(1048576, 0);
        uint64_t mibBlocks = size >> 20;
        uint64_t restBlock = size & 0xfffff;

        if (restBlock) {
            stream.writeRawData(buf.data(), restBlock);
        }

        if (mibBlocks) {
            for (uint64_t i = 0; i < mibBlocks; ++i) {
                stream.writeRawData(buf.data(), buf.size());
                emit fileProgress(static_cast<int>((i * 100) / mibBlocks));
            }
        }
        emit fileProgress(100);
    }();

    QMessageBox::information(this, tr("Disk image created"), tr("Remember to partition and format the newly-created drive."));
    setResult(QDialog::Accepted);
}

static void adjust_vhd_geometry_for_86box(MVHDGeom *vhd_geometry) {
    if (vhd_geometry->spt <= 63)
        return;

    int desired_sectors = vhd_geometry->cyl * vhd_geometry->heads * vhd_geometry->spt;
    if (desired_sectors > 267321600)
        desired_sectors = 267321600;

    int remainder = desired_sectors % 85680; /* 8560 is the LCM of 1008 (63*16) and 4080 (255*16) */
    if (remainder > 0)
        desired_sectors -= remainder;

    vhd_geometry->cyl = desired_sectors / (16 * 63);
    vhd_geometry->heads = 16;
    vhd_geometry->spt = 63;
}

void HarddiskDialog::recalcSelection() {
    int selection = 127;
    for (int i = 0; i < 127; i++) {
        if ((cylinders_ == hdd_table[i][0]) &&
            (heads_ == hdd_table[i][1]) &&
            (sectors_ == hdd_table[i][2]))
            selection = i;
    }
    if ((selection == 127) && (heads_ == 16) && (sectors_ == 63)) {
        selection = 128;
    }
    ui->comboBoxType->setCurrentIndex(selection);
}

void HarddiskDialog::onExistingFileSelected(const QString &fileName) {
    // TODO : Over to non-existing file selected
    /*
    if (!(existing & 1)) {
        f = _wfopen(wopenfilestring, L"rb");
        if (f != NULL) {
            fclose(f);
            if (settings_msgbox_ex(MBX_QUESTION_YN, (wchar_t *) IDS_4111, (wchar_t *) IDS_4118, (wchar_t *) IDS_4120, (wchar_t *) IDS_4121, NULL) != 0)	/ * yes * /
                return FALSE;
        }
    }

    f = _wfopen(wopenfilestring, (existing & 1) ? L"rb" : L"wb");
    if (f == NULL) {
    hdd_add_file_open_error:
        fclose(f);
        settings_msgbox_header(MBX_ERROR, (existing & 1) ? (wchar_t *) IDS_4114 : (wchar_t *) IDS_4115, (existing & 1) ? (wchar_t *) IDS_4107 : (wchar_t *) IDS_4108);
        return TRUE;
    }
    */

    uint64_t size = 0;
    uint32_t sector_size = 0;
    uint32_t sectors = 0;
    uint32_t heads = 0;
    uint32_t cylinders = 0;
    int vhd_error = 0;

    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
    QFile file(fileName);
    if (! file.open(QIODevice::ReadOnly)) {
        QMessageBox::critical(this, tr("Unable to read file"), tr("Make sure the file exists and is readable."));
        return;
    }
    QByteArray fileNameUtf8 = fileName.toUtf8();

    QFileInfo fi(file);
    if (image_is_hdi(fileNameUtf8.data()) || image_is_hdx(fileNameUtf8.data(), 1)) {
        file.seek(0x10);
        QDataStream stream(&file);
        stream.setByteOrder(QDataStream::LittleEndian);
        stream >> sector_size;
        if (sector_size != 512) {
            QMessageBox::critical(this, tr("Unsupported disk image"), tr("HDI or HDX images with a sector size other than 512 are not supported."));
            return;
        }

        sectors = heads = cylinders = 0;
        stream >> sectors;
        stream >> heads;
        stream >> cylinders;
    } else if (image_is_vhd(fileNameUtf8.data(), 1)) {
        MVHDMeta* vhd = mvhd_open(fileNameUtf8.data(), 0, &vhd_error);
        if (vhd == nullptr) {
            QMessageBox::critical(this, tr("Unable to read file"), tr("Make sure the file exists and is readable"));
            return;
        } else if (vhd_error == MVHD_ERR_TIMESTAMP) {
            QMessageBox::StandardButton btn = QMessageBox::warning(this, tr("Parent and child disk timestamps do not match"), tr("This could mean that the parent image was modified after the differencing image was created.\n\nIt can also happen if the image files were moved or copied, or by a bug in the program that created this disk.\n\nDo you want to fix the timestamps?"), QMessageBox::Yes | QMessageBox::No);
            if (btn == QMessageBox::Yes) {
                int ts_res = mvhd_diff_update_par_timestamp(vhd, &vhd_error);
                if (ts_res != 0) {
                    QMessageBox::critical(this, tr("Error"), tr("Could not fix VHD timestamp"));
                    mvhd_close(vhd);
                    return;
                }
            } else {
                mvhd_close(vhd);
                return;
            }
        }

        MVHDGeom vhd_geom = mvhd_get_geometry(vhd);
        adjust_vhd_geometry_for_86box(&vhd_geom);
        cylinders = vhd_geom.cyl;
        heads = vhd_geom.heads;
        sectors = vhd_geom.spt;
        size = static_cast<uint64_t>(cylinders * heads * sectors * 512);
        mvhd_close(vhd);
    } else {
        size = file.size();
        if (((size % 17) == 0) && (size <= 142606336)) {
            sectors = 17;
            if (size <= 26738688)
                heads = 4;
            else if (((size % 3072) == 0) && (size <= 53477376))
                heads = 6;
            else {
                int i;
                for (i = 5; i < 16; i++) {
                    if (((size % (i << 9)) == 0) && (size <= ((i * 17) << 19)))
                        break;
                    if (i == 5)
                        i++;
                }
                heads = i;
            }
        } else {
            sectors = 63;
            heads = 16;
        }

        cylinders = ((size >> 9) / heads) / sectors;
    }

    if ((sectors > max_sectors) || (heads > max_heads) || (cylinders > max_cylinders)) {
        QMessageBox::critical(this, tr("Unable to read file"), tr("Make sure the file exists and is readable"));
        return;
    }

    heads_ = heads;
    sectors_ = sectors;
    cylinders_ = cylinders;
    ui->lineEditCylinders->setText(QString::number(cylinders));
    ui->lineEditHeads->setText(QString::number(heads));
    ui->lineEditSectors->setText(QString::number(sectors));
    recalcSize();
    recalcSelection();

    ui->lineEditCylinders->setEnabled(true);
    ui->lineEditHeads->setEnabled(true);
    ui->lineEditSectors->setEnabled(true);
    ui->lineEditSize->setEnabled(true);
    ui->comboBoxType->setEnabled(true);
    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
}

void HarddiskDialog::recalcSize() {
    uint64_t size = (static_cast<uint64_t>(cylinders_) * static_cast<uint64_t>(heads_) * static_cast<uint64_t>(sectors_)) << 9;
    ui->lineEditSize->setText(QString::number(size >> 20));
}

bool HarddiskDialog::checkAndAdjustSectors() {
    if (sectors_ > max_sectors) {
        sectors_ = max_sectors;
        ui->lineEditSectors->setText(QString::number(max_sectors));
        recalcSize();
        recalcSelection();
        return false;
    }
    return true;
}

bool HarddiskDialog::checkAndAdjustHeads() {
    if (heads_ > max_heads) {
        heads_ = max_heads;
        ui->lineEditHeads->setText(QString::number(max_heads));
        recalcSize();
        recalcSelection();
        return false;
    }
    return true;
}

bool HarddiskDialog::checkAndAdjustCylinders() {
    if (cylinders_ > max_cylinders) {
        cylinders_ = max_cylinders;
        ui->lineEditCylinders->setText(QString::number(max_cylinders));
        recalcSize();
        recalcSelection();
        return false;
    }
    return true;
}


void HarddiskDialog::on_comboBoxBus_currentIndexChanged(int index) {
    int chanIdx = 0;
    if (index < 0) {
        return;
    }

    switch (ui->comboBoxBus->currentData().toInt()) {
    case HDD_BUS_DISABLED:
    default:
        max_sectors = max_heads = max_cylinders = 0;
        break;
    case HDD_BUS_MFM:
        max_sectors = 26;	/* 17 for MFM, 26 for RLL. */
        max_heads = 15;
        max_cylinders = 2047;
        break;
    case HDD_BUS_XTA:
        max_sectors = 63;
        max_heads = 16;
        max_cylinders = 1023;
        break;
    case HDD_BUS_ESDI:
        max_sectors = 99;	/* ESDI drives usually had 32 to 43 sectors per track. */
        max_heads = 16;
        max_cylinders = 266305;
        break;
    case HDD_BUS_IDE:
        max_sectors = 63;
        max_heads = 255;
        max_cylinders = 266305;
        break;
    case HDD_BUS_ATAPI:
    case HDD_BUS_SCSI:
        max_sectors = 99;
        max_heads = 255;
        max_cylinders = 266305;
        break;
    }

    checkAndAdjustCylinders();
    checkAndAdjustHeads();
    checkAndAdjustSectors();

    if (ui->lineEditCylinders->validator() != nullptr) {
        delete ui->lineEditCylinders->validator();
    }
    if (ui->lineEditHeads->validator() != nullptr) {
        delete ui->lineEditHeads->validator();
    }
    if (ui->lineEditSectors->validator() != nullptr) {
        delete ui->lineEditSectors->validator();
    }

    ui->lineEditCylinders->setValidator(new QIntValidator(1, max_cylinders, this));
    ui->lineEditHeads->setValidator(new QIntValidator(1, max_heads, this));
    ui->lineEditSectors->setValidator(new QIntValidator(1, max_sectors, this));

    Harddrives::populateBusChannels(ui->comboBoxChannel->model(), ui->comboBoxBus->currentData().toInt());
    switch (ui->comboBoxBus->currentData().toInt())
    {
        case HDD_BUS_MFM:
            chanIdx = (Harddrives::busTrackClass->next_free_mfm_channel());
            break;
        case HDD_BUS_XTA:
            chanIdx = (Harddrives::busTrackClass->next_free_xta_channel());
            break;
        case HDD_BUS_ESDI:
            chanIdx = (Harddrives::busTrackClass->next_free_esdi_channel());
            break;
        case HDD_BUS_ATAPI:
        case HDD_BUS_IDE:
            chanIdx = (Harddrives::busTrackClass->next_free_ide_channel());
            break;
        case HDD_BUS_SCSI:
            chanIdx = (Harddrives::busTrackClass->next_free_scsi_id());
            break;
    }

    if (chanIdx == 0xFF) chanIdx = 0;
    ui->comboBoxChannel->setCurrentIndex(chanIdx);
}

void HarddiskDialog::on_lineEditSize_textEdited(const QString &text) {
    uint32_t size = text.toUInt();
    /* This is needed to ensure VHD standard compliance. */
    hdd_image_calc_chs(&cylinders_, &heads_, &sectors_, size);
    ui->lineEditCylinders->setText(QString::number(cylinders_));
    ui->lineEditHeads->setText(QString::number(heads_));
    ui->lineEditSectors->setText(QString::number(sectors_));
    recalcSelection();

    checkAndAdjustCylinders();
    checkAndAdjustHeads();
    checkAndAdjustSectors();
}

void HarddiskDialog::on_lineEditCylinders_textEdited(const QString &text) {
    cylinders_ = text.toUInt();
    if (checkAndAdjustCylinders()) {
        recalcSize();
        recalcSelection();
    }
}

void HarddiskDialog::on_lineEditHeads_textEdited(const QString &text) {
    heads_ = text.toUInt();
    if (checkAndAdjustHeads()) {
        recalcSize();
        recalcSelection();
    }
}

void HarddiskDialog::on_lineEditSectors_textEdited(const QString &text) {
    sectors_ = text.toUInt();
    if (checkAndAdjustSectors()) {
        recalcSize();
        recalcSelection();
    }
}

void HarddiskDialog::on_comboBoxType_currentIndexChanged(int index) {
    if (index < 0) {
        return;
    }

    if ((index != 127) && (index != 128)) {
        cylinders_ = hdd_table[index][0];
        heads_ = hdd_table[index][1];
        sectors_ = hdd_table[index][2];
        ui->lineEditCylinders->setText(QString::number(cylinders_));
        ui->lineEditHeads->setText(QString::number(heads_));
        ui->lineEditSectors->setText(QString::number(sectors_));
        recalcSize();
    } else if (index == 128) {
        heads_ = 16;
        sectors_ = 63;
        ui->lineEditHeads->setText(QString::number(heads_));
        ui->lineEditSectors->setText(QString::number(sectors_));
        recalcSize();
    }

    checkAndAdjustCylinders();
    checkAndAdjustHeads();
    checkAndAdjustSectors();
}

void HarddiskDialog::accept()
{
    if (ui->fileField->createFile()) onCreateNewFile();
    else setResult(QDialog::Accepted);
    QDialog::done(result());
}
