#include "Calculator.hpp"
#include <spdlog/fmt/fmt.h>
#include <fstream>

using namespace vera;

std::unique_ptr<Calculator> vera::makeCalculatorFromConfig(
  GeneralConfiguration const& config)
{
  auto calibration =
    ConcentrationCalibration::fromFile(config.calibrationFile());

  if (!config.useBaseLine())
  {
    return std::make_unique<HighPassCalculator>(
      std::move(calibration), config.smoothing(), config.peakPosition());
  }

  return std::make_unique<BaseLineCalculator>(std::move(calibration),
    config.smoothing(), config.peakPosition(), config.peakSearchWidth(),
    config.peakIsolation());
}

HighPassCalculator::HighPassCalculator(
  ConcentrationCalibration calibration, double smoothing, double peakPosition)
: mCalibration(std::move(calibration))
, mSmoothing(smoothing)
, mPeakPosition(peakPosition)
{
}

void CalculatedResult::saveTagged(
  std::string const& experimentId, std::chrono::duration<float> duration)
{
  std::string filename(fmt::format("{0}-result.txt", experimentId));
  std::ofstream file(filename, std::ios::trunc);
  if (!file.good())
    throw std::invalid_argument(
      "Unable to open file " + filename + " for writing");

  file << "Position[wn]: " << position << std::endl;
  file << "Intensity: " << intensity << std::endl;
  file << "PPM: " << ppm << std::endl;
  file << "Duration: " << fmt::format("{0:.1f}", duration.count()) << std::endl;
}

CalculatedResult HighPassCalculator::from(Ptr<Spectrum> const& nullMeasurement,
  Ptr<Spectrum> const& sampleMeasurement) const
{
  auto difference = -logOrZero(*sampleMeasurement / *nullMeasurement);
  auto smoothed = gauss(difference, mSmoothing);
  auto normalized = difference - smoothed;
  auto intensity = normalized.at(mPeakPosition);
  auto ppm = mCalibration.findPPM(intensity);

  auto baseSpectrum = std::make_shared<Spectrum>(std::move(smoothed));
  return {mPeakPosition, intensity, ppm,
    std::make_shared<Spectrum>(std::move(difference)), baseSpectrum,
    baseSpectrum};
}

BaseLineCalculator::BaseLineCalculator(ConcentrationCalibration calibration,
  double smoothing, double peakPosition, double peakSearchWidth,
  double peakIsolation)
: mCalibration(calibration)
, mSmoothing(smoothing)
, mPeakPosition(peakPosition)
, mPeakSearchWidth(peakSearchWidth)
, mPeakIsolation(peakIsolation)
{
}

CalculatedResult BaseLineCalculator::from(Ptr<Spectrum> const& nullMeasurement,
  Ptr<Spectrum> const& sampleMeasurement) const
{
  auto difference = -logOrZero(*sampleMeasurement / *nullMeasurement);

  auto smoothed = gauss(difference, mSmoothing);
  auto peakInfo = PeakInfo::computeFrom(
    smoothed, mPeakPosition, mPeakSearchWidth, mPeakIsolation);

  auto intensity = peakInfo.relativePeak();
  auto ppm = mCalibration.findPPM(intensity);

  // Discretize the base-line into a spectrum
  auto lineSpectrum = std::make_shared<Spectrum>(
    peakInfo.baseLine.withRange(smoothed.wavenumbers()), smoothed.binCount());

  return {peakInfo.peakWavenumber, intensity, ppm,
    std::make_shared<Spectrum>(std::move(difference)),
    std::make_shared<Spectrum>(std::move(smoothed)), lineSpectrum};
}
