#include "plugin.hpp"
#include "marbles/random/random_generator.h"
#include "marbles/random/random_stream.h"
#include "marbles/random/t_generator.h"
#include "marbles/random/x_y_generator.h"
#include "marbles/note_filter.h"


static const int BLOCK_SIZE = 5;


static const marbles::Scale preset_scales[6] = {
	// C major
	{
		1.0f,
		12,
		{
			{ 0.0000f, 255 },  // C
			{ 0.0833f, 16 },   // C#
			{ 0.1667f, 96 },   // D
			{ 0.2500f, 24 },   // D#
			{ 0.3333f, 128 },  // E
			{ 0.4167f, 64 },   // F
			{ 0.5000f, 8 },    // F#
			{ 0.5833f, 192 },  // G
			{ 0.6667f, 16 },   // G#
			{ 0.7500f, 96 },   // A
			{ 0.8333f, 24 },   // A#
			{ 0.9167f, 128 },  // B
		}
	},

	// C minor
	{
		1.0f,
		12,
		{
			{ 0.0000f, 255 },  // C
			{ 0.0833f, 16 },   // C#
			{ 0.1667f, 96 },   // D
			{ 0.2500f, 128 },  // Eb
			{ 0.3333f, 8 },    // E
			{ 0.4167f, 64 },   // F
			{ 0.5000f, 4 },    // F#
			{ 0.5833f, 192 },  // G
			{ 0.6667f, 16 },   // G#
			{ 0.7500f, 96 },   // A
			{ 0.8333f, 128 },  // Bb
			{ 0.9167f, 16 },   // B
		}
	},

	// Pentatonic
	{
		1.0f,
		12,
		{
			{ 0.0000f, 255 },  // C
			{ 0.0833f, 4 },    // C#
			{ 0.1667f, 96 },   // D
			{ 0.2500f, 4 },    // Eb
			{ 0.3333f, 4 },    // E
			{ 0.4167f, 140 },  // F
			{ 0.5000f, 4 },    // F#
			{ 0.5833f, 192 },  // G
			{ 0.6667f, 4 },    // G#
			{ 0.7500f, 96 },   // A
			{ 0.8333f, 4 },    // Bb
			{ 0.9167f, 4 },    // B
		}
	},

	// Pelog
	{
		1.0f,
		7,
		{
			{ 0.0000f, 255 },  // C
			{ 0.1275f, 128 },  // Db+
			{ 0.2625f, 32 },  // Eb-
			{ 0.4600f, 8 },    // F#-
			{ 0.5883f, 192 },  // G
			{ 0.7067f, 64 },  // Ab
			{ 0.8817f, 16 },    // Bb+
		}
	},

	// Raag Bhairav That
	{
		1.0f,
		12,
		{
			{ 0.0000f, 255 }, // ** Sa
			{ 0.0752f, 128 }, // ** Komal Re
			{ 0.1699f, 4 },   //    Re
			{ 0.2630f, 4 },   //    Komal Ga
			{ 0.3219f, 128 }, // ** Ga
			{ 0.4150f, 64 },  // ** Ma
			{ 0.4918f, 4 },   //    Tivre Ma
			{ 0.5850f, 192 }, // ** Pa
			{ 0.6601f, 64 },  // ** Komal Dha
			{ 0.7549f, 4 },   //    Dha
			{ 0.8479f, 4 },   //    Komal Ni
			{ 0.9069f, 64 },  // ** Ni
		}
	},

	// Raag Shri
	{
		1.0f,
		12,
		{
			{ 0.0000f, 255 }, // ** Sa
			{ 0.0752f, 4 },   //    Komal Re
			{ 0.1699f, 128 }, // ** Re
			{ 0.2630f, 64 },  // ** Komal Ga
			{ 0.3219f, 4 },   //    Ga
			{ 0.4150f, 128 }, // ** Ma
			{ 0.4918f, 4 },   //    Tivre Ma
			{ 0.5850f, 192 }, // ** Pa
			{ 0.6601f, 4 },   //    Komal Dha
			{ 0.7549f, 64 },  // ** Dha
			{ 0.8479f, 128 }, // ** Komal Ni
			{ 0.9069f, 4 },   //    Ni
		}
	},
};


struct Marbles : Module {
	enum ParamIds {
		T_DEJA_VU_PARAM,
		X_DEJA_VU_PARAM,
		DEJA_VU_PARAM,
		T_RATE_PARAM,
		X_SPREAD_PARAM,
		T_MODE_PARAM,
		X_MODE_PARAM,
		DEJA_VU_LENGTH_PARAM,
		T_BIAS_PARAM,
		X_BIAS_PARAM,
		T_RANGE_PARAM,
		X_RANGE_PARAM,
		EXTERNAL_PARAM,
		T_JITTER_PARAM,
		X_STEPS_PARAM,
		NUM_PARAMS
	};
	enum InputIds {
		T_BIAS_INPUT,
		X_BIAS_INPUT,
		T_CLOCK_INPUT,
		T_RATE_INPUT,
		T_JITTER_INPUT,
		DEJA_VU_INPUT,
		X_STEPS_INPUT,
		X_SPREAD_INPUT,
		X_CLOCK_INPUT,
		NUM_INPUTS
	};
	enum OutputIds {
		T1_OUTPUT,
		T2_OUTPUT,
		T3_OUTPUT,
		Y_OUTPUT,
		X1_OUTPUT,
		X2_OUTPUT,
		X3_OUTPUT,
		NUM_OUTPUTS
	};
	enum LightIds {
		T_DEJA_VU_LIGHT,
		X_DEJA_VU_LIGHT,
		ENUMS(T_MODE_LIGHTS, 2),
		ENUMS(X_MODE_LIGHTS, 2),
		ENUMS(T_RANGE_LIGHTS, 2),
		ENUMS(X_RANGE_LIGHTS, 2),
		EXTERNAL_LIGHT,
		T1_LIGHT,
		T2_LIGHT,
		T3_LIGHT,
		Y_LIGHT,
		X1_LIGHT,
		X2_LIGHT,
		X3_LIGHT,
		NUM_LIGHTS
	};

	marbles::RandomGenerator random_generator;
	marbles::RandomStream random_stream;
	marbles::TGenerator t_generator;
	marbles::XYGenerator xy_generator;
	marbles::NoteFilter note_filter;

	// State
	dsp::BooleanTrigger tDejaVuTrigger;
	dsp::BooleanTrigger xDejaVuTrigger;
	dsp::BooleanTrigger tModeTrigger;
	dsp::BooleanTrigger xModeTrigger;
	dsp::BooleanTrigger tRangeTrigger;
	dsp::BooleanTrigger xRangeTrigger;
	dsp::BooleanTrigger externalTrigger;
	bool t_deja_vu;
	bool x_deja_vu;
	int t_mode;
	int x_mode;
	int t_range;
	int x_range;
	bool external;
	int x_scale;
	int y_divider_index;
	int x_clock_source_internal;

	// Buffers
	stmlib::GateFlags t_clocks[BLOCK_SIZE] = {};
	stmlib::GateFlags last_t_clock = 0;
	stmlib::GateFlags xy_clocks[BLOCK_SIZE] = {};
	stmlib::GateFlags last_xy_clock = 0;
	float ramp_master[BLOCK_SIZE] = {};
	float ramp_external[BLOCK_SIZE] = {};
	float ramp_slave[2][BLOCK_SIZE] = {};
	bool gates[BLOCK_SIZE * 2] = {};
	float voltages[BLOCK_SIZE * 4] = {};
	int blockIndex = 0;

	Marbles() {
		config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
		configParam(T_DEJA_VU_PARAM, 0.0, 1.0, 0.0, "t deja vu");
		configParam(X_DEJA_VU_PARAM, 0.0, 1.0, 0.0, "X deja vu");
		configParam(DEJA_VU_PARAM, 0.0, 1.0, 0.5, "Deja vu probability");
		configParam(T_RATE_PARAM, -1.0, 1.0, 0.0, "Clock rate");
		configParam(X_SPREAD_PARAM, 0.0, 1.0, 0.5, "Probability distribution");
		configParam(T_MODE_PARAM, 0.0, 1.0, 0.0, "t mode");
		configParam(X_MODE_PARAM, 0.0, 1.0, 0.0, "X mode");
		configParam(DEJA_VU_LENGTH_PARAM, 0.0, 1.0, 0.0, "Loop length");
		configParam(T_BIAS_PARAM, 0.0, 1.0, 0.5, "Gate bias");
		configParam(X_BIAS_PARAM, 0.0, 1.0, 0.5, "Distribution bias");
		configParam(T_RANGE_PARAM, 0.0, 1.0, 0.0, "Clock range mode");
		configParam(X_RANGE_PARAM, 0.0, 1.0, 0.0, "Output voltage range mode");
		configParam(EXTERNAL_PARAM, 0.0, 1.0, 0.0, "External processing mode");
		configParam(T_JITTER_PARAM, 0.0, 1.0, 0.0, "Randomness amount");
		configParam(X_STEPS_PARAM, 0.0, 1.0, 0.5, "Smoothness");

		random_generator.Init(1);
		random_stream.Init(&random_generator);
		note_filter.Init();
		onSampleRateChange();
		onReset();
	}

	void onReset() override {
		t_deja_vu = false;
		x_deja_vu = false;
		t_mode = 0;
		x_mode = 0;
		t_range = 1;
		x_range = 1;
		external = false;
		x_scale = 0;
		y_divider_index = 8;
		x_clock_source_internal = 0;
	}

	void onRandomize() override {
		t_mode = random::u32() % 3;
		x_mode = random::u32() % 3;
		t_range = random::u32() % 3;
		x_range = random::u32() % 3;
	}

	void onSampleRateChange() override {
		float sampleRate = APP->engine->getSampleRate();
		t_generator.Init(&random_stream, sampleRate);
		xy_generator.Init(&random_stream, sampleRate);

		// Set scales
		for (int i = 0; i < 6; i++) {
			xy_generator.LoadScale(i, preset_scales[i]);
		}
	}

	json_t* dataToJson() override {
		json_t* rootJ = json_object();

		json_object_set_new(rootJ, "t_deja_vu", json_boolean(t_deja_vu));
		json_object_set_new(rootJ, "x_deja_vu", json_boolean(x_deja_vu));
		json_object_set_new(rootJ, "t_mode", json_integer(t_mode));
		json_object_set_new(rootJ, "x_mode", json_integer(x_mode));
		json_object_set_new(rootJ, "t_range", json_integer(t_range));
		json_object_set_new(rootJ, "x_range", json_integer(x_range));
		json_object_set_new(rootJ, "external", json_boolean(external));
		json_object_set_new(rootJ, "x_scale", json_integer(x_scale));
		json_object_set_new(rootJ, "y_divider_index", json_integer(y_divider_index));
		json_object_set_new(rootJ, "x_clock_source_internal", json_integer(x_clock_source_internal));

		return rootJ;
	}

	void dataFromJson(json_t* rootJ) override {
		json_t* t_deja_vuJ = json_object_get(rootJ, "t_deja_vu");
		if (t_deja_vuJ)
			t_deja_vu = json_boolean_value(t_deja_vuJ);

		json_t* x_deja_vuJ = json_object_get(rootJ, "x_deja_vu");
		if (x_deja_vuJ)
			x_deja_vu = json_boolean_value(x_deja_vuJ);

		json_t* t_modeJ = json_object_get(rootJ, "t_mode");
		if (t_modeJ)
			t_mode = json_integer_value(t_modeJ);

		json_t* x_modeJ = json_object_get(rootJ, "x_mode");
		if (x_modeJ)
			x_mode = json_integer_value(x_modeJ);

		json_t* t_rangeJ = json_object_get(rootJ, "t_range");
		if (t_rangeJ)
			t_range = json_integer_value(t_rangeJ);

		json_t* x_rangeJ = json_object_get(rootJ, "x_range");
		if (x_rangeJ)
			x_range = json_integer_value(x_rangeJ);

		json_t* externalJ = json_object_get(rootJ, "external");
		if (externalJ)
			external = json_boolean_value(externalJ);

		json_t* x_scaleJ = json_object_get(rootJ, "x_scale");
		if (x_scaleJ)
			x_scale = json_integer_value(x_scaleJ);

		json_t* y_divider_indexJ = json_object_get(rootJ, "y_divider_index");
		if (y_divider_indexJ)
			y_divider_index = json_integer_value(y_divider_indexJ);

		json_t* x_clock_source_internalJ = json_object_get(rootJ, "x_clock_source_internal");
		if (x_clock_source_internalJ)
			x_clock_source_internal = json_integer_value(x_clock_source_internalJ);
	}

	void process(const ProcessArgs& args) override {
		// Buttons
		if (tDejaVuTrigger.process(params[T_DEJA_VU_PARAM].getValue() <= 0.f)) {
			t_deja_vu = !t_deja_vu;
		}
		if (xDejaVuTrigger.process(params[X_DEJA_VU_PARAM].getValue() <= 0.f)) {
			x_deja_vu = !x_deja_vu;
		}
		if (tModeTrigger.process(params[T_MODE_PARAM].getValue() <= 0.f)) {
			t_mode = (t_mode + 1) % 3;
		}
		if (xModeTrigger.process(params[X_MODE_PARAM].getValue() <= 0.f)) {
			x_mode = (x_mode + 1) % 3;
		}
		if (tRangeTrigger.process(params[T_RANGE_PARAM].getValue() <= 0.f)) {
			t_range = (t_range + 1) % 3;
		}
		if (xRangeTrigger.process(params[X_RANGE_PARAM].getValue() <= 0.f)) {
			x_range = (x_range + 1) % 3;
		}
		if (externalTrigger.process(params[EXTERNAL_PARAM].getValue() <= 0.f)) {
			external = !external;
		}

		// Clocks
		bool t_gate = (inputs[T_CLOCK_INPUT].getVoltage() >= 1.7f);
		last_t_clock = stmlib::ExtractGateFlags(last_t_clock, t_gate);
		t_clocks[blockIndex] = last_t_clock;

		bool x_gate = (inputs[X_CLOCK_INPUT].getVoltage() >= 1.7f);
		last_xy_clock = stmlib::ExtractGateFlags(last_xy_clock, x_gate);
		xy_clocks[blockIndex] = last_xy_clock;

		// Process block
		if (++blockIndex >= BLOCK_SIZE) {
			blockIndex = 0;
			stepBlock();
		}

		// Lights and outputs

		lights[T_DEJA_VU_LIGHT].setBrightness(t_deja_vu);
		lights[X_DEJA_VU_LIGHT].setBrightness(x_deja_vu);

		lights[T_MODE_LIGHTS + 0].setBrightness(t_mode == 0 || t_mode == 1);
		lights[T_MODE_LIGHTS + 1].setBrightness(t_mode == 1 || t_mode == 2);

		lights[X_MODE_LIGHTS + 0].setBrightness(x_mode == 0 || x_mode == 1);
		lights[X_MODE_LIGHTS + 1].setBrightness(x_mode == 1 || x_mode == 2);

		lights[T_RANGE_LIGHTS + 0].setBrightness(t_range == 0 || t_range == 1);
		lights[T_RANGE_LIGHTS + 1].setBrightness(t_range == 1 || t_range == 2);

		lights[X_RANGE_LIGHTS + 0].setBrightness(x_range == 0 || x_range == 1);
		lights[X_RANGE_LIGHTS + 1].setBrightness(x_range == 1 || x_range == 2);

		lights[EXTERNAL_LIGHT].setBrightness(external);

		outputs[T1_OUTPUT].setVoltage(gates[blockIndex * 2 + 0] ? 10.f : 0.f);
		lights[T1_LIGHT].setSmoothBrightness(gates[blockIndex * 2 + 0], args.sampleTime);
		outputs[T2_OUTPUT].setVoltage((ramp_master[blockIndex] < 0.5f) ? 10.f : 0.f);
		lights[T2_LIGHT].setSmoothBrightness(ramp_master[blockIndex] < 0.5f, args.sampleTime);
		outputs[T3_OUTPUT].setVoltage(gates[blockIndex * 2 + 1] ? 10.f : 0.f);
		lights[T3_LIGHT].setSmoothBrightness(gates[blockIndex * 2 + 1], args.sampleTime);

		outputs[X1_OUTPUT].setVoltage(voltages[blockIndex * 4 + 0]);
		lights[X1_LIGHT].setSmoothBrightness(voltages[blockIndex * 4 + 0], args.sampleTime);
		outputs[X2_OUTPUT].setVoltage(voltages[blockIndex * 4 + 1]);
		lights[X2_LIGHT].setSmoothBrightness(voltages[blockIndex * 4 + 1], args.sampleTime);
		outputs[X3_OUTPUT].setVoltage(voltages[blockIndex * 4 + 2]);
		lights[X3_LIGHT].setSmoothBrightness(voltages[blockIndex * 4 + 2], args.sampleTime);
		outputs[Y_OUTPUT].setVoltage(voltages[blockIndex * 4 + 3]);
		lights[Y_LIGHT].setSmoothBrightness(voltages[blockIndex * 4 + 3], args.sampleTime);
	}

	void stepBlock() {
		// Ramps

		marbles::Ramps ramps;
		ramps.master = ramp_master;
		ramps.external = ramp_external;
		ramps.slave[0] = ramp_slave[0];
		ramps.slave[1] = ramp_slave[1];

		float deja_vu = clamp(params[DEJA_VU_PARAM].getValue() + inputs[DEJA_VU_INPUT].getVoltage() / 5.f, 0.f, 1.f);
		static const int loop_length[] = {
			1, 1, 1, 2, 2,
			2, 2, 2, 3, 3,
			3, 3, 4, 4, 4,
			4, 4, 5, 5, 6,
			6, 6, 7, 7, 8,
			8, 8, 10, 10, 12,
			12, 12, 14, 14, 16,
			16
		};
		float deja_vu_length_index = params[DEJA_VU_LENGTH_PARAM].getValue() * (LENGTHOF(loop_length) - 1);
		int deja_vu_length = loop_length[(int) roundf(deja_vu_length_index)];

		// Set up TGenerator

		bool t_external_clock = inputs[T_CLOCK_INPUT].isConnected();

		t_generator.set_model((marbles::TGeneratorModel) t_mode);
		t_generator.set_range((marbles::TGeneratorRange) t_range);
		float t_rate = 60.f * (params[T_RATE_PARAM].getValue() + inputs[T_RATE_INPUT].getVoltage() / 5.f);
		t_generator.set_rate(t_rate);
		float t_bias = clamp(params[T_BIAS_PARAM].getValue() + inputs[T_BIAS_INPUT].getVoltage() / 5.f, 0.f, 1.f);
		t_generator.set_bias(t_bias);
		float t_jitter = clamp(params[T_JITTER_PARAM].getValue() + inputs[T_JITTER_INPUT].getVoltage() / 5.f, 0.f, 1.f);
		t_generator.set_jitter(t_jitter);
		t_generator.set_deja_vu(t_deja_vu ? deja_vu : 0.f);
		t_generator.set_length(deja_vu_length);
		// TODO
		t_generator.set_pulse_width_mean(0.f);
		t_generator.set_pulse_width_std(0.f);

		t_generator.Process(t_external_clock, t_clocks, ramps, gates, BLOCK_SIZE);

		// Set up XYGenerator

		marbles::ClockSource x_clock_source = (marbles::ClockSource) x_clock_source_internal;
		if (inputs[X_CLOCK_INPUT].isConnected())
			x_clock_source = marbles::CLOCK_SOURCE_EXTERNAL;

		marbles::GroupSettings x;
		x.control_mode = (marbles::ControlMode) x_mode;
		x.voltage_range = (marbles::VoltageRange) x_range;
		// TODO Fix the scaling
		float note_cv = 0.5f * (params[X_SPREAD_PARAM].getValue() + inputs[X_SPREAD_INPUT].getVoltage() / 5.f);
		float u = note_filter.Process(0.5f * (note_cv + 1.f));
		x.register_mode = external;
		x.register_value = u;

		float x_spread = clamp(params[X_SPREAD_PARAM].getValue() + inputs[X_SPREAD_INPUT].getVoltage() / 5.f, 0.f, 1.f);
		x.spread = x_spread;
		float x_bias = clamp(params[X_BIAS_PARAM].getValue() + inputs[X_BIAS_INPUT].getVoltage() / 5.f, 0.f, 1.f);
		x.bias = x_bias;
		float x_steps = clamp(params[X_STEPS_PARAM].getValue() + inputs[X_STEPS_INPUT].getVoltage() / 5.f, 0.f, 1.f);
		x.steps = x_steps;
		x.deja_vu = x_deja_vu ? deja_vu : 0.f;
		x.length = deja_vu_length;
		x.ratio.p = 1;
		x.ratio.q = 1;
		x.scale_index = x_scale;

		marbles::GroupSettings y;
		y.control_mode = marbles::CONTROL_MODE_IDENTICAL;
		// TODO
		y.voltage_range = (marbles::VoltageRange) x_range;
		y.register_mode = false;
		y.register_value = 0.0f;
		// TODO
		y.spread = x_spread;
		y.bias = x_bias;
		y.steps = x_steps;
		y.deja_vu = 0.0f;
		y.length = 1;
		static const marbles::Ratio y_divider_ratios[] = {
			{ 1, 64 },
			{ 1, 48 },
			{ 1, 32 },
			{ 1, 24 },
			{ 1, 16 },
			{ 1, 12 },
			{ 1, 8 },
			{ 1, 6 },
			{ 1, 4 },
			{ 1, 3 },
			{ 1, 2 },
			{ 1, 1 },
		};
		y.ratio = y_divider_ratios[y_divider_index];
		y.scale_index = x_scale;

		xy_generator.Process(x_clock_source, x, y, xy_clocks, ramps, voltages, BLOCK_SIZE);
	}
};


template <typename BASE>
struct CKD6Light : BASE {
	CKD6Light() {
		this->box.size = Vec(22, 22);
	}
};


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

		addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
		addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
		addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
		addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));

		addParam(createParamCentered<CKD6>(mm2px(Vec(16.545, 17.794)), module, Marbles::T_DEJA_VU_PARAM));
		addParam(createParamCentered<CKD6>(mm2px(Vec(74.845, 17.794)), module, Marbles::X_DEJA_VU_PARAM));
		addParam(createParamCentered<Rogan2PSWhite>(mm2px(Vec(45.695, 22.244)), module, Marbles::DEJA_VU_PARAM));
		addParam(createParamCentered<Rogan3PSWhite>(mm2px(Vec(23.467, 35.264)), module, Marbles::T_RATE_PARAM));
		addParam(createParamCentered<Rogan3PSWhite>(mm2px(Vec(67.945, 35.243)), module, Marbles::X_SPREAD_PARAM));
		addParam(createParamCentered<TL1105>(mm2px(Vec(6.945, 38.794)), module, Marbles::T_MODE_PARAM));
		addParam(createParamCentered<TL1105>(mm2px(Vec(84.445, 38.793)), module, Marbles::X_MODE_PARAM));
		addParam(createParamCentered<Rogan2PSWhite>(mm2px(Vec(45.695, 51.144)), module, Marbles::DEJA_VU_LENGTH_PARAM));
		addParam(createParamCentered<Rogan2PSWhite>(mm2px(Vec(9.545, 58.394)), module, Marbles::T_BIAS_PARAM));
		addParam(createParamCentered<Rogan2PSWhite>(mm2px(Vec(81.844, 58.394)), module, Marbles::X_BIAS_PARAM));
		addParam(createParamCentered<TL1105>(mm2px(Vec(26.644, 59.694)), module, Marbles::T_RANGE_PARAM));
		addParam(createParamCentered<TL1105>(mm2px(Vec(64.744, 59.694)), module, Marbles::X_RANGE_PARAM));
		addParam(createParamCentered<TL1105>(mm2px(Vec(45.694, 67.294)), module, Marbles::EXTERNAL_PARAM));
		addParam(createParamCentered<Rogan2PSWhite>(mm2px(Vec(31.544, 73.694)), module, Marbles::T_JITTER_PARAM));
		addParam(createParamCentered<Rogan2PSWhite>(mm2px(Vec(59.845, 73.694)), module, Marbles::X_STEPS_PARAM));

		addInput(createInputCentered<PJ301MPort>(mm2px(Vec(9.545, 81.944)), module, Marbles::T_BIAS_INPUT));
		addInput(createInputCentered<PJ301MPort>(mm2px(Vec(81.844, 81.944)), module, Marbles::X_BIAS_INPUT));
		addInput(createInputCentered<PJ301MPort>(mm2px(Vec(9.545, 96.544)), module, Marbles::T_CLOCK_INPUT));
		addInput(createInputCentered<PJ301MPort>(mm2px(Vec(21.595, 96.544)), module, Marbles::T_RATE_INPUT));
		addInput(createInputCentered<PJ301MPort>(mm2px(Vec(33.644, 96.544)), module, Marbles::T_JITTER_INPUT));
		addInput(createInputCentered<PJ301MPort>(mm2px(Vec(45.695, 96.544)), module, Marbles::DEJA_VU_INPUT));
		addInput(createInputCentered<PJ301MPort>(mm2px(Vec(57.745, 96.544)), module, Marbles::X_STEPS_INPUT));
		addInput(createInputCentered<PJ301MPort>(mm2px(Vec(69.795, 96.544)), module, Marbles::X_SPREAD_INPUT));
		addInput(createInputCentered<PJ301MPort>(mm2px(Vec(81.844, 96.544)), module, Marbles::X_CLOCK_INPUT));

		addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(9.545, 111.144)), module, Marbles::T1_OUTPUT));
		addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(21.595, 111.144)), module, Marbles::T2_OUTPUT));
		addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(33.644, 111.144)), module, Marbles::T3_OUTPUT));
		addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(45.695, 111.144)), module, Marbles::Y_OUTPUT));
		addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(57.745, 111.144)), module, Marbles::X1_OUTPUT));
		addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(69.795, 111.144)), module, Marbles::X2_OUTPUT));
		addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(81.844, 111.144)), module, Marbles::X3_OUTPUT));

		addChild(createLightCentered<CKD6Light<GreenLight>>(mm2px(Vec(16.545, 17.794)), module, Marbles::T_DEJA_VU_LIGHT));
		addChild(createLightCentered<CKD6Light<GreenLight>>(mm2px(Vec(74.845, 17.794)), module, Marbles::X_DEJA_VU_LIGHT));
		addChild(createLightCentered<MediumLight<GreenRedLight>>(mm2px(Vec(6.944, 29.894)), module, Marbles::T_MODE_LIGHTS));
		addChild(createLightCentered<MediumLight<GreenRedLight>>(mm2px(Vec(84.444, 29.894)), module, Marbles::X_MODE_LIGHTS));
		addChild(createLightCentered<MediumLight<GreenRedLight>>(mm2px(Vec(26.644, 53.994)), module, Marbles::T_RANGE_LIGHTS));
		addChild(createLightCentered<MediumLight<GreenRedLight>>(mm2px(Vec(64.744, 53.994)), module, Marbles::X_RANGE_LIGHTS));
		addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(45.695, 76.194)), module, Marbles::EXTERNAL_LIGHT));
		addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(6.044, 104.794)), module, Marbles::T1_LIGHT));
		addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(18.094, 104.794)), module, Marbles::T2_LIGHT));
		addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(30.145, 104.794)), module, Marbles::T3_LIGHT));
		addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(42.194, 104.794)), module, Marbles::Y_LIGHT));
		addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(54.244, 104.794)), module, Marbles::X1_LIGHT));
		addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(66.294, 104.794)), module, Marbles::X2_LIGHT));
		addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(78.344, 104.794)), module, Marbles::X3_LIGHT));
	}

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

		struct ScaleItem : MenuItem {
			Marbles* module;
			int scale;
			void onAction(const event::Action& e) override {
				module->x_scale = scale;
			}
		};

		menu->addChild(new MenuSeparator);
		menu->addChild(createMenuLabel("Scales"));
		const std::string scaleLabels[] = {
			"Major",
			"Minor",
			"Pentatonic",
			"Pelog",
			"Raag Bhairav That",
			"Raag Shri",
		};
		for (int i = 0; i < (int) LENGTHOF(scaleLabels); i++) {
			ScaleItem* item = createMenuItem<ScaleItem>(scaleLabels[i], CHECKMARK(module->x_scale == i));
			item->module = module;
			item->scale = i;
			menu->addChild(item);
		}

		struct XClockSourceInternal : MenuItem {
			Marbles* module;
			int source;
			void onAction(const event::Action& e) override {
				module->x_clock_source_internal = source;
			}
		};

		menu->addChild(new MenuSeparator);
		menu->addChild(createMenuLabel("Internal X clock source"));
		const std::string sourceLabels[] = {
			"T₁ → X₁, T₂ → X₂, T₃ → X₃",
			"T₁ → X₁, X₂, X₃",
			"T₂ → X₁, X₂, X₃",
			"T₃ → X₁, X₂, X₃",
		};
		for (int i = 0; i < (int) LENGTHOF(sourceLabels); i++) {
			XClockSourceInternal* item = createMenuItem<XClockSourceInternal>(sourceLabels[i], CHECKMARK(module->x_clock_source_internal == i));
			item->module = module;
			item->source = i;
			menu->addChild(item);
		}

		struct YDividerIndexItem : MenuItem {
			Marbles* module;
			int index;
			void onAction(const event::Action& e) override {
				module->y_divider_index = index;
			}
		};

		struct YDividerItem : MenuItem {
			Marbles* module;
			Menu* createChildMenu() override {
				Menu* menu = new Menu();
				const std::string yDividerRatioLabels[] = {
					"1/64",
					"1/48",
					"1/32",
					"1/24",
					"1/16",
					"1/12",
					"1/8",
					"1/6",
					"1/4",
					"1/3",
					"1/2",
					"1",
				};
				for (int i = 0; i < (int) LENGTHOF(yDividerRatioLabels); i++) {
					YDividerIndexItem* item = createMenuItem<YDividerIndexItem>(yDividerRatioLabels[i], CHECKMARK(module->y_divider_index == i));
					item->module = module;
					item->index = i;
					menu->addChild(item);
				}
				return menu;
			}
		};

		menu->addChild(new MenuSeparator);
		YDividerItem* yDividerItem = createMenuItem<YDividerItem>("Y divider ratio");
		yDividerItem->module = module;
		menu->addChild(yDividerItem);
	}
};


Model* modelMarbles = createModel<Marbles, MarblesWidget>("Marbles");
