#include "plugin.hpp"


struct Octave : Module {
	enum ParamIds {
		OCTAVE_PARAM,
		NUM_PARAMS
	};
	enum InputIds {
		PITCH_INPUT,
		OCTAVE_INPUT,
		NUM_INPUTS
	};
	enum OutputIds {
		PITCH_OUTPUT,
		NUM_OUTPUTS
	};
	enum LightIds {
		NUM_LIGHTS
	};

	Octave() {
		config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
		configParam(OCTAVE_PARAM, -4.f, 4.f, 0.f, "Octave shift");
	}

	void process(const ProcessArgs& args) override {
		int channels = std::max(inputs[PITCH_INPUT].getChannels(), 1);
		float octaveParam = params[OCTAVE_PARAM].getValue();

		for (int c = 0; c < channels; c++) {
			float octave = octaveParam + inputs[OCTAVE_INPUT].getPolyVoltage(c);
			octave = std::round(octave);
			float pitch = inputs[PITCH_INPUT].getVoltage(c);
			pitch += octave;
			outputs[PITCH_OUTPUT].setVoltage(pitch, c);
		}
		outputs[PITCH_OUTPUT].setChannels(channels);
	}

	void dataFromJson(json_t* rootJ) override {
		// In Fundamental 1.1.1 and earlier, the octave param was internal data.
		json_t* octaveJ = json_object_get(rootJ, "octave");
		if (octaveJ) {
			params[OCTAVE_PARAM].setValue(json_integer_value(octaveJ));
		}
	}
};


struct OctaveButton : Widget {
	int octave;

	void draw(const DrawArgs& args) override {
		Vec c = box.size.div(2);

		int activeOctave = 0;
		ParamWidget* paramWidget = getAncestorOfType<ParamWidget>();
		if (paramWidget && paramWidget->paramQuantity) {
			activeOctave = std::round(paramWidget->paramQuantity->getValue());
		}

		if (activeOctave == octave) {
			// Enabled
			nvgBeginPath(args.vg);
			nvgCircle(args.vg, c.x, c.y, mm2px(4.0 / 2));
			if (octave < 0)
				nvgFillColor(args.vg, color::RED);
			else if (octave > 0)
				nvgFillColor(args.vg, color::GREEN);
			else
				nvgFillColor(args.vg, color::alpha(color::WHITE, 0.33));
			nvgFill(args.vg);
		}
		else {
			// Disabled
			nvgBeginPath(args.vg);
			nvgCircle(args.vg, c.x, c.y, mm2px(4.0 / 2));
			nvgFillColor(args.vg, color::alpha(color::WHITE, 0.33));
			nvgFill(args.vg);

			nvgBeginPath(args.vg);
			nvgCircle(args.vg, c.x, c.y, mm2px(3.0 / 2));
			nvgFillColor(args.vg, color::BLACK);
			nvgFill(args.vg);

			if (octave == 0) {
				nvgBeginPath(args.vg);
				nvgCircle(args.vg, c.x, c.y, mm2px(1.0 / 2));
				nvgFillColor(args.vg, color::alpha(color::WHITE, 0.33));
				nvgFill(args.vg);
			}
		}
	}

	void onDragHover(const event::DragHover& e) override {
		if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
			e.consume(this);
		}
		Widget::onDragHover(e);
	}

	void onDragEnter(const event::DragEnter& e) override;
};


struct OctaveParam : ParamWidget {
	OctaveParam() {
		box.size = mm2px(Vec(15.24, 63.0));
		const int octaves = 9;
		const float margin = mm2px(2.0);
		float height = box.size.y - 2 * margin;
		for (int i = 0; i < octaves; i++) {
			OctaveButton* octaveButton = new OctaveButton();
			octaveButton->box.pos = Vec(0, height / octaves * i + margin);
			octaveButton->box.size = Vec(box.size.x, height / octaves);
			octaveButton->octave = 4 - i;
			addChild(octaveButton);
		}
	}

	void draw(const DrawArgs& args) override {
		// Background
		nvgBeginPath(args.vg);
		nvgRect(args.vg, 0, 0, box.size.x, box.size.y);
		nvgFillColor(args.vg, nvgRGB(0, 0, 0));
		nvgFill(args.vg);

		ParamWidget::draw(args);
	}
};


inline void OctaveButton::onDragEnter(const event::DragEnter& e) {
	if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
		OctaveParam* origin = dynamic_cast<OctaveParam*>(e.origin);
		if (origin) {
			ParamWidget* paramWidget = getAncestorOfType<ParamWidget>();
			if (paramWidget && paramWidget->paramQuantity) {
				paramWidget->paramQuantity->setValue(octave);
			}
		}
	}
	Widget::onDragEnter(e);
}



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

		addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
		addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));

		addInput(createInputCentered<PJ301MPort>(mm2px(Vec(7.62, 82.753)), module, Octave::OCTAVE_INPUT));
		addInput(createInputCentered<PJ301MPort>(mm2px(Vec(7.62, 97.253)), module, Octave::PITCH_INPUT));

		addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(7.62, 112.253)), module, Octave::PITCH_OUTPUT));

		addParam(createParam<OctaveParam>(mm2px(Vec(0.0, 12.817)), module, Octave::OCTAVE_PARAM));
	}
};


Model* modelOctave = createModel<Octave, OctaveWidget>("Octave");
