/**
 * @file LogQSO.cpp
 * @brief implementation of the LogQSO dialog for the UI
 */

#include "LogQSO.h"
#include "Configuration.h"
#include "JS8_Include/Maidenhead.h"
#include "JS8_Logbook/ADIF.h"
#include "JS8_Main/Bands.h"
#include "JS8_Main/DriftingDateTime.h"
#include "JS8_Main/JS8MessageBox.h"
#include "ui_LogQSO.h"

#include <QAbstractItemView>
#include <QComboBox>
#include <QDebug>
#include <QDir>
#include <QLoggingCategory>
#include <QSettings>
#include <QStandardPaths>
#include <QString>
#include <QUdpSocket>

#include "moc_LogQSO.cpp"

Q_DECLARE_LOGGING_CATEGORY(logqso_js8)

LogQSO::LogQSO(QString const &programTitle, QSettings *settings,
               Configuration const *config, QWidget *parent)
    : QDialog{parent, Qt::WindowStaysOnTopHint | Qt::WindowTitleHint |
                          Qt::WindowSystemMenuHint},
      ui(new Ui::LogQSO), m_settings(settings), m_config{config} {
    ui->setupUi(this);
    setWindowTitle(programTitle + " - Log QSO");
    ui->grid->setValidator(new Maidenhead::StandardValidator{this});

    auto b = ui->buttonBox->button(QDialogButtonBox::Save);
    if (b) {
        b->setText("Add to Log");
    }
}

LogQSO::~LogQSO() {}

bool LogQSO::acceptText(QString text) {
    auto w = focusWidget();
    if (!w) {
        return false;
    }

    auto name = QString(w->metaObject()->className());
    if (name != "QLineEdit") {
        return false;
    }

    auto l = static_cast<QLineEdit *>(w);
    if (l->text().isEmpty()) {
        // set
        l->setText(text);
    } else {
        // append
        l->setText(QString("%1 %2").arg(l->text()).arg(text));
    }

    return true;
}

QString LogQSO::currentCall() { return ui->call->text().trimmed(); }

void LogQSO::on_start_now_button_pressed() {
    ui->start_date_time->setDateTime(DriftingDateTime::currentDateTimeUtc());
}

void LogQSO::on_end_now_button_pressed() {
    ui->end_date_time->setDateTime(DriftingDateTime::currentDateTimeUtc());
}

void LogQSO::on_add_new_field_button_pressed() { createAdditionalField(); }

void LogQSO::createAdditionalField(QString key, QString value) {
    QLineEdit *l = new QLineEdit(this);
    if (!value.isEmpty()) {
        l->setText(value);
    }

    QComboBox *c = new QComboBox(this);
    c->insertItems(0, ADIF_FIELDS);
    c->insertItem(0, "");
    c->setEditable(true);
    c->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    connect(c, &QComboBox::currentTextChanged, this, [l](const QString &text) {
        l->setProperty("fieldKey", QVariant(text));
    });
    c->setCurrentText(key);

    auto layout = static_cast<QFormLayout *>(ui->additionalFields->layout());
    layout->addRow(c, l);

    // set tab ordering
    if (m_additionalFieldsControls.isEmpty()) {
        setTabOrder(ui->cbComments, c);
    } else {
        setTabOrder(m_additionalFieldsControls.last(), c);
    }
    setTabOrder(c, l);
    setTabOrder(l, ui->add_new_field_button);
    c->setFocus();

    m_additionalFieldsControls.append(l);
    ui->additionalFields->setVisible(true);
    ui->additionalFields->adjustSize();

    // update the window layout
    updateGeometry();
}

QVariantMap LogQSO::collectAdditionalFields() {
    QVariantMap additionalFields;
    foreach (auto field, m_additionalFieldsControls) {
        auto key = field->property("fieldKey").toString();
        if (key.isEmpty()) {
            continue;
        }
        additionalFields[key] = QVariant(field->text());
    }
    return additionalFields;
}

void LogQSO::resetAdditionalFields() {
    ui->additionalFields->setVisible(false);

    if (!m_additionalFieldsControls.isEmpty()) {
        auto layout =
            static_cast<QFormLayout *>(ui->additionalFields->layout());

#if QT_VERSION >= 0x050800
        for (int i = 0, count = layout->rowCount(); i < count; i++) {
            layout->removeRow(0);
        }
#else
        QLayoutItem *child;
        while ((child = layout->takeAt(0)) != 0) {
            delete child;
        }
#endif

        m_additionalFieldsControls.clear();
    }

    setTabOrder(ui->cbComments, ui->add_new_field_button);
    updateGeometry();
}

void LogQSO::loadSettings() {
    m_settings->beginGroup("LogQSO");
    restoreGeometry(
        m_settings->value("geometry", saveGeometry()).toByteArray());
    ui->cbComments->setChecked(
        m_settings->value("SaveComments", false).toBool());
    m_comments = m_settings->value("LogComments", "").toString();

    resetAdditionalFields();
    auto additionalFields =
        m_settings->value("AdditionalFields", {}).toStringList();
    QSet<QString> additionalFieldsSet;
    foreach (auto key, additionalFields) {
        if (key.isEmpty()) {
            continue;
        }
        if (additionalFieldsSet.contains(key)) {
            continue;
        }

        createAdditionalField(key);
        additionalFieldsSet.insert(key);
    }

    m_settings->endGroup();
}

void LogQSO::storeSettings() const {
    m_settings->beginGroup("LogQSO");
    m_settings->setValue("geometry", saveGeometry());
    m_settings->setValue("SaveComments", ui->cbComments->isChecked());
    m_settings->setValue("LogComments", m_comments);

    auto additionalFields = QStringList{};
    foreach (auto field, m_additionalFieldsControls) {
        auto key = field->property("fieldKey").toString();
        if (key.isEmpty()) {
            continue;
        }
        additionalFields.append(key);
    }
    m_settings->setValue("AdditionalFields", additionalFields);

    m_settings->endGroup();
}

void LogQSO::initLogQSO(QString const &hisCall, QString const &hisGrid,
                        QString mode, QString const &rptSent,
                        QString const &rptRcvd, QDateTime const &dateTimeOn,
                        QDateTime const &dateTimeOff, Radio::Frequency dialFreq,
                        QString const &myCall, QString const &myGrid,
                        QString const &opCall, QString const &comments) {
    if (!isHidden())
        return;

    loadSettings();

    ui->call->setFocus();
    ui->call->setText(hisCall);
    ui->grid->setText(hisGrid);
    ui->name->setText("");
    ui->comments->setText("");

    if (ui->cbComments->isChecked())
        ui->comments->setText(m_comments);

    if (!comments.isEmpty()) {
        ui->comments->setText(comments);
    }

    ui->mode->setText(mode);
    ui->sent->setText(rptSent);
    ui->rcvd->setText(rptRcvd);
    ui->start_date_time->setDateTime(dateTimeOn);
    ui->end_date_time->setDateTime(dateTimeOff);

    m_dialFreq = dialFreq;
    m_myCall = myCall;
    m_myGrid = myGrid;

    ui->band->setText(m_config->bands()->find(dialFreq));
    ui->loggedOperator->setText(opCall);

    qCDebug(logqso_js8) << "initLogQSO rptSent (" << rptSent << ")";
    qCDebug(logqso_js8) << "initLogQSO rptRcvd (" << rptRcvd << ")";

    show();
}

void LogQSO::accept() {
    QString hisCall, hisGrid, mode, submode, rptSent, rptRcvd, dateOn, dateOff,
        timeOn, timeOff, band, operator_call;
    QString comments, name;

    hisCall = ui->call->text().toUpper();
    hisGrid = ui->grid->text().toUpper();
    mode = ui->mode->text().toUpper();
    if (mode == "JS8") {
        mode = "MFSK";
        submode = "JS8";
    }
    rptSent = ui->sent->text();
    rptRcvd = ui->rcvd->text();
    m_dateTimeOn = ui->start_date_time->dateTime();
    m_dateTimeOff = ui->end_date_time->dateTime();
    band = ui->band->text();
    name = ui->name->text();
    comments = ui->comments->text();
    m_comments = comments;
    QString strDialFreq(QString::number(m_dialFreq / 1.e6, 'f', 6));
    operator_call = ui->loggedOperator->text();

    // Log this QSO to ADIF file "js8call_log.adi"
    QString filename = "js8call_log.adi"; // TODO allow user to set
    ADIF adifile;
    auto adifilePath = QDir{QStandardPaths::writableLocation(
                                QStandardPaths::AppLocalDataLocation)}
                           .absoluteFilePath(filename);
    adifile.init(adifilePath);

    auto additionalFields = collectAdditionalFields();

    QByteArray ADIF{adifile.QSOToADIF(
        hisCall, hisGrid, mode, submode, rptSent, rptRcvd, m_dateTimeOn,
        m_dateTimeOff, band, comments, name, strDialFreq, m_myCall, m_myGrid,
        operator_call, additionalFields)};

    if (!adifile.addQSOToFile(ADIF)) {
        JS8MessageBox::warning_message(
            this, tr("Log file error"),
            tr("Cannot open \"%1\"").arg(adifilePath));
    }

    // Log this QSO to file "js8call.log"
    static QFile f{QDir{
        QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)}
                       .absoluteFilePath("js8call.log")};
    if (!f.open(QIODevice::Text | QIODevice::Append)) {
        JS8MessageBox::warning_message(
            this, tr("Log file error"),
            tr("Cannot open \"%1\" for append").arg(f.fileName()),
            tr("Error: %1").arg(f.errorString()));
    } else {
        QStringList logEntryItems = {
            m_dateTimeOn.date().toString("yyyy-MM-dd"),
            m_dateTimeOn.time().toString("hh:mm:ss"),
            m_dateTimeOff.date().toString("yyyy-MM-dd"),
            m_dateTimeOff.time().toString("hh:mm:ss"),
            hisCall,
            hisGrid,
            strDialFreq,
            (mode == "MFSK" ? "JS8" : mode),
            rptSent,
            rptRcvd,
            comments,
            name};

        if (!additionalFields.isEmpty()) {
            foreach (auto value, additionalFields.values()) {
                logEntryItems.append(value.toString());
            }
        }

        QTextStream out(&f);
        out << logEntryItems.join(",") << Qt::endl;
        out.flush();
        flushFileBuffer(f);
        f.close();
    }

    Q_EMIT acceptQSO(m_dateTimeOff, hisCall, hisGrid, m_dialFreq, mode, submode,
                     rptSent, rptRcvd, comments, name, m_dateTimeOn,
                     operator_call, m_myCall, m_myGrid, ADIF, additionalFields);

    // Clean up and finish logging
    ui->call->clear();

    QDialog::accept();
}

// closeEvent is only called from the system menu close widget for a
// modeless dialog so we use the hideEvent override to store the
// window settings
void LogQSO::hideEvent(QHideEvent *e) {
    storeSettings();
    QDialog::hideEvent(e);
}

Q_LOGGING_CATEGORY(logqso_js8, "logqso.js8", QtWarningMsg)
