//
// Created by Doug Clinton on 28/07/2025.
//

/**
 * Useful wrappers around the VCV Rack SDK's Input, Output, Param, etc, providing abstractions
 * and utilities for handling things like triggers and normal paths.
 */
#pragma once

#include "../plugin.hpp"

class VoltageSource {
public:
  virtual float getVoltage() = 0;

  virtual bool isConnected() { return true; }
  virtual ~VoltageSource() = default;

protected:
  VoltageSource() = default;
};

class TriggerInput {
public:
  virtual bool isTriggered() = 0;
  virtual ~TriggerInput()    = default;

protected:
  TriggerInput() = default;
};

class VCVParam final : public VoltageSource {
  Param* param;

public:
  explicit VCVParam(Param* param) : param(param) {}

  float getVoltage() override { return param->getValue(); }
};

class VCVInput final : public VoltageSource {
  Input* input;

public:
  explicit VCVInput(Input* input) : input(input) {}

  float getVoltage() override { return input->getVoltage(); }

  bool isConnected() override { return input->isConnected(); }
};

/**
 * Provides the voltage from the mainInput if it's connected, otherwise from the normalInput
 */
class NormalledInput final : public VoltageSource {
  VoltageSource* mainInput;
  VoltageSource* normalInput;

public:
  NormalledInput(VoltageSource* mainInput, VoltageSource* normalInput) : mainInput(mainInput), normalInput(normalInput) {}

  float getVoltage() override { return mainInput->isConnected() ? mainInput->getVoltage() : normalInput->getVoltage(); }
};

/**
 * Uses the value from a VoltageSource to drive a SchmittTrigger. A voltage moving above 2V will trigger, and
 *  moving back below 2V will reset it.
 */
class SimpleTrigger final : public TriggerInput {
  VoltageSource* input;
  dsp::SchmittTrigger trigger = dsp::SchmittTrigger();

public:
  explicit SimpleTrigger(VoltageSource* voltageInput) : input(voltageInput) {}

  bool isTriggered() override {
    const float voltage = input->getVoltage();
    return trigger.process(rescale(voltage, 0.1f, 2.f, 0.f, 1.f));
  }
};

/**
 * Will build a trigger that monitors the main input if it is connected. If the main input is not
 * connected, then the normalInput will be used to drive the SmittTrigger
 */
class NormalledTrigger final : public TriggerInput {
  TriggerInput* trigger;

public:
  NormalledTrigger(VoltageSource* mainInput, VoltageSource* normalInput) : trigger(new SimpleTrigger(new NormalledInput(mainInput, normalInput))) {}

  bool isTriggered() override { return trigger->isTriggered(); }
};

/**
 * Will provide a voltage of 10V when flipped, and 0V when flopped
 */
class FlipFlop final : public VoltageSource {
  bool state = false;

public:
  void flip() { state = !state; }

  void reset() { state = false; }

  float getVoltage() override { return state ? 10.f : 0.f; }
};

/**
 * Count the number of times we've been triggered. When the count reaches the maximum then the output
 * voltage goes high (10V) and the next trigger loops us back to a count of one.
 *
 * Initially, or after a reset, the count starts at zero.
 */
class CountTo final : public VoltageSource {
  int count    = 0;
  int maxCount = 0;

public:
  explicit CountTo(const int maxCount) : maxCount(maxCount) {}

  float getVoltage() override { return count < maxCount ? 0.f : 10.f; }

  void trigger() {
    if (count >= maxCount)
      count = 1;
    else
      count++;
  }

  void reset() { count = 0; }
};

class ZeroVoltage final : public VoltageSource {
public:
  float getVoltage() override { return 0.f; }
  bool isConnected() override { return false; }
};
