/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2013 Frederik Heber. All rights reserved.
 * 
 *
 *   This file is part of MoleCuilder.
 *
 *    MoleCuilder 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 2 of the License, or
 *    (at your option) any later version.
 *
 *    MoleCuilder 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 MoleCuilder.  If not, see .
 */
/*
 * ActionQueue.cpp
 *
 *  Created on: Aug 16, 2013
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
#include "CodePatterns/MemDebug.hpp"
#include "Actions/ActionQueue.hpp"
#include "CodePatterns/Assert.hpp"
#include "CodePatterns/IteratorAdaptors.hpp"
#include "CodePatterns/Log.hpp"
#include "CodePatterns/Singleton_impl.hpp"
#include 
#include 
#include 
#include 
#include 
#include "Actions/ActionExceptions.hpp"
#include "Actions/ActionHistory.hpp"
#include "Actions/ActionRegistry.hpp"
#include "World.hpp"
using namespace MoleCuilder;
ActionQueue::ActionQueue() :
    AR(new ActionRegistry()),
    history(new ActionHistory),
#ifndef HAVE_ACTION_THREAD
    CurrentAction(0)
#else
    CurrentAction(0),
    run_thread(boost::bind(&ActionQueue::run, this)),
    run_thread_isIdle(true)
#endif
{}
ActionQueue::~ActionQueue()
{
#ifdef HAVE_ACTION_THREAD
  stop();
#endif
  // free all actions contained in actionqueue
  for (ActionQueue_t::iterator iter = actionqueue.begin(); !actionqueue.empty(); iter = actionqueue.begin()) {
    delete *iter;
    actionqueue.erase(iter);
  }
  delete history;
  delete AR;
}
void ActionQueue::queueAction(const std::string &name, enum Action::QueryOptions state)
{
  queueAction(AR->getActionByName(name), state);
}
void ActionQueue::queueAction(Action *_action, enum Action::QueryOptions state)
{
  Action *newaction = _action->clone(state);
  newaction->prepare(state);
#ifdef HAVE_ACTION_THREAD
  mtx_queue.lock();
#endif
  actionqueue.push_back( newaction );
#ifndef HAVE_ACTION_THREAD
  try {
    newaction->call();
  } catch(ActionFailureException &e) {
    std::cerr << "Action " << *boost::get_error_info(e) << " has failed." << std::endl;
    World::getInstance().setExitFlag(5);
  }
#else
  {
    boost::lock_guard lock(mtx_idle);
    run_thread_isIdle = (CurrentAction == actionqueue.size());
  }
  mtx_queue.unlock();
#endif
}
void ActionQueue::insertAction(Action *_action, enum Action::QueryOptions state)
{
#ifndef HAVE_ACTION_THREAD
  queueAction(_action, state);
#else
  Action *newaction = _action->clone(state);
  newaction->prepare(state);
  mtx_queue.lock();
  tempqueue.push_back( newaction );
  {
    boost::lock_guard lock(mtx_idle);
    run_thread_isIdle = !((CurrentAction != actionqueue.size()) || !tempqueue.empty());
  }
  mtx_queue.unlock();
#endif
}
#ifdef HAVE_ACTION_THREAD
void ActionQueue::run()
{
  bool Interrupted = false;
  do {
    // sleep for some time and wait for queue to fill up again
    try {
#if BOOST_VERSION < 105000
      run_thread.sleep(boost::get_system_time() + boost::posix_time::milliseconds(100));
#else
      run_thread.sleep_for(100);
#endif
    } catch(boost::thread_interrupted &e) {
      LOG(2, "INFO: ActionQueue has received stop signal.");
      Interrupted = true;
    }
//    LOG(1, "DEBUG: Start of ActionQueue's run() loop.");
    // call all currently present Actions
    mtx_queue.lock();
    insertTempQueue();
    bool status = (CurrentAction != actionqueue.size());
    mtx_queue.unlock();
    while (status) {
      //      boost::this_thread::disable_interruption di;
      LOG(0, "Calling Action " << actionqueue[CurrentAction]->getName() << " ... ");
      try {
        actionqueue[CurrentAction]->call();
      } catch(ActionFailureException &e) {
        std::cerr << "Action " << *boost::get_error_info(e) << " has failed." << std::endl;
        World::getInstance().setExitFlag(5);
      }
      // access actionqueue, hence using mutex
      mtx_queue.lock();
      // step on to next action and check for end
      CurrentAction++;
      // insert new actions (before [CurrentAction]) if they have been spawned
      // we must have an extra vector for this, as we cannot change actionqueue
      // while an action instance is "in-use"
      insertTempQueue();
      status = (CurrentAction != actionqueue.size());
      mtx_queue.unlock();
    }
    {
      boost::lock_guard lock(mtx_idle);
      run_thread_isIdle = !((CurrentAction != actionqueue.size()) || !tempqueue.empty());
    }
    cond_idle.notify_one();
//    LOG(1, "DEBUG: End of ActionQueue's run() loop.");
  } while (!Interrupted);
}
#endif
void ActionQueue::insertTempQueue()
{
  if (!tempqueue.empty()) {
    ActionQueue_t::iterator InsertionIter = actionqueue.begin();
    std::advance(InsertionIter, CurrentAction);
    actionqueue.insert( InsertionIter, tempqueue.begin(), tempqueue.end() );
    tempqueue.clear();
  }
}
#ifdef HAVE_ACTION_THREAD
void ActionQueue::wait()
{
  boost::unique_lock lock(mtx_idle);
  while(!run_thread_isIdle)
  {
      cond_idle.wait(lock);
  }
}
#endif
#ifdef HAVE_ACTION_THREAD
void ActionQueue::stop()
{
  // notify actionqueue thread that we wish to terminate
  run_thread.interrupt();
  // wait till it ends
  run_thread.join();
}
#endif
Action* ActionQueue::getActionByName(const std::string &name)
{
  return AR->getActionByName(name);
}
bool ActionQueue::isActionKnownByName(const std::string &name) const
{
  return AR->isActionPresentByName(name);
}
void ActionQueue::registerAction(Action *_action)
{
  AR->registerInstance(_action);
}
void ActionQueue::outputAsCLI(std::ostream &output) const
{
  for (ActionQueue_t::const_iterator iter = actionqueue.begin();
      iter != actionqueue.end();
      ++iter) {
    // skip store-session in printed list
    if ( ((*iter)->getName() != std::string("store-session"))
        && ((*iter)->getName() != std::string("load-session"))) {
      if (iter != actionqueue.begin())
        output << " ";
      (*iter)->outputAsCLI(output);
    }
  }
  output << std::endl;
}
void ActionQueue::outputAsPython(std::ostream &output) const
{
  const std::string prefix("pyMoleCuilder");
  output << "import " << prefix << std::endl;
  output << "# ========================== Stored Session BEGIN ==========================" << std::endl;
  for (ActionQueue_t::const_iterator iter = actionqueue.begin();
      iter != actionqueue.end();
      ++iter) {
    // skip store-session in printed list
    if ( ((*iter)->getName() != std::string("store-session"))
        && ((*iter)->getName() != std::string("load-session")))
      (*iter)->outputAsPython(output, prefix);
  }
  output << "# =========================== Stored Session END ===========================" << std::endl;
}
const ActionTrait& ActionQueue::getActionsTrait(const std::string &name) const
{
  // this const_cast is just required as long as we have a non-const getActionByName
  const Action * const action = const_cast(this)->getActionByName(name);
  return action->Traits;
}
void ActionQueue::addElement(Action* _Action,ActionState::ptr _state)
{
  history->addElement(_Action, _state);
}
void ActionQueue::clear()
{
  history->clear();
}
const ActionQueue::ActionTokens_t ActionQueue::getListOfActions() const
{
  ActionTokens_t returnlist;
  returnlist.insert(
      returnlist.end(),
      MapKeyConstIterator(AR->getBeginIter()),
      MapKeyConstIterator(AR->getEndIter()));
  return returnlist;
}
void ActionQueue::undoLast()
{
	history->undoLast();
}
void ActionQueue::redoLast()
{
	history->redoLast();
}
CONSTRUCT_SINGLETON(ActionQueue)