/***************************************************************************
**
**  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: 2013-04-07
**  Copyright: 2013-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include <TapePositioningSystem.h>

#include "DistanceWidget.h"
#include "DistanceItem.h"
#include "DistanceDelegate.h"
#include "Station.h"
#include "TapeCoordinateItem.h"
#include "TapeCoordinateDelegate.h"

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

  Full description of class still missing
*/

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

  DistanceItem * distanceModel=new DistanceItem(this, &_pointNames);
  connect(distanceModel, SIGNAL(pointNameChanged()), this, SLOT(updateCoordinateList()));
  connect(distanceModel, SIGNAL(distanceChanged()), this, SLOT(updateCoordinates()));
  distanceTable->setModel(distanceModel);
  distanceTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
  distanceTable->setSelectionBehavior(QAbstractItemView::SelectRows);
  distanceTable->setEditTriggers(QAbstractItemView::AllEditTriggers);
  distanceTable->setItemDelegate(new DistanceDelegate(this, &_pointNames));
  distanceTable->installEventFilter(this);

  TapeCoordinateItem * coordinateModel=new TapeCoordinateItem(this);
  connect(coordinateModel, SIGNAL(pointChanged()), this, SLOT(updateCoordinates()));
  connect(coordinateModel, SIGNAL(typeChanged()), this, SLOT(updateCoordinates()));
  coordinateTable->setModel(coordinateModel);
  coordinateTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
  coordinateTable->setSelectionBehavior(QAbstractItemView::SelectRows);
  coordinateTable->setEditTriggers(QAbstractItemView::AllEditTriggers);
  coordinateTable->setItemDelegate(new TapeCoordinateDelegate(this));

  _rawLayer=new LineLayer(map);
  _rawLayer->setReferenceLine(new PlotLine2D);
  _rawLayer->setReferencePen(Pen(Qt::NoPen));
  _rawLayer->setReferenceSymbol(Symbol(Symbol::Circle, 1, Pen(Qt::lightGray), Brush(Qt::lightGray, Qt::SolidPattern)));
  _rawLayer->addLine();

  _meanLayer=new NameLineLayer(map);
  _meanLayer->setReferenceLine(new NameLine);
  _meanLayer->setReferencePen(Pen(Qt::NoPen));
  _meanLayer->setReferenceSymbol(Symbol(Symbol::Cross, 2));
  _meanLayer->addLine();

  _covarianceLayer=new CircleViewer(map);


  map->xAxis()->setSizeType(Axis::Scaled);
  map->yAxis()->setSizeType(Axis::Scaled);

  connect(priorPrecision, SIGNAL(valueChanged(double)), this, SLOT(updateCoordinates()));
  connect(priorRadius, SIGNAL(valueChanged(double)), this, SLOT(updateCoordinates()));
  connect(posteriorPrecision, SIGNAL(valueChanged(double)), this, SLOT(updateCoordinates()));
}

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

void DistanceWidget::setStations(QList<Station *> * list)
{
  TRACE;
  for(QList<Station *>::iterator it=list->begin(); it!=list->end(); it++) {
    Station * s=*it;
    if(s->type()==Station::AcquisitionUnit) {
      _pointNames.insert(s->name());
    }
  }
}

void DistanceWidget::on_addDistance_clicked()
{
  TRACE;
  DistanceItem * model=static_cast<DistanceItem *>(distanceTable->model());
  model->add();
  distanceTable->scrollToBottom();
  distanceTable->setCurrentIndex(model->index(model->rowCount(QModelIndex())-1, 2));
  updateCoordinateList();
}

void DistanceWidget::on_removeDistance_clicked()
{
  TRACE;
  QModelIndexList l=distanceTable->selectionModel()->selectedRows();
  QList<int> rowIndex;
#if(QT_VERSION >= QT_VERSION_CHECK(4, 7, 0))
  rowIndex.reserve(l.count());
#endif
  for(int i=l.count()-1; i>=0; i--) {
    rowIndex.append(l.at(i).row());
  }
  std::sort(rowIndex.begin(), rowIndex.end());
  unique(rowIndex);
  for(int i=rowIndex.count()-1; i>=0; i--) {
    static_cast<DistanceItem *>(distanceTable->model())->remove(rowIndex.at(i));
  }
  updateCoordinateList();
}

void DistanceWidget::on_saveDistances_clicked()
{
  TRACE;
  QString fileName=Message::getSaveFileName(tr("Save distances"),
                                            tr("Distance file (*.dist)"));
  if(!fileName.isEmpty()) {
    static_cast<DistanceItem *>(distanceTable->model())->save(fileName);
  }
}

void DistanceWidget::on_loadDistances_clicked()
{
  TRACE;
  QString fileName=Message::getOpenFileName(tr("Load distances"),
                                            tr("Distance files (*.dist);;All files (*)"));
  if(!fileName.isEmpty()) {
    static_cast<DistanceItem *>(distanceTable->model())->load(fileName);
    updateCoordinateList();
  }
}

void DistanceWidget::on_saveCoordinates_clicked()
{
  TRACE;
  QString fileName=Message::getSaveFileName(tr("Save coordinates"),
                                            tr("Coordinate files (*)"));
  if(!fileName.isEmpty()) {
    static_cast<TapeCoordinateItem *>(coordinateTable->model())->save(fileName);
  }
}

void DistanceWidget::on_loadCoordinates_clicked()
{
  TRACE;
  QString fileName=Message::getOpenFileName(tr("Load coordinates"),
                                            tr("Coordinate files (*)"));
  if(!fileName.isEmpty()) {
    static_cast<TapeCoordinateItem *>(coordinateTable->model())->load(fileName);
    updateCoordinateList();
  }
}

void DistanceWidget::on_setPriorCoordinates_clicked()
{
  TRACE;
  QModelIndexList l=coordinateTable->selectionModel()->selectedRows();
  QList<int> rowIndex;
#if(QT_VERSION >= QT_VERSION_CHECK(4, 7, 0))
  rowIndex.reserve(l.count());
#endif
  for(int i=l.count()-1; i>=0; i--) {
    rowIndex.append(l.at(i).row());
  }
  std::sort(rowIndex.begin(), rowIndex.end());
  unique(rowIndex);
  TapeCoordinateItem * model=static_cast<TapeCoordinateItem *>(coordinateTable->model());
  for(int i=rowIndex.count()-1; i>=0; i--) {
    model->setPriorCoordinates(rowIndex.at(i));
  }
  coordinateTable->update();
}

bool DistanceWidget::eventFilter(QObject *obj, QEvent *event)
{
  if(event->type()==QEvent::KeyPress) {
    QKeyEvent * e=static_cast<QKeyEvent *>(event);
    if(e->key()==Qt::Key_Return || e->key()==Qt::Key_Enter) {
      DistanceDelegate * delegate=static_cast<DistanceDelegate *>(distanceTable->itemDelegate());
      if( delegate->editingValue()) {
        on_addDistance_clicked();
        return true;
      }
    }
  }
  return QObject::eventFilter(obj, event);
}

void DistanceWidget::updateCoordinateList()
{
  TRACE;
  const QList<Distance>& distances=static_cast<DistanceItem *>(distanceTable->model())->distances();
  TapeCoordinateItem * coordinates=static_cast<TapeCoordinateItem *>(coordinateTable->model());
  for(QList<Distance>::const_iterator it=distances.begin(); it!=distances.end(); it++) {
    const Distance& d=*it;
    if(coordinates->indexOf(d.node1())<0) {
      coordinates->add(d.node1());
    }
    if(coordinates->indexOf(d.node2())<0) {
      coordinates->add(d.node2());
    }
  }
  updateCoordinates();
}

void DistanceWidget::updateCoordinates()
{
  TRACE;

  DistanceItem * distanceModel=static_cast<DistanceItem *>(distanceTable->model());
  QList<Distance>& distances=distanceModel->distances();
  TapeCoordinateItem * coordinateModel=static_cast<TapeCoordinateItem *>(coordinateTable->model());
  const QList<TapePoint>& coordinates=coordinateModel->points();

  Node * origin=0;
  Node * north=0;
  Node * eastward=0;

  Triangulator triangulator;
  for(QList<TapePoint>::const_iterator it=coordinates.begin(); it!=coordinates.end(); it++) {
    const TapePoint& p=*it;
    Node * n=triangulator.addNode(p.name());
    switch(p.type()) {
    case TapePoint::Fixed:
      origin=n;
      break;
    case TapePoint::North:
      north=n;
      break;
    case TapePoint::Eastward:
      eastward=n;
      break;
    case TapePoint::Free:
    case TapePoint::Prior:
      break;
    }
  }
  if(!origin || !north || !eastward) {
    App::log(tr("Either origin, north or eastward node is missing.\n") );
    return;
  }
  for(QList<Distance>::const_iterator it=distances.begin(); it!=distances.end(); it++) {
    const Distance& d=*it;
    triangulator.addDistance(d.node1(), d.node2(), d.measured());
  }
  triangulator.setPosteriorPrecision(posteriorPrecision->value());
  triangulator.aggregate(100, priorPrecision->value());
  triangulator.setCoordinates(origin, north, eastward);

  QMap<QString, Point> refPoints;
  for(QList<TapePoint>::const_iterator it=coordinates.begin(); it!=coordinates.end(); it++) {
    const TapePoint& p=*it;
    switch(p.type()) {
    case TapePoint::Fixed:
    case TapePoint::North:
    case TapePoint::Free:
    case TapePoint::Eastward:
      break;
    case TapePoint::Prior:
      refPoints.insert(p.name(), p);
      break;
    }
  }
  triangulator.setPriorCoordinates(refPoints, priorRadius->value());

  coordinateModel->blockSignals(true);
  QList<Node *> nodes=triangulator.nodes();
  QMap<QString, Point2D> meanPoints;
  Curve<NamedPoint> tmpMeanCurve;
  Curve<Point2D> tmpRawCurve;
  _covarianceLayer->clear();
  for(QList<Node *>::iterator it=nodes.begin(); it!=nodes.end(); it++) {
    Node * n=*it;
    Covariance cov=triangulator.covariance(n->name());
    if(cov.count()>0) {
      NamedPoint p(cov.mean(0), cov.mean(1));
      p.setName(n->name());
      coordinateModel->setPoint(n->name(), p, cov.stddev2D());
      tmpMeanCurve.append(p);
      _covarianceLayer->add(p.x(), p.y(), cov.stddev2D().majorRadius(), cov.stddev2D().minorRadius(), cov.stddev2D().orientation().radians(), Qt::black);
      meanPoints.insert(n->name(), p);

      QSet<Point2D> rawCoords=triangulator.solutions(n->name());
      for(QSet<Point2D>::iterator its=rawCoords.begin(); its!=rawCoords.end(); its++) {
        tmpRawCurve.append(*its);
      }
    }
  }
  coordinateModel->blockSignals(false);

  if(!tmpMeanCurve.isEmpty()) {
    static_cast<NameLine *>(_meanLayer->line(0))->curve()=tmpMeanCurve;
  }
  if(!tmpRawCurve.isEmpty()) {
    static_cast<PlotLine2D *>(_rawLayer->line(0))->curve()=tmpRawCurve;
  }

  for(QList<Distance>::iterator it=distances.begin(); it!=distances.end(); it++) {
    Distance& d=*it;
    QMap<QString, Point2D>::iterator it1=meanPoints.find(d.node1());
    QMap<QString, Point2D>::iterator it2=meanPoints.find(d.node2());
    if(it1!=meanPoints.end() && it2!=meanPoints.end()) {
      d.setComputed(meanPoints[d.node1()].distanceTo(meanPoints[d.node2()]));
    } else {
      d.setComputed(0.0);
    }
  }

  Rect r=_meanLayer->boundingRect();
  AxisWindow * w=_meanLayer->graph();
  r.enlarge(10.0, 10.0, LinearScale, LinearScale);
  w->setMapScale(r.x1(), r.y1(), r.x2(), r.y2());
  w->deepUpdate();
  distanceTable->update();
  coordinateTable->update();
}
