/* -------------------------------------------------------------------------
 * BKG NTRIP Client
 * -------------------------------------------------------------------------
 *
 * Class:      bncRinex
 *
 * Purpose:    writes RINEX files
 *
 * Author:     L. Mervart
 *
 * Created:    27-Aug-2006
 *
 * Changes:    
 *
 * -----------------------------------------------------------------------*/

#include <QSettings>
#include <QDir>
#include <QUrl>
#include <QDate>
#include <QFile>
#include <QTextStream>
#include <iomanip>

#include "bncrinex.h"
#include "bncapp.h"
#include "bncutils.h"
#include "bncconst.h"

using namespace std;

// Constructor
////////////////////////////////////////////////////////////////////////////
bncRinex::bncRinex(const char* StatID) {
  _statID        = StatID;
  _headerWritten = false;
  readSkeleton();

  QSettings settings;
  _rnxScriptName = settings.value("rnxScript").toString();
  expandEnvVar(_rnxScriptName);

  // Find the corresponding mountPoint
  // ---------------------------------
  QListIterator<QString> it(settings.value("mountPoints").toStringList());
  while (it.hasNext()) {
    QString hlp = it.next();
    if (hlp.indexOf(_statID) != -1) {
      QUrl url(hlp);
      _mountPoint = url.host() + url.path();
      break;
    }
  }

  _pgmName  = ((bncApp*)qApp)->bncVersion().leftJustified(20, ' ', true);
  _userName = QString("${USER}");
  expandEnvVar(_userName);
  _userName = _userName.leftJustified(20, ' ', true);
}

// Destructor
////////////////////////////////////////////////////////////////////////////
bncRinex::~bncRinex() {
  _out.close();
}

// Read Skeleton Header File
////////////////////////////////////////////////////////////////////////////
void bncRinex::readSkeleton() {

  // Resolve Skeleton File Name
  // --------------------------
  QSettings settings;
  QString sklName = settings.value("rnxPath").toString();
  expandEnvVar(sklName);
  if ( sklName[sklName.length()-1] != QDir::separator() ) {
    sklName += QDir::separator();
  }
  sklName += _statID.left(4) + "." + settings.value("rnxSkel").toString();

  // Read the File
  // -------------
  QFile skl(sklName);
  if ( skl.exists() && skl.open(QIODevice::ReadOnly) ) {
    QTextStream in(&skl);
    while ( !in.atEnd() ) {
      _headerLines.append( in.readLine() );
      if (_headerLines.last().indexOf("END OF HEADER") != -1) {
        break;
      }
    }
  }
}

// File Name according to RINEX Standards
////////////////////////////////////////////////////////////////////////////
void bncRinex::resolveFileName(const QDateTime& datTim) {

  QSettings settings;
  QString path = settings.value("rnxPath").toString();
  expandEnvVar(path);

  if ( path[path.length()-1] != QDir::separator() ) {
    path += QDir::separator();
  }

  QString intStr = settings.value("rnxIntr").toString();
  QString hlpStr;

  QTime nextTime;
  QDate nextDate;

  if (intStr == "5 min") {
    char ch = 'A' + datTim.time().hour();
    hlpStr = ch;
    if      (datTim.time().minute() < 5) {
      hlpStr += "00";
      nextTime.setHMS(datTim.time().hour(), 5, 0);
      nextDate = datTim.date();
    }
    else if (datTim.time().minute() < 10) {
      hlpStr += "5";
      nextTime.setHMS(datTim.time().hour(), 10, 0);
      nextDate = datTim.date();
    }
    else if (datTim.time().minute() < 15) {
      hlpStr += "10";
      nextTime.setHMS(datTim.time().hour(), 15, 0);
      nextDate = datTim.date();
    }
    else if (datTim.time().minute() < 20) {
      hlpStr += "15";
      nextTime.setHMS(datTim.time().hour(), 20, 0);
      nextDate = datTim.date();
    }
    else if (datTim.time().minute() < 25) {
      hlpStr += "20";
      nextTime.setHMS(datTim.time().hour(), 25, 0);
      nextDate = datTim.date();
    }
    else if (datTim.time().minute() < 30) {
      hlpStr += "25";
      nextTime.setHMS(datTim.time().hour(), 30, 0);
      nextDate = datTim.date();
    }
    else if (datTim.time().minute() < 35) {
      hlpStr += "30";
      nextTime.setHMS(datTim.time().hour(), 35, 0);
      nextDate = datTim.date();
    }
    else if (datTim.time().minute() < 40) {
      hlpStr += "35";
      nextTime.setHMS(datTim.time().hour(), 40, 0);
      nextDate = datTim.date();
    }
    else if (datTim.time().minute() < 45) {
      hlpStr += "40";
      nextTime.setHMS(datTim.time().hour(), 45, 0);
      nextDate = datTim.date();
    }
    else if (datTim.time().minute() < 50) {
      hlpStr += "45";
      nextTime.setHMS(datTim.time().hour(), 50, 0);
      nextDate = datTim.date();
    }
    else if (datTim.time().minute() < 55) {
      hlpStr += "50";
      nextTime.setHMS(datTim.time().hour(), 55, 0);
      nextDate = datTim.date();
    }
    else {
      hlpStr += "55";
      if (datTim.time().hour() < 23) {
        nextTime.setHMS(datTim.time().hour() + 1 , 0, 0);
        nextDate = datTim.date();
      }
      else {
        nextTime.setHMS(0, 0, 0);
        nextDate = datTim.date().addDays(1);
      }
    }
  }
  else if (intStr == "10 min") {
    char ch = 'A' + datTim.time().hour();
    hlpStr = ch;
    if      (datTim.time().minute() < 10) {
      hlpStr += "00";
      nextTime.setHMS(datTim.time().hour(), 10, 0);
      nextDate = datTim.date();
    }
    else if (datTim.time().minute() < 20) {
      hlpStr += "10";
      nextTime.setHMS(datTim.time().hour(), 20, 0);
      nextDate = datTim.date();
    }
    else if (datTim.time().minute() < 30) {
      hlpStr += "20";
      nextTime.setHMS(datTim.time().hour(), 30, 0);
      nextDate = datTim.date();
    }
    else if (datTim.time().minute() < 40) {
      hlpStr += "30";
      nextTime.setHMS(datTim.time().hour(), 40, 0);
      nextDate = datTim.date();
    }
    else if (datTim.time().minute() < 50) {
      hlpStr += "40";
      nextTime.setHMS(datTim.time().hour(), 50, 0);
      nextDate = datTim.date();
    }
    else {
      hlpStr += "50";
      if (datTim.time().hour() < 23) {
        nextTime.setHMS(datTim.time().hour() + 1 , 0, 0);
        nextDate = datTim.date();
      }
      else {
        nextTime.setHMS(0, 0, 0);
        nextDate = datTim.date().addDays(1);
      }
    }
  }
  else if (intStr == "15 min") {
    char ch = 'A' + datTim.time().hour();
    hlpStr = ch;
    if      (datTim.time().minute() < 15) {
      hlpStr += "00";
      nextTime.setHMS(datTim.time().hour(), 15, 0);
      nextDate = datTim.date();
    }
    else if (datTim.time().minute() < 30) {
      hlpStr += "15";
      nextTime.setHMS(datTim.time().hour(), 30, 0);
      nextDate = datTim.date();
    }
    else if (datTim.time().minute() < 45) {
      hlpStr += "30";
      nextTime.setHMS(datTim.time().hour(), 45, 0);
      nextDate = datTim.date();
    }
    else {
      hlpStr += "45";
      if (datTim.time().hour() < 23) {
        nextTime.setHMS(datTim.time().hour() + 1 , 0, 0);
        nextDate = datTim.date();
      }
      else {
        nextTime.setHMS(0, 0, 0);
        nextDate = datTim.date().addDays(1);
      }
    }
  }
  else if (intStr == "30 min") {
    char ch = 'A' + datTim.time().hour();
    hlpStr = ch;
    if      (datTim.time().minute() < 30) {
      hlpStr += "00";
      nextTime.setHMS(datTim.time().hour(), 30, 0);
      nextDate = datTim.date();
    }
    else {
      hlpStr += "30";
      if (datTim.time().hour() < 23) {
        nextTime.setHMS(datTim.time().hour() + 1 , 0, 0);
        nextDate = datTim.date();
      }
      else {
        nextTime.setHMS(0, 0, 0);
        nextDate = datTim.date().addDays(1);
      }
    }
  }
  else if (intStr == "1 hour") {
    char ch = 'A' + datTim.time().hour();
    hlpStr = ch;
    if (datTim.time().hour() < 23) {
      nextTime.setHMS(datTim.time().hour() + 1 , 0, 0);
      nextDate = datTim.date();
    }
    else {
      nextTime.setHMS(0, 0, 0);
      nextDate = datTim.date().addDays(1);
    }
  }
  else {
    hlpStr = "0";
    nextTime.setHMS(0, 0, 0);
    nextDate = datTim.date().addDays(1);
  }
  _nextCloseEpoch = QDateTime(nextDate, nextTime);

  QString ID4 = _statID.left(4);

  // Check name conflict
  // -------------------
  QString distStr;
  int num = 0;
  QListIterator<QString> it(settings.value("mountPoints").toStringList());
  while (it.hasNext()) {
    QString mp = it.next();
    if (mp.indexOf(ID4) != -1) {
      ++num;
    }
  }
  if (num > 1) {
    distStr = "_" + _statID.mid(4);
  }

  path += ID4 +
          QString("%1").arg(datTim.date().dayOfYear(), 3, 10, QChar('0')) +
          hlpStr + distStr +
          datTim.toString(".yyO");

  _fName = path.toAscii();
}

// Write RINEX Header
////////////////////////////////////////////////////////////////////////////
void bncRinex::writeHeader(const QDateTime& datTim) {

  // Open the Output File
  // --------------------
  resolveFileName(datTim);
  _out.open(_fName.data());
  _out.setf(ios::showpoint | ios::fixed);

  // Copy Skeleton Header
  // --------------------
  if (_headerLines.size() > 0) {
    QStringListIterator it(_headerLines);
    while (it.hasNext()) {
      QString line = it.next();
      if      (line.indexOf("PGM / RUN BY / DATE") != -1) {
        QString hlp = QDate::currentDate().toString("dd-MMM-yyyy").leftJustified(20, ' ', true);
        _out << _pgmName.toAscii().data() << _userName.toAscii().data() 
             << hlp.toAscii().data() << "PGM / RUN BY / DATE" << endl;
      }
      else if (line.indexOf("# / TYPES OF OBSERV") != -1) {
        _out << "     4    P1    P2    L1    L2"
                "                              # / TYPES OF OBSERV"  << endl;
      }
      else if (line.indexOf("TIME OF FIRST OBS") != -1) {
        _out << datTim.toString("  yyyy    MM    dd"
                                "    hh    mm   ss.zzz0000").toAscii().data();
        _out << "                 TIME OF FIRST OBS"    << endl;
        QString hlp = QString("STREAM %1").arg(_mountPoint)
                             .leftJustified(60, ' ', true);
        _out << hlp.toAscii().data() << "COMMENT" << endl;
      }
      else {
        _out << line.toAscii().data() << endl;
      }
    }
  }

  // Write Dummy Header
  // ------------------
  else {
    double approxPos[3];  approxPos[0]  = approxPos[1]  = approxPos[2]  = 0.0;
    double antennaNEU[3]; antennaNEU[0] = antennaNEU[1] = antennaNEU[2] = 0.0;
    
    _out << "     2.10           OBSERVATION DATA    G (GPS)             RINEX VERSION / TYPE" << endl;
    QString hlp = QDate::currentDate().toString("dd-MMM-yyyy").leftJustified(20, ' ', true);
    _out << _pgmName.toAscii().data() << _userName.toAscii().data() 
         << hlp.toAscii().data() << "PGM / RUN BY / DATE" << endl;
    _out.setf(ios::left);
    _out << setw(60) << _statID.data()                               << "MARKER NAME"          << endl;
    _out << setw(60) << "BKG"                                        << "OBSERVER / AGENCY"    << endl;
    _out << setw(20) << "unknown"    
         << setw(20) << "unknown"
         << setw(20) << "unknown"                                    << "REC # / TYPE / VERS"  << endl;
    _out << setw(20) << "unknown"
         << setw(20) << "unknown"
         << setw(20) << " "                                          << "ANT # / TYPE"         << endl;
    _out.unsetf(ios::left);
    _out << setw(14) << setprecision(4) << approxPos[0]
         << setw(14) << setprecision(4) << approxPos[1]
         << setw(14) << setprecision(4) << approxPos[2] 
         << "                  "                                     << "APPROX POSITION XYZ"  << endl;
    _out << setw(14) << setprecision(4) << antennaNEU[0]
         << setw(14) << setprecision(4) << antennaNEU[1]
         << setw(14) << setprecision(4) << antennaNEU[2] 
         << "                  "                                     << "ANTENNA: DELTA H/E/N" << endl;
    _out << "     1     1                                                WAVELENGTH FACT L1/2" << endl;
    _out << "     4    P1    P2    L1    L2                              # / TYPES OF OBSERV"  << endl;
        _out << datTim.toString("  yyyy    MM    dd"
                                "    hh    mm   ss.zzz0000").toAscii().data();
    _out << "                 "                                      << "TIME OF FIRST OBS"    << endl;
    hlp = QString("STREAM %1").arg(_mountPoint)
                                             .leftJustified(60, ' ', true);
    _out << hlp.toAscii().data() << "COMMENT" << endl;
    _out << "                                                            END OF HEADER"        << endl;
  }

  _headerWritten = true;
}

// Stores Observation into Internal Array
////////////////////////////////////////////////////////////////////////////
void bncRinex::deepCopy(const Observation* obs) {
  Observation* newObs = new Observation();
  memcpy(newObs, obs, sizeof(*obs));
  _obs.push_back(newObs);
}

// Write One Epoch into the RINEX File
////////////////////////////////////////////////////////////////////////////
void bncRinex::dumpEpoch(long maxTime) {

  // Select observations older than maxTime
  // --------------------------------------
  QList<Observation*> dumpList;
  QMutableListIterator<Observation*> mIt(_obs);
  while (mIt.hasNext()) {
    Observation* ob = mIt.next();
    if (ob->GPSWeek * 7*24*3600 + ob->GPSWeeks < maxTime) {
      dumpList.push_back(ob);
      mIt.remove();
    }
  }

  // Easy Return
  // -----------
  if (dumpList.isEmpty()) {
    return;
  }

  // Time of Epoch
  // -------------
  Observation* firstObs = *dumpList.begin();

  QDateTime datTim = dateAndTimeFromGPSweek( firstObs->GPSWeek,
                                             firstObs->GPSWeeks + 
                                             fmod(firstObs->sec, 1.0) );

  // Close the file
  // --------------
  if (_nextCloseEpoch.isValid() && datTim >= _nextCloseEpoch) {
    closeFile();
    _headerWritten = false;
  }

  // Write RINEX Header
  // ------------------
  if (!_headerWritten) {
    writeHeader(datTim);
  }

  _out << datTim.toString(" yy MM dd hh mm ss.zzz0000").toAscii().data()
       << "  " << 0 << setw(3)  << dumpList.size();

  QListIterator<Observation*> it(dumpList); int iSat = 0;
  while (it.hasNext()) {
    iSat++;
    Observation* ob = it.next();
    _out << " " << setw(2) << int(ob->SVPRN);
    if (iSat == 12 && it.hasNext()) {
      _out << endl << "                                ";
      iSat = 0;
    }
  }
  _out << endl;

  it.toFront();
  while (it.hasNext()) {
    Observation* ob = it.next();

    char lli = ' ';
    char snr = ' ';
    _out << setw(14) << setprecision(3) << ob->C1 << lli << snr;
    _out << setw(14) << setprecision(3) << ob->P2 << lli << snr; 
    _out << setw(14) << setprecision(3) << ob->L1 / t_CST::lambda1 << lli << snr; 
    _out << setw(14) << setprecision(3) << ob->L2 / t_CST::lambda2 << lli << snr; 
    _out << endl;

    delete ob;
  }

  _out.flush();
}

// Close the Old RINEX File
////////////////////////////////////////////////////////////////////////////
void bncRinex::closeFile() {
  _out.close();
  if (!_rnxScriptName.isEmpty()) {
    _rnxScript.start(_rnxScriptName, QStringList() << _fName);
  }
}