
#include "Lgsw.hpp"

void Lgsw::resetChannel(int c) {
	_buttonTriggers[c].reset();
	_aTriggers[c].reset();
	_bTriggers[c].reset();
	_lastLogicTrue[c] = false;
}

void Lgsw::reset() {
	for (int i = 0; i < _channels; ++i) {
		resetChannel(i);
		_latchedHigh[i] = false;
	}
}

int Lgsw::channels() {
	return inputs[GATE_A_INPUT].getChannels();
}

void Lgsw::channelsChanged(int before, int after) {
	for (; before < after; ++before) {
		resetChannel(before);
	}
}

void Lgsw::modulate() {
	_latch = params[LATCH_PARAM].getValue() > 0.5f;
}

void Lgsw::processAlways(const ProcessArgs& args) {
	_highLightSum = 0;
	_lowLightSum = 0;
}

void Lgsw::processChannel(const ProcessArgs& args, int c) {
	bool buttonTriggered = _buttonTriggers[c].process(10.0f*params[GATE_PARAM].getValue());
	bool aTriggered = _aTriggers[c].process(inputs[GATE_A_INPUT].getVoltage(c));
	bool bTriggered = _bTriggers[c].process(inputs[GATE_B_INPUT].getVoltage(c));

	if (inputs[LOGIC_MODE_INPUT].isConnected()) {
		float cv = clamp(inputs[LOGIC_MODE_INPUT].getVoltage(c), 0.0f, 4.5f);
		_logic = (Logic)(int)cv;
	}
	else {
		_logic = (Logic)clamp((int)params[LOGIC_MODE_PARAM].getValue(), 0, 4);
	}

	bool triggered = buttonTriggered;
	bool logicTrue = false;
	if (inputs[GATE_A_INPUT].isConnected()) {
		if (inputs[GATE_B_INPUT].isConnected()) {
			switch (_logic) {
				case OR_LOGIC: {
					logicTrue = _aTriggers[c].isHigh() || _bTriggers[c].isHigh();
					break;
				}
				case AND_LOGIC: {
					logicTrue = _aTriggers[c].isHigh() && _bTriggers[c].isHigh();
					break;
				}
				case XOR_LOGIC: {
					logicTrue = _aTriggers[c].isHigh() != _bTriggers[c].isHigh();
					break;
				}
				case NOR_LOGIC: {
					logicTrue = !(_aTriggers[c].isHigh() || _bTriggers[c].isHigh());
					break;
				}
				case NAND_LOGIC: {
					logicTrue = !(_aTriggers[c].isHigh() && _bTriggers[c].isHigh());
					break;
				}
			}

			triggered = triggered || (logicTrue && !_lastLogicTrue[c]);
			_lastLogicTrue[c] = logicTrue;
		}
		else {
			triggered = triggered || aTriggered;
			logicTrue = _aTriggers[c].isHigh();
			_lastLogicTrue[c] = false;
		}
	}
	else if (inputs[GATE_B_INPUT].isConnected()) {
		triggered = triggered || bTriggered;
		logicTrue = _bTriggers[c].isHigh();
		_lastLogicTrue[c] = false;
	}

	if (_latch) {
		if (triggered) {
			_latchedHigh[c] = !_latchedHigh[c];
		}
	}
	else {
		_latchedHigh[c] = false;
	}

	if (_latchedHigh[c] || (!_latch && (logicTrue || _buttonTriggers[c].isHigh()))) {
		++_highLightSum;

		if (_channels == 1) {
			outputs[OUT_OUTPUT].setChannels(inputs[HIGH_INPUT].getChannels());
			outputs[OUT_OUTPUT].writeVoltages(inputs[HIGH_INPUT].getVoltages());
		}
		else {
			outputs[OUT_OUTPUT].setChannels(_channels);
			outputs[OUT_OUTPUT].setVoltage(inputs[HIGH_INPUT].getPolyVoltage(c), c);
		}
	}
	else {
		++_lowLightSum;

		if (_channels == 1) {
			outputs[OUT_OUTPUT].setChannels(inputs[LOW_INPUT].getChannels());
			outputs[OUT_OUTPUT].writeVoltages(inputs[LOW_INPUT].getVoltages());
		}
		else {
			outputs[OUT_OUTPUT].setChannels(_channels);
			outputs[OUT_OUTPUT].setVoltage(inputs[LOW_INPUT].getPolyVoltage(c), c);
		}
	}
}

void Lgsw::postProcessAlways(const ProcessArgs& args) {
	lights[HIGH_LIGHT].value = _highLightSum * _inverseChannels;
	lights[LOW_LIGHT].value = _lowLightSum * _inverseChannels;

	lights[LOGIC_OR_LIGHT].value = 0.0f;
	lights[LOGIC_AND_LIGHT].value = 0.0f;
	lights[LOGIC_XOR_LIGHT].value = 0.0f;
	lights[LOGIC_NOR_LIGHT].value = 0.0f;
	lights[LOGIC_NAND_LIGHT].value = 0.0f;
	switch (_logic) {
		case OR_LOGIC: {
			lights[LOGIC_OR_LIGHT].value = 1.0f;
			break;
		}
		case AND_LOGIC: {
			lights[LOGIC_AND_LIGHT].value = 1.0f;
			break;
		}
		case XOR_LOGIC: {
			lights[LOGIC_XOR_LIGHT].value = 1.0f;
			break;
		}
		case NOR_LOGIC: {
			lights[LOGIC_NOR_LIGHT].value = 1.0f;
			break;
		}
		case NAND_LOGIC: {
			lights[LOGIC_NAND_LIGHT].value = 1.0f;
			break;
		}
	}
}

struct LgswWidget : SaveLatchToPatchModuleWidget {
	static constexpr int hp = 3;

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

		// generated by svg_widgets.rb
		auto gateParamPosition = Vec(13.5, 22.0);
		auto latchParamPosition = Vec(31.5, 105.0);
		auto logicModeParamPosition = Vec(16.0, 178.0);

		auto gateAInputPosition = Vec(10.5, 44.0);
		auto gateBInputPosition = Vec(10.5, 72.0);
		auto logicModeInputPosition = Vec(10.5, 193.0);
		auto high1InputPosition = Vec(10.5, 230.0);
		auto low1InputPosition = Vec(10.5, 266.0);

		auto out1OutputPosition = Vec(10.5, 304.0);

		auto logicOrLightPosition = Vec(7.5, 121.0);
		auto logicAndLightPosition = Vec(7.5, 133.0);
		auto logicXorLightPosition = Vec(7.5, 145.0);
		auto logicNorLightPosition = Vec(7.5, 157.0);
		auto logicNandLightPosition = Vec(7.5, 169.0);
		auto high1LightPosition = Vec(7.5, 256.3);
		auto low1LightPosition = Vec(7.5, 292.3);
		// end generated by svg_widgets.rb

		addParam(createParam<Button18>(gateParamPosition, module, Lgsw::GATE_PARAM));
		addParam(createParam<IndicatorButtonGreen9>(latchParamPosition, module, Lgsw::LATCH_PARAM));
		addParam(createParam<StatefulButton9>(logicModeParamPosition, module, Lgsw::LOGIC_MODE_PARAM));

		addInput(createInput<Port24>(gateAInputPosition, module, Lgsw::GATE_A_INPUT));
		addInput(createInput<Port24>(gateBInputPosition, module, Lgsw::GATE_B_INPUT));
		addInput(createInput<Port24>(logicModeInputPosition, module, Lgsw::LOGIC_MODE_INPUT));
		addInput(createInput<Port24>(high1InputPosition, module, Lgsw::HIGH_INPUT));
		addInput(createInput<Port24>(low1InputPosition, module, Lgsw::LOW_INPUT));

		addOutput(createOutput<Port24>(out1OutputPosition, module, Lgsw::OUT_OUTPUT));

		addChild(createLight<BGSmallLight<GreenLight>>(logicOrLightPosition, module, Lgsw::LOGIC_OR_LIGHT));
		addChild(createLight<BGSmallLight<GreenLight>>(logicAndLightPosition, module, Lgsw::LOGIC_AND_LIGHT));
		addChild(createLight<BGSmallLight<GreenLight>>(logicXorLightPosition, module, Lgsw::LOGIC_XOR_LIGHT));
		addChild(createLight<BGSmallLight<GreenLight>>(logicNorLightPosition, module, Lgsw::LOGIC_NOR_LIGHT));
		addChild(createLight<BGSmallLight<GreenLight>>(logicNandLightPosition, module, Lgsw::LOGIC_NAND_LIGHT));
		addChild(createLight<BGSmallLight<GreenLight>>(high1LightPosition, module, Lgsw::HIGH_LIGHT));
		addChild(createLight<BGSmallLight<GreenLight>>(low1LightPosition, module, Lgsw::LOW_LIGHT));
	}
};

Model* modelLgsw = createModel<Lgsw, LgswWidget>("Bogaudio-Lgsw", "LGSW", "2-way signal router with logic", "Switch", "Logic", "Polyphonic");
