#pragma once

#include "tools/InsAndOuts.hpp"

#define NUM_STAGES 4

enum ParamIds { CHANNEL_A_WIDER_VOLTAGE_RANGE_PARAM, CHANNEL_B_WIDER_VOLTAGE_RANGE_PARAM, NUM_PARAMS };

enum InputIds { RESET_A_INPUT, RESET_B_INPUT, NUM_INPUTS };

enum OutputIds { CHANNEL_A_OUTPUT, CHANNEL_B_OUTPUT, NUM_OUTPUTS };

enum LightIds { CHANNEL_A_LIGHT, CHANNEL_B_LIGHT, NUM_LIGHTS };

enum ChannelId { CHANNEL_A, CHANNEL_B };

constexpr int firstStageParamA = NUM_PARAMS;
constexpr int firstStageParamB = NUM_PARAMS + NUM_STAGES;

constexpr int firstStageInput  = NUM_INPUTS;
constexpr int firstStageOutput = NUM_OUTPUTS;

constexpr int firstStageLightA = NUM_LIGHTS;
constexpr int firstStageLightB = NUM_LIGHTS + NUM_STAGES;

struct Stage {
  ChannelId energisedChannel = CHANNEL_B;

  CountTo triggerCounter = CountTo(2);

  VoltageSource* channelAVoltage;
  VoltageSource* channelBVoltage;
  Light* channelALight;
  Light* channelBLight;
  TriggerInput* trigger;

  Stage(VoltageSource* channelAVoltage, VoltageSource* channelBVoltage, Light* channelALight, Light* channelBLight, TriggerInput* trigger) :
      channelAVoltage(channelAVoltage), channelBVoltage(channelBVoltage), channelALight(channelALight), channelBLight(channelBLight), trigger(trigger) {}

  void process() {
    if (trigger->isTriggered()) {
      triggerCounter.trigger();
      energisedChannel = energisedChannel == CHANNEL_A ? CHANNEL_B : CHANNEL_A;
    }

    channelALight->setBrightness(energisedChannel == CHANNEL_A ? 1.f : 0.f);
    channelBLight->setBrightness(energisedChannel == CHANNEL_B ? 1.f : 0.f);
  }

  void reset(ChannelId channel) {
    triggerCounter.reset();
    energisedChannel = channel;
  }

  float getChannelAVoltage() const { return energisedChannel == CHANNEL_A ? channelAVoltage->getVoltage() : 0.f; }

  float getChannelBVoltage() const { return energisedChannel == CHANNEL_B ? channelBVoltage->getVoltage() : 0.f; }

  float getVoltageForChannel(ChannelId channel) const { return channel == CHANNEL_A ? getChannelAVoltage() : getChannelBVoltage(); }
};

class Channel {
  ChannelId channelId;
  Light* light;
  Output* output;
  Param* voltageRangeParam;
  std::vector<Stage*> stages;

public:
  Channel(const ChannelId channel, Light* light, Output* output, Param* voltageRangeParam, const std::vector<Stage*>& stages) :
      channelId(channel), light(light), output(output), voltageRangeParam(voltageRangeParam), stages(stages) {}

  void process() const {
    const float voltage = getVoltage();
    light->setBrightness(rescale(voltage, 0.f, getVoltageRange() * NUM_STAGES, 0.f, 1.f));
    output->setVoltage(voltage);
  }

  float getVoltageRange() const { return voltageRangeParam->getValue() == 1.f ? 2.f : 1.25f; }

  float getVoltage() const {
    float voltage = 0;
    for (auto& stage : stages)
      voltage += stage->getVoltageForChannel(channelId) * getVoltageRange();
    return voltage;
  }
};
