#include "astro.h"

#include <stdio.h>

#include <QApplication>
#include <QFile>
#include <QTextStream>
#include <QSettings>
#include <QDateTime>
#include <QTimeZone>
#include <QDir>
#include <QCloseEvent>
#include <QDebug>
#include <math.h>

#include "commons.h"
#include "MessageBox.hpp"
#include "Configuration.hpp"
#include "SettingsGroup.hpp"
#include "qt_helpers.hpp"

#include "ui_astro.h"
#include "moc_astro.cpp"


extern "C" {
  void astrosub(int nyear, int month, int nday, double uth, double freqMoon,
                const char * mygrid, const char * hisgrid,
                double * azsun, double * elsun, double * azmoon,
                double * elmoon, double * azmoondx, double * elmoondx, int * ntsky,
                int * ndop, int * ndop00, double * ramoon, double * decmoon, double * dgrd,
                double * poloffset, double * xnr, double * techo, double * width1,
                double * width2, bool bTx, const char * AzElFileName,
                const char * jpleph);
}

Astro::Astro(QSettings * settings, Configuration const * configuration, QWidget * parent)
  : QDialog {parent, Qt::WindowTitleHint}
  , settings_ {settings}
  , configuration_ {configuration}
  , ui_ {new Ui::Astro}
  , m_DopplerMethod {0}
  , m_dop {0}
  , m_dop00 {0}
  //, m_dx_two_way_dop {0}
{
  ui_->setupUi (this);
  setWindowTitle (QApplication::applicationName () + " - " + tr ("Astronomical Data"));
  setBackgroundRole (QPalette::Base);
  setAutoFillBackground (true);
  connect (ui_->cbDopplerTracking, &QAbstractButton::toggled, ui_->doppler_widget, &QWidget::setVisible);
  read_settings ();
  ui_->text_label->clear ();
}

Astro::~Astro ()
{
  Q_EMIT tracking_update ();
  if (isVisible ()) write_settings ();
}

void Astro::closeEvent (QCloseEvent * e)
{
  write_settings ();
  e->ignore ();                 // do not allow closure by the window system
}

void Astro::read_settings ()
{
  SettingsGroup g (settings_, "Astro");
  bool b=settings_->value("DopplerTracking",false).toBool();
  ui_->cbDopplerTracking->setChecked(b);
  ui_->doppler_widget->setVisible (ui_->cbDopplerTracking->isChecked ());
  m_DopplerMethod=settings_->value("DopplerMethod",0).toInt();
  switch (m_DopplerMethod)
    {
    case 0: ui_->rbNoDoppler->setChecked (true); break;
    case 1: ui_->rbFullTrack->setChecked (true); break;
    case 2: ui_->rbConstFreqOnMoon->setChecked (true); break;
    case 3: ui_->rbOwnEcho->setChecked (true); break;
    case 4: ui_->rbOnDxEcho->setChecked (true); break;
    case 5: ui_->rbCallDx->setChecked (true); break;
    }
  move (settings_->value ("window/pos", pos ()).toPoint ());
}

void Astro::write_settings ()
{
  SettingsGroup g (settings_, "Astro");
  settings_->setValue ("DopplerTracking", ui_->cbDopplerTracking->isChecked());
  settings_->setValue ("DopplerMethod",m_DopplerMethod);
  settings_->setValue ("window/pos", pos ());
}

auto Astro::astroUpdate(QDateTime const& t, QString const& mygrid, QString const& hisgrid, Frequency freq,
                        bool bEchoMode, bool bTx, bool bAuto, bool no_tx_QSY, double TR_period) -> Correction
{
  Frequency freq_moon {freq};
  double azsun,elsun,azmoon,elmoon,azmoondx,elmoondx;
  double ramoon,decmoon,dgrd,poloffset,xnr,techo,width1,width2;
  int ntsky;
  QString date {t.date().toString("yyyy MMM dd").trimmed ()};
  QString utc {t.time().toString().trimmed ()};
  int nyear {t.date().year()};
  int month {t.date().month()};
  int nday {t.date().day()};
  int nhr {t.time().hour()};
  int nmin {t.time().minute()};
  double sec {t.time().second() + 0.001*t.time().msec()};
  double uth {nhr + nmin/60.0 + sec/3600.0};
  if(freq_moon < 1) freq_moon = 144000000;
  auto const& AzElFileName = QDir::toNativeSeparators (configuration_->azel_directory ().absoluteFilePath ("azel.dat"));
  auto const& jpleph = configuration_->data_dir ().absoluteFilePath ("JPLEPH");

  astrosub(nyear, month, nday, uth, static_cast<double> (freq_moon),
           mygrid.toLatin1 ().data (),
           hisgrid.toLatin1().data(),
           &azsun, &elsun, &azmoon, &elmoon,
           &azmoondx, &elmoondx, &ntsky, &m_dop, &m_dop00, &ramoon, &decmoon,
           &dgrd, &poloffset, &xnr, &techo, &width1, &width2,
           bTx,
           AzElFileName.toLocal8Bit ().constData (),
           jpleph.toLocal8Bit ().constData ());

  QString message;
  {
    QTextStream out {&message};
    out << " " << date << "\n"
      "UTC:  " << utc << "\n"
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
        << Qt::fixed
#else
      << fixed
#endif
      << qSetFieldWidth (6)
      << qSetRealNumberPrecision (1)
      << "Az:     " << azmoon << "\n"
      "El:     " << elmoon << "\n"
      "SelfDop:" << m_dop00 << "\n"
      "Width:  " << int(width1+0.5) << "\n"
      << qSetRealNumberPrecision (2)
      << "Delay:  " << techo << "\n"
      << qSetRealNumberPrecision (1)
      << "DxAz:   " << azmoondx << "\n"
      "DxEl:   " << elmoondx << "\n"
      "DxDop:  " << m_dop << "\n"
      "DxWid:  " << int(width2+0.5) << "\n"
      "Dec:    " << decmoon << "\n"
      "SunAz:  " << azsun << "\n"
      "SunEl:  " << elsun << "\n"
      "Freq:   " << freq / 1.e6 << "\n";
    if(freq>=5000000ull) {                     //Suppress data not relevant below VHF
      out << "Tsky:   " << ntsky << "\n"
        "Dpol:   " << poloffset << "\n"
        "MNR:    " << xnr << "\n"
        "Dist:   " << int((techo*149896)) << "\n" //wdg
        "Dgrd:   " << dgrd;
    }
  }
  ui_->text_label->setText(message);

  Correction correction;
  correction.dop=m_dop00;
  correction.width=width1;
  if(hisgrid!="" and !bAuto) {
    correction.dop=m_dop;
    correction.width=width2;
  }
  if (ui_->cbDopplerTracking->isChecked()) {
    ui_->sbRIT->setEnabled(bEchoMode and m_DopplerMethod==0);
    switch (m_DopplerMethod)
      {
      case 1: // All Doppler correction done here; DX station stays at nominal dial frequency.
        correction.rx =  m_dop;
        break;
      case 4: // All Doppler correction done here; DX station stays at nominal dial frequency. (Trial for OnDxEcho)
        correction.rx =  m_dop;
        break;
        //case 5: // All Doppler correction done here; DX station stays at nominal dial frequency.
      case 3: // Both stations do full correction on Rx and none on Tx
        //correction.rx = bEchoMode ? m_dop00 : m_dop;
        correction.rx =  m_dop00; // Now always sets RX to *own* echo freq
        break;
      case 2:
        // Doppler correction to constant frequency on Moon
        correction.rx = m_dop00 / 2;
        break;
      }
    switch (m_DopplerMethod)
      {
      case 1: correction.tx = -correction.rx;
        break;
      case 2: correction.tx = -correction.rx;
        break;
      case 3: correction.tx = 0;
        break;
      case 4: // correction.tx = m_dop - m_dop00;
        correction.tx = (2 * (m_dop - (m_dop00/2))) - m_dop;
        //qDebug () << "correction.tx:" << correction.tx;
        break;
      case 5: correction.tx = - m_dop00;
        break;
      }
    //if (3 != m_DopplerMethod || 4 != m_DopplerMethod) correction.tx = -correction.rx;
    
    if(bEchoMode && m_DopplerMethod == 1) correction.rx = 0;

    if (no_tx_QSY && 3 != m_DopplerMethod && 0 != m_DopplerMethod)
      {
       // calculate a single correction for transmit half way through
       // the period as a compromise for rigs that can't CAT QSY
       // while transmitting
        //
        // use a base time of (secs-since-epoch + 2) so as to be sure
        // we do the next period if we calculate just before it starts
        auto sec_since_epoch = t.toMSecsSinceEpoch ()/1000 + 2;
        auto target_sec = sec_since_epoch - fmod(double(sec_since_epoch),TR_period) + 0.5*TR_period;
        auto target_date_time = QDateTime::fromMSecsSinceEpoch (target_sec * 1000, Qt::UTC);
        int nyear {target_date_time.date().year()};
        int month {target_date_time.date().month()};
        int nday {target_date_time.date().day()};
        int nhr {target_date_time.time().hour()};
        int nmin {target_date_time.time().minute()};
        double sec {target_date_time.time().second() + 0.001*target_date_time.time().msec()};
        double uth {nhr + nmin/60.0 + sec/3600.0};
        astrosub(nyear, month, nday, uth, static_cast<double> (freq_moon),
                  mygrid.toLatin1 ().data (),
                  hisgrid.toLatin1().data(),
                  &azsun, &elsun, &azmoon, &elmoon,
                  &azmoondx, &elmoondx, &ntsky, &m_dop, &m_dop00, &ramoon, &decmoon,
                  &dgrd, &poloffset, &xnr, &techo, &width1, &width2,
                  bTx,
                  nullptr,      // don't overwrite azel.dat
                  jpleph.toLocal8Bit ().constData ());
        FrequencyDelta offset {0};
        switch (m_DopplerMethod)
          {
          case 1:
            // All Doppler correction done here; DX station stays at nominal dial frequency.
            offset = bEchoMode ? m_dop00 : m_dop;
            break;

          case 2:
            // Doppler correction to constant frequency on Moon
            offset = m_dop00 / 2;
            break;

          case 4:
            // Doppler correction for OnDxEcho
            offset = m_dop - (2 * (m_dop - (m_dop00/2)));
            break;

            //case 5: correction.tx = - m_dop00;
          case 5: offset = m_dop00;// version for _7
            break;
          }
        correction.tx = -offset;
        //qDebug () << "correction.tx (no tx qsy):" << correction.tx;
      }
  }

//  qDebug() << "AA0" << m_DopplerMethod << bAuto << correction.tx << correction.rx << correction.width;
  return correction;
}

void Astro::check_split ()
{
  /*  TEMPORARILY DISABLE
  if (doppler_tracking () && !configuration_->split_mode ())
    {
      MessageBox::warning_message (this, tr ("Doppler Tracking Error"),
                                   tr ("Split operating is required for Doppler tracking"),
                                   tr ("Go to \"Menu->File->Settings->Radio\" to enable split operation"));
      ui_->rbNoDoppler->click ();
    }
  */
}

void Astro::on_rbFullTrack_clicked(bool)
{
  m_DopplerMethod = 1;
  check_split ();
  Q_EMIT tracking_update ();
}

void Astro::on_rbOnDxEcho_clicked(bool)
{
  m_DopplerMethod = 4;
  check_split ();
  //if (checked) {
  //  m_dx_two_way_dop = 2 * (m_dop - (m_dop00/2));
  //  qDebug () << "Starting Doppler:" << m_dx_two_way_dop;
  //}
  Q_EMIT tracking_update ();
}

void Astro::on_rbOwnEcho_clicked(bool)
{
  m_DopplerMethod = 3;
  check_split ();
  Q_EMIT tracking_update ();
}

void Astro::on_rbCallDx_clicked(bool)
{
  m_DopplerMethod = 5;
  check_split ();
  Q_EMIT tracking_update ();
}

void Astro::on_rbConstFreqOnMoon_clicked(bool)
{
  m_DopplerMethod = 2;
  check_split ();
  Q_EMIT tracking_update ();
}

void Astro::on_rbNoDoppler_clicked(bool)
{
  m_DopplerMethod = 0;
  Q_EMIT tracking_update ();
}

bool Astro::doppler_tracking () const
{
  return ui_->cbDopplerTracking->isChecked () && m_DopplerMethod;
}

void Astro::on_cbDopplerTracking_toggled(bool)
{
  check_split ();
  Q_EMIT tracking_update ();
}

void Astro::nominal_frequency (Frequency rx, Frequency tx)
{
  ui_->sked_frequency_label->setText (Radio::pretty_frequency_MHz_string (rx));
  ui_->sked_tx_frequency_label->setText (Radio::pretty_frequency_MHz_string (tx));
}

void Astro::hideEvent (QHideEvent * e)
{
  Q_EMIT tracking_update ();
  QWidget::hideEvent (e);
}

bool Astro::bDither()
{
  return ui_->cbDither->isChecked();
}

void Astro::selectOwnEcho()
{
  ui_->rbOwnEcho->click();
}

void Astro::selectOnDxEcho()
{
  ui_->rbOnDxEcho->click();
}

qint32 Astro::nfRIT()
{
  if(m_DopplerMethod==0) {
    return ui_->sbRIT->value();
  } else {
    return 0;
  }
}

qint32 Astro::DopplerMethod()
{
  return m_DopplerMethod;
}
