//===-- deluxe/DeluxeAgent.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/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/DeluxeSystem.hpp"

#include <algorithm>

namespace rosa {
namespace deluxe {

bool DeluxeAgent::inv(void) const noexcept {
  // Check execution policy.
  // \note The \c rosa::System the \c rosa::Unit is created with is a
  // \c rosa::DeluxeSystem.
  const DeluxeSystem &DS = static_cast<DeluxeSystem &>(Unit::system());
  if (!ExecutionPolicy || !ExecutionPolicy->canHandle(Self, DS)) {
    return false;
  }

  // Check number of inputs and master-outputs.
  if (NumberOfInputs != NumberOfMasterOutputs) {
    return false;
  }

  // Check container sizes.
  if (!(InputTypes.size() == NumberOfInputs &&
        InputNextPos.size() == NumberOfInputs &&
        InputChanged.size() == NumberOfInputs &&
        InputValues.size() == NumberOfInputs &&
        MasterOutputTypes.size() == NumberOfInputs // == NumberOfMasterOutputs
        && 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) {

    // Fetch type-related information for the input position.
    const Token T = InputTypes[I];
    const token_size_t TL = lengthOfToken(T);

    // Validate size of storage at position \c I.
    if (InputValues[I]->size() != TL) {
      return false;
    }

    // Validate input types at position \c I.
    for (token_size_t TI = 0; TI < TL; ++TI) {
      if (InputValues[I]->typeAt(static_cast<token_size_t>(TI)) !=
          typeAtPositionOfToken(T, TI)) {
        return false;
      }
    }

    // Check the index of next expected element for position \c I.
    if (InputNextPos[I] >= TL) {
      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;

    // Prepare master-output related info for the *slave*.
    const Token MT = MasterOutputTypes[I];
    const bool hasMT = !emptyToken(MT);

    // \c Slave is not empty here.
    // Check the `OutputType` and `MasterInputType` of the registered *slave*.
    const auto &A = unwrapAgent(*Slave);
    if (!((A.Kind == atoms::SensorKind &&
           static_cast<const DeluxeSensor &>(A).OutputType == T &&
           (!hasMT ||
            static_cast<const DeluxeSensor &>(A).MasterInputType == MT)) ||
          (A.Kind == atoms::AgentKind &&
           static_cast<const DeluxeAgent &>(A).OutputType == T &&
           (!hasMT ||
            static_cast<const DeluxeAgent &>(A).MasterInputType == MT)))) {
      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;
  }

  // Check the size of the master-input storage.
  if (MasterInputValue->size() != lengthOfToken(MasterInputType)) {
    return false;
  }

  // Check the index of next expected element from the *master*.
  const token_size_t MITL = lengthOfToken(MasterInputType);
  if ((MITL != 0 && MasterInputNextPos >= MITL) ||
      (MITL == 0 && MasterInputNextPos != 0)) {
    return false;
  }

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

DeluxeAgent::~DeluxeAgent(void) noexcept {
  ASSERT(inv());
  LOG_TRACE_STREAM << "Destroying DeluxeAgent " << FullName << "..."
                   << std::endl;

  // 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.
}

id_t DeluxeAgent::masterId(void) const noexcept {
  ASSERT(inv() && Master);
  return unwrapAgent(*Master).Id;
}

const DeluxeExecutionPolicy &DeluxeAgent::executionPolicy(void) const noexcept {
  ASSERT(inv());
  return *ExecutionPolicy;
}

bool DeluxeAgent::setExecutionPolicy(
    std::unique_ptr<DeluxeExecutionPolicy> &&EP) noexcept {
  ASSERT(inv());
  LOG_TRACE_STREAM << "DeluxeAgent " << FullName << " setting execution policy "
                   << *EP << std::endl;
  bool Success = false;
  // \note The \c rosa::System the \c rosa::Unit is created with is a
  // \c rosa::DeluxeSystem.
  const DeluxeSystem &DS = static_cast<DeluxeSystem &>(Unit::system());
  if (EP && EP->canHandle(self(), DS)) {
    ExecutionPolicy.swap(EP);
    Success = true;
  } else {
    LOG_TRACE_STREAM << "Execution policy " << *EP
                     << " cannot handle DeluxeAgent " << FullName << std::endl;
  }
  ASSERT(inv());
  return Success;
}

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());
}

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

Token DeluxeAgent::masterOutputType(const size_t Pos) const noexcept {
  ASSERT(inv() && Pos < NumberOfMasterOutputs);
  return MasterOutputTypes[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] &&
           (emptyToken(MasterOutputTypes[Pos]) ||
            static_cast<const DeluxeSensor &>(unwrapAgent(*Slave))
                    .MasterInputType == MasterOutputTypes[Pos])) ||
          (unwrapAgent(*Slave).Kind == atoms::AgentKind &&
           static_cast<const DeluxeAgent &>(unwrapAgent(*Slave)).OutputType ==
               InputTypes[Pos] &&
           (emptyToken(MasterOutputTypes[Pos]) ||
            static_cast<const DeluxeAgent &>(unwrapAgent(*Slave))
                    .MasterInputType == MasterOutputTypes[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
