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

#include "Aristotle.hpp"

class Aristotle final : public Module {
  TriggerInput* resetTriggerA;
  TriggerInput* resetTriggerB;

  Stage* stages[NUM_STAGES] = {};

  Channel* channelA;
  Channel* channelB;


public:
  Aristotle() {
    config(NUM_PARAMS + NUM_STAGES * 2, NUM_INPUTS + NUM_STAGES, NUM_OUTPUTS, NUM_LIGHTS + NUM_STAGES * 2);

    configParam(CHANNEL_A_WIDER_VOLTAGE_RANGE_PARAM, 0.0, 1.0, 0.0, "Channel A Wider Voltage Range");
    configParam(CHANNEL_B_WIDER_VOLTAGE_RANGE_PARAM, 0.0, 1.0, 0.0, "Channel B Wider Voltage Range");

    for (int stageNumber = 0; stageNumber < NUM_STAGES; stageNumber++) {
      std::string stageNum = std::to_string(stageNumber + 1);
      configParam(firstStageParamA + stageNumber, 0.0, 1.0, 0.0, "Channel A Stage " + stageNum);
      configParam(firstStageParamB + stageNumber, 0.0, 1.0, 0.0, "Channel B Stage " + stageNum);

      configInput(firstStageInput + stageNumber, "Trigger " + std::to_string(stageNumber + 1));
    }

    configInput(RESET_A_INPUT, "Reset A");
    configInput(RESET_B_INPUT, "Reset B");

    configOutput(CHANNEL_A_OUTPUT, "Channel A Output V/Oct");
    configOutput(CHANNEL_B_OUTPUT, "Channel B Output V/Oct");

    for (int stageNumber = 0; stageNumber < NUM_STAGES; stageNumber++) {
      TriggerInput* trigger;
      if (stageNumber == 0)
        trigger = new SimpleTrigger(new VCVInput(&inputs[firstStageInput]));
      else
        trigger = new NormalledTrigger(new VCVInput(&inputs[firstStageInput + stageNumber]), &stages[stageNumber - 1]->triggerCounter);

      stages[stageNumber] = new Stage(new VCVParam(&params[firstStageParamA + stageNumber]), new VCVParam(&params[firstStageParamB + stageNumber]),
                                      &lights[firstStageLightA + stageNumber], &lights[firstStageLightB + stageNumber], trigger);
    }

    resetTriggerA = new SimpleTrigger(new VCVInput(&inputs[RESET_A_INPUT]));
    resetTriggerB = new SimpleTrigger(new VCVInput(&inputs[RESET_B_INPUT]));

    const std::vector<Stage*> stagesVector(std::begin(stages), std::end(stages));
    channelA = new Channel(CHANNEL_A, &lights[CHANNEL_A_LIGHT], &outputs[CHANNEL_A_OUTPUT], &params[CHANNEL_A_WIDER_VOLTAGE_RANGE_PARAM], stagesVector);
    channelB = new Channel(CHANNEL_B, &lights[CHANNEL_B_LIGHT], &outputs[CHANNEL_B_OUTPUT], &params[CHANNEL_B_WIDER_VOLTAGE_RANGE_PARAM], stagesVector);
  }

  void process(const ProcessArgs& args) override {
    // A trigger input to channel A should turn all its stages off, and hence all of channel B's stages on
    if (resetTriggerA->isTriggered())
      for (auto& stage : stages)
        stage->reset(CHANNEL_B);
    else if (resetTriggerB->isTriggered())
      for (auto& stage : stages)
        stage->reset(CHANNEL_A);
    else
      for (const auto stage : stages)
        stage->process();

    channelA->process();
    channelB->process();
  }

  void onReset(const ResetEvent& e) override {
    Module::onReset(e);
    for (const auto stage : stages)
      stage->reset(CHANNEL_B);
  }

  void onRandomize(const RandomizeEvent& e) override {
    Module::onRandomize(e);
    for (const auto stage : stages)
      stage->reset(random::uniform() < 0.5f ? CHANNEL_A : CHANNEL_B);
  }

  json_t* dataToJson() override {
    json_t* rootJ = json_object();
    for (int i = 0; i < NUM_STAGES; i++) {
      char key[32];
      snprintf(key, sizeof(key), "energized_channel%d", i);
      json_object_set_new(rootJ, key, json_integer(stages[i]->energisedChannel));
    }
    return rootJ;
  }

  void dataFromJson(json_t* rootJ) override {
    for (int i = 0; i < NUM_STAGES; i++) {
      char key[32];
      snprintf(key, sizeof(key), "energized_channel%d", i);
      json_t* channelId = json_object_get(rootJ, key);
      if (channelId)
        stages[i]->energisedChannel = static_cast<ChannelId>(json_integer_value(channelId));
      else
        stages[i]->energisedChannel = CHANNEL_B;
    }
  };
};

struct AristotleWidget final : ModuleWidget {
  explicit AristotleWidget(Aristotle* module) {
    setModule(module);
    setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Aristotle.svg")));

    constexpr float firstRowTop  = 17.62;
    constexpr float secondRowTop = 30.7;

    const float triggerXs[NUM_STAGES] = {6.0, 18.85, 31.75, 44.7};
    const float sliderXs[NUM_STAGES]  = {6.1, 18.85, 31.7, 44.5};

    constexpr float channelAStagesTop = 58.2;
    constexpr float channelBStagesTop = 97.4;

    addInput(createInputCentered<PJ301MPort>(mm2px(Vec(sliderXs[0] - 0.2f, firstRowTop)), module, RESET_A_INPUT));
    addInput(createInputCentered<PJ301MPort>(mm2px(Vec(sliderXs[1], firstRowTop)), module, RESET_B_INPUT));

    addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(sliderXs[2], firstRowTop)), module, CHANNEL_A_OUTPUT));
    addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(sliderXs[2] - 5.9f, firstRowTop - 4.1)), module, CHANNEL_A_LIGHT));

    addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(sliderXs[3] + 0.2f, firstRowTop)), module, CHANNEL_B_OUTPUT));
    addChild(createLightCentered<MediumLight<RedLight>>(mm2px(Vec(sliderXs[3] - 6.52f, firstRowTop - 4.1)), module, CHANNEL_B_LIGHT));

    for (int stageNumber = 0; stageNumber < NUM_STAGES; stageNumber++) {
      addInput(createInputCentered<PJ301MPort>(mm2px(Vec(triggerXs[stageNumber], secondRowTop)), module, firstStageInput + stageNumber));

      const float sliderX = sliderXs[stageNumber];
      addParam(createLightParamCentered<VCVLightSlider<GreenLight>>(mm2px(Vec(sliderX, channelAStagesTop)), module, firstStageParamA + stageNumber,
                                                                    firstStageLightA + stageNumber));
      addParam(createLightParamCentered<VCVLightSlider<RedLight>>(mm2px(Vec(sliderX, channelBStagesTop)), module, firstStageParamB + stageNumber,
                                                                  firstStageLightB + stageNumber));
    }
  }

  void appendContextMenu(Menu* menu) override {
    auto* module = dynamic_cast<Aristotle*>(this->module);
    assert(module);

    menu->addChild(new MenuSeparator);
    menu->addChild(createBoolMenuItem(
      "2V range for channel A", "", [=] { return module->params[CHANNEL_A_WIDER_VOLTAGE_RANGE_PARAM].getValue() > 0.5f; },
      [=](const bool value) { module->params[CHANNEL_A_WIDER_VOLTAGE_RANGE_PARAM].setValue(value ? 1.f : 0.f); }));
    menu->addChild(createBoolMenuItem(
      "2V range for channel B", "", [=] { return module->params[CHANNEL_B_WIDER_VOLTAGE_RANGE_PARAM].getValue() > 0.5f; },
      [=](const bool value) { module->params[CHANNEL_B_WIDER_VOLTAGE_RANGE_PARAM].setValue(value ? 1.f : 0.f); }));
  }
};

Model* modelAristotle = createModel<Aristotle, AristotleWidget>("Aristotle");
