/***************************************************************************
**
**  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 <DinverCore.h>
#include <QGpGuiTools.h>

#include "ThreadLauncher.h"
#include "LaunchThreadItem.h"
#include "LaunchThreadDelegate.h"
#include "MaxNumProcess.h"
#include "ProcessStatus.h"
#include "WindowEnvironment.h"
#include "MainWindow.h"

/*
 *  Constructs a ThreadLauncher as a child of 'parent', with the
 *  name 'name' and widget flags set to 'f'.
 */
ThreadLauncher::ThreadLauncher(QWidget* parent)
    : QWidget(parent)
{
  TRACE;
  _runningProcess=0;

  setupUi(this);

  threadTable->setModel(new LaunchThreadItem(this) );
  LaunchThreadDelegate * del=new LaunchThreadDelegate(this);
  threadTable->setItemDelegate(del);
  threadTable->setSelectionBehavior(QAbstractItemView::SelectRows);
  threadTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
  threadTable->setEditTriggers(QAbstractItemView::AllEditTriggers);
  threadTable->resizeColumnsToContents();
  Settings::columnWidth(threadTable, "ThreadLauncher");

  connect(del, SIGNAL(dataChanged()), this, SLOT(applyAllSelected()) );
  connect(threadTable->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection & )),
          this, SLOT(selectionChanged()));
}

/*
 *  Destroys the object and frees any allocated resources
 */
ThreadLauncher::~ThreadLauncher()
{
  TRACE;
  Settings::setColumnWidth(threadTable, "ThreadLauncher");
}

InversionThread * ThreadLauncher::newThread()
{
  TRACE;
  MainWindow * w=WindowEnvironment::window(this);
  int i=w->threads().count();
  QString runName;
  InversionThread * lastT=nullptr;
  if(i==0) {
    runName="run_01";
  } else {
    lastT=w->threads().at(i-1);
    QString str=lastT->objectName();
    runName=processCompleteName(processBaseName( str), processNameRank(str) + 1);
  }
  InversionThread * t=new InversionThread(this);
  t->setObjectName(runName);
  w->logs()->setViewName(t, runName);
  StreamRedirection sr(App::stream(t));
  AbstractForward * forward=w->plugin()->createForward();
  if(!w->plugin()->initForward(forward)) {
    Message::warning(MSG_ID, tr("New thread"), tr("Error creating a new inversion thread.\n\n%1")
                  .arg(w->logs()->text(t)), Message::ok());
    delete forward;
    delete t;
    return nullptr;
  }
  t->setReportDir(w->currentReportDir());
  if(!t->setForward(forward)) {
    Message::warning(MSG_ID, tr("New thread"), tr("Error creating a new inversion thread.\n\n%1")
                .arg(w->logs()->text(t)), Message::ok());
    delete t;
    delete forward;
    return nullptr;
  }
  t->setStorage();
   if(lastT) t->setTuningParameters(lastT);
  w->addThread(t);
  w->setModified(true);
  delete forward;
  return t;
}

/*!
  Emit setCurrentRuns() with the list of selected runs
*/
void ThreadLauncher::selectionChanged()
{
  ThreadList tList;
  MainWindow * w=WindowEnvironment::window(this);
  int n=w->threads().count();
  for(int i=0;i < n;i++ ) {
    if(threadTable->isThreadSelected(i) ) {
      tList.append(w->threads().at(i));
    }
  }
  emit setCurrentRuns(tList);
}

QString ThreadLauncher::processBaseName(QString str)
{
  TRACE;
  if(str.contains( "_" )) {
    bool ok;
    QString sufix=str.section( '_', -1);
    sufix.toInt(&ok);
    if(ok) return str.left(str.length() - sufix.length() - 1);
  }
  return str;
}

int ThreadLauncher::processNameRank(QString str)
{
  TRACE;
  if(str.contains( "_" )) {
    bool ok;
    int num=str.section( '_', -1).toInt(&ok);
    if(ok) return num;
  }
  return 1;
}

QString ThreadLauncher::processCompleteName(QString str, int num)
{
  TRACE;
  if(num < 10) {
    str += "_0%1";
    return str.arg(num);
  } else {
    str += "_%1";
    return str.arg(num);
  }
}

/*!
  Return true is at least one process is selected
*/
bool ThreadLauncher::isAnySelected()
{
  TRACE;
  QModelIndexList l=threadTable->selectionModel()->selectedIndexes();
  return !l.isEmpty();
}

/*!
  Prompt user if he wants to slect all of them. Return true if so.
  The processes are not really selected.
*/
bool ThreadLauncher::assumeAllSelected(QString title, QString action)
{
  TRACE;
  if(Message::question(MSG_ID, title,
                              tr( "No run selected, do you want to %1 all of them?" ).arg(action),
                              Message::yes(), Message::cancel())==Message::Answer1) {
    return false;
  }
  return true;
}

void ThreadLauncher::applyAllSelected()
{
  TRACE;
  QAbstractItemModel& model=*threadTable->model();
  QModelIndex curIndex=threadTable->currentIndex();
  QModelIndexList l=threadTable->selectionModel()->selectedRows();
  if(l.count()>1) {
    QVariant val=model.data(curIndex);
    QModelIndex index;
    foreach(index, l) {
      model.setData(model.index(index.row(), curIndex.column()), val);
    }
  }
}

void ThreadLauncher::start()
{
  TRACE;
  bool selectAll;
  if( !isAnySelected()) {
    if(assumeAllSelected( tr( "Starting inversion run" ), tr("start")) )
      selectAll=true;
    else
      return;
  } else selectAll=false;

  MessageContext mc;
  MainWindow * w=WindowEnvironment::window(this);
  int n=w->threads().count();
  for(int i=0;i < n;i++ ) {
    if(selectAll || threadTable->isThreadSelected(i) ) {
      InversionThread * t=w->threads().at(i);
      start(t);
    }
  }
  startQueued();
}

void ThreadLauncher::start(InversionThread * t)
{
  TRACE;
  if(t->isReportExists()) {
    if(!ReportReader::isReportFormat(t->reportFileName())) {
      switch(Message::warning(MSG_ID, tr("Starting inversion run"),
                                  tr("A report file already exist for run %1, but it has not a report format.").arg(t->objectName()),
                                  Message::cancel(), tr("Overwrite"), QString(), true)) {
      case Message::Answer0:
        App::log(tr("Run %1 not started because report file already exists\n").arg(t->objectName()) );
        return;
      default:
        t->removeFiles(true);
        break;
      }
    }
  }
  _processToStart.enqueue(t);
}

void ThreadLauncher::startQueued()
{
  TRACE;
  while(_runningProcess<1 && !_processToStart.empty()) {
    InversionThread * t=_processToStart.dequeue();
    if(!t->isRunning()) {
      _runningProcess++;
      QString msg=tr(":-p Starting process %1, %2 are running, %3 are queued")
                              .arg(t->objectName())
                              .arg(_runningProcess)
                              .arg(_processToStart.count());
      App::log(msg+"\n");
      DinverCoreEngine::instance()->showMessage(this, msg);
      t->start();
    } else {
      QString msg=tr(":-(process %1 is already running, hence removed from queue, %2 are running, %3 are queued")
                              .arg(t->objectName())
                              .arg(_runningProcess)
                              .arg(_processToStart.count());
      App::log(msg+"\n");
      DinverCoreEngine::instance()->showMessage(this, msg);
    }
  }
}

void ThreadLauncher::processFinished()
{
  TRACE;
  InversionThread * t=qobject_cast<InversionThread *>(sender());
  _runningProcess--;
  QString msg=tr(":-) Process %1 finished, %2 are running, %3 are queued")
              .arg(t ? t->objectName() : QString())
                          .arg(_runningProcess)
                          .arg(_processToStart.count());
  App::log(msg+"\n");
  DinverCoreEngine::instance()->showMessage(this, msg);
  startQueued();
}

void ThreadLauncher::stop()
{
  TRACE;
  bool selectAll;
  if( !isAnySelected()) {
    if(assumeAllSelected(tr("Stoping inversion run"), tr("stop")))
      selectAll=true;
    else
      return;
  } else selectAll=false;

  MainWindow * w=WindowEnvironment::window(this);
  int n=w->threads().count();
  for(int i=0;i < n;i++ ) {
    if(selectAll || threadTable->isThreadSelected(i)) {
      InversionThread * t=w->threads().at(i);
      t->terminate();
      for(int i=0; i<_processToStart.count();) {
        if(_processToStart.at(i)==t) {
          _processToStart.removeAt(i);
        } else {
          i++;
        }
      }
    }
  }
  startQueued();
}

void ThreadLauncher::clear()
{
  TRACE;
  bool selectAll;
  if(!isAnySelected()) {
    if(assumeAllSelected(tr("Clearing inversion run"), tr("clear")))
      selectAll=true;
    else
      return;
  } else selectAll=false;

  MainWindow * w=WindowEnvironment::window(this);
  int n=w->threads().count();
  for(int i=0; i<n; i++) {
    if(selectAll || threadTable->isThreadSelected(i)) {
      w->clearThread(w->threads().at(i));
    }
  }
  w->setModified(true);
  w->bigBen().synchronize();
}

void ThreadLauncher::show(bool)
{
  TRACE;
  selectionChanged();
}

void ThreadLauncher::remove()
{
  TRACE;
  MainWindow * w=WindowEnvironment::window(this);
  if(!isAnySelected()) {
    if(assumeAllSelected(tr("Removing inversion run"), tr("remove")) ) {
      if(!w->closeAllPSViewers()) return;
      for(int i=w->threads().count()-1; i>=0 ; i--) {
          w->removeThread(w->threads().at(i));
      }
    }
  } else {
    if(!w->closeAllPSViewers()) return;

    QModelIndexList l=threadTable->selectionModel()->selectedRows();
    // First store pointers to remove
    QList<InversionThread *> tList;
    for(int i=l.count()-1; i>=0; i-- ) {
      tList.append(w->threads().at(l.at(i).row()));
    }
    for(int i=tList.count()-1; i>=0; i-- ) {
      InversionThread * t=tList.at(i);
      if(t->isRunning()) {
        t->terminate();
      }
      int indexToStart=_processToStart.indexOf(t);
      if(indexToStart>=0) {
        _processToStart.removeAt(indexToStart);
      }
      w->removeThread(t);
    }
  }
  w->setModified(true);
}

void ThreadLauncher::importModels()
{
  TRACE;
  InversionThread * t=threadTable->currentEditableThread();
  if(t) {
    QString reportName=Message::getOpenFileName(tr("Importing models"),
                                                   tr("Inversion report file (*.report)"));
    if(!reportName.isEmpty() && !t->isRunning()) {
      t->importModels(reportName);
      WindowEnvironment::window(this)->status()->synchronize();
    }
  }
}

/*!
  Wait until all processes are finished (even the queued ones).
*/
void ThreadLauncher::wait()
{
  TRACE;
  while(_runningProcess>0 || !_processToStart.empty()) {
    App::sleep(1000);
    QCoreApplication::processEvents();
  }
}
