//===-- apps/ccam/ccam.cpp --------------------------------------*- C++ -*-===//
//
//                 The RoSA Framework -- Application CCAM
//
//===----------------------------------------------------------------------===//
///
/// \file apps/ccam/ccam.cpp
///
/// \author Maximilian Goetzinger (maximilian.goetzinger@tuwien.ac.at)
/// \author Benedikt Tutzer (benedikt.tutzer@tuwien.ac.at)
///
/// \date 2019
///
/// \brief The application CCAM implements the case study from the paper:
/// M. Goetzinger, N. TaheriNejad, H. A. Kholerdi, A. Jantsch, E. Willegger,
/// T. Glatzl, A.M. Rahmani, T.Sauter, P. Liljeberg: Model - Free Condition
/// Monitoring with Confidence
//===----------------------------------------------------------------------===//
#include "rosa/agent/Abstraction.hpp"
#include "rosa/agent/Confidence.hpp"
#include "rosa/agent/FunctionAbstractions.hpp"
#include <iostream>

#include "rosa/config/version.h"

#include "rosa/agent/SignalStateDetector.hpp"
#include "rosa/agent/SystemStateDetector.hpp"
#include "rosa/deluxe/DeluxeContext.hpp"

#include "rosa/support/csv/CSVReader.hpp"
#include "rosa/support/csv/CSVWriter.hpp"

#include <fstream>
#include <limits>
#include <memory>
#include <streambuf>

#include "configuration.h"

using namespace rosa;
using namespace rosa::agent;
using namespace rosa::deluxe;
using namespace rosa::terminal;

const std::string AppName = "CCAM";

using SignalStateTuple =
    std::tuple<Optional<float>, Optional<unsigned int>, Optional<float>,
               Optional<uint8_t>, Optional<unsigned int>, Optional<bool>,
               Optional<bool>, Optional<bool>, Optional<bool>>;

AgentHandle createSignalStateDetectorAgent(
    std::unique_ptr<DeluxeContext> &C, const std::string &Name,
    std::shared_ptr<
        SignalStateDetector<float, float, float, HistoryPolicy::FIFO>>
        SigSD) {
  (void)SigSD;

  using Handler = std::function<SignalStateTuple(std::pair<float, bool>)>;

  return C->createAgent(
      Name,
      Handler([&Name, &SigSD](std::pair<float, bool> I) -> SignalStateTuple {
        LOG_INFO_STREAM << "\n******\n"
                        << Name << " " << (I.second ? "<New>" : "<Old>")
                        << " value: " << I.first << "\n******\n";
        auto StateInfo = SigSD->detectSignalState(I.first);

        if (I.second)
          return std::make_tuple(
              Optional<float>(I.first),
              Optional<unsigned int>(StateInfo.SignalStateID),
              Optional<float>(StateInfo.SignalStateConfidence),
              Optional<uint8_t>(StateInfo.SignalStateCondition),
              Optional<unsigned int>(
                  StateInfo.NumberOfInsertedSamplesAfterEntrance),
              Optional<bool>(StateInfo.SignalStateIsValid),
              Optional<bool>(StateInfo.SignalStateJustGotValid),
              Optional<bool>(StateInfo.SignalStateIsValidAfterReentrance),
              Optional<bool>(StateInfo.SignalIsStable));
        return SignalStateTuple();
      }));
}

int main(int argc, char **argv) {

  LOG_INFO_STREAM << '\n'
                  << library_string() << " -- " << Color::Red << AppName
                  << "app" << Color::Default << '\n';

  if (argc < 2) {
    LOG_ERROR("Specify config File!\nUsage:\n\tccam config.json");
    return 1;
  }
  std::string ConfigPath = argv[1];

  if (!readConfigFile(ConfigPath)) {
    LOG_ERROR_STREAM << "Could not read config from \"" << ConfigPath << "\"\n";
    return 2;
  }

  std::string InputFilePath, OutputFilePath;

  LOG_INFO("Creating Context");
  std::unique_ptr<DeluxeContext> C = DeluxeContext::create(AppName);

  LOG_INFO("Creating sensors, SignalStateDetector functionalities and their "
           "Abstractions.");
  std::vector<AgentHandle> Sensors;
  std::vector<std::shared_ptr<PartialFunction<float, float>>>
      SampleMatchesFunctions;
  std::vector<std::shared_ptr<PartialFunction<float, float>>>
      SampleMismatchesFunctions;
  std::vector<std::shared_ptr<PartialFunction<float, float>>>
      SignalIsStableFunctions;
  std::vector<std::shared_ptr<PartialFunction<float, float>>>
      SignalIsDriftingFunctions;
  std::vector<std::shared_ptr<StepFunction<float, float>>>
      NumOfSamplesMatchFunctions;
  std::vector<std::shared_ptr<StepFunction<float, float>>>
      NumOfSamplesMismatchFunctions;
  std::vector<SignalStateDetector<float, float, float, HistoryPolicy::FIFO>>
      SignalStateDetectors;
  std::vector<AgentHandle> SignalStateDetectorAgents;
  for (auto SignalConfiguration : AppConfig.SignalConfigurations) {
    //
    // Create deluxe sensors.
    //
    Sensors.emplace_back(C->createSensor<float>(SignalConfiguration.Name));

    //
    // Create functionalities for SignalStateDetector.
    //
    SampleMatchesFunctions.emplace_back(new PartialFunction<float, float>(
        {
            {{-SignalConfiguration.OuterBound, -SignalConfiguration.InnerBound},
             std::make_shared<LinearFunction<float, float>>(
                 -SignalConfiguration.OuterBound, 0.f,
                 -SignalConfiguration.InnerBound, 1.f)},
            {{-SignalConfiguration.InnerBound, SignalConfiguration.InnerBound},
             std::make_shared<LinearFunction<float, float>>(1.f, 0.f)},
            {{SignalConfiguration.InnerBound, SignalConfiguration.OuterBound},
             std::make_shared<LinearFunction<float, float>>(
                 SignalConfiguration.InnerBound, 1.f,
                 SignalConfiguration.OuterBound, 0.f)},
        },
        0));

    SampleMismatchesFunctions.emplace_back(new PartialFunction<float, float>(
        {
            {{-SignalConfiguration.OuterBound, -SignalConfiguration.InnerBound},
             std::make_shared<LinearFunction<float, float>>(
                 -SignalConfiguration.OuterBound, 1.f,
                 -SignalConfiguration.InnerBound, 0.f)},
            {{-SignalConfiguration.InnerBound, SignalConfiguration.InnerBound},
             std::make_shared<LinearFunction<float, float>>(0.f, 0.f)},
            {{SignalConfiguration.InnerBound, SignalConfiguration.OuterBound},
             std::make_shared<LinearFunction<float, float>>(
                 SignalConfiguration.InnerBound, 0.f,
                 SignalConfiguration.OuterBound, 1.f)},
        },
        1));

    SignalIsStableFunctions.emplace_back(new PartialFunction<float, float>(
        {
            {{-SignalConfiguration.OuterBoundDrift,
              -SignalConfiguration.InnerBoundDrift},
             std::make_shared<LinearFunction<float, float>>(
                 -SignalConfiguration.OuterBoundDrift, 0.f,
                 -SignalConfiguration.InnerBoundDrift, 1.f)},
            {{-SignalConfiguration.InnerBoundDrift,
              SignalConfiguration.InnerBoundDrift},
             std::make_shared<LinearFunction<float, float>>(1.f, 0.f)},
            {{SignalConfiguration.InnerBoundDrift,
              SignalConfiguration.OuterBoundDrift},
             std::make_shared<LinearFunction<float, float>>(
                 SignalConfiguration.InnerBoundDrift, 1.f,
                 SignalConfiguration.OuterBoundDrift, 0.f)},
        },
        0));

    SignalIsDriftingFunctions.emplace_back(new PartialFunction<float, float>(
        {
            {{-SignalConfiguration.OuterBoundDrift,
              -SignalConfiguration.InnerBoundDrift},
             std::make_shared<LinearFunction<float, float>>(
                 -SignalConfiguration.OuterBoundDrift, 1.f,
                 -SignalConfiguration.InnerBoundDrift, 0.f)},
            {{-SignalConfiguration.InnerBoundDrift,
              SignalConfiguration.InnerBoundDrift},
             std::make_shared<LinearFunction<float, float>>(0.f, 0.f)},
            {{SignalConfiguration.InnerBoundDrift,
              SignalConfiguration.OuterBoundDrift},
             std::make_shared<LinearFunction<float, float>>(
                 SignalConfiguration.InnerBoundDrift, 0.f,
                 SignalConfiguration.OuterBoundDrift, 1.f)},
        },
        1));

    NumOfSamplesMatchFunctions.emplace_back(new StepFunction<float, float>(
        1.0f / SignalConfiguration.SampleHistorySize, StepDirection::StepUp));

    NumOfSamplesMismatchFunctions.emplace_back(new StepFunction<float, float>(
        1.0f / SignalConfiguration.SampleHistorySize, StepDirection::StepDown));

    //
    // Create SignalStateDetector functionality
    //
    SignalStateDetectors.emplace_back(
        std::numeric_limits<int>::max(), SampleMatchesFunctions.back(),
        SampleMismatchesFunctions.back(), NumOfSamplesMatchFunctions.back(),
        NumOfSamplesMismatchFunctions.back(), SignalIsDriftingFunctions.back(),
        SignalIsStableFunctions.back(), SignalConfiguration.SampleHistorySize,
        SignalConfiguration.DABSize, SignalConfiguration.DABHistorySize);

    //
    // Create low-level deluxe agents
    //
    // SignalStateDetectorAgents.push_back(createSignalStateDetectorAgent(
    //    C, SignalConfiguration.Name, SignalStateDetectors.back()));

    //
    // Connect sensors to low-level agents.
    //
    LOG_INFO("Connect sensors to their corresponding low-level agents.");

    C->connectSensor(SignalStateDetectorAgents.back(), 0, Sensors.back(),
                     "HR Sensor Channel");
  }

  std::shared_ptr<PartialFunction<float, float>> BrokenDelayFunction(
      new PartialFunction<float, float>(
          {{{0, AppConfig.BrokenCounter},
            std::make_shared<LinearFunction<float, float>>(
                0, 0.f, AppConfig.BrokenCounter, 1.f)},
           {{AppConfig.BrokenCounter, std::numeric_limits<float>::max()},
            std::make_shared<LinearFunction<float, float>>(1.f, 0.f)}},
          0));

  std::shared_ptr<PartialFunction<float, float>> OkDelayFunction(
      new PartialFunction<float, float>(
          {{{0, AppConfig.BrokenCounter},
            std::make_shared<LinearFunction<float, float>>(
                0, 1.f, AppConfig.BrokenCounter, 0.f)},
           {{AppConfig.BrokenCounter, std::numeric_limits<float>::max()},
            std::make_shared<LinearFunction<float, float>>(0.f, 0.f)}},
          1));

  std::shared_ptr<
      SystemStateDetector<float, float, float, HistoryPolicy::FIFO, 5, 5>>
  SystemStateDetectorF(
      new SystemStateDetector<float, float, float, HistoryPolicy::FIFO, 5, 5>(
          std::numeric_limits<uint32_t>::max(), BrokenDelayFunction,
          OkDelayFunction));

  //
  // Create a high-level deluxe agent.
  //
  LOG_INFO("Create high-level agent.");

  // The new agent logs its input values and results in the the sum of them.
  /**  AgentHandle BodyAgent = C->createAgent(
        "Body Agent",
        DeluxeAgent::D<uint32_t, uint32_t, uint32_t, uint32_t, uint32_t,
                       uint32_t>(
            [](std::pair<uint32_t, bool> HR, std::pair<uint32_t, bool> BR,
               std::pair<uint32_t, bool> SpO2, std::pair<uint32_t, bool> BPSys,
               std::pair<uint32_t, bool> BodyTemp) -> Optional<uint32_t> {
              LOG_INFO_STREAM << "\n*******\nBody Agent trigged with values:\n"
                              << (HR.second ? "<New>" : "<Old>")
                              << " HR warning score: " << HR.first << "\n"
                              << (BR.second ? "<New>" : "<Old>")
                              << " BR warning score: " << BR.first << "\n"
                              << (SpO2.second ? "<New>" : "<Old>")
                              << " SpO2 warning score: " << SpO2.first << "\n"
                              << (BPSys.second ? "<New>" : "<Old>")
                              << " BPSys warning score: " << BPSys.first << "\n"
                              << (BodyTemp.second ? "<New>" : "<Old>")
                              << " BodyTemp warning score: " << BodyTemp.first
                              << "\n******\n";
              return {HR.first + BR.first + SpO2.first + BPSys.first +
                      BodyTemp.first};
            }));
  */
  //
  // Connect low-level agents to the high-level agent.
  //
  LOG_INFO("Connect low-level agents to the high-level agent.");

  /// C->connectAgents(BodyAgent, 0, HRAgent, "HR Agent Channel");

  //
  // For simulation output, create a logger agent writing the output of the
  // high-level agent into a CSV file.
  //
  LOG_INFO("Create a logger agent.");

  // Create CSV writer.
  /// std::ofstream ScoreCSV(ScoreCSVPath);
  /// csv::CSVWriter<uint32_t> ScoreWriter(ScoreCSV);

  // The agent writes each new input value into a CSV file and produces nothing.
  /** AgentHandle LoggerAgent = C->createAgent(
      "Logger Agent",
      DeluxeAgent::D<unit_t, uint32_t>(
          [&ScoreWriter](std::pair<uint32_t, bool> Score) -> Optional<unit_t> {
            if (Score.second) {
              // The state of \p ScoreWriter is not checked, expecting good.
              ScoreWriter << Score.first;
            }
            return {};
          }));
  */
  //
  // Connect the high-level agent to the logger agent.
  //
  LOG_INFO("Connect the high-level agent to the logger agent.");

  /// C->connectAgents(LoggerAgent, 0, BodyAgent, "Body Agent Channel");

  //
  // Do simulation.
  //
  LOG_INFO("Setting up and performing simulation.");

  //
  // Initialize deluxe context for simulation.
  //

  // C->initializeSimulation();

  //
  // Open CSV files and register them for their corresponding sensors.
  //

  //
  // Simulate.
  //

  /// C->simulate(NumberOfSimulationCycles);

  return 0;
}
