#include "Vera.hpp"
#include <spdlog/fmt/fmt.h>
#include <chrono>
#include <fstream>
#include <iostream>
#include "Calculator.hpp"
#include "ConcentrationCalibration.hpp"
#include "Configuration.hpp"
#include "Hardware.hpp"
#include "HardwareFactory.hpp"
#include "schneide_base/Logger.hpp"
#include "schneide_base/Vocabulary.hpp"

using namespace vera;

namespace
{
constexpr auto NULL_COLUMN_TITLE = "Null";
constexpr auto SAMPLE_COLUMN_TITLE = "Sample";
constexpr auto DIFFERENCE_COLUMN_TITLE = "Difference";
constexpr auto SMOOTHED_COLUMN_TITLE = "Smoothed";
constexpr auto BASE_COLUMN_TITLE = "Baseline";
using Clock = std::chrono::high_resolution_clock;

std::string toString(clara::Parser const& p)
{
  std::ostringstream oss;
  oss << p;
  return oss.str();
}

std::string toString(clara::Opt const& opt)
{
  return toString(clara::Parser() | opt);
}

std::string timeStamp()
{
  time_t now;
  time(&now);
  char buffer[255];
  strftime(buffer, sizeof(buffer), "%Y-%m-%d_%H-%M-%S", localtime(&now));
  return buffer;
}

void setup(Interactor& interactor, Hardware& hardware)
{
  hardware.positioner()->measurementPosition(interactor);
  hardware.pump()->loadUp(interactor);
  hardware.heater()->prepare(interactor);
  hardware.ftir()->prepare(interactor);
}

void shutdown(Interactor& interactor, Hardware& hardware)
{
  hardware.heater()->shutdown(interactor);
}

void loop(
  Interactor& interactor, Hardware& hardware, Calculator const& calculator)
{
  auto const startedTime = Clock::now();
  // Use the time-stamp as an id for this experiment
  auto const experimentTag = "experiment-" + timeStamp();

  hardware.heater()->burnClean(interactor);

  // Chip is in measurement position here
  auto nullMeasurement = hardware.ftir()->scan(interactor);

  // Maybe save?
  hardware.positioner()->loadingPosition(interactor);
  hardware.pump()->dropSample(interactor);
  hardware.positioner()->measurementPosition(interactor);

  auto sampleMeasurement = hardware.ftir()->scan(interactor);

  auto result = calculator.from(nullMeasurement, sampleMeasurement);
  auto endedTime = Clock::now();
  auto duration = std::chrono::duration<float>(endedTime - startedTime);
  interactor.say("Position: {3} -- Intensity: {0} -- PPM: {1} -- Duration: {2:.1f} seconds",
    result.intensity, result.ppm, duration.count(), result.position);
  hardware.pump()->dump(interactor);

  saveTaggedCSVFor({{NULL_COLUMN_TITLE, nullMeasurement},
                     {SAMPLE_COLUMN_TITLE, sampleMeasurement},
                     {DIFFERENCE_COLUMN_TITLE, result.difference},
                     {SMOOTHED_COLUMN_TITLE, result.smoothed},
                     {BASE_COLUMN_TITLE, result.base}},
    experimentTag, "spectrums");
  result.saveTagged(experimentTag, duration);
}

}  // namespace

void vera::run(
  clara::Args args, std::string const& version, std::string const& versionName)
{
  bool showHelp = false;
  std::vector<std::string> filesToRecompute;

  auto parser = clara::Help(showHelp) |
                clara::Arg(filesToRecompute, "experiment .csv files");
  if (!parser.parse(args) || showHelp)
  {
    std::cout << toString(parser);
    return;
  }

  if (!filesToRecompute.empty())
  {
    recompute(filesToRecompute);
    return;
  }

  Logger::setupFileLogging("vera.log");
  Logger::get()->info("VERA Version {0} - {1}", version, versionName);

  vera::run_experiments();
}

void vera::recompute(std::vector<std::string> const& filenames)
{
  for (auto const& each : filenames) recompute(each);
}

void vera::recompute(std::string const& filename)
{
  auto const recalculationTag = "recalculation-" + timeStamp();
  Interactor interactor;
  auto startedTime = Clock::now();
  auto configuration = std::make_shared<Configuration>("vera.ini");
  auto generalConfig = configuration->general();
  auto calculator = makeCalculatorFromConfig(generalConfig);

  auto spectrums = loadSpectrums(filename);

  auto nullMeasurement = spectrums.at(NULL_COLUMN_TITLE);
  auto sampleMeasurement = spectrums.at(SAMPLE_COLUMN_TITLE);

  auto result = calculator->from(nullMeasurement, sampleMeasurement);
  auto endedTime = Clock::now();
  auto duration = std::chrono::duration<float>(endedTime - startedTime);
  interactor.say("Intensity: {0} -- PPM: {1} -- Duration: {2:.1f} seconds",
    result.intensity, result.ppm, duration.count());

  saveTaggedCSVFor({{NULL_COLUMN_TITLE, nullMeasurement},
                     {SAMPLE_COLUMN_TITLE, sampleMeasurement},
                     {DIFFERENCE_COLUMN_TITLE, result.difference},
                     {SMOOTHED_COLUMN_TITLE, result.smoothed},
                     {BASE_COLUMN_TITLE, result.base}},
    recalculationTag, "spectrums");
  result.saveTagged(recalculationTag, duration);
}

void vera::run_experiments()
{
  Logger::get()->info("Starting experiments...");
  auto configuration = std::make_shared<Configuration>("vera.ini");

  auto generalConfig = configuration->general();
  auto repeatWait = generalConfig.repeatWait();

  auto calculator = makeCalculatorFromConfig(generalConfig);
  auto hardware = HardwareFactory(configuration).make();

  Interactor interactor;

  setup(interactor, hardware);

  bool keepRunning = true;
  while (keepRunning)
  {
    loop(interactor, hardware, *calculator);

    if (generalConfig.repeatAutomatically())
    {
      interactor.say(
        "Repeating experiment in {0:.1f} seconds...", repeatWait.count());
      interactor.wait(repeatWait);
    }
    else
    {
      auto answer = interactor.question("Go on?", {"yes", "no"});
      if (answer != "yes")
        break;
    }
  }

  shutdown(interactor, hardware);

  interactor.say("Good bye!");
}
