/***************************************************************************
**
**  This file is part of dinver.
**
**  dinver 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.
**
**  dinver 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: 2005-10-31
**  Copyright: 2005-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QGpGuiTools.h>
#include <DinverGui.h>

#include "WindowEnvironment.h"
#include "MainWindow.h"
#include "ProcessStatus.h"
#include "InversionThread.h"

void ProcessStatusDelegate::paint (QPainter * painter, const QStyleOptionViewItem & option,
                                   const QModelIndex & index) const
{
  TRACE;
  if(index.column()==0) {
    ProcessStatus * table=qobject_cast<ProcessStatus *>(QObject::parent());
    ASSERT(table);
    LegendItemDisplay item(table->legend().item(index.row()));
    item.setDisplayText(false);
    item.paint(*painter, SciFigsGlobal::screenResolution(), option.rect.x(), option.rect.y(),
               option.rect.width(), option.rect.height());
  } else QItemDelegate::paint(painter, option, index);
}

QSize ProcessStatusDelegate::sizeHint (const QStyleOptionViewItem & option, const QModelIndex & index) const
{
  TRACE;
  if(index.column()==0) {
    ProcessStatus * table=qobject_cast<ProcessStatus *>(QObject::parent());
    ASSERT(table);
    LegendItemDisplay item(table->legend().item(index.row()));
    item.setDisplayText(false);
    return item.sizeHint(option.font);
  } else return QItemDelegate::sizeHint(option, index);
}

/*!
  Constructs a ProcessStatus as a child of 'parent', with the
  name 'name' and widget flags set to 'f'.
*/
ProcessStatus::ProcessStatus(QWidget *parent, Qt::WindowFlags f)
    : QWidget(parent, f)
{
  TRACE;
  setupUi(this);
  _threadsInitialized=false;
  _curveLayer=nullptr;

  threadTable->setSelectionMode(QAbstractItemView::NoSelection);
  threadTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
  threadTable->setModel(new StatusThreadItem(&_threads, this));
  threadTable->setItemDelegate(new ProcessStatusDelegate(this));
  threadTable->resizeColumnsToContents();
  Settings::columnWidth(threadTable, "ProcessStatus");

  _legend.generateColorScale(10);
  _curveLayer=new LineLayer(historyGraph);
  PlotLine2D * line;
  line=new PlotLine2D;
  line->setPen(Pen(Qt::black, 0.3, Qt::SolidLine));
  line->setSymbol(Symbol());
  _curveLayer->setReferenceLine(line);

  historyGraph->xAxis()->setRange(0, 100);
  historyGraph->xAxis()->setTitle("Generated models");
  historyGraph->yAxis()->setRange(0, 5);
  historyGraph->yAxis()->setTitle("Minimum misfit");
  connect(legendBut, SIGNAL(clicked()), this, SLOT(changeLegend()));

  Settings::splitter(splitter, "ProcessStatus");
}

ProcessStatus::~ProcessStatus()
{
  TRACE;
  StatusThreadMap::iterator it;
  for(it=_threads.begin();it!=_threads.end();++it) delete *it;
  Settings::setSplitter(splitter, "ProcessStatus");
  Settings::setColumnWidth(threadTable, "ProcessStatus");
}

void ProcessStatus::synchronize()
{
  TRACE;
  bool threadAdded=false;
  int totalNModels=0;
  int nThreads=WindowEnvironment::window(this)->threads().count();
  static_cast<StatusThreadItem *>(threadTable->model())->beginChange();
  for(int i=0; i<nThreads; i++) {
    InversionThread * t=WindowEnvironment::window(this)->threads().at(i);
    // Get info about thread
    StatusThreadMap::iterator itInfo=_threads.find(t);
    if(itInfo==_threads.end()) {
      _threads.insert(t, new StatusThreadInfo);
      threadAdded=true;
      itInfo=_threads.find(t);
    }
    StatusThreadInfo& tInfo=*itInfo.value();

    tInfo.nVisitedModels=t->visitedModelCount();
    tInfo.deltaNModels=t->validModelCount() - tInfo.nValidModels;
    tInfo.deltaNRejected=t->rejectedCount() - tInfo.nRejected;
    tInfo.nValidModels=t->validModelCount();
    tInfo.nActiveModels=t->activeModelCount();
    totalNModels += tInfo.nValidModels;
    tInfo.nr=t->bestModelCount();
    tInfo.nRejected=t->rejectedCount();
    tInfo.nGiveUp=t->giveUpCount();
    tInfo.deltaTime=tInfo.time.elapsed();
    tInfo.time.start();
    updateMisfitCurve(t);
    if(t->expectedModelCount()>historyGraph->xAxis()->maximum()) {
      historyGraph->xAxis()->setRange(0.0, t->expectedModelCount());
    }
  }
  static_cast<StatusThreadItem *>(threadTable->model())->endChange();
  if(threadAdded || !_threadsInitialized) {
    // Try to automatically adjust Y scale without annoying the user
    if(!_threadsInitialized) {
      if(totalNModels>0) {
        if(setMisfitRange()) {
          _threadsInitialized=true;
        }
      }
    }
    int nRows=threadTable->model() ->rowCount();
    for(int i=0; i<nRows; i++) {
      threadTable->resizeRowToContents(i);
    }
    //Rect r=_curveLayer->boundingRect();
    //historyGraph->xAxis()->setRange(0, r.x2());
    if(_legend.count()<nRows) {
      _legend.resize(nRows);
    }
  }
  historyGraph->deepUpdate();
}

bool ProcessStatus::setMisfitRange()
{
  TRACE;
  Rect r=_curveLayer->boundingRect();
  if(r.y2()>0.0) {
    historyGraph->yAxis()->setRange(0, r.y2());
    return true;
  } else {
    return false;
  }
}

void ProcessStatus::changeLegend()
{
  TRACE;
  Dialog * d=new Dialog(this);
  LegendProperties * dl=new LegendProperties(this);
  dl->setPropertySections(LegendTable::All);
  d->setMainWidget(dl);
  dl->setLegend(_legend);
  dl->setReadOnlyText(true);
  Settings::getRect(d, "ProcessStatus::legend");
  if(d->exec()==QDialog::Accepted) {
    Settings::setRect(d, "ProcessStatus::legend");
    _legend=dl->legend();
    updatePens();
    historyGraph->graphContent() ->deepUpdate();
  }
  delete d;
}

void ProcessStatus::removeThread(InversionThread * t)
{
  TRACE;
  StatusThreadMap::iterator itInfo=_threads.find(t);
  if(itInfo!=_threads.end()) {
    StatusThreadInfo& tInfo=*itInfo.value();
    if(tInfo.line) _curveLayer->removeLine(tInfo.line);
    delete &tInfo;
    _threads.remove(t);
  }
}

void ProcessStatus::updateMisfitCurve(InversionThread * t)
{
  TRACE;
  StatusThreadMap::iterator itInfo=_threads.find(t);
  if(itInfo==_threads.end()) return;
  StatusThreadInfo& tInfo=*itInfo.value();
  if(!tInfo.line) {
    int nCurves=_curveLayer->count();
    _curveLayer->lockDelayPainting();
    if(_legend.count()<nCurves) {
      _legend.resize(nCurves+1);
      _legend.setText(nCurves, t->objectName());
    }
    _curveLayer->addLine(_legend.pen(nCurves), _legend.symbol(nCurves));
    _curveLayer->unlock();
    tInfo.line=static_cast<PlotLine2D *>(_curveLayer->line(nCurves));
  }
  Curve<Point2D>& curve=tInfo.line->curve();
  double bestMisfit=tInfo.bestMisfit==0 ? std::numeric_limits<double>::infinity() : tInfo.bestMisfit;
  LayerLocker ll(_curveLayer);
  if(t->hasModelsToImport()) {
    tInfo.bestMisfit=t->reportBestMisfit();
  } else {
    t->lock();
    //printf("ProcessStatus::updateMisfitCurve %i -> %i\n", tInfo.lastVisitedIndex+1, tInfo.nVisitedModels);
    for(int iModel=tInfo.lastVisitedIndex+1; iModel<tInfo.nVisitedModels; iModel++) {
      double m=t->misfit(iModel);
      if(m!=std::numeric_limits<double>::infinity()) {
        if(m<bestMisfit) {
          bestMisfit=m;
          curve.append(Point(tInfo.lastValidIndex, bestMisfit));
        }
        tInfo.lastValidIndex++;
      }
      //printf("%i last valid index %i @ %lf : %lf\n", iModel, tInfo.lastValidIndex, m, bestMisfit);
    }
    tInfo.lastVisitedIndex=tInfo.nVisitedModels-1;
    t->unlock();
    if(bestMisfit!=std::numeric_limits<double>::infinity()) tInfo.bestMisfit=bestMisfit; else tInfo.bestMisfit=0;
  }
}

void ProcessStatus::updatePens()
{
  TRACE;
  _curveLayer->setLegend(_legend);
}

void ProcessStatus::clear()
{
  TRACE;
  qDeleteAll(_threads);
  _threads.clear();
  _threadsInitialized=false;
  _curveLayer->clear();
}

void ProcessStatus::clearThread(InversionThread * t)
{
  TRACE;
  StatusThreadMap::iterator itInfo=_threads.find(t);
  if(itInfo==_threads.end()) {
    return;
  }
  static_cast<StatusThreadItem *>(threadTable->model())->beginChange();
  StatusThreadInfo * tInfo=itInfo.value();
  _curveLayer->removeLine(tInfo->line);
  _threads.remove(t);
  delete tInfo;
  historyGraph->deepUpdate();
  static_cast<StatusThreadItem *>(threadTable->model())->endChange();
}

StatusThreadItem::StatusThreadItem(StatusThreadMap * threads, QObject * parent)
    : QAbstractItemModel(parent)
{
  TRACE;
  MainWindow * w=WindowEnvironment::window(this);
  ASSERT(w);
  connect(w, SIGNAL(beginNewThread()), this, SLOT(beginChange()));
  connect(w, SIGNAL(endNewThread()), this, SLOT(endChange()));
  _threads=threads;
}

StatusThreadItem::~StatusThreadItem()
{
  TRACE;
}

void StatusThreadItem::beginChange()
{
  TRACE;
  beginResetModel();
}

void StatusThreadItem::endChange()
{
  TRACE;
  endResetModel();
}

int StatusThreadItem::rowCount(const QModelIndex &parent) const
{
  TRACE;
  if(!parent.isValid())
    return WindowEnvironment::window(this)->threads().count();
  return 0;
}

int StatusThreadItem::columnCount(const QModelIndex &) const
{
  TRACE;
  return 10;
}

QVariant StatusThreadItem::data(const QModelIndex &index, int role) const
{
  TRACE;
  MainWindow * w=WindowEnvironment::window(this);
  InversionThread * t=w->threads().at(index.row());
  StatusThreadMap::iterator itInfo=_threads->find(t);
  StatusThreadInfo * tInfo;
  if(itInfo!=_threads->end()) {
    tInfo=itInfo.value();
  } else {
    tInfo=0;
  }
  switch (role) {
  case Qt::DisplayRole:
    switch (index.column()) {
    case 0: return QVariant();
    case 1: return t->objectName();
    case 2:
      if(tInfo)
        return QString::number(tInfo->bestMisfit);
      else
        return tr("?");
    case 3:
      if(tInfo)
        return tr("%1/%2").arg(tInfo->nValidModels).arg(t->expectedModelCount());
      else
        return tr("?");
    case 4:
      if(tInfo)
        return tr("%1").arg(tInfo->nActiveModels);
      else
        return tr("?");
    case 5:
      if(tInfo)
        return tr("%1").arg(tInfo->nVisitedModels);
      else
        return tr("?");
    case 6:
      if(tInfo)
        return tr("%1 m/s").arg((double) tInfo->deltaNModels/(tInfo->deltaTime * 0.001));
      else
        return tr("? m/s");
    case 7:
      if(tInfo) {
        return tr("%1 m").arg(tInfo->nr);
      }
    case 8:
      if(tInfo) {
        if(tInfo->deltaNModels > 0)
          return tr("%1 m/m").arg((double) tInfo->deltaNRejected/(double) tInfo->deltaNModels);
        else
          return tr("%1 m").arg(tInfo->nRejected);
      } else
        return tr("? m");
    case 9:
      if(tInfo)
        return tr("%1 m").arg(tInfo->nGiveUp);
      else
        return tr("? m");
    default: return QVariant();
    }
  default:
    return QVariant();
  }
}

QVariant StatusThreadItem::headerData(int section, Qt::Orientation orientation, int role) const
{
  TRACE;
  if(role!=Qt::DisplayRole) return QVariant();
  if(orientation==Qt::Horizontal) {
    switch (section) {
    case 0: return tr("Pen");
    case 1: return tr("Run name");
    case 2: return tr("Min misfit");
    case 3: return tr("Valid models");
    case 4: return tr("Active models");
    case 5: return tr("Visited models");
    case 6: return tr("Rate");
    case 7: return tr("Eff. Nr");
    case 8: return tr("Rejected");
    case 9: return tr("Give up");
    default: return QVariant();
    }
  } else {
    return section + 1;
  }
}

QModelIndex StatusThreadItem::parent (const QModelIndex &) const
{
  TRACE;
  return QModelIndex();
}

QModelIndex StatusThreadItem::index (int row, int column, const QModelIndex &) const
{
  TRACE;
  return createIndex(row, column);
}
