#ifndef SSRQC_H
#define SSRQC_H

#include <QtCore>
#include <fstream>
#include "bncephuser.h"
#include "bnctime.h"
#include "satObs.h"

// Quality control of the decoded SSR streams: epochs and update intervals,
// latency, completeness with respect to the broadcast ephemerides, IOD
// matching, code bias signals, clock jumps, orbit correction size and the
// ionospheric (VTEC) models. A
// report per stream and system is written to the QC logfile at the end of
// each interval and at the end of the program.
////////////////////////////////////////////////////////////////////////////
class bncBiasSnx;

class t_ssrQc : public QObject {
 Q_OBJECT

 public:
  t_ssrQc(const QString& dirName, double interval, const QStringList& streams,
          const QString& reference);
  ~t_ssrQc();

 public slots:
  void slotNewOrbCorrections(QList<t_orbCorr> orbCorrections);
  void slotNewClkCorrections(QList<t_clkCorr> clkCorrections);
  void slotNewCodeBiases(QList<t_satCodeBias> codeBiases);
  void slotNewPhaseBiases(QList<t_satPhaseBias> phaseBiases);
  void slotNewTec(t_vTec vTec);

 signals:
  void newMessage(QByteArray msg, bool showOnScreen);

 private:
  enum e_type {ORB = 0, CLK = 1, CBIAS = 2, PBIAS = 3, VTEC = 4};

  class t_epoStat {          // per stream, system and message type
   public:
    t_epoStat() : nominal(0), numEpo(0), numOutOfOrder(0) {}
    int             nominal;       // nominal update interval [s]
    unsigned        numEpo;
    unsigned        numOutOfOrder;
    bncTime         last;
    QVector<double> intervals;
    QVector<double> latencies;
    QSet<QString>   sats;
  };

  class t_satStat {          // per stream and satellite
   public:
    t_satStat() : numOrb(0), numClk(0), iodUnknown(0), ephUnhealthy(0), noEph(0),
                  clkJumps(0), lastIod(0), lastClk(0.0), maxJump(0.0),
                  maxRadial(0.0), maxAlongCross(0.0) {}
    unsigned      numOrb;
    unsigned      numClk;
    unsigned      iodUnknown;    // IOD of no received broadcast ephemeris
    unsigned      ephUnhealthy;  // broadcast ephemeris unhealthy/bad
    unsigned      noEph;         // no broadcast ephemeris at all
    unsigned      clkJumps;
    bncTime       lastClkTime;
    unsigned      lastIod;
    double        lastClk;       // [m]
    double        maxJump;       // [m]
    double        maxRadial;     // [m]
    double        maxAlongCross; // [m]
    QSet<QString> missingRefSig; // reference signals without code bias
    bool          refBiasOK = false;
    double        biasIF = 0.0;  // ionosphere-free combination of the reference biases [m]
    unsigned      numPB  = 0;    // phase bias records
    unsigned      numYaw = 0;    // phase bias records with yaw angle
  };

  void   newEpoch(const std::string& staID, char sys, e_type type, const bncTime& time,
                  unsigned updateInt, const QString& prn);
  void   checkEph(t_satStat& stat, const QString& prnInt, unsigned iod, const bncTime& time,
                  const std::string& staID);
  void   checkInterval(const bncTime& time);
  void   writeReport(bool final);
  static QString key(const std::string& staID, char sys, e_type type);
  static bool    refSignals(char sys, QString& sig1, QString& sig2,
                            t_frequency::type& frq1, t_frequency::type& frq2);

  QMutex                    _mutex;
  bool           selected(const std::string& staID) const;
  bool           votes(const QString& staID) const;
  static QString acOf(const QString& staID);
  static QString solOf(const QString& staID);   // AC solution, e.g. GFZ00, GFZ01
  static QString acOfSol(const QString& sol);

  // Stream comparison: every CMP_SAMPL seconds, the orbits and clocks of each
  // stream are compared with a reference: the consensus (median) of all
  // other streams, or a selected stream (e.g. INTERNAL, the combination)
  class t_cmpStat {          // per stream and satellite
   public:
    t_cmpStat() : numClk(0), numOrb(0), numSis(0), clkNotReferred(0),
                  sClk(0.0), sR(0.0), sA(0.0), sC(0.0), sSis(0.0), mClk(0.0), mSis(0.0) {}
    unsigned numClk;
    unsigned numOrb;
    unsigned numSis;
    unsigned clkNotReferred;          // epochs without reference signal biases
    double   sClk, sR, sA, sC, sSis;  // sums of squares [m^2]
    double   mClk, mSis;              // sums [m]
  };
  class t_satCrd {           // satellite position, velocity, clock of a stream
   public:
    ColumnVector xx;   // [m]
    ColumnVector vv;   // [m/s]
    double       clk;  // [m], referred to the reference signals (+ B_IF) if clkOK
    bool         clkOK;
    char         refPoint; // 'A' APC, 'C' CoM, '?' unknown
  };
  void   storeHist(const t_orbCorr* orb, const t_clkCorr* clk);
  void   compareEpoch(const bncTime& tt);
  char   refPoint(const QString& staID) const;
  QString                                      _reference;   // "CONSENSUS" or stream name
  bncTime                                      _nextCmpEpoch;
  QMap<QString, QMap<QString, QMap<double, t_orbCorr> > > _orbHist; // stream, PRN (internal), time
  QMap<QString, QMap<QString, QMap<double, t_clkCorr> > > _clkHist;
  QMap<QString, QMap<QString, t_cmpStat> >     _cmpStat;     // stream, PRN
  QMap<QString, unsigned>                      _cmpEpochs;   // stream: compared epochs
  std::ofstream& streamOut(const QString& staID, const bncTime& time);

  QString                   _dirName;
  QSet<QString>             _streams;   // selected streams, empty: all
  QSet<QString>             _excluded;  // excluded streams ('!' in the selection)
  QMap<QString, std::ofstream*> _outs;  // per stream
  QMap<QString, QString>    _outNames;  // per stream
  double                    _interval;
  bncTime                   _intervalStart;
  bncTime                   _lastTime;
  QMap<QString, bncTime>    _staStart;   // first correction epoch per stream
  QMap<QString, bncTime>    _staLast;    // latest correction epoch per stream
  bncEphUser                _ephUser;
  QMap<QString, t_epoStat>  _epoStat;    // key "STA SYS TYPE"
  QMap<QString, t_satStat>  _satStat;    // key "STA PRN"
  QMap<QString, QSet<QString> > _codeSignals;  // key "STA SYS": signals with code biases
  QMap<QString, QSet<QString> > _phaseSignals; // key "STA SYS": signals with phase biases

  // Code bias check: DCBs of each stream compared with the other ACs (or
  // the reference stream) and optionally with a Bias SINEX file
  void   compareCodeBiases(const QString& staID, char sys, QStringList& lines);
  bool   refDcbs(const QString& staID, const QString& prn, const QString& base, bool fromFile,
                 QMap<QString, double>& dcbs, const QString& onlyAC = QString(),
                 QMap<QString, int>* numVotes = 0);
  QMap<QString, QMap<QString, QMap<QString, double> > > _cbLast; // stream, PRN, signal: latest code bias [m]
  bncBiasSnx*               _bsxRef;    // reference code biases (option ssrQcBiasFile)
  QString                   _bsxName;

  // VTEC check: model content, values on a global grid and comparison with
  // the consensus of the other ACs (or the reference stream)
  class t_vtecStat {         // per stream
   public:
    unsigned numCmp  = 0;     // compared epochs
    unsigned numSol  = 0;     // sum of the solutions in the consensus
    unsigned numDiff = 0;     // compared grid values
    double   sDiff   = 0.0;   // sum of the differences [TECU]
    double   sDiff2  = 0.0;   // sum of squares [TECU^2]
    double   maxAbs  = 0.0;   // [TECU]
    unsigned numGrid = 0;     // evaluated grid values
    unsigned numNeg  = 0;     // negative grid values
    double   minVal  = 0.0;   // [TECU]
    double   maxVal  = 0.0;
    QString  model;           // layers, heights, degree/order of the latest model
  };
  void          compareVtec(const bncTime& tt);
  static double vtecAt(const t_vTec& vTec, double lat, double lon, const bncTime& tt, bool& negative);
  QMap<QString, QMap<double, t_vTec> > _vtecHist;  // stream, time
  QMap<QString, t_vtecStat>            _vtecStat;  // stream
  bncTime                              _nextVtecCmp;
};

#endif
