/***************************************************************************
**
**  This file is part of SciFigs.
**
**  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: 2008-02-12
**  Copyright: 2008-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "LayerPainter.h"
#include "LayerPainterThread.h"
#include "GraphContent.h"

namespace SciFigs {

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

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
LayerPainter::LayerPainter()
{
  TRACE;
  // We ignore user specification for parallel jobs in this case
  // Forced to hardware maximum.
  int maximumThreads=QThread::idealThreadCount();
  //int maximumThreads=1;
  if(maximumThreads<1) maximumThreads=1;
  // _threads is never modified in thread, only in main thread, hence no need for mutex
  for(int i=0; i<maximumThreads; i++) {
    LayerPainterThread * t=new LayerPainterThread(this);
    t->setObjectName(QString("layerPainter%1").arg(i+1));
    _threads.append(t);
    t->start(QThread::LowestPriority);
  }
}

/*!
  Description of destructor still missing
*/
LayerPainter::~LayerPainter()
{
  TRACE;
  _queueMutex.lock();
  qDeleteAll(_queue);
  _queue.clear();
  _queueMutex.unlock();
  for(QList<LayerPainterThread *>::iterator it=_threads.begin(); it!=_threads.end(); it++) {
    (*it)->terminate();
  }
  _paintEvent.wakeAll();
  for(QList<LayerPainterThread *>::iterator it=_threads.begin(); it!=_threads.end(); it++) {
    (*it)->wait();
  }
}

/*!
  Remove all requests corresponding to \a gc. This function blocks until effective termination of all paint requests
  attached to \a gc.
*/
void LayerPainter::clear(GraphContent * gc)
{
  _queueMutex.lock();
  // Clear the queue for any request corresponding to r
  for(int i=0; i<_queue.count(); i++) {
    LayerPainterRequest * oldRequest=_queue.at(i);
    if(gc==oldRequest->graphContent()) {
      delete oldRequest;
      _queue.removeAt(i);
      i--;
    }
  }
  // Terminate running request
  for(QList<LayerPainterThread *>::const_iterator it=_threads.begin(); it!=_threads.end(); it++) {
    LayerPainterRequest * request=(*it)->currentRequest();
    if(request && request->graphContent()==gc) {
      request->terminate();
    }
  }
  _queueMutex.unlock();
  // Wait for proper termination
  QList<LayerPainterThread *>::const_iterator it;
  do {
    _queueMutex.lock();
    for(it=_threads.begin(); it!=_threads.end(); it++) {
      LayerPainterRequest * request=(*it)->currentRequest();
      if(request && request->graphContent()==gc) {
        break;
      }
    }
    _queueMutex.unlock();
    App::sleep(10);
    //QWaitCondition
  } while(it!=_threads.end());
}

/*!
  Terminate asynchroneously all paint requests attached to \a gc.
  Return the maximum depth of terminated requests or -1 if no request to terminate.
  This is designed so to be able to requeue the terminated request (with the latest parameters).
*/
int LayerPainter::terminate(GraphContent * gc)
{
  TRACE;
  int depth=-1;
  _queueMutex.lock();
  for(QList<LayerPainterThread *>::iterator it=_threads.begin(); it!=_threads.end(); it++) {
    LayerPainterRequest * request=(*it)->currentRequest();
    if(request && request->graphContent()==gc) {
      if(depth==-1 || depth>request->depth()) depth=request->depth();
      request->terminate();
    }
  }
  _queueMutex.unlock();
  return depth;
}

/*!
  Send a paint request. \a r must have the graphContent, layers, size, options, intermediate images and depth set up.

  \code
    LayerPainterRequest * request=new LayerPainterRequest;
    request->setGraphContent(this);
    request->setLayers(_layers);
    request->setSize(size());
    request->setOptions(_d);
    request->setLayerImages(_layerImages);
    request->setDepth(_paintDepth);
  \endcode
*/
void LayerPainter::paint(LayerPainterRequest * r)
{
  TRACE;
  _queueMutex.lock();
  // Check running requests, stop them asynchroneously
  for(QList<LayerPainterThread *>::iterator it=_threads.begin(); it!=_threads.end(); it++) {
    LayerPainterRequest * oldRequest=(*it)->currentRequest();
    if(oldRequest && oldRequest->graphContent()==r->graphContent()) {
      if(oldRequest->depth()==r->depth() || oldRequest->size()!=r->size()) {
        oldRequest->terminate();
      }
    }
  }
  // All requests found in the queue are not running, start from the request to be handled first
  for(int i=0; i<_queue.count(); i++) {
    LayerPainterRequest * oldRequest=_queue.at(i);
    if(r->graphContent()==oldRequest->graphContent()) {
      if(oldRequest->size()==r->size()) {
        if(oldRequest->depth()<r->depth()) {
          /* Old request has to repaint more layers.
            Because old request is still not running we inverse the priorities to accelerate rendering
            for the latest user request that requires less layers to be repainted. */
          _queue[i]=r;
          r=oldRequest;
        } else if(oldRequest->depth()==r->depth()) {
          /* The old request and the new one will repaint the same layers. The old one will do useless job */
          delete oldRequest;
          _queue.removeAt(i);
          i--;
        } /* Else leave the old request as it is. */
      } else {
        /* Old request has not the correct size or options, remove it but keep its depth if less than new one */
        if(oldRequest->depth()<r->depth()) {
          r->setDepth(oldRequest->depth());
        }
        delete oldRequest;
        _queue.removeAt(i);
        i--;
      }
    }
  }
  _queue.enqueue(r);
  // Draw text for layers that contain text (text depends upon QFont which cannot be used in thread)
  // paintText is run from the main thread.
  // After unlock, the main paint request may start at any time and paintText must be finished.
  // textPaths created by paintText must be protected by specific mutex
  r->paintText();
  //printf("--- painting request %p (%i)\n",r->graphContent(), _queue.count());
  // Wake at least one thread. After timing measurements, this is the longest operation ???
  _paintEvent.wakeOne();
  // Unlock after wake up to avoid block of this thread
  _queueMutex.unlock();
}


} // namespace SciFigs
