
#include "DGate.hpp"

void DGate::Engine::reset() {
	trigger.reset();
	triggerOuptutPulseGen.process(10.0);
	stage = STOPPED_STAGE;
	stageProgress = 0.0;
	delayLight = 0.0;
	gateLight = 0.0;
}

void DGate::reset() {
	for (int c = 0; c < _channels; ++c) {
		_engines[c]->reset();
	}
}

int DGate::channels() {
	return inputs[TRIGGER_INPUT].getChannels();
}

void DGate::addChannel(int c) {
	_engines[c] = new Engine();
	_engines[c]->reset();
}

void DGate::removeChannel(int c) {
	delete _engines[c];
	_engines[c] = NULL;
}

void DGate::processChannel(const ProcessArgs& args, int c) {
	Engine& e = *_engines[c];

	float envelope = 0.0;
	bool complete = false;
	if (
		e.trigger.process(params[TRIGGER_PARAM].getValue() + inputs[TRIGGER_INPUT].getPolyVoltage(c)) ||
		(e.firstStep && _triggerOnLoad && _shouldTriggerOnLoad && params[LOOP_PARAM].getValue() <= 0.0)
	) {
		e.stage = DELAY_STAGE;
		e.stageProgress = 0.0;
	}
	else {
		switch (e.stage) {
			case STOPPED_STAGE: {
				break;
			}
			case DELAY_STAGE: {
				if (stepStage(c, params[DELAY_PARAM])) {
					e.stage = GATE_STAGE;
					e.stageProgress = 0.0;
				}
				break;
			}
			case GATE_STAGE: {
				if (stepStage(c, params[GATE_PARAM])) {
					complete = true;
					if (params[LOOP_PARAM].getValue() <= 0.0 || e.trigger.isHigh()) {
						e.stage = DELAY_STAGE;
						e.stageProgress = 0.0;
					}
					else {
						e.stage = STOPPED_STAGE;
					}
				}
				else {
					envelope = 1.0;
				}
				break;
			}
		}
	}

	outputs[GATE_OUTPUT].setChannels(_channels);
	outputs[GATE_OUTPUT].setVoltage(envelope * 10.0, c);
	if (complete) {
		e.triggerOuptutPulseGen.trigger(0.001);
	}
	outputs[END_OUTPUT].setChannels(_channels);
	outputs[END_OUTPUT].setVoltage(e.triggerOuptutPulseGen.process(APP->engine->getSampleTime()) ? 5.0 : 0.0, c);

	e.delayLight = e.stage == DELAY_STAGE;
	e.gateLight  = e.stage == GATE_STAGE;

	e.firstStep = false;
}

void DGate::postProcess(const ProcessArgs& args) {
	float delaySum = 0.0f;
	float gateSum = 0.0f;
	for (int c = 0; c < _channels; ++c) {
		delaySum += _engines[c]->delayLight;
		gateSum += _engines[c]->gateLight;
	}
	lights[DELAY_LIGHT].value = delaySum * _inverseChannels;
	lights[GATE_LIGHT].value = gateSum * _inverseChannels;
}

bool DGate::stepStage(int c, Param& knob) {
	float t = knob.getValue();
	t = pow(t, 2);
	t *= 10.0;
	_engines[c]->stageProgress += APP->engine->getSampleTime();
	return _engines[c]->stageProgress > t;
}

bool DGate::shouldTriggerOnNextLoad() {
	for (int c = 0; c < _channels; ++c) {
		if (_engines[c] && _engines[c]->stage != STOPPED_STAGE) {
			return true;
		}
	}
	return false;
};

struct DGateWidget : TriggerOnLoadModuleWidget {
	static constexpr int hp = 3;

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

		// generated by svg_widgets.rb
		auto delayParamPosition = Vec(8.0, 33.0);
		auto gateParamPosition = Vec(8.0, 92.0);
		auto loopParamPosition = Vec(15.0, 144.5);
		auto triggerParamPosition = Vec(13.5, 191.0);

		auto triggerInputPosition = Vec(10.5, 213.0);

		auto gateOutputPosition = Vec(10.5, 252.0);
		auto endOutputPosition = Vec(10.5, 287.0);

		auto delayLightPosition = Vec(20.8, 65.0);
		auto gateLightPosition = Vec(20.8, 124.0);
		// end generated by svg_widgets.rb

		addParam(createParam<Knob29>(delayParamPosition, module, DGate::DELAY_PARAM));
		addParam(createParam<Knob29>(gateParamPosition, module, DGate::GATE_PARAM));
		addParam(createParam<SliderSwitch2State14>(loopParamPosition, module, DGate::LOOP_PARAM));
		addParam(createParam<Button18>(triggerParamPosition, module, DGate::TRIGGER_PARAM));

		addInput(createInput<Port24>(triggerInputPosition, module, DGate::TRIGGER_INPUT));

		addOutput(createOutput<Port24>(gateOutputPosition, module, DGate::GATE_OUTPUT));
		addOutput(createOutput<Port24>(endOutputPosition, module, DGate::END_OUTPUT));

		addChild(createLight<BGTinyLight<GreenLight>>(delayLightPosition, module, DGate::DELAY_LIGHT));
		addChild(createLight<BGTinyLight<GreenLight>>(gateLightPosition, module, DGate::GATE_LIGHT));
	}
};

Model* modelDGate = bogaudio::createModel<DGate, DGateWidget>("Bogaudio-DGate", "DGATE", "Trigger-to-gate converter with delay", "Polyphonic");
