//===-- examples/CSVFiles/main.cpp ------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file examples/basic-system/basic-system.cpp
///
/// \author Edwin Willegger (edwin.willegger@tuwien.ac.at)
///
/// \date 2019
///
/// \brief A simple example on the basic \c rosa::csv, \c rosa::iterator and
/// \c rosa::writer classes. Focus is on the tuple impementations.
///
//===----------------------------------------------------------------------===//
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <istream>
#include <map>
#include <ostream>
#include <sstream>
#include <string>
#include <tuple>
#include <type_traits>
#include <typeinfo>
#include <vector>
//#include <unistd.h>

// includes for an complete example to read and write
// with sensors and agents.
#include "rosa/deluxe/DeluxeContext.hpp"

#include "rosa/config/version.h"

// includes to test the basic functionality
// to read and write tuples.
#include "rosa/support/csv/CSVReader.hpp"
#include "rosa/support/csv/CSVWriter.hpp"
#include "rosa/support/iterator/split_tuple_iterator.hpp"
#include "rosa/support/writer/split_tuple_writer.hpp"

/// the name of the example
const std::string ExampleName = "csvfiles";

/// How many cycles of simulation to perform.
const size_t NumberOfSimulationCycles = 10;

/// Paths for the CSV files for simulation.
/// input csv files
const std::string csvPath = "../examples/CSVFiles/";
const std::string csvFileWithHeader = csvPath + "HR-New.csv";
const std::string csvFileNoHeader = csvPath + "HR.csv";
const std::string csvFileHeaderSemi = csvPath + "HR-New-Semicolon.csv";
/// output csv files
const std::string csvFileWriteHea = csvPath + "csvwriter_noheader.csv";
const std::string csvFileWriteNoHeaSplit =
    csvPath + "csvSplitwriter_noheader.csv";

using namespace rosa;

///
/// This function tests the basic CSVIterator capablities, and shows you
/// how you could work with this class.
///
void testtupleCSVReader(void) {

  // different streams to get the csv data out of the files
  // file contains header and valid data entries, delimter = ','
  std::ifstream file_header_data(csvFileWithHeader);
  // file contains header and valid data entries, delimter = ','
  std::ifstream file_header_data_2(csvFileWithHeader);
  // file contains header and valid data entries, delimter = ','
  std::ifstream file_header_data_3(csvFileWithHeader);
  // file contains header and valid data entries, delimter = ','
  std::ifstream file_header_data_4(csvFileWithHeader);
  // file contains header and valid data entries, delimter = ','
  std::ifstream file_header_data_5(csvFileWithHeader);
  // file contains header and valid data entries, delimter = ','
  std::ifstream file_header_data_6(csvFileWithHeader);
  // file contains no header an valid data entries, delimter = ','
  std::ifstream file2(csvFileNoHeader);
  // file contains header and valid data entries, delimter = ';'
  std::ifstream file3(csvFileHeaderSemi);

  csv::CSVIterator<int, std::string, std::string, int, int> it(
      file_header_data);

  it.setDelimiter(',');

  it++;
  it++;
  // if you iterate over the end of file, the last values
  // of the file will remain in the data structure but no
  // error occurs.
  it++;
  it++;

  //-------------------------------------------------------------------
  // a possiblity to get the data out of the iterator
  std::tuple<int, std::string, std::string, int, int> value = *it;

  //
  // Show the value of one iterator
  //
  LOG_INFO("Values are: ");
  LOG_INFO(std::get<0>(value));
  LOG_INFO(std::get<1>(value));

  //--------------------------------------------------------------------
  // testing differnet parameters to the constructor

  // uncomment to see that it is not possible to iterate over an vector in the
  // tuple.
  // rosa::csv::CSVIterator<double, std::vector<int>> it2(file, 1);

  // try to skip a valid number of lines after the header
  csv::CSVIterator<double, float, int, int, float> it2_0(file_header_data_2, 1);
  // try to skip a valid number of lines after the header, but you assume that
  // the file has no header
  // uncomment this line to crash the programm
  // csv::CSVIterator<double, float, int, int, float> it2_1(file_header_data_3,
  // 0, csv::HeaderInformation::HasNoHeader);

  // try to skip a valid number of lines after the header, but you assume that
  // the file has no header
  // uncomment this line to crash the program
  // csv::CSVIterator<double, float, int, int, float> it2_2(file_header_data_4,
  // 1, csv::HeaderInformation::HasNoHeader);

  // try to skip a valid number of lines of a file without header
  csv::CSVIterator<double, float, int, int, float> it2_3(
      file2, 1, csv::HeaderInformation::HasNoHeader);

  // try to skip a valid number of lines after the header, but with different
  // delimeter
  csv::CSVIterator<double, float, int, int, float> it2_4(
      file3, 2, csv::HeaderInformation::HasHeader, ';');

  // if you skip more lines than valid, you generate an infinte loop
  // csv::CSVIterator<double, float, int, int, float> it3(file_header_data_5,
  // 500);

  // if you don't need data from all columns just select the number of columns
  // you
  // need. You get the data back from the first column (index 0) to the fourth
  // column
  // all values from the fifth column are ignored.
  csv::CSVIterator<double, float, int, float> it4(file_header_data_6);
}

///
/// This function tests the basic CSVTupleWriter capablities, and shows you
/// how you could work with this class.
///
void testtupleCSVWriter(void) {
  //
  // Create output writer with an file
  //
  std::ofstream file_header_out(csvFileWriteHea);
  csv::CSVTupleWriter<int, float, std::string> wri(file_header_out);

  //
  // Create test tuples
  //
  std::tuple<int, float, std::string> values(5, 8.3f, "hallo");
  std::tuple<int, float, std::string> values2(3, 8.3f, "end");

  //
  // Create test header lines for the test tuples
  //
  std::array<std::string, 3> header{"zero column", "first column",
                                    "second column"};

  std::array<std::string, 4> headerWrong{"zero column", "first column",
                                         "second column", "third column"};

  std::array<std::string, 2> headerWrongShort{"zero column", "first column"};

  // if you uncomment this line than it would be possible for you to write the
  // header into the stream
  // in the next line.
  // wri.write(values);
  wri.writeHeader(header);
  wri.write(values);
  wri.write(values);
  // it is not possible to write an additional header into the stream.
  wri.writeHeader(header);
  wri.write(values);
  wri << values;
  wri << values2;

  // uncomment this line to see, that you can't write a header with the too many
  // elements.
  // wri.writeHeader(headerWrong);
  // uncomment this line to see, that you can't write a header with the too few
  // elements.
  // wri.writeHeader(headerWrongShort);
}

///
/// This function tests the basic splitTupleIterator capablities, and shows you
/// how you could work with this class, this class is used if you want to split
/// a CSVIterator in separate parts.
///
void testsplitTupleIterator(void) {
  //
  // Create deluxe context
  //
  std::unique_ptr<rosa::deluxe::DeluxeContext> C =
      deluxe::DeluxeContext::create(ExampleName);

  //
  // Create deluxe sensors.
  //
  LOG_INFO("Creating sensors.");

  // All sensors are created without defining a normal generator function, but
  // with the default value of the second argument. That, however, requires the
  // data type to be explicitly defined. This is good for simulation only.
  // Three different sensors were created, this is just a random number taken.
  AgentHandle Elem0Sensor = C->createSensor<int>("Element1 Sensor");
  AgentHandle Elem1Sensor = C->createSensor<float>("Element2 Sensor");
  AgentHandle Elem2Sensor = C->createSensor<std::string>("Element3 Sensor");

  //
  // Initialize deluxe context for simulation.
  //
  C->initializeSimulation();

  // Type aliases for iterators
  using Iterator = rosa::csv::CSVIterator<int, float, std::string>;
  using IteratorValue = std::tuple<int, float, std::string>;

  static_assert(
      std::is_same<typename Iterator::value_type, IteratorValue>::value,
      "Iterator must provide tuples");

  //
  // Open CSV file and register the columns to the corresponding sensors.
  //
  std::ifstream TestCSV(csvFileWithHeader);

  //
  // Test data looks like:
  // Element1, Element2, Element3, Element4, Element5 -- is the header line
  // 3, 5, 8, 9.5, 17                                 -- first line of values
  // 100, -8, 30, 18.8, 29                            -- other line of values
  // were also in the file
  // 5, 20, -100, -200.1, -30                         -- if you have less number
  // of values than simulation rounds all values
  //                                                  -- beyond your last value
  //                                                  will be zero.

  // get element iterator ranges
  auto[Elem0Range, Elem1Range, Elem2Range] =
      iterator::splitTupleIterator(Iterator(TestCSV), Iterator());

  // dissect a range into begin and end iterators by structred bindings
  auto[Elem0Begin, Elem0End] = Elem0Range;

  // deissect a range with functions
  auto Elem1Begin = iterator::begin(Elem1Range);
  auto Elem1End = iterator::end(Elem1Range);

  C->registerSensorValues(Elem0Sensor, std::move(Elem0Begin), Elem0End);
  C->registerSensorValues(Elem1Sensor, std::move(Elem1Begin), Elem1End);
  C->registerSensorValues(Elem2Sensor, std::move(iterator::begin(Elem2Range)),
                          iterator::end(Elem2Range));

  //
  // Simulate.
  //
  C->simulate(NumberOfSimulationCycles);
}

///
/// This function tests the basic splitTupleWriter capablities, and shows you
/// how you could work with this class, this class is used if you want to split
/// a CSVWriter in separate parts.
///
// NOTE (Maxi): I had to add an "f" to some numbers to declare them as float,
// otherwise: "C:\Users\maxgot\Source\SoC_RoSA\examples\CSVFiles\main.cpp:314:
// warning: C4305: 'argument': truncation from 'double' to 'const float'"
void testsplitTupleWriter(void) {
  //
  // Create output writer with an file
  //
  std::ofstream file_header_out(csvFileWriteNoHeaSplit);
  csv::CSVTupleWriter<int, float, std::string> wri(file_header_out);

  // if you omit, the type definition in the template, than auto generated types
  // were used,
  // and they may not fit to the used CSVTupleWriter.
  wri << std::make_tuple<int, float, std::string>(1000, 50.6f, "tuple_created");
  auto[T0Writer, T1Writer, T2Writer] = writer::splitTupleWriter(
      std::move(wri), writer::IncompleteTuplePolicy::Ignore);
  // writing elements in sequential order into the writer classes, but you can
  // write the values into the writers in
  // a random order.
  T0Writer << (500);
  T1Writer << (3.0);
  T2Writer << "splitted writter";

  T2Writer << "splitting is cool";
  T0Writer << (-30);
  T1Writer << (-0.004f);

  // you can also write more often values into a writer and later into the other
  // writers
  // all data will be processed correctly into the right order.
  T0Writer << (1);
  T0Writer << (2);
  T1Writer << (-0.4f);
  T0Writer << (3);
  T2Writer << "again";
  T0Writer << (4);
  T1Writer << (-0.1f);
  T1Writer << (-0.2f);
  T2Writer << "and";
  T1Writer << (-0.3f);
  T2Writer << "splitting";
  T2Writer << "once again";

  // again writing data of one tuple entry to the different writers in a random
  // fashion.
  T1Writer << (-0.004f);
  T2Writer << "splitting is cool";
  T0Writer << (-30);
}

int main(void) {
  LOG_INFO_STREAM << library_string() << " -- " << terminal::Color::Red
                  << ExampleName << " example" << terminal::Color::Default
                  << '\n';

  //
  // Testing CSVWriter.
  //
  LOG_INFO("Testing CSVWriter CSVTupleItrator implementation: ");

  testtupleCSVWriter();

  //
  // Testing CSVReader.
  //
  LOG_INFO("Testing CSVReader CSVTupleIterator implementation: ");

  testtupleCSVReader();

  //
  // Testing SplitTupleIterator.
  //
  LOG_INFO("Testing SplitTupleIterator: ");

  testsplitTupleIterator();

  //
  // Testing SplitTupleWriter.
  //
  LOG_INFO("Testing SplitTupleWriter: ");
  testsplitTupleWriter();

  //
  // info that user knows programm has finished.
  //
  LOG_INFO("All tests finished.");

  return 0;
}
