#include "HardwareFactory.hpp"
#include "OneTimeFactory.hpp"
#include "module_ftir/EmulatedFTIR.hpp"
#include "module_heater/EmulatedHeater.hpp"
#include "module_heater/AmpereControllingHeater.hpp"
#include "module_heater/TDKLambdaAmpereControl.hpp"
#include "module_heater/PS3000CAmpereControl.hpp"
#include "module_positioner/ArduinoPositioner.hpp"
#include "module_positioner/EmulatedPositioner.hpp"
#include "module_positioner/ManualPositioner.hpp"
#include "module_pump/ArduinoPump.hpp"
#include "module_pump/EmulatedPump.hpp"
#include "module_pump/ManualPump.hpp"

#ifdef VERA_PLATFORM_WIN32
#include "module_ftir/AgilentFTIR.hpp"
#endif

using namespace vera;
namespace
{
  auto constexpr HEATER_MODE_EA_PS3000C = "ea-ps3000c";
  auto constexpr HEATER_MODE_TDK_LAMBDA = "tdk-lambda";

  std::unique_ptr<AmpereControl> AmpereControlFor(HeaterConfiguration const& config)
  {
    if (config.mode() == HEATER_MODE_TDK_LAMBDA)
    {
      return std::make_unique<TDKLambdaAmpereControl>(config.COMPort(), config.TDKAddress());
    }
    if (config.mode() == HEATER_MODE_EA_PS3000C)
    {
      return std::make_unique<PS3000CAmpereControl>(config.networkAddress(), config.networkPort());
    }
    throw std::invalid_argument("Unknown heater mode: " + config.mode());
  }
}  // namespace

Hardware vera::HardwareFactory::make() const
{
  auto arduinoTerminalFactory = OneTimeFactory<schneide::SerialConnection>([this] {
    return std::make_shared<schneide::SerialConnection>(
      "Arduino", config().general().arduinoComPort(), '\n');
  });

  return Hardware(makeHeater(), makePositioner(arduinoTerminalFactory),
    makePump(arduinoTerminalFactory), makeFTIR());
}

Ptr<Pump> HardwareFactory::makePump(ArduinoFactory& arduinoFactory) const
{
  auto const pumpConfig = config().pump();
  auto const mode = pumpConfig.mode();

  if (mode == "emulated")
  {
    return std::make_shared<EmulatedPump>();
  }
  if (mode == "manual")
  {
    return std::make_shared<ManualPump>();
  }
  if (mode == "arduino")
  {
    return std::make_shared<ArduinoPump>(arduinoFactory(),
      pumpConfig.loadTime(), pumpConfig.sampleTime(),
      pumpConfig.evaporationTime(), pumpConfig.unloadTime());
  }
  throw std::runtime_error("Unknown pump mode: " + mode);
}

Ptr<Heater> HardwareFactory::makeHeater() const
{
  auto heaterConfig = config().heater();
  auto const mode = heaterConfig.mode();
  if (mode == "emulated")
  {
    return std::make_shared<EmulatedHeater>();
  }
  if (mode == HEATER_MODE_TDK_LAMBDA || mode == HEATER_MODE_EA_PS3000C)
  {
    return std::make_shared<AmpereControllingHeater>(AmpereControlFor(heaterConfig), heaterConfig);
  }
  throw std::runtime_error("Unknown heater mode: " + mode);
}

Ptr<FTIR> HardwareFactory::makeFTIR() const
{
  auto const ftirConfig = config().ftir();
  auto const mode = ftirConfig.mode();
  if (mode == "emulated")
    return std::make_shared<EmulatedFTIR>();
#ifdef VERA_PLATFORM_WIN32
  if (mode == "agilent")
    return std::make_shared<AgilentFTIR>(ftirConfig);
#endif

  throw std::runtime_error("Unknown FTIR mode: " + mode);
}

Ptr<Positioner> HardwareFactory::makePositioner(
  ArduinoFactory& arduinoFactory) const
{
  auto const positionerConfig = config().positioner();
  auto const mode = positionerConfig.mode();
  if (mode == "emulated")
  {
    return std::make_shared<EmulatedPositioner>();
  }
  if (mode == "manual")
  {
    return std::make_shared<ManualPositioner>();
  }
  if (mode == "arduino")
  {
    return std::make_shared<ArduinoPositioner>(arduinoFactory(),
      positionerConfig.measurementPosition(),
      positionerConfig.loadingPosition(), positionerConfig.waitTime());
  }
  throw std::runtime_error("Unknown positioner mode: " + mode);
}
