/***************************************************************************
**
**  This file is part of waran.
**
**  waran 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.
**
**  waran 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: 2008-03-20
**  Copyright: 2008-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <GeopsyGui.h>
#include <ArrayGui.h>
#include <QGpGuiWave.h>
#include <QGpGuiMath.h>
#include <SciFigs.h>

#include "WaranWidget.h"
#include "Station.h"
#include "RealTimeArrayManager.h"

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

  Full description of class still missing
*/

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

  connect(acquisitionWidget, SIGNAL(networkParametersChanged()), this, SLOT(setStations()));
  setStations();

  _arrayProcess=0;
  //_hvProcess=0;

  // By default the seed link is requesting from now to an unlimited buffer, this is what we want.
  // Users cannot change these options like in Geopsy main frame
  seedlink->setCurrentDatabase(GeopsyCoreEngine::instance()->defaultDatabase());
  seedlink->removeOptions();
  seedlink->removeView();
  seedlink->seedLink()->setBufferType(SeedLinkStream::Rotate);
  setHVWindowLength(); // Just because seedLink rotate buffer is linked to this parameter
  connect(seedlink->seedLink(), SIGNAL(endSignalChange(Signal *, TimeRange)),
          this, SLOT(dataChanged(Signal *, TimeRange)), Qt::QueuedConnection);

  signalViewer->setSignals(&_signals);

  freqSamp->setUnit(tr(" Hz"));
  freqSamp->setPrecision(2);
  freqSamp->setSingleStep(0.25);
  freqSamp->setAdmissibleRange(0.001, std::numeric_limits<double>::infinity());
  connect(freqSamp, SIGNAL(parametersChanged()), this, SLOT(setSamplingParameters()));
  connect(arrayWindowLength, SIGNAL(valueChanged(double)), this, SLOT(setArrayWindowLength()));
  connect(theoreticalResponse, SIGNAL(kminChanged(double)), this, SLOT(setKmin(double)));
  connect(theoreticalResponse, SIGNAL(kmaxChanged(double)), this, SLOT(setKmax(double)));
  connect(hvWindowLength, SIGNAL(valueChanged(double)), this, SLOT(setHVWindowLength()));

  arrayActivity->setMeasurementDelay(5.0);
  arrayActivity->setDisplayRange(300.0);
  hvActivity->setMeasurementDelay(5.0);
  hvActivity->setDisplayRange(300.0);
  _processUpdateTimer.setInterval(10000);
  connect (&_processUpdateTimer, SIGNAL(timeout()), this, SLOT(updateDataProcess()));
  _displayUpdateTimer.setInterval(5000);
  connect (&_displayUpdateTimer, SIGNAL(timeout()), this, SLOT(updateDataDisplay()));

  connect (startArrayBut, SIGNAL(clicked()), this, SLOT(startArrayProcess()));
  connect (stopArrayBut, SIGNAL(clicked()), this, SLOT(stopArrayProcess()));
  connect (startHVBut, SIGNAL(clicked()), this, SLOT(startHVProcess()));
  connect (stopHVBut, SIGNAL(clicked()), this, SLOT(stopHVProcess()));

  theoreticalResponse->createObjects(false);
  ringEditor->setCoArrayGraph(coArrayMap);
  coArrayMap->mapLayer()->setShowNames(false);

  StationCoordinatesItem * model=new StationCoordinatesItem(this);
  model->setStations(&_array);
  coordinates->setModel(model);
  coordinates->setSelectionMode(QAbstractItemView::NoSelection);
  coordinates->setEditTriggers(QAbstractItemView::NoEditTriggers);

  initResultGraph(fkHistogram, _fkGridLayer, _fkDispLimits);
  initResultGraph(hrfkHistogram, _hrfkGridLayer, _hrfkDispLimits);
  initResultGraph(spacHistogram, _spacGridLayer, _spacDispLimits);


  Settings::getWidget(this, "waran");

  // Currently not all tab are implemented
  tab->widget(2)->setEnabled(false);
  tab->widget(4)->setEnabled(false);
  tab->widget(5)->setEnabled(false);
  tab->widget(6)->setEnabled(false);
  tab->widget(7)->setEnabled(false);
  tab->widget(8)->setEnabled(false);
  tab->widget(9)->setEnabled(false);
  tab->widget(10)->setEnabled(false);
}

/*!
  Description of destructor still missing
*/
WaranWidget::~WaranWidget()
{
  TRACE;
  qDeleteAll(_stations);
  _stations.clear();

  delete _arrayProcess;
  // delete _hvProcess;
}

void WaranWidget::closeEvent(QCloseEvent * e)
{
  TRACE;
  Settings::setWidget(this, "waran");
  e->accept();
}

void WaranWidget::initResultGraph(AxisWindow * g, IrregularGrid2DPlot *& gridLayer,
                                  DispersionLimitLayer *& dispLimits)
{
  TRACE;
  g->xAxis()->setRange(0.1,100.0);
  g->xAxis()->setTitle(tr("Frequency (Hz)"));
  g->xAxis()->setTitleInversedScale(tr("Period (s)"));
  g->xAxis()->setScaleType(Scale::Log);
  g->xAxis()->setAutoTicks(false); 
  g->xAxis()->setMajorTicks(2.0);
  g->xAxis()->setMinorTicks(0.5);
  g->yAxis()->setRange(0.0,5.0);
  g->yAxis()->setTitle(tr("Slowness (s/m)"));
  g->yAxis()->setTitleInversedScale(tr("Velocity (m/s)"));
  g->updateExternalGeometry();
  gridLayer=new IrregularGrid2DPlot(g);
  gridLayer->setObjectName("histogram");
  gridLayer->setLinearColorMap(0.0, 10.0);
  dispLimits=new DispersionLimitLayer(g);
  dispLimits->setObjectName("dispersionLimits");
  dispLimits->addArrayLimits();
  connect(theoreticalResponse, SIGNAL(kminChanged(double)), dispLimits, SLOT(setArrayKmin(double)));
  connect(theoreticalResponse, SIGNAL(kmaxChanged(double)), dispLimits, SLOT(setArrayKmax(double)));
  LineLayer * curveLayer=new LineLayer(g);
  curveLayer->setObjectName("pickCurves");
  curveLayer->addTrackingAction(tr("&Pick"), LineLayer::Pick, tr("Add points to the current curve."));
  curveLayer->addTrackingAction(tr("&Pick ordered"), LineLayer::PickOrdered,
                                tr("Add points to the current curve, keeping x sample ordering."));
  curveLayer->addTrackingAction(tr("&Edit"), LineLayer::Edit, tr("Edit points of the current curve."));
  curveLayer->setEditable(true);
}


void WaranWidget::startArrayProcess()
{
  TRACE;
  if(_array.count()>1 &&
     _array.hasAllComponents()) {
    _array.setRelativePos();
    delete _arrayProcess;
    _arrayProcess=new RealTimeArrayManager(&_array);
    _arrayProcess->setfrequencyBandwidth(_frequencyBandwidth);
    setArrayWindowLength();
    setSamplingParameters();
    stopArrayBut->setEnabled(true);
    startArrayBut->setEnabled(false);
    _arrayProcess->setMaximumTime(_maximumTime);
    _arrayProcess->start();
    if(!_processUpdateTimer.isActive()) _processUpdateTimer.start();
  }
}

void WaranWidget::stopArrayProcess()
{
  TRACE;
  if(_arrayProcess) {
    _arrayProcess->stop();
    stopArrayBut->setEnabled(false);
    startArrayBut->setEnabled(true);
  }
}

void WaranWidget::startHVProcess()
{
  TRACE;
  stopHVBut->setEnabled(true);
  startHVBut->setEnabled(false);
}

void WaranWidget::stopHVProcess()
{
  TRACE;
  stopHVBut->setEnabled(false);
  startHVBut->setEnabled(true);
}

/*!
  Collect all events from seed link module, maintain the list of signals to process into a subSignalPool
*/
void WaranWidget::dataChanged(Signal * sig, TimeRange tr)
{
  TRACE;
  if(!_stationNames.contains(sig->nameComponent()) && sig->component()==Signal::Vertical) {
    signalViewer->lock();
    _stationNames.insert(sig->nameComponent());
    QMap<QString,Point>::iterator it =_stationCoordinates.find(sig->name());
    if(it!=_stationCoordinates.end()) sig->setReceiver(it.value());
    _signals.addSignal(sig);
    SortKey::clear();
    SortKey::add(MetaDataFactory::Name);
    SortKey::add(MetaDataFactory::Component);
    _signals.sort();
    signalViewer->setSignals(&_signals);
    signalViewer->unlock();
    _array.addSignal(sig);
    signalViewer->setSignals(&_signals);
    updateArrayMap();
    updateCoArrayMap();
    updateArrayResponse();
    if(!_displayUpdateTimer.isActive()) _displayUpdateTimer.start();
  }
  _maximumTime=tr.end();
}

void WaranWidget::updateDataProcess()
{
  TRACE;
  if(_arrayProcess) {
    _arrayProcess->setMaximumTime(_maximumTime);
    _arrayProcess->createTasks();
    _fkGridLayer->setGrid(_arrayProcess->fkDispersion());
    Rect r=_fkGridLayer->boundingRect();
    fkHistogram->xAxis()->setRange(r.x1(), r.x2());
    fkHistogram->yAxis()->setRange(r.y1(), r.y2());
    fkHistogram->deepUpdate();
  }
  /*if(_hvProcess) {
    _hvProcess->setMaximumTime(_maximumTime);
  }*/
}

void WaranWidget::updateDataDisplay()
{
  TRACE;
  signalViewer->setMaximumTime(_maximumTime);
}

bool WaranWidget::arrayParameterChanged()
{
  TRACE;
  if(_arrayProcess && _arrayProcess->isActive()) {
    switch (Message::question(MSG_ID, tr("Array parameter changed"),
                                   tr("Do you want to stop array processing before changing parameters?"),
                                   Message::yes(), Message::no(), Message::cancel()) ) {
    case Message::Answer0:
      stopArrayProcess();
      break;
    case Message::Answer1:
      break;
    default:
      return false;
    }
  }
  return _arrayProcess;
}

void WaranWidget::setSamplingParameters()
{
  TRACE;
  SamplingParameters p;
  freqSamp->getParameters(p);
  _fkDispLimits->setFrequencySampling(p);
  _hrfkDispLimits->setFrequencySampling(p);
  _spacDispLimits->setFrequencySampling(p);
  theoreticalResponse->setKmin(theoreticalResponse->kmin());
  theoreticalResponse->setKmax(theoreticalResponse->kmax());

  if(arrayParameterChanged()) {
    _arrayProcess->setSamplingParameters(p);
  }
  if(hvParameterChanged()) {
    //_hvProcess->setSamplingParameters(p);
  }
}

void WaranWidget::setArrayWindowLength()
{
  TRACE;
  if(!arrayParameterChanged()) return;

  SamplingParameters p;
  freqSamp->getParameters(p);
  double tw=arrayWindowLength->value();
  seedlink->seedLink()->setMaximumDuration(10.0*tw/p.value(0));
  _arrayProcess->setWindowLenght(tw);
}

void WaranWidget::setKmin(double k)
{
  TRACE;
  if(!arrayParameterChanged()) return;
  _arrayProcess->setKmin(k);
}

void WaranWidget::setKmax(double k)
{
  TRACE;
  if(!arrayParameterChanged()) return;
  _arrayProcess->setKmax(k);
}

bool WaranWidget::hvParameterChanged()
{
  TRACE;
  /*if(_hvProcess && _hvprocess->isActive()) {
    switch (Message::question(MSG_ID, tr("H/V parameter changed"),
                                   tr("Do you want to stop H/V processing before changing parameters?"),
                                   Message::yes, Message::no, Message::cancel) ) {
    case Message::Answer0:
      stopHVProcess();
      break;
    case Message::Answer1:
      break;
    default:
      return false;
    }
  }
  return _hvProcess;*/
  return true;
}

void WaranWidget::setHVWindowLength()
{
  TRACE;
  if(!hvParameterChanged()) return;

  double hvTw=hvWindowLength->value();
  SamplingParameters p;
  freqSamp->getParameters(p);
  double arrayTw=arrayWindowLength->value()/p.value(0);
  double maxTw=arrayTw>hvTw ? arrayTw : hvTw;
  seedlink->seedLink()->setMaximumDuration(10.0*maxTw); // For circular buffer we must be sure that array process has enough data
  signalViewer->setDisplayRange(2.0*hvTw);  // For display any lenght is ok
  //_hvProcess->setWindowLenght(tw);
}

void WaranWidget::on_loadCoordinateButton_clicked()
{
  if(_arrayProcess && !arrayParameterChanged()) return;
  CoordinateFileWidget cf;
  if(cf.read()) {
    // Collect coordinates and check for duplicates
    QList<NamedPoint> points=cf.points();
    for(QList<NamedPoint>::iterator it=points.begin(); it!=points.end();it++) {
      if(_stationCoordinates.contains(it->name())) {
        Message::warning(MSG_ID, tr("Read coordinates"), tr("Duplicate station name %1.")
                            .arg(it->name()), true);
      } else {
        _stationCoordinates.insert(it->name(), *it);
      }
    }
    // Set these coordinates to all signals identified so far
    for(int i=_array.count()-1;i>=0;i--) {
      const StationSignals * stat=_array.at(i);
      int nComp=stat->nComponents();
      QMap<QString,Point>::iterator it =_stationCoordinates.find(stat->name());
      if(it!=_stationCoordinates.end()) {
        for(int iComp=0; iComp<nComp;iComp++ ) {
          const SubSignalPool& subPool=stat->originals(iComp);
          if(!subPool.isEmpty()) {
            subPool.first()->setReceiver(it.value());
          }
        }
      }
    }
    // Restart process and update maps and theoretical response
    updateArrayMap();
    updateCoArrayMap();
    updateArrayResponse();
  }
}

void WaranWidget::on_saveCoordinateButton_clicked()
{
  TRACE;
  CoordinateFileWidget::write(_array.pointList(), _utmZone);
}

void WaranWidget::on_relativePositionButton_clicked()
{
  TRACE;
  RelativePositions * d=new RelativePositions(this);
  QList<NamedPoint> points=_array.pointList();
  d->setStationList(&points);
  Settings::getWidget(d);
  d->exec();
  Settings::setWidget(d);
  delete d;
}

/*!
*/
void WaranWidget::updateArrayResponse()
{
  TRACE;
  QVector<Point2D> respArray;
  for(int i=_array.count()-1;i>=0;i--) {
    respArray.append(_array.at(i)->coordinates());
  }
  SamplingParameters p;
  freqSamp->getParameters(p);
  _fkDispLimits->setFrequencySampling(p);
  _hrfkDispLimits->setFrequencySampling(p);
  _spacDispLimits->setFrequencySampling(p);
  theoreticalResponse->setArray(respArray);
}

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

/*!
  Set all points of co-array map from scratch
*/
void WaranWidget::updateCoArrayMap()
{
  TRACE;
  // Construct the couple list
  int nStations=_array.count();
  int nCouples=nStations * (nStations - 1)/2;
  QVector<StationPair> stationCouples(nCouples);
  QList<StationSignals *>::iterator statIt1, statIt2;
  int iCouple=0;
  for(statIt1=_array.begin();statIt1!=_array.end();++statIt1) {
    statIt2=statIt1;
    //for( ++statIt2;statIt2!=_array.end();++statIt2, iCouple++ )
    //  stationCouples[ iCouple ].setStations( *statIt1, *statIt2);
  }

  LayerLocker ll(coArrayMap->mapLayer());
  NameLine * l=static_cast<NameLine *>(coArrayMap->line(0));
  Curve<NamedPoint>& c=l->curve();
  c.resize(nCouples);
  for(int i=0;i < nCouples;i++ ) {
    NamedPoint& p=c[ i ];
    const StationPair& couple=stationCouples[ i ];
    /*const Point& p1=couple.station1()->coordinates();
    const Point& p2=couple.station2()->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(couple.name());*/
  }
  coArrayMap->setLimitsCoArray( );
}

void WaranWidget::setStations()
{
  TRACE;
  const NetworkParameters& param=acquisitionWidget->networkParameters();

  QFile f(param.unitList());
  if(!f.open(QIODevice::ReadOnly)) {
    Message::warning(MSG_ID, tr("Reading station list"), tr("Cannot read station list in file %1").arg(param.unitList()));
    return;
  }
  QTextStream s(&f);
  LineParser lp;
  qDeleteAll(_stations);
  _stations.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(param.acquisitionUnitPrefix())) {
      Station * stat=new Station;
      stat->setType(Station::AcquisitionUnit);
      stat->setName(name);
      stat->setAddress(lp.toString(0, ok));
      stat->setIndex(_stations.count());
      _stations.append(stat);
    } else if(name==param.processingUnitName()) {
      _processingUnitAddress=lp.toString(0, ok);
      Station * stat=new Station;
      stat->setType(Station::ProcessingUnit);
      stat->setName(name);
      stat->setAddress(lp.toString(0, ok));
      stat->setIndex(_stations.count());
      _stations.append(stat);
    } else if(name.startsWith(param.repeaterUnitPrefix())) {
      Station * stat=new Station;
      stat->setType(Station::RepeaterUnit);
      stat->setName(name);
      stat->setAddress(lp.toString(0, ok));
      stat->setIndex(_stations.count());
      _stations.append(stat);
    }
    if(!ok) {
      Message::warning(MSG_ID, tr("Reading station list"),
                       tr("Error reading line %1 in file %2").arg(lineNumber).arg(param.unitList()),
                       Message::ok(), true);
      ok=true;
    }
  }

  acquisitionWidget->setStations(&_stations);
  commentsWidget->setStations(&_stations);
  distanceWidget->setStations(&_stations);
}
