//===-- deluxe/DeluxeContext.cpp --------------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file deluxe/DeluxeContext.cpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017
///
/// \brief Implementation for rosa/deluxe/DeluxeContext.hpp.
///
//===----------------------------------------------------------------------===//

#define ROSA_LIB_DELUXE_DELUXECONTEXT_CPP // For including helper macros.

#include "rosa/deluxe/DeluxeContext.hpp"

#include <algorithm>

namespace rosa {
namespace deluxe {

std::unique_ptr<DeluxeContext>
DeluxeContext::create(const std::string &Name) noexcept {
  return std::unique_ptr<DeluxeContext>(new DeluxeContext(Name));
}

DeluxeContext::DeluxeContext(const std::string &Name) noexcept
    : System(DeluxeSystem::createSystem(Name)) {
  LOG_TRACE("DeluxeContext for '" + System->name() + "' is created.");
}

DeluxeContext::~DeluxeContext(void) noexcept {
  // \c rosa::deluxe::DeluxeContext::System is not used outside, just clean it.
  for(auto U : DeluxeUnits) {
    System->destroyAgent(U);
  }
  // \note \c System will be marked clean by SystemImpl::~SystemImpl.
  LOG_TRACE("DeluxeContext for '" + System->name() +
            "' prepared for destruction.");
}

DeluxeContext::ErrorCode
DeluxeContext::connectSensor(AgentHandle Agent, const size_t Pos,
                             AgentHandle Sensor,
                             const std::string &Description) noexcept {
  // Generate trace log.
  auto &Trace = LOG_TRACE_STREAM;
  Trace << "Establishing connection";
  if (!Description.empty()) {
    Trace << " '" << Description << "'";
  }
  Trace << " between '" << System->unwrapAgent(Sensor).FullName << "' and '"
        << System->unwrapAgent(Agent).FullName << "'\n";

  // Make sure preconditions are met.
  if (!System->isDeluxeAgent(Agent)) {
    DCRETERROR(ErrorCode::NotAgent);
  } else if (!System->isDeluxeSensor(Sensor)) {
    DCRETERROR(ErrorCode::NotSensor);
  }
  auto A = System->getDeluxeAgent(Agent);
  auto S = System->getDeluxeSensor(Sensor);
  ASSERT(A && S); // Sanity check.
  if (Pos >= A->NumberOfInputs) {
    DCRETERROR(ErrorCode::WrongPosition);
  } else if (A->inputType(Pos) != S->OutputType) {
    DCRETERROR(ErrorCode::TypeMismatch);
  } else if (A->slave(Pos)) {
    DCRETERROR(ErrorCode::AlreadyHasSlave);
  } else if (S->master()) {
    DCRETERROR(ErrorCode::AlreadyHasMaster);
  }

  // Do register.
  A->registerSlave(Pos, {Sensor});
  S->registerMaster({Agent});

  return ErrorCode::NoError;
}

DeluxeContext::ErrorCode
DeluxeContext::connectAgents(AgentHandle Master, const size_t Pos,
                             AgentHandle Slave,
                             const std::string &Description) noexcept {
  // Generate trace log.
  auto &Trace = LOG_TRACE_STREAM;
  Trace << "Establishing connection";
  if (!Description.empty()) {
    Trace << " '" << Description << "'";
  }
  Trace << " between '" << System->unwrapAgent(Slave).FullName << "' and '"
        << System->unwrapAgent(Master).FullName << "'\n";

  // Make sure preconditions are met.
  if (!(System->isDeluxeAgent(Master) && System->isDeluxeAgent(Slave))) {
    DCRETERROR(ErrorCode::NotAgent);
  }
  auto M = System->getDeluxeAgent(Master);
  auto S = System->getDeluxeAgent(Slave);
  ASSERT(M && S); // Sanity check.
  if (Pos >= M->NumberOfInputs) {
    DCRETERROR(ErrorCode::WrongPosition);
  } else if (M->inputType(Pos) != S->OutputType) {
    DCRETERROR(ErrorCode::TypeMismatch);
  } else if (M->slave(Pos)) {
    DCRETERROR(ErrorCode::AlreadyHasSlave);
  } else if (S->master()) {
    DCRETERROR(ErrorCode::AlreadyHasMaster);
  }

  // Do register.
  M->registerSlave(Pos, {Slave});
  S->registerMaster({Master});

  return ErrorCode::NoError;
}

std::weak_ptr<MessagingSystem> DeluxeContext::getSystem(void) const noexcept {
  return std::weak_ptr<MessagingSystem>(System);
}

void DeluxeContext::initializeSimulation(void) noexcept {
  // Clear simulation data sources from sensors.
  for (auto U : DeluxeUnits) {
    if (auto S = System->getDeluxeSensor(U)) {
      S->clearSimulationDataSource();
    }
  }
}

void DeluxeContext::simulate(const size_t NumCycles) const noexcept {
  ASSERT(std::all_of(
      DeluxeUnits.begin(), DeluxeUnits.end(), [&](const AgentHandle &H) {
        return System->isDeluxeAgent(H) ||
               System->isDeluxeSensor(H) &&
                   System->getDeluxeSensor(H)->simulationDataSourceIsSet();
      }));
  for (size_t I = 1; I <= NumCycles; ++I) {
    LOG_TRACE("Simulation cycle: " + std::to_string(I));
    for (auto U : DeluxeUnits) {
      U.sendMessage(Message::create(atoms::Trigger::Value));
    }
  }
}

} // End namespace deluxe
} // End namespace rosa
