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

#include "rosa/deluxe/DeluxeAgent.hpp"

#include "rosa/deluxe/DeluxeSensor.hpp"

#include <algorithm>

namespace rosa {
namespace deluxe {

bool DeluxeAgent::inv(void) const noexcept {
  // Check container sizes.
  if (!(InputTypes.size() == NumberOfInputs &&
        InputChanged.size() == NumberOfInputs &&
        InputValues->size() == NumberOfInputs &&
        Slaves.size() == NumberOfInputs)) {
    return false;
  }

  // Check *slave* types and validate *slave* registrations and reverse lookup
  // information.
  std::map<id_t, size_t> RefIds; // Build up a reference of SlaveIds in this.
  for (size_t I = 0; I < NumberOfInputs; ++I) {
    // First, validate input types at position \c I.
    const TypeNumber T = InputTypes[I];

    if (InputValues->typeAt(I) != T) {
      return false;
    }

    // Check the registered *slave* at position \c I.
    const auto &Slave = Slaves[I];
    // If \c Slave is empty, nothing to check.
    if (!Slave)
      continue;

    // \c Slave is not empty here.
    // Check the `OutputType` of the registered *slave*.
    const auto &A = unwrapAgent(*Slave);
    if (!((A.Kind == atoms::SensorKind &&
           static_cast<const DeluxeSensor &>(A).OutputType == T) ||
          (A.Kind == atoms::AgentKind &&
           static_cast<const DeluxeAgent &>(A).OutputType == T))) {
      return false;
    }

    // Validate that the *slave* is not registered more than once.
    if (std::any_of(
            Slaves.begin() + I + 1, Slaves.end(),
            [&Slave](const Optional<AgentHandle> &O) { return O && *Slave == *O; })) {
      return false;
    }

    // Build the content of \c RefIds.
    RefIds.emplace(A.Id, I);
  }

  // Validate *slave* reverse lookup information against our reference.
  if (RefIds != SlaveIds) {
    return false;
  }

  // All checks were successful, the invariant is held.
  return true;
}

DeluxeAgent::~DeluxeAgent(void) noexcept {
  ASSERT(inv());
  LOG_TRACE("Destroying DeluxeAgent...");

  // Make sure \p this object is not a registered *slave*.
  if (Master) {
    ASSERT(unwrapAgent(*Master).Kind == atoms::AgentKind); // Sanity check.
    DeluxeAgent &M = static_cast<DeluxeAgent&>(unwrapAgent(*Master));
    ASSERT(M.positionOfSlave(self()) != M.NumberOfInputs); // Sanity check.
    M.registerSlave(M.positionOfSlave(self()), {});
    Master = {};
  }

  // Also, make sure \p this object is no acting *master*.
  for (size_t Pos = 0; Pos < NumberOfInputs; ++Pos) {
    registerSlave(Pos, {});
  }

  // Now there is no connection with other entities, safe to destroy.
}

Optional<AgentHandle> DeluxeAgent::master(void) const noexcept {
  ASSERT(inv());
  return Master;
}

void DeluxeAgent::registerMaster(const Optional<AgentHandle> _Master) noexcept {
  ASSERT(inv() && (!_Master || unwrapAgent(*_Master).Kind == atoms::AgentKind));

  Master = _Master;

  ASSERT(inv());
}

TypeNumber DeluxeAgent::inputType(const size_t Pos) const noexcept {
  ASSERT(inv() && Pos < NumberOfInputs);
  return InputTypes[Pos];
}

Optional<AgentHandle> DeluxeAgent::slave(const size_t Pos) const noexcept {
  ASSERT(inv() && Pos < NumberOfInputs);
  return Slaves[Pos];
}

void DeluxeAgent::registerSlave(const size_t Pos,
                                const Optional<AgentHandle> Slave) noexcept {
  ASSERT(inv() && Pos < NumberOfInputs &&
         (!Slave ||
          (unwrapAgent(*Slave).Kind == atoms::SensorKind &&
           static_cast<const DeluxeSensor &>(unwrapAgent(*Slave)).OutputType ==
               InputTypes[Pos]) ||
          (unwrapAgent(*Slave).Kind == atoms::AgentKind &&
           static_cast<const DeluxeAgent &>(unwrapAgent(*Slave)).OutputType ==
               InputTypes[Pos])));

  // If registering an actual *slave*, not just clearing the slot, make sure
  // the same *slave* is not registered to another slot.
  if (Slave) {
    auto It = SlaveIds.find(unwrapAgent(*Slave).Id);
    if (It != SlaveIds.end()) {
      Slaves[It->second] = {};//Optional<AgentHandle>();
      SlaveIds.erase(It);
    }
  }

  // Obtain the place whose content is to be replaced with \p Slave
  auto &OldSlave = Slaves[Pos];

  // If there is already a *slave* registered at \p Pos, clear reverse lookup
  // information for it, and make sure it no longer has \p this object as
  // *master*.
  if (OldSlave) {
    auto &A = unwrapAgent(*OldSlave);
    ASSERT(SlaveIds.find(A.Id) != SlaveIds.end()); // Sanity check.
    SlaveIds.erase(A.Id);

    if (A.Kind == atoms::AgentKind) {
      static_cast<DeluxeAgent &>(A).registerMaster({});
    } else {
      ASSERT(A.Kind == atoms::SensorKind); // Sanity check.
      static_cast<DeluxeSensor &>(A).registerMaster({});
    }
  }

  // Register \p Slave at \p Pos.
  OldSlave = Slave;

  // If registering an actual *slave*, not just clearing the slot, register
  // reverse lookup information for the new *slave*.
  if (Slave) {
    SlaveIds.emplace(unwrapAgent(*Slave).Id, Pos);
  }

  ASSERT(inv());
}

size_t DeluxeAgent::positionOfSlave(const AgentHandle Slave) const noexcept {
  ASSERT(inv());

  bool Found = false;
  size_t Pos = 0;
  while (!Found && Pos < NumberOfInputs) {
    auto &ExistingSlave = Slaves[Pos];
    if (ExistingSlave && *ExistingSlave == Slave) {
      Found = true;
    } else {
      ++Pos;
    }
  }
  ASSERT(Found || Pos == NumberOfInputs); // Sanity check.

  return Pos;
}

void DeluxeAgent::handleTrigger(atoms::Trigger) noexcept {
  ASSERT(inv());

  FP();

  ASSERT(inv());
}

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