/***************************************************************************
**
**  This file is part of QGpCoreTools.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file 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 Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2007-12-26
**  Copyright: 2007-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "Thread.h"
#include "TraceInfo.h"
#include "CoreApplication.h"

namespace QGpCoreTools {

/*!
  \class Thread Thread.h
  \brief Thread with an internal stack to record trace points.
*/

Mutex Thread::_threadsMutex;
QList<Thread *> Thread::_threads;
QSet<Thread *> Thread::_runningThreads;
QVector<int> Thread::_cpuAffinities;

Thread::Thread(QObject * parent)
   : QThread(parent)
{
  QMutexLocker ml(&_threadsMutex);
  _threads.append(this);
  _sleeping=true;
}

Thread::~Thread()
{
  QMutexLocker ml(&_threadsMutex);
  int index=_threads.indexOf(this);
  if(index>=0) {
    _threads.removeAt(index);
    CoreApplication::instance()->threadDeleted(this);
  }
  qDeleteAll(_logVector);
}

QStringList Thread::threadNames()
{
  QMutexLocker ml(&_threadsMutex);
  QStringList list;
  list.append("main");
  for(QList<Thread *>::iterator it=_threads.begin(); it!=_threads.end(); it++ ) {
    list.append((*it)->objectName());
  }
  return list;
}

#ifdef TRACE_ENABLED
QList< QStack<const TraceStamp *> * > Thread::stacks()
{
  QList< QStack<const TraceStamp *> * > list;
  list.append(Trace::mainStack());
  for(QList<Thread *>::iterator it=_threads.begin(); it!=_threads.end(); it++ ) {
    list.append((*it)->stack());
  }
  return list;
}
#endif

QList< QStack<TraceBug *> * > Thread::bugStacks()
{
  QList< QStack<TraceBug *> * > list;
  list.append(TraceBug::mainStack());
  for(QList<Thread *>::iterator it=_threads.begin(); it!=_threads.end(); it++ ) {
    list.append((*it)->bugStack());
  }
  return list;
}

QList< QVector<TraceLog *> * > Thread::logVectors()
{
  QList< QVector<TraceLog *> * > list;
  list.append(TraceLog::mainVector());
  for(QList<Thread *>::iterator it=_threads.begin(); it!=_threads.end(); it++ ) {
    list.append((*it)->logVector());
  }
  return list;
}

/*!
  Get CPU affinity list, to be called by CoreApplication
*/
void Thread::initCPUAffinityList()
{
#ifdef Q_OS_LINUX
  cpu_set_t cpuset;
  pthread_t thread=pthread_self();
  // Get affinity mask of the main thread (children threads will inherits the same mask)

  CPU_ZERO(&cpuset);
  int s=pthread_getaffinity_np(thread, sizeof(cpu_set_t), &cpuset);
  if(s!=0) {
    App::log(tr("Thead affinity mask cannot be accessed, skipping CPU affinity optimization\n"));
  } else {
    for (int i=0; i<CPU_SETSIZE; i++) {
      if(CPU_ISSET(i, &cpuset)) {
        _cpuAffinities.append(i);
      }
    }
  }
#endif
}

/*!
  An implementation that tries to take already running threads into
  account. The return number is always less or equal to the number of
  physical cores. Never returns a number less than 1.
*/
int Thread::idealThreadCount()
{
  int nThreads=CoreApplication::instance()->maximumThreadCount();
  if(nThreads==1) {
    return 1; // Exit as soon as possible for single processor machines
  }
  QSet<Thread *>::iterator it;
  QMutexLocker ml(&_threadsMutex);
  for(it=_runningThreads.begin(); it!=_runningThreads.end(); it++) {
    if(!(*it)->sleeping()) {
      nThreads--;
    }
  }
  if(!_cpuAffinities.isEmpty() && nThreads>_cpuAffinities.count()) {
    nThreads=_cpuAffinities.count();
  }
  return nThreads>0 ? nThreads : 1;
}

void Thread::start(QThread::Priority priority)
{
  _threadsMutex.MUTEX_LOCK;
  _runningThreads.insert(this);
  _threadsMutex.unlock();
  _sleeping=false;
  QThread::start(priority);
  _sleeping=true;
  _threadsMutex.MUTEX_LOCK;
  _runningThreads.remove(this);
  _threadsMutex.unlock();
}

void Thread::sleep(unsigned long secs)
{
  Thread * t = currentThread();
  _threadsMutex.MUTEX_LOCK;
  if (_runningThreads.contains( t ) ) {
    _threadsMutex.unlock();
    t->setSleeping(true);
    QThread::sleep(secs);
    t->setSleeping(false);
  } else {
    _threadsMutex.unlock();
    QThread::sleep( secs );
  }
}

void Thread::msleep(unsigned long msecs)
{
  Thread * t=currentThread();
  _threadsMutex.MUTEX_LOCK;
  if(msecs>10 && _runningThreads.contains(t)) {
    _threadsMutex.unlock();
    t->setSleeping(true);
    QThread::msleep(msecs);
    t->setSleeping(false);
  } else {
    _threadsMutex.unlock();
    QThread::msleep(msecs);
  }
}

void Thread::usleep(unsigned long usecs)
{
  Thread * t = currentThread();
  _threadsMutex.MUTEX_LOCK;
  if (usecs>10000 && _runningThreads.contains(t)) {
    _threadsMutex.unlock();
    t->setSleeping(true);
    QThread::usleep(usecs);
    t->setSleeping(false);
  } else {
    _threadsMutex.unlock();
    QThread::usleep(usecs);
  }
}

} // namespace QGpCoreTools
