
#include "Offset.hpp"

#define OFFSET_FIRST "offset_first"

json_t* Offset::saveToJson(json_t* root) {
	root = DisableOutputLimitModule::saveToJson(root);
	json_object_set_new(root, OFFSET_FIRST, json_boolean(_offsetFirst));
	return root;
}

void Offset::loadFromJson(json_t* root) {
	DisableOutputLimitModule::loadFromJson(root);
	json_t* of = json_object_get(root, OFFSET_FIRST);
	if (of) {
		_offsetFirst = json_boolean_value(of);
	}
}

int Offset::channels() {
	return inputs[IN_INPUT].getChannels();
}

void Offset::processChannel(const ProcessArgs& args, int c) {
	float offset = knobValue(params[OFFSET_PARAM], inputs[OFFSET_INPUT], c);
	offset *= 10.0f;

	float scale = knobValue(params[SCALE_PARAM], inputs[SCALE_INPUT], c);
	scale = scale < 0.0f ? -pow(scale, 2.0f) : pow(scale, 2.0f);
	scale *= 10.0;

	float out = inputs[IN_INPUT].getVoltage(c);
	if (_offsetFirst) {
		out += offset;
		out *= scale;
	}
	else {
		out *= scale;
		out += offset;
	}
	if (!_disableOutputLimit) {
		out = clamp(out, -12.0f, 12.0f);
	}
	outputs[OUT_OUTPUT].setChannels(_channels);
	outputs[OUT_OUTPUT].setVoltage(out, c);
}

float Offset::knobValue(Param& knob, Input& cv, int c) const {
	float v = knob.getValue();
	if (cv.isConnected()) {
		v *= clamp(cv.getPolyVoltage(c) / 10.0f, -1.0f, 1.0f);
	}
	return v;
}

struct OffsetWidget : DisableOutputLimitModuleWidget {
	static constexpr int hp = 3;

	OffsetWidget(Offset* module) {
		setModule(module);
		box.size = Vec(RACK_GRID_WIDTH * hp, RACK_GRID_HEIGHT);
		setPanel(box.size, "Offset");
		createScrews();

		// generated by svg_widgets.rb
		auto offsetParamPosition = Vec(8.0, 40.0);
		auto scaleParamPosition = Vec(8.0, 152.0);

		auto offsetInputPosition = Vec(10.5, 81.0);
		auto scaleInputPosition = Vec(10.5, 193.0);
		auto inInputPosition = Vec(10.5, 243.0);

		auto outOutputPosition = Vec(10.5, 281.0);
		// end generated by svg_widgets.rb

		addParam(createParam<Knob29>(offsetParamPosition, module, Offset::OFFSET_PARAM));
		addParam(createParam<Knob29>(scaleParamPosition, module, Offset::SCALE_PARAM));

		addInput(createInput<Port24>(offsetInputPosition, module, Offset::OFFSET_INPUT));
		addInput(createInput<Port24>(scaleInputPosition, module, Offset::SCALE_INPUT));
		addInput(createInput<Port24>(inInputPosition, module, Offset::IN_INPUT));

		addOutput(createOutput<Port24>(outOutputPosition, module, Offset::OUT_OUTPUT));
	}

	void contextMenu(Menu* menu) override {
		DisableOutputLimitModuleWidget::contextMenu(menu);

		auto m = dynamic_cast<Offset*>(module);
		assert(m);

		OptionsMenuItem* ooo = new OptionsMenuItem("Order of operations");
		ooo->addItem(OptionMenuItem("Scale, then offset", [m]() { return !m->_offsetFirst; }, [m]() { m->_offsetFirst = false; }));
		ooo->addItem(OptionMenuItem("Offset, then scale", [m]() { return m->_offsetFirst; }, [m]() { m->_offsetFirst = true; }));
		OptionsMenuItem::addToMenu(ooo, menu);
	}
};

Model* modelOffset = bogaudio::createModel<Offset, OffsetWidget>("Bogaudio-Offset", "OFFSET", "CV offset and scaler", "Attenuator", "Polyphonic");
