//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#include "Progress.hpp"
#ifndef WIN32
#  include <unistd.h>
#endif
#include <QMutexLocker>

#include <stdio.h>
#include <omp.h>
#include <iostream>
#include <sys/time.h>

#include <Information.hpp>

#define SHOW_TIMER 2000

namespace mgx 
{
  Progress* Progress::_instance = 0;
   struct StartProgressEvent : public QEvent 

  {
    StartProgressEvent(const QString& _msg, int _steps, bool _allowCancel)
      : QEvent(QEvent::User), msg(_msg), steps(_steps), allowCancel(_allowCancel) {}
  
    QString msg;
    int steps;
    bool allowCancel;
  };
  
  struct AdvanceProgressEvent : public QEvent 
  {
    AdvanceProgressEvent(int i) : QEvent(QEvent::User), step(i) {}
  
    int step;
  };
  
  struct SetMaxProgressEvent : public QEvent 
  {
    SetMaxProgressEvent(int n) : QEvent(QEvent::User), steps(n) {}
  
    int steps;
  };
  
  struct SetMsgProgressEvent : public QEvent 
  {
    SetMsgProgressEvent(QString _msg) : QEvent(QEvent::User), msg(_msg) {}
  
    QString msg;
  };
  
  struct StopProgressEvent : public QEvent 
  {
    StopProgressEvent() : QEvent(QEvent::User) {}
  };


  // Progress class
  Progress::Progress() : _parent(0), _canceled(false), _step(0), _steps(0), _msg("Ready.") {}
  
  Progress::~Progress() {} 

  Progress& Progress::instance()
  {
    if(_instance == 0)
      _instance = new Progress();
    return *_instance;
  }

  // Process progress related events
  bool Progress::processEvent(QEvent *e)
  {
    StartProgressEvent* spe = dynamic_cast<StartProgressEvent*>(e);
    if(spe) {
      start(spe->msg, spe->steps, spe->allowCancel);
      return true;
    }
    AdvanceProgressEvent* ape = dynamic_cast<AdvanceProgressEvent*>(e);
    if(ape) {
      advance(ape->step);
      return true;
    }
    SetMaxProgressEvent* mpe = dynamic_cast<SetMaxProgressEvent*>(e);
    if(mpe) {
      setMax(mpe->steps);
      return true;
    }
    SetMsgProgressEvent* gpe = dynamic_cast<SetMsgProgressEvent*>(e);
    if(gpe) {
      setMsg(gpe->msg);
      return true;
    }
    StopProgressEvent* ppe = dynamic_cast<StopProgressEvent*>(e);
    if(ppe) {
      stop();
      return true;
    }
    return false;
  }

  void Progress::setupProgress(QWidget* parent, QToolBar* progressToolBar)
  {
    _parent = parent;
    _progressText = new QLabel;
    _progressText->setLayoutDirection(Qt::RightToLeft);
    _progressText->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    _progressText->setAlignment(Qt::AlignVCenter | Qt::AlignRight);
    _progressText->setText(_msg);
    _progressBar = new QProgressBar;
    _progressBar->setFixedWidth(100);
    _progressStop = new QPushButton;
    _progressStop->setIcon(QIcon(":images/Cancel.png"));
    QList<QAction *> actions = progressToolBar->actions();
    progressToolBar->insertWidget(actions[0], _progressText);
    progressToolBar->insertSeparator(actions[0]);
    progressToolBar->insertWidget(actions[0], _progressBar);
    connect(_progressStop, SIGNAL(clicked()), parent, SLOT(ProgressStopSlot()));
  }
  
  void Progress::cancel() 
  {
    _canceled = true;
  }

  QString Progress::makeMsg() const
  {
    QString ret;
    if(_steps > 0)
      ret = QString("%1 (%2%)").arg(_msg).arg(int(double(_step)/double(_steps) * 100.0));
    else
      ret = _msg;

    return ret;
  }
 
  void Progress::start(const QString& msg, int steps, bool allowCancel)
  {
    if(thread() != QThread::currentThread()) {
      QCoreApplication::postEvent(_parent, new StartProgressEvent(msg, steps, allowCancel));
      QThread::yieldCurrentThread();
      return;
    }
    _msg = msg;
    _canceled = false;
    _step = 0;
    _steps = steps;
    _progressBar->setValue(0); 
    _progressBar->setMaximum(0); 
    _progressText->setText(makeMsg()); 
    _progressBar->setTextVisible(false);
    if(allowCancel)
      _progressStop->setEnabled(true);
    else
      _progressStop->setEnabled(false);
  }

  bool Progress::advance(int step)
  {
    if(thread() != QThread::currentThread()) {
      if(canceled())
        return false;
      if(omp_get_thread_num() != 0)
        return true;

      // Only post at most 10 events per second
      static long lastEvent = 0;
      struct timeval tv;
      gettimeofday(&tv, NULL);
      if(long(tv.tv_usec) - lastEvent > 0 and long(tv.tv_usec) - lastEvent < 100000)
        return true;
      lastEvent = long(tv.tv_usec);

      QCoreApplication::postEvent(_parent, new AdvanceProgressEvent(step));
      QThread::yieldCurrentThread();
      return true;
    }

    // Only update at most 10 times per second
    static long lastAdvance = 0;
    struct timeval tv;
    gettimeofday(&tv, NULL);
    if(long(tv.tv_usec) - lastAdvance > 0 and long(tv.tv_usec) - lastAdvance < 100000)
      return true;
    lastAdvance = long(tv.tv_usec);
    
    _progressBar->setValue(0);
		if(step < 0)
			return true;
    int newPos = int(double(step)/double(_steps) * 100);
    int oldPos = int(double(_step)/double(_steps) * 100);
    _step = step;
    if(newPos > oldPos)
      _progressText->setText(makeMsg());
    return true;
  }
  
  bool Progress::canceled()
  {
    return _canceled;
  }
  
  void Progress::setMsg(const QString& msg)
  {
    if(thread() != QThread::currentThread()) {
      QCoreApplication::postEvent(this, new SetMsgProgressEvent(msg));
      QThread::yieldCurrentThread();
      return;
    }
    _msg = msg;
    _progressText->setText(makeMsg());
    _progressBar->setValue(0);
  }

  void Progress::setMax(int steps)
  {
    if(thread() != QThread::currentThread()) {
      QCoreApplication::postEvent(this, new SetMaxProgressEvent(steps));
      QThread::yieldCurrentThread();
      return;
    }
    _steps = steps;
    _progressBar->setValue(0);
  }
 
  void Progress::stop()
  {
    if(thread() != QThread::currentThread()) {
      QCoreApplication::postEvent(_parent, new StopProgressEvent());
      QThread::yieldCurrentThread();
      return;
    }
    _progressBar->reset();
    _progressBar->setValue(0);
    _progressBar->setMaximum(1);
    _progressBar->setTextVisible(false);
    _progressStop->setEnabled(false);
    _msg = "Ready.";
    _progressText->setText(_msg);
  }

  // Progress functions that are called externally
  void progressStart(const QString &msg, int steps, bool allowCancel)
  {
    Progress::instance().start(msg, steps, allowCancel);
  }
  void progressStart(const char *msg, int steps, bool allowCancel)
  {
    Progress::instance().start(msg, steps, allowCancel);
  }
  void progressStart(const std::string &msg, int steps, bool allowCancel)
  {
    Progress::instance().start(msg, steps, allowCancel);
  } 
  void progressStop() 
  {
    Progress::instance().stop();
  }
  bool progressAdvance(int step) 
  {
    return Progress::instance().advance(step);
  }
	bool progressAdvance() 
  {
    return Progress::instance().advance();
  }
  bool progressCanceled() 
  {
    return Progress::instance().canceled();
  }
  void progressCancel() 
  {
    Progress::instance().cancel();
  }
  void progressSetMsg(const QString &msg)
  {
    Progress::instance().setMsg(msg);
  }
  void progressSetMsg(const char *msg)
  {
    Progress::instance().setMsg(msg);
  }
  void progressSetMsg(const std::string &msg)
  {
    Progress::instance().setMsg(msg);
  } 
	void progressSetMax(int steps)
  {
    Progress::instance().setMax(steps);
  } 
}
