//===-- 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"
#include "statehandlerutils.h"

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

const std::string AppName = "CCAM";

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

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

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

  //
  // Create a DeluxeAgent with SystemStateDetector functionality.
  //
  LOG_INFO("Create SystemStateDetector agent.");
  AgentHandle SystemStateDetectorAgent = createSystemStateDetectorAgent(
      C, "SystemStateDetector", AppConfig.SignalConfigurations.size(),
      BrokenDelayFunction, OkDelayFunction);
  C->setExecutionPolicy(SystemStateDetectorAgent,
                        DeluxeExecutionPolicy::awaitAll({}));

  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<std::shared_ptr<
      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));
    C->setExecutionPolicy(Sensors.back(), DeluxeExecutionPolicy::decimation(1));

    //
    // 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(
        new SignalStateDetector<float, float, float, HistoryPolicy::FIFO>(
            SignalConfiguration.Output ? SignalProperties::OUTPUT
                                       : SignalProperties::INPUT,
            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()));
    C->setExecutionPolicy(SignalStateDetectorAgents.back(),
                          DeluxeExecutionPolicy::awaitAny({}));

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

    C->connectSensor(SignalStateDetectorAgents.back(), 0, Sensors.back(),
                     SignalConfiguration.Name);

    C->connectAgents(SystemStateDetectorAgent, SignalStateDetectors.size() - 1,
                     SignalStateDetectorAgents.back(),
                     SignalConfiguration.Name);

    std::ifstream SensorData(SignalConfiguration.InputPath);
    if (!SensorData) {
      LOG_ERROR_STREAM << "Cannot open Input File \""
                       << SignalConfiguration.InputPath << "\" for Signal \""
                       << SignalConfiguration.Name << "\"" << std::endl;
      return 3;
    }

    C->registerSensorValues(Sensors.back(), csv::CSVIterator<float>(SensorData),
                            csv::CSVIterator<float>());
  }

  //
  // 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 OutputCSV(AppConfig.OutputFilePath);

  // The agent writes each new input value into a CSV file and produces nothing.
  using Input = std::pair<SystemStateTuple, bool>;
  using Result = Optional<DeluxeTuple<unit_t>>;
  using Handler = std::function<Result(Input)>;
  std::string Name = "Logger Agent";

  AgentHandle LoggerAgent =
      C->createAgent("Logger Agent", Handler([&OutputCSV](Input I) -> Result {
                       OutputCSV << std::get<0>(I.first) << std::endl;
                       return Result();
                     }));
  C->setExecutionPolicy(LoggerAgent, DeluxeExecutionPolicy::awaitAny({}));
  //
  // Connect the high-level agent to the logger agent.
  //
  LOG_INFO("Connect the high-level agent to the logger agent.");

  C->connectAgents(LoggerAgent, 0, SystemStateDetectorAgent,
                   "SystemStateDetector 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(AppConfig.NumberOfSimulationCycles);

  return 0;
}
