// src/backend/concrete_simulation.cc - Copyright 2005, 2006, University
//               of Padova, dept. of Pure and Applied
//               Mathematics
//
// This file is part of SGPEMv2.
//
// This 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.
//
// SGPEMv2 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 SGPEMv2; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

#include "concrete_simulation.hh"

#include <sgpemv2/simulation_observer.hh>
#include <sgpemv2/scheduler.hh>
#include <sgpemv2/cpu_policies_gatekeeper.hh>
#include <sgpemv2/resource_policies_gatekeeper.hh>

#include <cassert>

#include <sgpemv2/templates/smartp.tcc>

#include <algorithm>
#include <cassert>
#include <functional>
#include <iostream>

using namespace std;
using namespace sgpem;
using namespace memory;

ConcreteSimulation::ConcreteSimulation() :
  Simulation(), _state(state_stopped), 
  _mode(mode_continuous), _policy(NULL), _resource_policy(NULL)
{
}

void
ConcreteSimulation::set_mode(const mode new_mode)
{
  _mode = new_mode;
}

Simulation::mode
ConcreteSimulation::get_mode() const
{
  return _mode;
}

void
ConcreteSimulation::jump_to(History::position p) throw(UserInterruptException, NullPolicyException, MalformedPolicyException)
{
  switch (_state)
  {
  case state_running:
    // pauses the simulation (done below)
 		break;
  case state_stopped:
    _history.set_front(0);
    break;
  default:
    break;
  }

  // Disable momentarily updates for registered observers on 
  // sgpem::Simulation and sgpem::History.
  bool his_enabled = _history.set_notify_enabled(false);
  bool sim_enabled = set_notify_enabled(false);

	pause();

	bool yet_to_finish = true;
  History::position increment = 0;
	while (yet_to_finish && p > _history.get_front() + increment)
  {
    try
      {
        yet_to_finish = step();
        increment++;
      }
    catch(const CPUPolicyException&)
      {
        // Lookout that notifications have to be re-enabled.
        _history.set_notify_enabled(his_enabled);
        set_notify_enabled(sim_enabled);
        throw;
      }
  }
  get_history().set_front(std::min(p, _history.get_size()));
	if (!yet_to_finish)
		stop();

  // Reenables updates to registered observers. 
  // Calls _history.notify_change() on reactivation.
  _history.set_notify_enabled(his_enabled);

  // Do the same for notifications on the state
  // of Simulation
  set_notify_enabled(sim_enabled);
}


void
ConcreteSimulation::pause()
{
  if(_state != state_paused)
    {
      _state = state_paused;
      notify_change();
    }
}

void
ConcreteSimulation::stop()
{
  if(_state != state_stopped)
    {
      _state = state_stopped;
      notify_change();
    }
}

void
ConcreteSimulation::run() throw(UserInterruptException, NullPolicyException, MalformedPolicyException)
{
  switch (_state)
  {
  case state_stopped:
    _history.set_front(0);
    break;
  default:
    break;
  }

  _state = state_running;

  //step forward
  bool yet_to_finish = step();
  _history.set_front(_history.get_front() + 1);
  if (yet_to_finish)
    {

      if(_mode == mode_step_by_step) 
        pause();
      else
        // We remain in running state, and we notify everybody!
        // This is non-trivial, and we must do so since we
        // put the state to running inconditionally. Don't
        // touch this if you don't provide another way to tell 
        // a SimulationObserver that the simulation advanced 
        // and *yet* it is in running state!
        notify_change();
    }
  else
    // Simulation ended
    stop();
}

bool
ConcreteSimulation::step()
	throw(UserInterruptException, NullPolicyException, MalformedPolicyException)
{
  if (get_policy() == NULL)
    {
      stop();
      throw NullPolicyException("no CPU policy selected");
    }

  if (get_resource_policy() == NULL)
    {
      stop();
      throw NullPolicyException("no resource policy selected");
    }
  
  try
    {
      // step forward
      bool yet_to_finish = true;
			if (_history.get_front() == _history.get_size() - 1)
				if(!_history.is_sealed())
					yet_to_finish = Scheduler::get_instance().step_forward(_history, *get_policy(), *get_resource_policy());
        else 
          yet_to_finish = false;      

      if (!yet_to_finish) _history.seal();

      // since the simulation expects to be notified
      // of simulation termination when reaching the last environment
      // and the front will be updated just out of this method,
      // we have to make this horrible thing
      if (_history.get_front() == _history.get_size() - 2 && _history.is_sealed())
        yet_to_finish = false;
     
			return yet_to_finish;
    }
  catch (const CPUPolicyException& e)
    {
      stop();
      throw;
    }
}


Simulation::state
ConcreteSimulation::get_state() const
{
  return _state;
}

ConcreteHistory&
ConcreteSimulation::get_history()
{
  return _history;
}

const ConcreteHistory&
ConcreteSimulation::get_history() const
{
  return _history;
}


void
ConcreteSimulation::set_policy(CPUPolicy* p) throw(CPUPolicyException)
{
  stop();

  // NOTE: restoring of the previous policy is done here because I
  // couldn't think of a clean way to do it
  // inside activate_policy()
 
  try
  {
    CPUPoliciesGatekeeper::get_instance().activate_policy(&_history, p);
    _policy = p;
  }
  catch(const CPUPolicyException& e1)
  {
    try
    {
      // this is a no-op if _policy is NULL
      CPUPoliciesGatekeeper::get_instance().activate_policy(&_history, _policy);
    }
    catch(const CPUPolicyException& e2)
    {
      _policy = NULL;

      string msg = _("unable to change policy and to restore the previous: ");
      msg += e2.what();

      throw CPUPolicyException(msg);
    }

    string msg = _("unable to change policy: ");
    msg+= e1.what();
    
    throw CPUPolicyException(msg);
  }
}


CPUPolicy*
ConcreteSimulation::get_policy()
{
  return _policy;
}


void
ConcreteSimulation::set_resource_policy(ResourcePolicy* p)
{
  stop();
  ResourcePoliciesGatekeeper::get_instance().activate_policy(&_history, p);
  _resource_policy = p;
}


ResourcePolicy*
ConcreteSimulation::get_resource_policy()
{
  return _resource_policy;
}
