//===-- deluxe/DeluxeContext.cpp --------------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
// Distributed under the terms and conditions of the Boost Software License 1.0.
// See accompanying file LICENSE.
//
// If you did not receive a copy of the license file, see
// http://www.boost.org/LICENSE_1_0.txt.
//
//===----------------------------------------------------------------------===//
///
/// \file deluxe/DeluxeContext.cpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017-2019
///
/// \brief Implementation for rosa/deluxe/DeluxeContext.hpp.
///
//===----------------------------------------------------------------------===//

#define ROSA_LIB_DELUXE_DELUXECONTEXT_CPP // For including helper macros.

#include "rosa/deluxe/DeluxeContext.hpp"

#include <algorithm>
#include <sstream>

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.");
}

Optional<const DeluxeExecutionPolicy &>
DeluxeContext::getExecutionPolicy(AgentHandle Unit) const noexcept {
  if (System->isDeluxeSensor(Unit)) {
    return {System->getDeluxeSensor(Unit)->executionPolicy()};
  } else if (System->isDeluxeAgent(Unit)) {
    return {System->getDeluxeAgent(Unit)->executionPolicy()};
  } else {
    return {};
  }
}

DeluxeContext::ErrorCode DeluxeContext::setExecutionPolicy(
    AgentHandle Unit,
    std::unique_ptr<DeluxeExecutionPolicy> &&ExecutionPolicy) noexcept {
  // Generate trace log.
  auto &Trace = LOG_TRACE_STREAM;
  Trace << "Setting execution policy of " << System->unwrapAgent(Unit).FullName
        << " to ";
  if (ExecutionPolicy) {
    Trace << "'" << ExecutionPolicy->dump() << "'\n";
  } else {
    Trace << "[]\n";
    DCRETERROR(ErrorCode::UnsuitableExecutionPolicy);
  }

  if (System->isDeluxeSensor(Unit)) {
    const bool Success = System->getDeluxeSensor(Unit)->setExecutionPolicy(
        std::move(ExecutionPolicy));
    if (!Success) {
      DCRETERROR(ErrorCode::UnsuitableExecutionPolicy);
    } else {
      return ErrorCode::NoError;
    }
  } else if (System->isDeluxeAgent(Unit)) {
    const bool Success = System->getDeluxeAgent(Unit)->setExecutionPolicy(
        std::move(ExecutionPolicy));
    if (!Success) {
      DCRETERROR(ErrorCode::UnsuitableExecutionPolicy);
    } else {
      return ErrorCode::NoError;
    }
  } else {
    DCRETERROR(ErrorCode::NotUnit);
  }
}

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 ||
             (!emptyToken(A->masterOutputType(Pos)) &&
              A->masterOutputType(Pos) != S->MasterInputType)) {
    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 ||
             (!emptyToken(M->masterOutputType(Pos)) &&
              M->masterOutputType(Pos) != S->MasterInputType)) {
    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 {
  LOG_INFO_STREAM << "Initializing simulation for " << System->name()
                  << ". Clearing all data sources.\n";
  // 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 {
  DEBUG(for (auto H
             : DeluxeUnits) {
    std::stringstream Message;
    Message << System->unwrapAgent(H).FullName << " is a Deluxe "
            << " " << (System->isDeluxeSensor(H) ? "Sensor" : "Agent");
    if (System->isDeluxeSensor(H))
      Message << " with it's data source "
              << (!System->getDeluxeSensor(H)->simulationDataSourceIsSet()
                      ? "not "
                      : "set");
    Message << '\n';
    LOG_TRACE_STREAM << Message.str();
  });

  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
