#include "logqso.h"

#include <QString>
#include <QSettings>
#include <QStandardPaths>
#include <QDir>
#include <QDebug>
#include <QUdpSocket>

#include "logbook/adif.h"
#include "MessageBox.hpp"
#include "Configuration.hpp"
#include "Bands.hpp"
#include "MaidenheadLocatorValidator.hpp"
#include "DriftingDateTime.h"

#include "ui_logqso.h"
#include "moc_logqso.cpp"

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 MaidenheadLocatorValidator {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->setAutoCompletion(true);
    c->setAutoCompletionCaseSensitivity(Qt::CaseInsensitive);
    c->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    connect(c, &QComboBox::currentTextChanged, this, [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();
}

QMap<QString, QVariant> LogQSO::collectAdditionalFields(){
    QMap<QString, QVariant> 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,
                        bool toDATA, bool dBtoComments, bool bFox, 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(dBtoComments) {
    QString t=mode;
    if(rptSent!="") t+="  Sent: " + rptSent;
    if(rptRcvd!="") t+="  Rcvd: " + rptRcvd;
    ui->comments->setText(t);
  }

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

  if(toDATA) mode="DATA";

  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);

  if(bFox) {
    accept();
  } else {
    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::DataLocation)}.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))
  {
    MessageBox::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::DataLocation)}.absoluteFilePath ("js8call.log")};
  if(!f.open(QIODevice::Text | QIODevice::Append)) {
    MessageBox::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(",") << 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);
}
