/***************************************************************************
**
**  This file is part of QGpCoreTools.
**
**  QGpCoreTools 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.
**
**  QGpCoreTools 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: 2020-02-18
**  Copyright: 2020
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "TaskManager.h"
#include "AbstractTask.h"

namespace QGpCoreTools {

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

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  TaskManager::TaskManager()
    : ParallelLoop()
  {
    TRACE;
    _nextAvailableTask=0;
  }

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

  void TaskManager::setTasks(int n)
  {
    qDeleteAll(_tasks);
    _tasks.clear();
    for(int i=0; i<n; i++) {
      AbstractTask * t=createTask(i);
      connect(t, SIGNAL(statusChanged(int, QString)), this, SIGNAL(statusChanged(int, QString)));
      connect(t, SIGNAL(progressInit(int, int)), this, SIGNAL(progressInit(int, int)));
      connect(t, SIGNAL(progressChanged(int, int)), this, SIGNAL(progressChanged(int, int)));
      _tasks.append(t);
    }
    _nextAvailableTask=0;
  }

  /*!
    Start tasks from \a iStart to \a iEnd-1
  */
  void TaskManager::start(bool forceParallel)
  {
    TRACE;
    ParallelLoop::start(0, Thread::idealThreadCount(), forceParallel);
  }

  /*!
    Return the next available fresh task (still unprocessed).
    The task is returned locked, the caller must unlock it.
    A null value is returned if there are not more fresh task available.
  */
  AbstractTask * TaskManager::nextFreshTask()
  {
    _taskLock.lockForRead();
    _nextAvailableMutex.lock();
    if(_nextAvailableTask<_tasks.count()) {
      AbstractTask * t=_tasks.at(_nextAvailableTask++);
      _nextAvailableMutex.unlock();
      t->init();
      t->lock();
      _taskLock.unlock();
      return t;
    } else {
      _nextAvailableMutex.unlock();
      _taskLock.unlock();
      return nullptr;
    }
  }

  /*!
    Call this function when there is no more fresh task available.
    All finished tasks are removed.
  */
  void TaskManager::purgeTasks()
  {
    QList<AbstractTask *> finishedTasks;
    _taskLock.lockForWrite();
    for(int i=0; i<_tasks.count(); i++) {
      AbstractTask * t=_tasks.at(i);
      if(t->tryLock()) {
        if(t->isFinishing() && !t->isRunning()) {
          finishedTasks.append(t);
          t->unlock();
          _tasks.removeAt(i);
          i--;
        } else {
          t->unlock();
        }
      }
    }
    _taskLock.unlock();
    // Polish finished tasks after unlocking, processed by the current worker
    for(int i=finishedTasks.count()-1; i>=0; i--) {
      AbstractTask * t=finishedTasks.at(i);
      t->polish();
      delete t;
    }
  }

  /*!
    Return the next available task.
    The task is returned locked, the caller must unlock it.
    A null value is returned if there are not more fresh task available.
  */
  AbstractTask * TaskManager::nextTask()
  {
    _taskLock.lockForRead();
    // Scan for unlocked tasks, without waiting
    for(int i=0; i<_tasks.count(); i++) {
      AbstractTask * t=_tasks.at(i);
      if(t->tryLock()) {
        if(t->isFinishing()) {
          t->unlock();
        } else {
          _taskLock.unlock();
          return t;
        }
      }
    }
    // Scan for unlocked tasks, but accept a little wait (occurs at the end)
    for(int i=0; i<_tasks.count(); i++) {
      AbstractTask * t=_tasks.at(i);
      t->lock();
      if(t->isFinishing()) {
        t->unlock();
      } else {
        _taskLock.unlock();
        return t;
      }
    }
    _taskLock.unlock();
    return nullptr;
  }

} // namespace QGpCoreTools

