#include <app/AudioDisplay.hpp>
#include <ui/MenuSeparator.hpp>
#include <helpers.hpp>
#include <set>


namespace rack {
namespace app {


static std::string getDetailTemplate(std::string name, int numInputs, int inputOffset, int numOutputs, int outputOffset) {
	std::string text = name;
	text += " (";
	if (numInputs > 0) {
		text += string::f("%d-%d in", inputOffset + 1, inputOffset + numInputs);
	}
	if (numInputs > 0 && numOutputs > 0) {
		text += ", ";
	}
	if (numOutputs > 0) {
		text += string::f("%d-%d out", outputOffset + 1, outputOffset + numOutputs);
	}
	text += ")";
	return text;
}


struct AudioDriverValueItem : ui::MenuItem {
	audio::Port* port;
	int driverId;
	void onAction(const ActionEvent& e) override {
		port->setDriverId(driverId);
	}
};

static void appendAudioDriverMenu(ui::Menu* menu, audio::Port* port) {
	if (!port)
		return;

	for (int driverId : audio::getDriverIds()) {
		AudioDriverValueItem* item = new AudioDriverValueItem;
		item->port = port;
		item->driverId = driverId;
		item->text = audio::getDriver(driverId)->getName();
		item->rightText = CHECKMARK(item->driverId == port->getDriverId());
		menu->addChild(item);
	}
}

void AudioDriverChoice::onAction(const ActionEvent& e) {
	ui::Menu* menu = createMenu();
	menu->addChild(createMenuLabel(string::translate("AudioDisplay.audioDriver")));
	appendAudioDriverMenu(menu, port);
}

void AudioDriverChoice::step() {
	text = "";
	if (box.size.x >= 200.0)
		text += string::translate("AudioDisplay.driver");
	audio::Driver* driver = port ? port->getDriver() : NULL;
	std::string driverName = driver ? driver->getName() : "";
	if (driverName != "") {
		text += driverName;
		color.a = 1.0;
	}
	else {
		text += "(" + string::translate("AudioDisplay.noDriver") + ")";
		color.a = 0.5;
	}
}

struct AudioDriverItem : ui::MenuItem {
	audio::Port* port;
	ui::Menu* createChildMenu() override {
		ui::Menu* menu = new ui::Menu;
		appendAudioDriverMenu(menu, port);
		return menu;
	}
};


struct AudioDeviceValueItem : ui::MenuItem {
	audio::Port* port;
	int deviceId;
	int inputOffset;
	int outputOffset;
	void onAction(const ActionEvent& e) override {
		port->setDeviceId(deviceId);
		port->inputOffset = inputOffset;
		port->outputOffset = outputOffset;
	}
};

static void appendAudioDeviceMenu(ui::Menu* menu, audio::Port* port) {
	if (!port)
		return;

	{
		AudioDeviceValueItem* item = new AudioDeviceValueItem;
		item->port = port;
		item->deviceId = -1;
		item->text = "(" + string::translate("AudioDisplay.noDevice") + ")";
		item->rightText = CHECKMARK(item->deviceId == port->getDeviceId());
		menu->addChild(item);
	}

	for (int deviceId : port->getDeviceIds()) {
		int numDeviceInputs = port->getDeviceNumInputs(deviceId);
		int numDeviceOutputs = port->getDeviceNumOutputs(deviceId);
		std::string name = port->getDeviceName(deviceId);

		// Display only 32 channel offsets per device, because some virtual devices (e.g. ALSA) can have thousands of useless channels.
		for (int i = 0; i < 32; i++) {
			int inputOffset = i * port->maxInputs;
			int outputOffset = i * port->maxOutputs;
			if (inputOffset >= numDeviceInputs && outputOffset >= numDeviceOutputs)
				break;
			int numInputs = math::clamp(numDeviceInputs - inputOffset, 0, port->maxInputs);
			int numOutputs = math::clamp(numDeviceOutputs - outputOffset, 0, port->maxOutputs);

			AudioDeviceValueItem* item = new AudioDeviceValueItem;
			item->port = port;
			item->deviceId = deviceId;
			item->inputOffset = inputOffset;
			item->outputOffset = outputOffset;
			item->text = getDetailTemplate(name, numInputs, inputOffset, numOutputs, outputOffset);
			item->rightText = CHECKMARK(deviceId == port->getDeviceId() && inputOffset == port->inputOffset && outputOffset == port->outputOffset);
			menu->addChild(item);
		}
	}
}

void AudioDeviceChoice::onAction(const ActionEvent& e) {
	ui::Menu* menu = createMenu();
	menu->addChild(createMenuLabel(string::translate("AudioDisplay.audioDevice")));
	appendAudioDeviceMenu(menu, port);
}

void AudioDeviceChoice::step() {
	text = "";
	if (box.size.x >= 200.0)
		text += string::translate("AudioDisplay.device");
	std::string detail = "";
	if (port && port->getDevice())
		detail = getDetailTemplate(port->getDevice()->getName(), port->getNumInputs(), port->inputOffset, port->getNumOutputs(), port->outputOffset);

	if (detail != "") {
		text += detail;
		color.a = 1.0;
	}
	else {
		text += string::translate("AudioDisplay.noDevice");
		color.a = 0.5;
	}
}

struct AudioDeviceItem : ui::MenuItem {
	audio::Port* port;
	ui::Menu* createChildMenu() override {
		ui::Menu* menu = new ui::Menu;
		appendAudioDeviceMenu(menu, port);
		return menu;
	}
};


struct AudioSampleRateValueItem : ui::MenuItem {
	audio::Port* port;
	float sampleRate;
	void onAction(const ActionEvent& e) override {
		port->setSampleRate(sampleRate);
	}
};

static void appendAudioSampleRateMenu(ui::Menu* menu, audio::Port* port) {
	if (!port)
		return;

	std::set<float> sampleRates = port->getSampleRates();
	// Add current sample rate in case it's not in the list
	sampleRates.insert(port->getSampleRate());

	if (sampleRates.empty()) {
		menu->addChild(createMenuLabel("(" + string::translate("AudioDisplay.lockedByDevice") + ")"));
	}
	for (float sampleRate : sampleRates) {
		if (sampleRate <= 0)
			continue;
		AudioSampleRateValueItem* item = new AudioSampleRateValueItem;
		item->port = port;
		item->sampleRate = sampleRate;
		item->text = string::f("%g kHz", sampleRate / 1000.0);
		item->rightText = CHECKMARK(item->sampleRate == port->getSampleRate());
		menu->addChild(item);
	}
}

void AudioSampleRateChoice::onAction(const ActionEvent& e) {
	ui::Menu* menu = createMenu();
	menu->addChild(createMenuLabel(string::translate("AudioDisplay.sampleRate")));
	appendAudioSampleRateMenu(menu, port);
}

void AudioSampleRateChoice::step() {
	text = "";
	if (box.size.x >= 100.0)
		text += string::translate("AudioDisplay.sampleRateColon");
	float sampleRate = port ? port->getSampleRate() : 0;
	if (sampleRate > 0) {
		text += string::f("%g", sampleRate / 1000.f);
		color.a = 1.0;
	}
	else {
		text += "---";
		color.a = 0.5;
	}
	text += " kHz";
}

struct AudioSampleRateItem : ui::MenuItem {
	audio::Port* port;
	ui::Menu* createChildMenu() override {
		ui::Menu* menu = new ui::Menu;
		appendAudioSampleRateMenu(menu, port);
		return menu;
	}
};


struct AudioBlockSizeValueItem : ui::MenuItem {
	audio::Port* port;
	int blockSize;
	void onAction(const ActionEvent& e) override {
		port->setBlockSize(blockSize);
	}
};

static void appendAudioBlockSizeMenu(ui::Menu* menu, audio::Port* port) {
	if (!port)
		return;

	std::set<int> blockSizes = port->getBlockSizes();
	// Add current block size in case it's not in the list
	blockSizes.insert(port->getBlockSize());

	if (blockSizes.empty()) {
		menu->addChild(createMenuLabel("(" + string::translate("AudioDisplay.lockedByDevice") + ")"));
	}
	for (int blockSize : blockSizes) {
		if (blockSize <= 0)
			continue;
		AudioBlockSizeValueItem* item = new AudioBlockSizeValueItem;
		item->port = port;
		item->blockSize = blockSize;
		float latency = (float) blockSize / port->getSampleRate() * 1000.0;
		item->text = string::f("%d (%.1f ms)", blockSize, latency);
		item->rightText = CHECKMARK(item->blockSize == port->getBlockSize());
		menu->addChild(item);
	}
}

void AudioBlockSizeChoice::onAction(const ActionEvent& e) {
	ui::Menu* menu = createMenu();
	menu->addChild(createMenuLabel(string::translate("AudioDisplay.blockSize")));
	appendAudioBlockSizeMenu(menu, port);
}

void AudioBlockSizeChoice::step() {
	text = "";
	if (box.size.x >= 100.0)
		text += string::translate("AudioDisplay.blockSizeColon");
	int blockSize = port ? port->getBlockSize() : 0;
	if (blockSize > 0) {
		text += string::f("%d", blockSize);
		color.a = 1.0;
	}
	else {
		text += "---";
		color.a = 0.5;
	}
}

struct AudioBlockSizeItem : ui::MenuItem {
	audio::Port* port;
	ui::Menu* createChildMenu() override {
		ui::Menu* menu = new ui::Menu;
		appendAudioBlockSizeMenu(menu, port);
		return menu;
	}
};


void AudioDisplay::setAudioPort(audio::Port* port) {
	clearChildren();

	math::Vec pos;

	AudioDriverChoice* driverChoice = createWidget<AudioDriverChoice>(pos);
	driverChoice->box.size.x = box.size.x;
	driverChoice->port = port;
	addChild(driverChoice);
	pos = driverChoice->box.getBottomLeft();
	this->driverChoice = driverChoice;

	this->driverSeparator = createWidget<LedDisplaySeparator>(pos);
	this->driverSeparator->box.size.x = box.size.x;
	addChild(this->driverSeparator);

	AudioDeviceChoice* deviceChoice = createWidget<AudioDeviceChoice>(pos);
	deviceChoice->box.size.x = box.size.x;
	deviceChoice->port = port;
	addChild(deviceChoice);
	pos = deviceChoice->box.getBottomLeft();
	this->deviceChoice = deviceChoice;

	this->deviceSeparator = createWidget<LedDisplaySeparator>(pos);
	this->deviceSeparator->box.size.x = box.size.x;
	addChild(this->deviceSeparator);

	AudioSampleRateChoice* sampleRateChoice = createWidget<AudioSampleRateChoice>(pos);
	sampleRateChoice->box.size.x = box.size.x / 2;
	sampleRateChoice->port = port;
	addChild(sampleRateChoice);
	this->sampleRateChoice = sampleRateChoice;

	this->sampleRateSeparator = createWidget<LedDisplaySeparator>(pos);
	this->sampleRateSeparator->box.pos.x = box.size.x / 2;
	this->sampleRateSeparator->box.size.y = this->sampleRateChoice->box.size.y;
	addChild(this->sampleRateSeparator);

	AudioBlockSizeChoice* bufferSizeChoice = createWidget<AudioBlockSizeChoice>(pos);
	bufferSizeChoice->box.pos.x = box.size.x / 2;
	bufferSizeChoice->box.size.x = box.size.x / 2;
	bufferSizeChoice->port = port;
	addChild(bufferSizeChoice);
	this->bufferSizeChoice = bufferSizeChoice;
}


void AudioDeviceMenuChoice::onAction(const ActionEvent& e) {
	ui::Menu* menu = createMenu();
	appendAudioMenu(menu, port);
}


void AudioButton::setAudioPort(audio::Port* port) {
	this->port = port;
}


void AudioButton::onAction(const ActionEvent& e) {
	ui::Menu* menu = createMenu();
	appendAudioMenu(menu, port);
}


void appendAudioMenu(ui::Menu* menu, audio::Port* port) {
	menu->addChild(createMenuLabel(string::translate("AudioDisplay.audioDriver")));
	appendAudioDriverMenu(menu, port);

	menu->addChild(new ui::MenuSeparator);
	menu->addChild(createMenuLabel(string::translate("AudioDisplay.audioDevice")));
	appendAudioDeviceMenu(menu, port);

	menu->addChild(new ui::MenuSeparator);
	menu->addChild(createMenuLabel(string::translate("AudioDisplay.sampleRate")));
	appendAudioSampleRateMenu(menu, port);

	menu->addChild(new ui::MenuSeparator);
	menu->addChild(createMenuLabel(string::translate("AudioDisplay.blockSize")));
	appendAudioBlockSizeMenu(menu, port);

	// Uncomment this to use sub-menus instead of one big menu.

	// AudioDriverItem* driverItem = createMenuItem<AudioDriverItem>(string::translate("AudioDisplay.audioDriver"), RIGHT_ARROW);
	// driverItem->port = port;
	// menu->addChild(driverItem);

	// AudioDeviceItem* deviceItem = createMenuItem<AudioDeviceItem>(string::translate("AudioDisplay.audioDevice"), RIGHT_ARROW);
	// deviceItem->port = port;
	// menu->addChild(deviceItem);

	// AudioSampleRateItem* sampleRateItem = createMenuItem<AudioSampleRateItem>(string::translate("AudioDisplay.sampleRate"), RIGHT_ARROW);
	// sampleRateItem->port = port;
	// menu->addChild(sampleRateItem);

	// AudioBlockSizeItem* blockSizeItem = createMenuItem<AudioBlockSizeItem>(string::translate("AudioDisplay.blockSize"), RIGHT_ARROW);
	// blockSizeItem->port = port;
	// menu->addChild(blockSizeItem);
}


} // namespace app
} // namespace rack
