
#include "Assign.hpp"

void Assign::reset() {
	_resetTrigger.reset();
	_nextAssign = 0;
	for (int c = 0; c < maxChannels; ++c) {
		_gateTrigger[c].reset();
		_gateHigh[c] = false;
		_pitchInAssignment[c] = -1;
		_gateInAssignment[c] = -1;
		_pitchOutAssignment[c] = -1;
		_gateOutAssignment[c] = -1;
		_lastPitchOut[c] = 0.0f;
		_assignedAtStep[c] = 0;
	}
}

int Assign::channels() {
	return inputs[GATE_INPUT].getChannels();
}

void Assign::addChannel(int c) {
	_gateTrigger[c].reset();
}

void Assign::removeChannel(int c) {
	_gateHigh[c] = false;
	_assignedAtStep[c] = 0;
	if (_pitchInAssignment[c] >= 0) {
		_pitchOutAssignment[_pitchInAssignment[c]] = -1;
		_lastPitchOut[_pitchInAssignment[c]] = 0.0f;
		_pitchInAssignment[c] = -1;
	}
	if (_gateInAssignment[c] >= 0) {
		_gateOutAssignment[_gateInAssignment[c]] = -1;
		_gateInAssignment[c] = -1;
	}
	if (_nextAssign == c) {
		_nextAssign = 0;
	}
}

void Assign::modulate() {
	_channelsOut = clamp((int)params[CHANNELS_PARAM].getValue(), 1, 16);
}

void Assign::postProcess(const ProcessArgs& args) {
	++_step;
	if (_resetTrigger.process(inputs[RESET_INPUT].getVoltage())) {
		_nextAssign = 0;
	}

	for (int c = 0; c < _channels; ++c) {
		if (_gateTrigger[c].process(inputs[GATE_INPUT].getPolyVoltage(c))) {
			_gateHigh[c] = true;

			if (_gateOutAssignment[_nextAssign] >= 0) {
				int a = _nextAssign + 1;
				int n = a + _channelsOut;
				unsigned long minStep = -1;
				int minI = _nextAssign;
				for (; a < n; ++a) {
					int i = a % _channelsOut;
					if (_gateOutAssignment[i] < 0) {
						_nextAssign = i;
						goto CHANNEL_SELECTED;
					}
					if (_assignedAtStep[i] < minStep) {
						minStep = _assignedAtStep[i];
						minI = i;
					}
				}
				_nextAssign = minI;
			}

		CHANNEL_SELECTED:
			_pitchInAssignment[c] = _nextAssign;
			_gateInAssignment[c] = _nextAssign;
			_pitchOutAssignment[_nextAssign] = c;
			_gateOutAssignment[_nextAssign] = c;
			_assignedAtStep[c] = _step;
			_nextAssign = (_nextAssign + 1) % _channelsOut;
		}
		else if (!_gateTrigger[c].isHigh() && _gateHigh[c]) {
			_gateHigh[c] = false;
			_pitchOutAssignment[_pitchInAssignment[c]] = -1;
			_pitchInAssignment[c] = -1;
			_gateOutAssignment[_gateInAssignment[c]] = -1;
			_gateInAssignment[c] = -1;
		}
	}

	outputs[PITCH_OUTPUT].setChannels(_channelsOut);
	outputs[GATE_OUTPUT].setChannels(_channelsOut);
	for (int c = 0; c < _channelsOut; ++c) {
		float pitch = _lastPitchOut[c];
		if (_pitchOutAssignment[c] >= 0) {
			_lastPitchOut[c] = pitch = inputs[PITCH_INPUT].getPolyVoltage(_pitchOutAssignment[c]);
		}
		outputs[PITCH_OUTPUT].setVoltage(pitch, c);

		float gate = _gateOutAssignment[c] >= 0 ? inputs[GATE_INPUT].getPolyVoltage(_gateOutAssignment[c]) : 0.0f;
		outputs[GATE_OUTPUT].setVoltage(gate, c);
	}
}

struct AssignWidget : BGModuleWidget {
	static constexpr int hp = 3;

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

		// generated by svg_widgets.rb
		auto channelsParamPosition = Vec(9.5, 34.0);

		auto pitchInputPosition = Vec(10.5, 78.0);
		auto gateInputPosition = Vec(10.5, 113.0);
		auto resetInputPosition = Vec(10.5, 148.0);

		auto pitchOutputPosition = Vec(10.5, 186.0);
		auto gateOutputPosition = Vec(10.5, 221.0);
		// end generated by svg_widgets.rb

		addParam(createParam<Knob26>(channelsParamPosition, module, Assign::CHANNELS_PARAM));

		addInput(createInput<Port24>(pitchInputPosition, module, Assign::PITCH_INPUT));
		addInput(createInput<Port24>(gateInputPosition, module, Assign::GATE_INPUT));
		addInput(createInput<Port24>(resetInputPosition, module, Assign::RESET_INPUT));

		addOutput(createOutput<Port24>(pitchOutputPosition, module, Assign::PITCH_OUTPUT));
		addOutput(createOutput<Port24>(gateOutputPosition, module, Assign::GATE_OUTPUT));
	}
};

Model* modelAssign = createModel<Assign, AssignWidget>("Bogaudio-Assign", "ASSIGN", "Polyphonic voice (re)assigner", "Polyphonic");
