//===-- apps/ccam/ccam.cpp --------------------------------------*- C++ -*-===//
//
//                 The RoSA Framework -- Application CCAM
//
// 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 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
///
/// \todo Clean up source files of this app: add standard RoSA header comment
/// for own files and do something with 3rd party files...
//===----------------------------------------------------------------------===//
#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/app/Application.hpp"

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

#include "rosa/support/mqtt/MQTTReader.hpp"

#include "rosa/app/AppTuple.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::app;
using namespace rosa::terminal;
using namespace rosa::mqtt;

const std::string AppName = "CCAM";

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

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

  //
  // Read the filepath of the config file of the observed system. The filepath
  // is in the first argument passed to the application. Fuzzy functions etc.
  // are described in this file.
  //
  if (argc < 2) {
    LOG_ERROR("Specify config File!\nUsage:\n\tccam config.json");
    return 1;
  }
  std::string ConfigPath = argv[1];

  //
  // Load config file and read in all parameters. Fuzzy functions etc. are
  // described in this file.
  //
  if (!readConfigFile(ConfigPath)) {
    LOG_ERROR_STREAM << "Could not read config from \"" << ConfigPath << "\"\n";
    return 2;
  }

  //
  // Create a CCAM context.
  //
  LOG_INFO("Creating Context");
  std::unique_ptr<Application> AppCCAM = Application::create(AppName);

  //
  // Create following function which shall give information if the time gap
  // between changed input(s) and changed output(s) shows already a malfunction
  // of the system.
  //
  //              ____________
  //             /
  //            /
  // __________/
  //
  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));

  //
  // Create following function which shall give information if the time gap
  // between changed input(s) and changed output(s) still shows a
  // well-functioning system.
  //
  // ____________
  //             \
    //              \
    //               \__________
  //
  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 AppAgent with SystemStateDetector functionality.
  //
  LOG_INFO("Create SystemStateDetector agent.");
  AgentHandle SystemStateDetectorAgent = createSystemStateDetectorAgent(
      AppCCAM, "SystemStateDetector", AppConfig.SignalConfigurations.size(),
      BrokenDelayFunction, OkDelayFunction);

  //
  // Set policy of SystemStateDetectorAgent that it wait for all
  // SignalStateDetectorAgents
  //
  std::set<size_t> pos;
  for (size_t i = 0; i < AppConfig.SignalConfigurations.size(); ++i)
    pos.insert(pos.end(), i);
  AppCCAM->setExecutionPolicy(SystemStateDetectorAgent,
                              AppExecutionPolicy::awaitAll(pos));

  //
  // Create Vectors for all sensors, all signal related fuzzy functions, all
  // signal state detectors, all signal state agents, and all input data files.
  //
  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<PartialFunction<float, float>>>
      SampleValidFunctions;
  std::vector<std::shared_ptr<PartialFunction<float, float>>>
      SampleInvalidFunctions;
  std::vector<std::shared_ptr<StepFunction<float, float>>>
      NumOfSamplesValidFunctions;
  std::vector<std::shared_ptr<StepFunction<float, float>>>
      NumOfSamplesInvalidFunctions;
  std::vector<std::shared_ptr<
      SignalStateDetector<float, float, float, HistoryPolicy::FIFO>>>
      SignalStateDetectors;
  std::vector<AgentHandle> SignalStateDetectorAgents;
  std::vector<std::ifstream> DataFiles;

  //
  // Go through all signal state configurations (number of signals), and create
  // functionalities for SignalStateDetector.
  //
  for (auto SignalConfiguration : AppConfig.SignalConfigurations) {

    //
    // Create application sensors.
    //
    Sensors.emplace_back(
        AppCCAM->createSensor<float>(SignalConfiguration.Name + "_Sensor"));

    //
    // Create following function(s) which shall give information whether one
    // sample matches another one (based on the relative distance between them).
    //
    //              ____________
    //             /            \
        //            /              \
        // __________/                \__________
    //
    //
    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));

    //
    // Create following function(s) which shall give information whether one
    // sample mismatches another one (based on the relative distance between
    // them).
    //
    // ____________                ____________
    //             \              /
    //              \            /
    //               \__________/
    //
    //
    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));

    //
    // Create following function(s) which shall give information whether a
    // signal is stable.
    //
    //              ____________
    //             /            \
        //            /              \
        // __________/                \__________
    //
    //
    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));

    //
    // Create following function(s) which shall give information whether a
    // signal is drifting.
    //
    // ____________                ____________
    //             \              /
    //              \            /
    //               \__________/
    //
    //
    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));

    //
    // Create following function(s) which shall give information how many
    // history samples match another sample.
    //
    //              ____________
    //             /
    //            /
    // __________/
    //
    NumOfSamplesMatchFunctions.emplace_back(new StepFunction<float, float>(
        1.0f / SignalConfiguration.SampleHistorySize, StepDirection::StepUp));

    //
    // Create following function(s) which shall give information how many
    // history samples mismatch another sample.
    //
    // ____________
    //             \
        //              \
        //               \__________
    //
    NumOfSamplesMismatchFunctions.emplace_back(new StepFunction<float, float>(
        1.0f / SignalConfiguration.SampleHistorySize, StepDirection::StepDown));

    //
    // Create following function(s) which shall give information how good all
    // samples in a state match each other.
    //
    //              ____________
    //             /            \
        //            /              \
        // __________/                \__________
    //
    //
    SampleValidFunctions.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));

    //
    // Create following function(s) which shall give information how good all
    // samples in a state mismatch each other.
    //
    // ____________                ____________
    //             \              /
    //              \            /
    //               \__________/
    //
    //
    SampleInvalidFunctions.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));

    //
    // Create following function(s) which shall give information how many
    // history samples match each other.
    //
    //              ____________
    //             /
    //            /
    // __________/
    //
    NumOfSamplesValidFunctions.emplace_back(new StepFunction<float, float>(
        1.0f / SignalConfiguration.SampleHistorySize, StepDirection::StepUp));

    //
    // Create following function(s) which shall give information how many
    // history samples mismatch each other.
    //
    // ____________
    //             \
        //              \
        //               \__________
    //
    NumOfSamplesInvalidFunctions.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(), SampleValidFunctions.back(),
            SampleInvalidFunctions.back(), NumOfSamplesValidFunctions.back(),
            NumOfSamplesInvalidFunctions.back(),
            SignalIsDriftingFunctions.back(), SignalIsStableFunctions.back(),
            SignalConfiguration.SampleHistorySize, SignalConfiguration.DABSize,
            SignalConfiguration.DABHistorySize));

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

    AppCCAM->setExecutionPolicy(
        SignalStateDetectorAgents.back(),
        AppExecutionPolicy::decimation(AppConfig.DownsamplingRate));
    //
    // Connect sensors to low-level agents.
    //
    LOG_INFO("Connect sensors to their corresponding low-level agents.");

    AppCCAM->connectSensor(SignalStateDetectorAgents.back(), 0, Sensors.back(),
                           SignalConfiguration.Name + "_Sensor ->" +
                               SignalConfiguration.Name +
                               "_SignalStateDetector_Agent-Channel");

    AppCCAM->connectAgents(
        SystemStateDetectorAgent, SignalStateDetectors.size() - 1,
        SignalStateDetectorAgents.back(),
        SignalConfiguration.Name +
            "_SignalStateDetector_Agent->SystemStateDetector_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 OutputCSV(AppConfig.OutputFilePath);

  for (auto SignalConfiguration : AppConfig.SignalConfigurations) {
    OutputCSV << SignalConfiguration.Name + ",";
  }
  OutputCSV << "StateID,";
  OutputCSV << "Confidence State Valid,";
  OutputCSV << "Confidence State Invalid,";
  OutputCSV << "Confidence Inputs Matching,";
  OutputCSV << "Confidence Outputs Matching,";
  OutputCSV << "Confidence Inputs Mismatching,";
  OutputCSV << "Confidence Outputs Mismatching,";
  OutputCSV << "State Condition,";
  OutputCSV << "Confidence System Functioning,";
  OutputCSV << "Confidence System Malfunctioning,";
  OutputCSV << "Overall Confidence,";
  OutputCSV << "\n";

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

  AgentHandle LoggerAgent = AppCCAM->createAgent(
      "Logger Agent", Handler([&OutputCSV](Input I) -> Result {
        const SystemStateTuple &T = I.first;
        OutputCSV << std::get<0>(
                         static_cast<const std::tuple<std::string> &>(T))
                  << std::endl;
        return Result();
      }));
  //
  // Connect the high-level agent to the logger agent.
  //
  LOG_INFO("Connect the high-level agent to the logger agent.");

  AppCCAM->connectAgents(LoggerAgent, 0, SystemStateDetectorAgent,
                         "SystemStateDetector Channel");

  //
  // Only log if the SystemStateDetector actually ran
  //
  AppCCAM->setExecutionPolicy(LoggerAgent, AppExecutionPolicy::awaitAll({0}));

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

  //
  // Initialize application for simulation.
  //
  AppCCAM->initializeSimulation();

  //
  // Open CSV files and register them for their corresponding sensors.
  //
  // Make sure DataFiles will not change capacity while adding elements to it.
  // Changing capacity moves elements away, which invalidates references
  // captured by CSVIterator.
  DataFiles.reserve(AppConfig.SignalConfigurations.size());
  uint32_t i = 0;
  for (auto SignalConfiguration : AppConfig.SignalConfigurations) {

    switch (SignalConfiguration.DataInterfaceType) {
    case DataInterfaceTypes::CSV:

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

      AppCCAM->registerSensorValues(Sensors.at(i),
                                    csv::CSVIterator<float>(DataFiles.at(i)),
                                    csv::CSVIterator<float>());

      LOG_INFO_STREAM << "Sensor " << SignalConfiguration.Name
                      << " is fed by csv file " << SignalConfiguration.InputPath
                      << std::endl;
      break;
    case DataInterfaceTypes::MQTT:
    {
      auto it = MQTTIterator<float>(SignalConfiguration.MQTTTopic);
      AppCCAM->registerSensorValues(Sensors.at(i), std::move(it),
                                    MQTTIterator<float>());
      LOG_INFO_STREAM << "Sensor " << SignalConfiguration.Name
                      << " is fed by MQTT topic "
                      << SignalConfiguration.MQTTTopic << std::endl;
      break;
    }
    default:
      LOG_ERROR_STREAM << "No data source for " << SignalConfiguration.Name
                       << std::endl;
      break;
    }
    i++;
  }

  //
  // Start simulation.
  //
  AppCCAM->simulate(AppConfig.NumberOfSimulationCycles);

  return 0;
}
