#pragma once
#include <common.hpp>
#include <engine/Light.hpp>


namespace rack {
namespace engine {


/** This is inspired by the number of MIDI channels. */
static const int PORT_MAX_CHANNELS = 16;


struct Port {
	/** Voltage of the port. */
	union {
		/** Unstable API. Use getVoltage() and setVoltage() instead. */
		float voltages[PORT_MAX_CHANNELS] = {};
		/** DEPRECATED. Unstable API. Use getVoltage() and setVoltage() instead. */
		float value;
	};
	union {
		/** Number of polyphonic channels.
		DEPRECATED. Unstable API. Use set/getChannels() instead.
		May be 0 to PORT_MAX_CHANNELS.
		0 channels means disconnected.
		*/
		uint8_t channels = 0;
		/** DEPRECATED. Unstable API. Use isConnected() instead. */
		uint8_t active;
	};
	/** For rendering plug lights on cables.
	Green for positive, red for negative, and blue for polyphonic.
	*/
	Light plugLights[3];

	enum Type {
		INPUT,
		OUTPUT,
	};

	/** Sets the voltage of the given channel. */
	void setVoltage(float voltage, uint8_t channel = 0) {
		voltages[channel] = voltage;
	}

	/** Returns the voltage of the given channel.
	Because of proper bookkeeping, all channels higher than the input port's number of channels should be 0V.
	*/
	float getVoltage(uint8_t channel = 0) {
		return voltages[channel];
	}

	/** Returns the given channel's voltage if the port is polyphonic, otherwise returns the first voltage (channel 0). */
	float getPolyVoltage(uint8_t channel) {
		return isMonophonic() ? getVoltage(0) : getVoltage(channel);
	}

	/** Returns the voltage if a cable is connected, otherwise returns the given normal voltage. */
	float getNormalVoltage(float normalVoltage, uint8_t channel = 0) {
		return isConnected() ? getVoltage(channel) : normalVoltage;
	}

	float getNormalPolyVoltage(float normalVoltage, uint8_t channel) {
		return isConnected() ? getPolyVoltage(channel) : normalVoltage;
	}

	/** Returns a pointer to the array of voltages beginning with firstChannel.
	The pointer can be used for reading and writing.
	*/
	float* getVoltages(uint8_t firstChannel = 0) {
		return &voltages[firstChannel];
	}

	/** Copies the port's voltages to an array of size at least `channels`. */
	void readVoltages(float* v) {
		for (uint8_t c = 0; c < channels; c++) {
			v[c] = voltages[c];
		}
	}

	/** Copies an array of size at least `channels` to the port's voltages.
	Remember to set the number of channels *before* calling this method.
	*/
	void writeVoltages(const float* v) {
		for (uint8_t c = 0; c < channels; c++) {
			voltages[c] = v[c];
		}
	}

	/** Sets all voltages to 0. */
	void clearVoltages() {
		for (uint8_t c = 0; c < channels; c++) {
			voltages[c] = 0.f;
		}
	}

	/** Returns the sum of all voltages. */
	float getVoltageSum() {
		float sum = 0.f;
		for (uint8_t c = 0; c < channels; c++) {
			sum += voltages[c];
		}
		return sum;
	}

	/** Returns the root-mean-square of all voltages.
	Uses sqrt() which is slow, so use a custom approximation if calling frequently.
	*/
	float getVoltageRMS() {
		if (channels == 0) {
			return 0.f;
		}
		else if (channels == 1) {
			return std::fabs(voltages[0]);
		}
		else {
			float sum = 0.f;
			for (uint8_t c = 0; c < channels; c++) {
				sum += std::pow(voltages[c], 2);
			}
			return std::sqrt(sum);
		}
	}

	template <typename T>
	T getVoltageSimd(uint8_t firstChannel) {
		return T::load(&voltages[firstChannel]);
	}

	template <typename T>
	T getPolyVoltageSimd(uint8_t firstChannel) {
		return isMonophonic() ? getVoltage(0) : getVoltageSimd<T>(firstChannel);
	}

	template <typename T>
	T getNormalVoltageSimd(T normalVoltage, uint8_t firstChannel) {
		return isConnected() ? getVoltageSimd<T>(firstChannel) : normalVoltage;
	}

	template <typename T>
	T getNormalPolyVoltageSimd(T normalVoltage, uint8_t firstChannel) {
		return isConnected() ? getPolyVoltageSimd<T>(firstChannel) : normalVoltage;
	}

	template <typename T>
	void setVoltageSimd(T voltage, uint8_t firstChannel) {
		voltage.store(&voltages[firstChannel]);
	}

	/** Sets the number of polyphony channels.
	Also clears voltages of higher channels.
	If disconnected, this does nothing (`channels` remains 0).
	If 0 is given, `channels` is set to 1 but all voltages are cleared.
	*/
	void setChannels(uint8_t channels) {
		// If disconnected, keep the number of channels at 0.
		if (this->channels == 0) {
			return;
		}
		// Set higher channel voltages to 0
		for (uint8_t c = channels; c < this->channels; c++) {
			voltages[c] = 0.f;
		}
		// Don't allow caller to set port as disconnected
		if (channels == 0) {
			channels = 1;
		}
		this->channels = channels;
	}

	/** Returns the number of channels.
	If the port is disconnected, it has 0 channels.
	*/
	int getChannels() {
		return channels;
	}

	/** Returns whether a cable is connected to the Port.
	You can use this for skipping code that generates output voltages.
	*/
	bool isConnected() {
		return channels > 0;
	}

	/** Returns whether the cable exists and has 1 channel. */
	bool isMonophonic() {
		return channels == 1;
	}

	/** Returns whether the cable exists and has more than 1 channel. */
	bool isPolyphonic() {
		return channels > 1;
	}

	/** Use getNormalVoltage() instead.
	"Normal" voltage is the correct term, not "normalized" voltage.
	*/
	DEPRECATED float normalize(float normalVoltage) {
		return getNormalVoltage(normalVoltage);
	}
};


struct Output : Port {};

struct Input : Port {};


} // namespace engine
} // namespace rack
