/***************************************************************************
**
**  This file is part of warangps.
**
**  warangps is free software: you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation, either version 3 of the License, or
**  (at your option) any later version.
**
**  warangps is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**  GNU General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2009-06-27
**  Copyright: 2009-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <GeopsyGui.h>
#include <QGpGuiMath.h>

#include "GpsTableModel.h"
#include "GpsStation.h"
#include "WaranGpsWidget.h"

/*!
  \class WaranGpsWidget WaranGpsWidget.h
  \brief Brief description of class still missing

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
WaranGpsWidget::WaranGpsWidget(QWidget * parent)
    : QWidget(parent)
{
  TRACE;
  setupUi(this);

  _distanceMapErrors=new CircleViewer(distanceMap);

  theoreticalResponse->createObjects(true);

  ringEditor->setCoArrayGraph(coArrayMap);
  coArrayMap->mapLayer()->setShowNames(false);
  _arrayEditAction=arrayMap->mapLayer()->addTrackingAction(tr("&Edit"), NameLineLayer::Edit, tr("Edit points."));
  connect(arrayMap->mapLayer(),SIGNAL(lineEdited(int)), this, SLOT(arrayMapChanged()));

  StationCoordinatesItem * arrayModel=new StationCoordinatesItem(this);
  arrayModel->setStations(&_arrayMultiplied);
  connect(arrayModel,SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(arrayTableChanged()));
  arrayTable->setModel(arrayModel);

  GpsTableModel * gpsModel=new GpsTableModel(this);
  gpsModel->setStations(&_gpsStations);
  gpsTable->setModel(gpsModel);
  gpsTable->setEditTriggers(QAbstractItemView::AllEditTriggers);
  gpsTable->setSelectionMode(QAbstractItemView::SingleSelection);

  connect(&_gpsUpdate, SIGNAL(timeout()), this, SLOT(updateGps()));
  _gpsUpdate.setInterval(10000);
  _gpsUpdate.start();

  updateUtmZone();
}

/*!
  Description of destructor still missing
*/
WaranGpsWidget::~WaranGpsWidget()
{
  TRACE;
  qDeleteAll(_gpsStations);
}

void WaranGpsWidget::polish()
{
  if(!backgroundEdit->text().isEmpty()) {
    arrayMap->loadBackground(backgroundEdit->text());
    arrayMap->setLimitsArray(true);
  }
}

void WaranGpsWidget::closeEvent(QCloseEvent * e)
{
  TRACE;
  e->accept();
}

void WaranGpsWidget::setKmaxThreshold(double t)
{
  TRACE;
  theoreticalResponse->setKmaxThreshold(t);
}

void WaranGpsWidget::setFrequencySampling(const SamplingParameters& param)
{
  TRACE;
  theoreticalResponse->setFrequencySampling(param);
}

void WaranGpsWidget::updateUtmZone()
{
  TRACE;
  if(_utmZone.isValid()) {
    utmZone->show();
    utmZone->setText(QString("UTM %1").arg(_utmZone.toString()));
  } else {
    utmZone->hide();
  }
}

void WaranGpsWidget::on_modeCombo_currentIndexChanged(int index)
{
  TRACE;
  if(index==0) {
    setMode(Gps);
  } else {
    setMode(NoGps);
  }
}

void WaranGpsWidget::setMode(Mode m)
{
  if(m==Gps) {
    if(_gpsStations.isEmpty()) {
      setGpsStations();
      if(_gpsStations.isEmpty()) {
        m=NoGps;
      }
    }
  }
  if(m==Gps) {
    arrayTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
    arrayTable->setSelectionMode(QAbstractItemView::NoSelection);
    _arrayEditAction->setEnabled(false);
  } else {
    arrayTable->setEditTriggers(QAbstractItemView::AllEditTriggers);
    arrayTable->setSelectionMode(QAbstractItemView::SingleSelection);
    _arrayEditAction->setEnabled(true);
  }
  gpsTab->setEnabled(m==Gps);
  distancesTab->setEnabled(m==Gps);
  arrayLoadBut->setEnabled(m==NoGps);
  arrayGenerateBut->setEnabled(m==NoGps);
  modeCombo->blockSignals(true);
  modeCombo->setCurrentIndex(m==Gps ? 0 : 1);
  modeCombo->blockSignals(false);
}

void WaranGpsWidget::setGpsStations(quint16 port)
{
  TRACE;
  QFile f("/etc/hosts");
  if(!f.open(QIODevice::ReadOnly)) {
    Message::warning(MSG_ID, tr("Reading station list"), tr("Cannot read station list in file %1").arg("/etc/hosts"));
    return;
  }
  QTextStream s(&f);
  LineParser lp;
  qDeleteAll(_gpsStations);
  _gpsStations.clear();
  bool ok=true;
  int lineNumber=0;
  while(!s.atEnd()) {
    QString l=s.readLine();
    lineNumber++;
    if(l.isEmpty() || l[0]=='#') {
      continue;
    }
    lp.setString(l);
    QString name=lp.toString(1, ok);
    if(name.startsWith("WAU")) {
      GpsStation * stat=new GpsStation;
      stat->setName(name);
      stat->setHostName(lp.toString(0, ok));
      stat->setPort(port);
      _gpsStations.append(stat);
    }
    if(!ok) {
      Message::warning(MSG_ID, tr("Reading station list"),
                       tr("Error reading line %1 in file %2").arg(lineNumber).arg("/etc/hosts"),
                       Message::ok(), true);
      ok=true;
    }
  }
  // Force reset of table
  static_cast<GpsTableModel *>(gpsTable->model())->setStations(&_gpsStations);
}

void WaranGpsWidget::setCoordinates(QString fileName, QString coordinateFormat)
{
  TRACE;
  CoordinateFileWidget cf;
  if(cf.read(fileName, coordinateFormat)) {
    if(cf.points().count()<=1000) {
      _arrayBase=cf.points();
      _utmZone=cf.utmZone();
      updateUtmZone();
      arrayChanged();
    } else {
      Message::critical(MSG_ID, tr("Loading stations"),
                        tr("Maximum 1000 stations are allowed in an array (%1)\n"
                           "Typical number of stations usually does not exceed 100. Did you "
                           "really want to load coordinates from file '%2'?")
                        .arg(cf.points().count()).arg(fileName));
    }
  }
}

void WaranGpsWidget::on_gpsTrackBut_clicked()
{
  TRACE;
  for(QList<GpsStation *>::iterator it=_gpsStations.begin();it!=_gpsStations.end();it++) {
    (*it)->setMode(GpsStation::Track);
  }
   _gpsUpdate.setInterval(1000);
}

void WaranGpsWidget::on_gpsStackBut_clicked()
{
  TRACE;
  for(QList<GpsStation *>::iterator it=_gpsStations.begin();it!=_gpsStations.end();it++) {
    (*it)->setMode(GpsStation::Stack);
  }
   _gpsUpdate.setInterval(10000);
}

void WaranGpsWidget::updateGps()
{
  TRACE;
  // Force reset of gps results
  static_cast<GpsTableModel *>(gpsTable->model())->resetGpsResults();
  // Update maps
  updateStatistics();
  updateDistanceMap();
  distanceMap->setLimitsArray();
  if(modeCombo->currentIndex()==0) {
    setArrayFromStatistics();
    arrayChanged();
  }
}

void WaranGpsWidget::updateStatistics()
{
  TRACE;
  int n=_gpsStations.count();
  Point refPoint;
  if(referencePoint->isChecked()) {
    refPoint=ref->reference();
  } else {
    // TODO: compute average long,lat from available coordinates
  }
  // Options are currently ignored, convert to UTM
  UtmZone utmZone;
  _xStat.resize(n);
  _yStat.resize(n);
  Point pAverage, pStddev;
  for(int i=0; i<n; i++) {
    GpsStation * stat=_gpsStations.at(i);
    RealStatisticalValue& x=_xStat[i];
    RealStatisticalValue& y=_yStat[i];
    int w=stat->longitude().count();
    if(stat->isAvailable() && w>0) {
      x.setValid(true);
      y.setValid(true);
      int w=stat->longitude().count();
      x.setWeight(w);
      y.setWeight(w);
      // TODO: true Monte Carlo simulation to propagate uncertainties
      pAverage.setX(stat->longitude().mean());
      pAverage.setY(stat->latitude().mean());
      pStddev.setX(pAverage.x()+stat->longitude().stddev());
      pStddev.setY(pAverage.y()+stat->latitude().stddev());
      //pAverage.geographicalToRectangular(refPoint);
      //pStddev.geographicalToRectangular(refPoint);
      pAverage.geographicalToUtm(utmZone);
      pStddev.geographicalToUtm(utmZone);
      x.setMean(pAverage.x());
      y.setMean(pAverage.y());
      x.setStddev(pStddev.x()-pAverage.x());
      y.setStddev(pStddev.y()-pAverage.y());
    } else {
      x.setValid(false);
      y.setValid(false);
    }
  }
}

void WaranGpsWidget::setMultipliedArray()
{
  TRACE;
  int n=_arrayBase.count();
  _arrayMultiplied.clear();
  double factor=arrayCoordinateFactor->value();
  Point o=arrayCenter(_arrayBase);
  for(int i=0; i<n; i++) {
    const NamedPoint& arrayBaseP=_arrayBase.at(i);
    NamedPoint arrayMultipliedP=o+(arrayBaseP-o)*factor;
    arrayMultipliedP.round(0.01);
    arrayMultipliedP.setName(arrayBaseP.name());
    _arrayMultiplied.append(arrayMultipliedP);
  }
}

Point WaranGpsWidget::arrayCenter(const QList<NamedPoint>& array)
{
  TRACE;
  Point p;
  if(!array.isEmpty()) {
    QList<NamedPoint>::const_iterator it=array.begin();
    p=*it;
    for(it++; it!=array.end(); it++) {
      p+=*it;
    }
    p/=array.count();
  }
  return p;
}

void WaranGpsWidget::arrayChanged()
{
  TRACE;
  setMultipliedArray();
  updateArrayMap();
  updateCoArrayMap();
  updateArrayResponse();
  arrayMap->setLimitsArray(true);
  coArrayMap->setLimitsCoArray();
  ringEditor->setArray(_arrayMultiplied);
  ringEditor->updateProperties();
}

void WaranGpsWidget::arrayTableChanged()
{
  TRACE;
  updateArrayMap();
  updateCoArrayMap();
  updateArrayResponse();
  arrayMap->setLimitsArray();
  coArrayMap->setLimitsCoArray();
  ringEditor->updateProperties();
}

void WaranGpsWidget::arrayMapChanged()
{
  TRACE;
  NameLine * l=static_cast<NameLine *>(arrayMap->line(0));
  const Curve<NamedPoint>& c=l->curve();
  double factor=1.0/arrayCoordinateFactor->value();
  Point o=arrayCenter(_arrayBase);
  for (int i=0; i<_arrayBase.count(); i++) {
    const NamedPoint& p=c[i];
    _arrayBase[i]=o+(p-o)*factor;
  }
  setMultipliedArray();
  updateArrayMap();
  updateCoArrayMap();
  updateArrayResponse();
  ringEditor->updateProperties();
}

void WaranGpsWidget::on_arrayLoadBut_clicked()
{
  setCoordinates();
}

void WaranGpsWidget::on_arraySaveBut_clicked()
{
  TRACE;
  CoordinateFileWidget::write(_arrayMultiplied, _utmZone);
}

void WaranGpsWidget::on_arrayCoordinateFactor_valueChanged(double)
{
  TRACE;
  arrayChanged();
}

void WaranGpsWidget::on_relativePositionBut_clicked()
{
  TRACE;
  RelativePositions * d=new RelativePositions(this);
  d->setStationList(&_arrayMultiplied);
  Settings::getWidget(d);
  d->exec();
  Settings::setWidget(d);
  delete d;
}

/*!
*/
void WaranGpsWidget::updateArrayResponse()
{
  TRACE;
  QVector<Point2D> respArray;
  for(int i=_arrayMultiplied.count()-1;i>=0;i--) {
    respArray.append(_arrayMultiplied.at(i).coordinates());
  }
  theoreticalResponse->setArray(respArray);
}

/*!
*/
void WaranGpsWidget::updateDistanceMap()
{
  TRACE;
  LayerLocker ll1(distanceMap->mapLayer());
  int n=_xStat.count();
  NameLine * l=static_cast<NameLine *>(distanceMap->line(0));
  Curve<NamedPoint>& c=l->curve();
  c.clear();
  _distanceMapErrors->clear();
  NamedPoint p;
  for(int i=0;i < n; i++ ) {
    const RealStatisticalValue& sx=_xStat.at(i);
    if(sx.isValid()) {
      p.setName(_gpsStations.at(i)->name());
      p.setX(sx.mean());
      p.setY(_yStat.at(i).mean());
      c.append(p);
      _distanceMapErrors->add(p.x(), p.y(), _xStat.at(i).stddev(), _yStat.at(i).stddev(), 0.0, Qt::black);
    }
  }
}

void WaranGpsWidget::setArrayFromStatistics()
{
  TRACE;

  int n=_xStat.count();
  _arrayBase.clear();
  NamedPoint p;
  for(int i=0;i < n; i++ ) {
    const RealStatisticalValue& sx=_xStat.at(i);
    if(sx.isValid()) {
      p.setX(sx.mean());
      p.setY(_yStat.at(i).mean());
      p.setName(_gpsStations.at(i)->name());
      _arrayBase.append(p);
    }
  }
}

/*!
  Set all points of array map from scratch
*/
void WaranGpsWidget::updateArrayMap()
{
  TRACE;
  LayerLocker ll(arrayMap->mapLayer());
  static_cast<StationCoordinatesItem *>(arrayTable->model())->beginStationChange();
  int n=_arrayMultiplied.count();
  NameLine * l=static_cast<NameLine *>(arrayMap->line(0));
  Curve<NamedPoint>& c=l->curve();
  c.resize(n);
  for(int i=0; i<n; i++) {
    NamedPoint& mapP=c[i];
    const NamedPoint& arrayP=_arrayMultiplied.at(i);
    mapP=arrayP;
    mapP.setZ(0.0); // Avoid error bars plotted from Z components
  }
  static_cast<StationCoordinatesItem *>(arrayTable->model())->endStationChange();
}

/*!
  Set all points of co-array map from scratch
*/
void WaranGpsWidget::updateCoArrayMap()
{
  TRACE;
  int nStations=_arrayMultiplied.count();
  int nCouples=nStations*(nStations-1)/2;

  LayerLocker ll(coArrayMap->mapLayer());
  NameLine * l=static_cast<NameLine *>(coArrayMap->line(0));
  Curve<NamedPoint>& c=l->curve();
  c.resize(nCouples);
  int iCouple=0;
  QList<NamedPoint>::iterator statIt1, statIt2;
  for(statIt1=_arrayMultiplied.begin();statIt1!=_arrayMultiplied.end();++statIt1) {
    statIt2=statIt1;
    for(++statIt2; statIt2!=_arrayMultiplied.end(); ++statIt2, iCouple++) {
      NamedPoint& p=c[iCouple];
      const NamedPoint& p1=statIt1->coordinates();
      const NamedPoint& p2=statIt2->coordinates();
      double dx=p1.x()-p2.x();
      double dy=p1.y()-p2.y();
      if(( dx < 0 && dy > 0) || (dx > 0 && dy < 0) ) p.setX(-fabs(dx)); else p.setX(fabs(dx));
      p.setY(fabs(dy));
      p.setName(p1.name()+"-"+p2.name());
    }
  }
}

void WaranGpsWidget::on_backgroundBut_clicked()
{
  TRACE;
  QString fileName=Message::getOpenFileName(tr("Image layer"), tr("Graph layer (*.layer)"));
  if(!fileName.isEmpty()) {
    arrayMap->loadBackground(fileName);
    backgroundEdit->setText(fileName);
    arrayMap->setLimitsArray(true);
  }
}
