/*
 * DISTRHO Cardinal Plugin
 * Copyright (C) 2021-2023 Filipe Coelho <falktx@falktx.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * For a full copy of the GNU General Public License see the LICENSE file.
 */

/**
 * This file is an edited version of VCVRack's ModuleWidget.cpp
 * Copyright (C) 2016-2023 VCV.
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 */

#include "../../CardinalCommon.hpp"

#include <thread>
#include <regex>

#include <app/ModuleWidget.hpp>
#include <app/Scene.hpp>
#include <engine/Engine.hpp>
#include <plugin/Plugin.hpp>
#include <app/SvgPanel.hpp>
#include <ui/MenuSeparator.hpp>
#include <system.hpp>
#include <asset.hpp>
#include <helpers.hpp>
#include <context.hpp>
#include <settings.hpp>
#include <history.hpp>
#include <string.hpp>
#include <componentlibrary.hpp>


namespace rack {
namespace app {


static const char PRESET_FILTERS[] = "VCV Rack module preset (.vcvm):vcvm";


struct ModuleWidget::Internal {
	/** The module position clicked on to start dragging in the rack.
	*/
	math::Vec dragOffset;

	/** Global rack position the user clicked on.
	*/
	math::Vec dragRackPos;
	bool dragEnabled = true;

	widget::Widget* panel = NULL;
};


ModuleWidget::ModuleWidget() {
	internal = new Internal;
	box.size = math::Vec(0, RACK_GRID_HEIGHT);
}

ModuleWidget::~ModuleWidget() {
	clearChildren();
	setModule(NULL);
	delete internal;
}

plugin::Model* ModuleWidget::getModel() {
	return model;
}

void ModuleWidget::setModel(plugin::Model* model) {
	assert(!this->model);
	this->model = model;
}

engine::Module* ModuleWidget::getModule() {
	return module;
}

void ModuleWidget::setModule(engine::Module* module) {
	if (this->module) {
		APP->engine->removeModule(this->module);
		delete this->module;
		this->module = NULL;
	}
	this->module = module;
}

widget::Widget* ModuleWidget::getPanel() {
	return internal->panel;
}

void ModuleWidget::setPanel(widget::Widget* panel) {
	// Remove existing panel
	if (internal->panel) {
		removeChild(internal->panel);
		delete internal->panel;
		internal->panel = NULL;
	}

	if (panel) {
		addChildBottom(panel);
		internal->panel = panel;
		box.size.x = std::round(panel->box.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH;
		// If width is zero, set it to 12HP for sanity
		if (box.size.x == 0.0)
			box.size.x = 12 * RACK_GRID_WIDTH;
	}
}

void ModuleWidget::setPanel(std::shared_ptr<window::Svg> svg) {
	// Create SvgPanel
	SvgPanel* panel = new SvgPanel;
	panel->setBackground(svg);
	setPanel(panel);
}

void ModuleWidget::addParam(ParamWidget* param) {
	addChild(param);
}

void ModuleWidget::addInput(PortWidget* input) {
	// Check that the port is an input
	assert(input->type == engine::Port::INPUT);
	// Check that the port doesn't have a duplicate ID
	PortWidget* input2 = getInput(input->portId);
	assert(!input2);
	// Add port
	addChild(input);
}

void ModuleWidget::addOutput(PortWidget* output) {
	// Check that the port is an output
	assert(output->type == engine::Port::OUTPUT);
	// Check that the port doesn't have a duplicate ID
	PortWidget* output2 = getOutput(output->portId);
	assert(!output2);
	// Add port
	addChild(output);
}

template <class T, typename F>
T* getFirstDescendantOfTypeWithCondition(widget::Widget* w, F f) {
	T* t = dynamic_cast<T*>(w);
	if (t && f(t))
		return t;

	for (widget::Widget* child : w->children) {
		T* foundT = getFirstDescendantOfTypeWithCondition<T>(child, f);
		if (foundT)
			return foundT;
	}
	return NULL;
}

ParamWidget* ModuleWidget::getParam(int paramId) {
	return getFirstDescendantOfTypeWithCondition<ParamWidget>(this, [&](ParamWidget* pw) -> bool {
		return pw->paramId == paramId;
	});
}

PortWidget* ModuleWidget::getInput(int portId) {
	return getFirstDescendantOfTypeWithCondition<PortWidget>(this, [&](PortWidget* pw) -> bool {
		return pw->type == engine::Port::INPUT && pw->portId == portId;
	});
}

PortWidget* ModuleWidget::getOutput(int portId) {
	return getFirstDescendantOfTypeWithCondition<PortWidget>(this, [&](PortWidget* pw) -> bool {
		return pw->type == engine::Port::OUTPUT && pw->portId == portId;
	});
}

template <class T, typename F>
void doIfTypeRecursive(widget::Widget* w, F f) {
	T* t = dynamic_cast<T*>(w);
	if (t)
		f(t);

	for (widget::Widget* child : w->children) {
		doIfTypeRecursive<T>(child, f);
	}
}

std::vector<ParamWidget*> ModuleWidget::getParams() {
	std::vector<ParamWidget*> pws;
	doIfTypeRecursive<ParamWidget>(this, [&](ParamWidget* pw) {
		pws.push_back(pw);
	});
	return pws;
}

std::vector<PortWidget*> ModuleWidget::getPorts() {
	std::vector<PortWidget*> pws;
	doIfTypeRecursive<PortWidget>(this, [&](PortWidget* pw) {
		pws.push_back(pw);
	});
	return pws;
}

std::vector<PortWidget*> ModuleWidget::getInputs() {
	std::vector<PortWidget*> pws;
	doIfTypeRecursive<PortWidget>(this, [&](PortWidget* pw) {
		if (pw->type == engine::Port::INPUT)
			pws.push_back(pw);
	});
	return pws;
}

std::vector<PortWidget*> ModuleWidget::getOutputs() {
	std::vector<PortWidget*> pws;
	doIfTypeRecursive<PortWidget>(this, [&](PortWidget* pw) {
		if (pw->type == engine::Port::OUTPUT)
			pws.push_back(pw);
	});
	return pws;
}

void ModuleWidget::draw(const DrawArgs& args) {
	nvgScissor(args.vg, RECT_ARGS(args.clipBox));

	if (module && module->isBypassed()) {
		nvgAlpha(args.vg, 0.33);
	}

	Widget::draw(args);

	// Meter
	if (module && settings::cpuMeter) {
		float sampleRate = APP->engine->getSampleRate();
		const float* meterBuffer = module->meterBuffer();
		int meterLength = module->meterLength();
		int meterIndex = module->meterIndex();

		// // Text background
		// nvgBeginPath(args.vg);
		// nvgRect(args.vg, 0.0, box.size.y - infoHeight, box.size.x, infoHeight);
		// nvgFillColor(args.vg, nvgRGBAf(0, 0, 0, 0.75));
		// nvgFill(args.vg);

		// Draw time plot
		const float plotHeight = box.size.y - BND_WIDGET_HEIGHT;
		nvgBeginPath(args.vg);
		nvgMoveTo(args.vg, 0.0, plotHeight);
		math::Vec p1;
		for (int i = 0; i < meterLength; i++) {
			int index = math::eucMod(meterIndex + i + 1, meterLength);
			float meter = math::clamp(meterBuffer[index] * sampleRate, 0.f, 1.f);
			meter = std::max(0.f, meter);
			math::Vec p;
			p.x = (float) i / (meterLength - 1) * box.size.x;
			p.y = (1.f - meter) * plotHeight;
			if (i == 0) {
				nvgLineTo(args.vg, VEC_ARGS(p));
			}
			else {
				math::Vec p2 = p;
				p2.x -= 0.5f / (meterLength - 1) * box.size.x;
				nvgBezierTo(args.vg, VEC_ARGS(p1), VEC_ARGS(p2), VEC_ARGS(p));
			}
			p1 = p;
			p1.x += 0.5f / (meterLength - 1) * box.size.x;
		}
		nvgLineTo(args.vg, box.size.x, plotHeight);
		nvgClosePath(args.vg);
		NVGcolor color = componentlibrary::SCHEME_ORANGE;
		nvgFillColor(args.vg, color::alpha(color, 0.75));
		nvgFill(args.vg);
		nvgStrokeWidth(args.vg, 2.0);
		nvgStrokeColor(args.vg, color);
		nvgStroke(args.vg);

		// Text background
		bndMenuBackground(args.vg, 0.0, plotHeight, box.size.x, BND_WIDGET_HEIGHT, BND_CORNER_ALL);

		// Text
		float percent = meterBuffer[meterIndex] * sampleRate * 100.f;
		// float microseconds = meterBuffer[meterIndex] * 1e6f;
		std::string meterText = string::f("%.1f", percent);
		// Only append "%" if wider than 2 HP
		if (box.getWidth() > RACK_GRID_WIDTH * 2)
			meterText += "%";
		math::Vec pt;
		pt.x = box.size.x - bndLabelWidth(args.vg, -1, meterText.c_str()) + 3;
		pt.y = plotHeight + 0.5;
		bndMenuLabel(args.vg, VEC_ARGS(pt), INFINITY, BND_WIDGET_HEIGHT, -1, meterText.c_str());
	}

	// Selection
	if (APP->scene->rack->isSelected(this)) {
		nvgBeginPath(args.vg);
		nvgRect(args.vg, 0.0, 0.0, VEC_ARGS(box.size));
		nvgFillColor(args.vg, nvgRGBAf(1, 0, 0, 0.25));
		nvgFill(args.vg);
		nvgStrokeWidth(args.vg, 2.0);
		nvgStrokeColor(args.vg, nvgRGBAf(1, 0, 0, 0.5));
		nvgStroke(args.vg);
	}

	nvgResetScissor(args.vg);
}

void ModuleWidget::drawLayer(const DrawArgs& args, int layer) {
	if (layer == -1) {
		nvgBeginPath(args.vg);
		float r = 20; // Blur radius
		float c = 20; // Corner radius
		math::Rect shadowBox = box.zeroPos().grow(math::Vec(10, -30));
		math::Rect shadowOutsideBox = shadowBox.grow(math::Vec(r, r));
		nvgRect(args.vg, RECT_ARGS(shadowOutsideBox));
		NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.2);
		NVGcolor transparentColor = nvgRGBAf(0, 0, 0, 0);
		nvgFillPaint(args.vg, nvgBoxGradient(args.vg, RECT_ARGS(shadowBox), c, r, shadowColor, transparentColor));
		nvgFill(args.vg);
	}
	else {
		Widget::drawLayer(args, layer);
	}
}

void ModuleWidget::onHover(const HoverEvent& e) {
	if (APP->scene->rack->isSelected(this)) {
		e.consume(this);
	}

	OpaqueWidget::onHover(e);
}

void ModuleWidget::onHoverKey(const HoverKeyEvent& e) {
	if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
		if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
			copyClipboard();
			e.consume(this);
		}
		if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
			if (pasteClipboardAction()) {
				e.consume(this);
			}
		}
		if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
			cloneAction(false);
			e.consume(this);
		}
		if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) {
			cloneAction(true);
			e.consume(this);
		}
		if (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
			resetAction();
			e.consume(this);
		}
		if (e.keyName == "r" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
			randomizeAction();
			e.consume(this);
		}
		if (e.keyName == "u" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
			disconnectAction();
			e.consume(this);
		}
		if (e.keyName == "e" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
			bypassAction(!module->isBypassed());
			e.consume(this);
		}
		if ((e.key == GLFW_KEY_DELETE || e.key == GLFW_KEY_BACKSPACE) && (e.mods & RACK_MOD_MASK) == 0) {
			// Deletes `this`
			removeAction();
			e.consume(NULL);
			return;
		}
		if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
			std::string manualUrl = model->getManualUrl();
			if (!manualUrl.empty())
				system::openBrowser(manualUrl);
			e.consume(this);
		}
	}

	if (e.isConsumed())
		return;
	OpaqueWidget::onHoverKey(e);
}

void ModuleWidget::onButton(const ButtonEvent& e) {
	bool selected = APP->scene->rack->isSelected(this);

	if (selected) {
		if (e.button == GLFW_MOUSE_BUTTON_RIGHT) {
			if (e.action == GLFW_PRESS) {
				// Open selection context menu on right-click
				ui::Menu* menu = createMenu();
				patchUtils::appendSelectionContextMenu(menu);
			}
			e.consume(this);
		}

		if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
			if (e.action == GLFW_PRESS) {
				// Toggle selection on Shift-click
				if ((e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
					APP->scene->rack->select(this, false);
					e.consume(NULL);
					return;
				}

				// If module positions are locked, don't consume left-click
				if (settings::lockModules) {
					e.consume(NULL);
					return;
				}

				internal->dragOffset = e.pos;
			}

			e.consume(this);
		}

		return;
	}

	// Dispatch event to children
	Widget::onButton(e);
	e.stopPropagating();
	if (e.isConsumed())
		return;

	if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
		if (e.action == GLFW_PRESS) {
			// Toggle selection on Shift-click
			if ((e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
				APP->scene->rack->select(this, true);
				e.consume(NULL);
				return;
			}

			// If module positions are locked, don't consume left-click
			if (settings::lockModules) {
				e.consume(NULL);
				return;
			}

			internal->dragOffset = e.pos;
		}
		e.consume(this);
	}

	// Open context menu on right-click
	if (e.button == GLFW_MOUSE_BUTTON_RIGHT && e.action == GLFW_PRESS) {
		createContextMenu();
		e.consume(this);
	}
}

void ModuleWidget::onDragStart(const DragStartEvent& e) {
	if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
		// HACK Disable FramebufferWidget redrawing subpixels while dragging
		APP->window->fbDirtyOnSubpixelChange() = false;

		// Clear dragRack so dragging in not enabled until mouse is moved a bit.
		internal->dragRackPos = math::Vec(NAN, NAN);

		// Prepare initial position of modules for history.
		APP->scene->rack->updateModuleOldPositions();
	}
}

void ModuleWidget::onDragEnd(const DragEndEvent& e) {
	if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
		APP->window->fbDirtyOnSubpixelChange() = true;

		// The next time the module is dragged, it should always move immediately
		internal->dragEnabled = true;

		history::ComplexAction* h = APP->scene->rack->getModuleDragAction();
		if (!h->isEmpty())
			APP->history->push(h);
		else
			delete h;
	}
}

void ModuleWidget::onDragMove(const DragMoveEvent& e) {
	if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
		math::Vec mousePos = APP->scene->rack->getMousePos();

		if (!internal->dragEnabled) {
			// Set dragRackPos on the first time after dragging
			if (!internal->dragRackPos.isFinite())
				internal->dragRackPos = mousePos;
			// Check if the mouse has moved enough to start dragging the module.
			const float minDist = RACK_GRID_WIDTH;
			if (internal->dragRackPos.minus(mousePos).square() >= std::pow(minDist, 2))
				internal->dragEnabled = true;
		}

		// Move module
		if (internal->dragEnabled) {
			// Round y coordinate to nearest rack height
			math::Vec pos = mousePos;
			pos.x -= internal->dragOffset.x;
			pos.y -= RACK_GRID_HEIGHT / 2;

			if (APP->scene->rack->isSelected(this)) {
				pos = (pos / RACK_GRID_SIZE).round() * RACK_GRID_SIZE;
				math::Vec delta = pos.minus(box.pos);
				APP->scene->rack->setSelectionPosNearest(delta);
			}
			else {
				if (settings::squeezeModules) {
					APP->scene->rack->setModulePosSqueeze(this, pos);
				}
				else {
					if ((APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL)
						APP->scene->rack->setModulePosForce(this, pos);
					else
						APP->scene->rack->setModulePosNearest(this, pos);
				}
			}
		}
	}
}

void ModuleWidget::onDragHover(const DragHoverEvent& e) {
	if (APP->scene->rack->isSelected(this)) {
		e.consume(this);
	}

	OpaqueWidget::onDragHover(e);
}

json_t* ModuleWidget::toJson() {
	json_t* moduleJ = APP->engine->moduleToJson(module);
	return moduleJ;
}

void ModuleWidget::fromJson(json_t* moduleJ) {
	APP->engine->moduleFromJson(module, moduleJ);
}

bool ModuleWidget::pasteJsonAction(json_t* moduleJ) {
	engine::Module::jsonStripIds(moduleJ);

	json_t* oldModuleJ = toJson();
	DEFER({json_decref(oldModuleJ);});

	try {
		fromJson(moduleJ);
	}
	catch (Exception& e) {
		WARN("%s", e.what());
		return false;
	}

	// history::ModuleChange
	history::ModuleChange* h = new history::ModuleChange;
	h->name = "paste module preset";
	h->moduleId = module->id;
	json_incref(oldModuleJ);
	h->oldModuleJ = oldModuleJ;
	json_incref(moduleJ);
	h->newModuleJ = moduleJ;
	APP->history->push(h);
	return true;
}

void ModuleWidget::copyClipboard() {
	json_t* moduleJ = toJson();
	engine::Module::jsonStripIds(moduleJ);

	DEFER({json_decref(moduleJ);});
	char* json = json_dumps(moduleJ, JSON_INDENT(2));
	DEFER({std::free(json);});
	glfwSetClipboardString(APP->window->win, json);
}

bool ModuleWidget::pasteClipboardAction() {
	const char* json = glfwGetClipboardString(APP->window->win);
	if (!json) {
		WARN("Could not get text from clipboard.");
		return false;
	}

	json_error_t error;
	json_t* moduleJ = json_loads(json, 0, &error);
	if (!moduleJ) {
		WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
		return false;
	}
	DEFER({json_decref(moduleJ);});

	return pasteJsonAction(moduleJ);
}

void ModuleWidget::load(std::string filename) {
	FILE* file = std::fopen(filename.c_str(), "r");
	if (!file)
		throw Exception("Could not load patch file %s", filename.c_str());
	DEFER({std::fclose(file);});

	INFO("Loading preset %s", filename.c_str());

	json_error_t error;
	json_t* moduleJ = json_loadf(file, 0, &error);
	if (!moduleJ)
		throw Exception("File is not a valid patch file. JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
	DEFER({json_decref(moduleJ);});

	engine::Module::jsonStripIds(moduleJ);
	fromJson(moduleJ);
}

void ModuleWidget::loadAction(std::string filename) {
	// history::ModuleChange
	history::ModuleChange* h = new history::ModuleChange;
	h->name = "load module preset";
	h->moduleId = module->id;
	h->oldModuleJ = toJson();

	try {
		load(filename);
	}
	catch (Exception& e) {
		delete h;
		throw;
	}

	// TODO We can use `moduleJ` here instead to save a toJson() call.
	h->newModuleJ = toJson();
	APP->history->push(h);
}

void ModuleWidget::loadTemplate() {
	std::string templatePath = system::join(model->getUserPresetDirectory(), "template.vcvm");
	try {
		load(templatePath);
	}
	catch (Exception& e) {
		// Do nothing
	}
}

void ModuleWidget::loadDialog() {
	std::string presetDir = model->getUserPresetDirectory();
	system::createDirectories(presetDir);

	WeakPtr<ModuleWidget> weakThis = this;
	async_dialog_filebrowser(false, nullptr, presetDir.c_str(), "Load preset", [=](char* pathC) {

	// Delete directories if empty
	DEFER({
		try {
			system::remove(presetDir);
			system::remove(system::getDirectory(presetDir));
		}
		catch (Exception& e) {
			// Ignore exceptions if directory cannot be removed.
		}
	});

	if (!weakThis)
		return;
	if (!pathC) {
		// No path selected
		return;
	}
	DEFER({std::free(pathC);});

	try {
		weakThis->loadAction(pathC);
	}
	catch (Exception& e) {
		async_dialog_message(e.what());
	}

	});
}

void ModuleWidget::save(std::string filename) {
	INFO("Saving preset %s", filename.c_str());

	json_t* moduleJ = toJson();
	assert(moduleJ);
	DEFER({json_decref(moduleJ);});

	engine::Module::jsonStripIds(moduleJ);

	FILE* file = std::fopen(filename.c_str(), "w");
	if (!file) {
		std::string message = string::f("Could not save preset to file %s", filename.c_str());
		async_dialog_message(message.c_str());
		return;
	}
	DEFER({std::fclose(file);});

	json_dumpf(moduleJ, file, JSON_INDENT(2));
}

void ModuleWidget::saveTemplate() {
	std::string presetDir = model->getUserPresetDirectory();
	system::createDirectories(presetDir);
	std::string templatePath = system::join(presetDir, "template.vcvm");
	save(templatePath);
}

void ModuleWidget::saveTemplateDialog() {
	if (hasTemplate()) {
		std::string message = string::f("Overwrite default preset for %s?", model->getFullName().c_str());
		WeakPtr<ModuleWidget> weakThis = this;
		async_dialog_message(message.c_str(), [=]{
			if (weakThis)
				weakThis->saveTemplate();
		});
	}
}

bool ModuleWidget::hasTemplate() {
	std::string presetDir = model->getUserPresetDirectory();
	std::string templatePath = system::join(presetDir, "template.vcvm");
	return system::exists(templatePath);;
}

void ModuleWidget::clearTemplate() {
	std::string presetDir = model->getUserPresetDirectory();
	std::string templatePath = system::join(presetDir, "template.vcvm");
	system::remove(templatePath);
}

void ModuleWidget::clearTemplateDialog() {
	std::string message = string::f("Delete default preset for %s?", model->getFullName().c_str());
	WeakPtr<ModuleWidget> weakThis = this;
	async_dialog_message(message.c_str(), [=]{
		if (weakThis)
			weakThis->clearTemplate();
	});
}

void ModuleWidget::saveDialog() {
	std::string presetDir = model->getUserPresetDirectory();
	system::createDirectories(presetDir);

	WeakPtr<ModuleWidget> weakThis = this;
	async_dialog_filebrowser(true, "preset.vcvm", presetDir.c_str(), "Save preset", [=](char* pathC) {

	// Delete directories if empty
	DEFER({
		try {
			system::remove(presetDir);
			system::remove(system::getDirectory(presetDir));
		}
		catch (Exception& e) {
			// Ignore exceptions if directory cannot be removed.
		}
	});

	if (!weakThis)
		return;
	if (!pathC) {
		// No path selected
		return;
	}
	DEFER({std::free(pathC);});

	std::string path = pathC;
	// Automatically append .vcvm extension
	if (system::getExtension(path) != ".vcvm")
		path += ".vcvm";

	weakThis->save(path);
	});
}

void ModuleWidget::disconnect() {
	for (PortWidget* pw : getPorts()) {
		APP->scene->rack->clearCablesOnPort(pw);
	}
}

void ModuleWidget::resetAction() {
	assert(module);

	// history::ModuleChange
	history::ModuleChange* h = new history::ModuleChange;
	h->name = "reset module";
	h->moduleId = module->id;
	h->oldModuleJ = toJson();

	APP->engine->resetModule(module);

	h->newModuleJ = toJson();
	APP->history->push(h);
}

void ModuleWidget::randomizeAction() {
	assert(module);

	// history::ModuleChange
	history::ModuleChange* h = new history::ModuleChange;
	h->name = "randomize module";
	h->moduleId = module->id;
	h->oldModuleJ = toJson();

	APP->engine->randomizeModule(module);

	h->newModuleJ = toJson();
	APP->history->push(h);
}

void ModuleWidget::appendDisconnectActions(history::ComplexAction* complexAction) {
	for (PortWidget* pw : getPorts()) {
		for (CableWidget* cw : APP->scene->rack->getCompleteCablesOnPort(pw)) {
			// history::CableRemove
			history::CableRemove* h = new history::CableRemove;
			h->setCable(cw);
			complexAction->push(h);
			// Delete cable
			APP->scene->rack->removeCable(cw);
			delete cw;
		}
	};
}

void ModuleWidget::disconnectAction() {
	history::ComplexAction* complexAction = new history::ComplexAction;
	complexAction->name = "disconnect cables";
	appendDisconnectActions(complexAction);

	if (!complexAction->isEmpty())
		APP->history->push(complexAction);
	else
		delete complexAction;
}

void ModuleWidget::cloneAction(bool cloneCables) {
	// history::ComplexAction
	history::ComplexAction* h = new history::ComplexAction;
	h->name = "duplicate module";

	// Save patch store in this module so we can copy it below
	APP->engine->prepareSaveModule(module);

	// JSON serialization is the obvious way to do this
	json_t* moduleJ = toJson();
	DEFER({
		json_decref(moduleJ);
	});
	engine::Module::jsonStripIds(moduleJ);

	// Clone Module
	INFO("Creating module %s", model->getFullName().c_str());
	engine::Module* clonedModule = model->createModule();

	// Set ID here so we can copy module storage dir
	clonedModule->id = random::u64() % (1ull << 53);
	system::copy(module->getPatchStorageDirectory(), clonedModule->getPatchStorageDirectory());

	// This doesn't need a lock (via Engine::moduleFromJson()) because the Module is not added to the Engine yet.
	try {
		clonedModule->fromJson(moduleJ);
	}
	catch (Exception& e) {
		WARN("%s", e.what());
	}
	APP->engine->addModule(clonedModule);

	// Clone ModuleWidget
	INFO("Creating module widget %s", model->getFullName().c_str());
	ModuleWidget* clonedModuleWidget = model->createModuleWidget(clonedModule);
	APP->scene->rack->updateModuleOldPositions();
	APP->scene->rack->addModule(clonedModuleWidget);
	// Place module to the right of `this` module, by forcing it to 1 HP to the right.
	math::Vec clonedPos = box.pos;
	clonedPos.x += clonedModuleWidget->box.getWidth();
	if (settings::squeezeModules)
		APP->scene->rack->squeezeModulePos(clonedModuleWidget, clonedPos);
	else
		APP->scene->rack->setModulePosNearest(clonedModuleWidget, clonedPos);
	h->push(APP->scene->rack->getModuleDragAction());
	APP->scene->rack->updateExpanders();

	// history::ModuleAdd
	history::ModuleAdd* hma = new history::ModuleAdd;
	hma->setModule(clonedModuleWidget);
	h->push(hma);

	if (cloneCables) {
		// Clone cables attached to input ports
		for (PortWidget* pw : getInputs()) {
			for (CableWidget* cw : APP->scene->rack->getCompleteCablesOnPort(pw)) {
				// Create cable attached to cloned ModuleWidget's input
				engine::Cable* clonedCable = new engine::Cable;
				clonedCable->inputModule = clonedModule;
				clonedCable->inputId = cw->cable->inputId;
				// If cable is self-patched, attach to cloned module instead
				if (cw->cable->outputModule == module)
					clonedCable->outputModule = clonedModule;
				else
					clonedCable->outputModule = cw->cable->outputModule;
				clonedCable->outputId = cw->cable->outputId;
				APP->engine->addCable(clonedCable);

				app::CableWidget* clonedCw = new app::CableWidget;
				clonedCw->setCable(clonedCable);
				clonedCw->color = cw->color;
				APP->scene->rack->addCable(clonedCw);

				// history::CableAdd
				history::CableAdd* hca = new history::CableAdd;
				hca->setCable(clonedCw);
				h->push(hca);
			}
		}
	}

	APP->history->push(h);
}

void ModuleWidget::bypassAction(bool bypassed) {
	assert(module);

	// history::ModuleBypass
	history::ModuleBypass* h = new history::ModuleBypass;
	h->moduleId = module->id;
	h->bypassed = bypassed;
	if (!bypassed)
		h->name = "un-bypass module";
	APP->history->push(h);

	APP->engine->bypassModule(module, bypassed);
}

void ModuleWidget::removeAction() {
	history::ComplexAction* h = new history::ComplexAction;
	h->name = "delete module";

	// Disconnect cables
	appendDisconnectActions(h);

	// Unset module position from rack.
	APP->scene->rack->updateModuleOldPositions();
	if (settings::squeezeModules)
		APP->scene->rack->unsqueezeModulePos(this);
	h->push(APP->scene->rack->getModuleDragAction());

	// history::ModuleRemove
	history::ModuleRemove* moduleRemove = new history::ModuleRemove;
	moduleRemove->setModule(this);
	h->push(moduleRemove);

	APP->history->push(h);

	// This removes the module and transfers ownership to caller
	APP->scene->rack->removeModule(this);
	delete this;

	APP->scene->rack->updateExpanders();
}


// Create ModulePresetPathItems for each patch in a directory.
static void appendPresetItems(ui::Menu* menu, WeakPtr<ModuleWidget> moduleWidget, std::string presetDir) {
	bool hasPresets = false;
	if (system::isDirectory(presetDir)) {
		// Note: This is not cached, so opening this menu each time might have a bit of latency.
		std::vector<std::string> entries = system::getEntries(presetDir);
		std::sort(entries.begin(), entries.end());
		for (std::string path : entries) {
			std::string name = system::getStem(path);
			// Remove "1_", "42_", "001_", etc at the beginning of preset filenames
			std::regex r("^\\d+_");
			name = std::regex_replace(name, r, "");

			if (system::isDirectory(path)) {
				hasPresets = true;

				menu->addChild(createSubmenuItem(name, "", [=](ui::Menu* menu) {
					if (!moduleWidget)
						return;
					appendPresetItems(menu, moduleWidget, path);
				}));
			}
			else if (system::getExtension(path) == ".vcvm" && name != "template") {
				hasPresets = true;

				menu->addChild(createMenuItem(name, "", [=]() {
					if (!moduleWidget)
						return;
					try {
						moduleWidget->loadAction(path);
					}
					catch (Exception& e) {
						async_dialog_message(e.what());
					}
				}));
			}
		}
	}
	if (!hasPresets) {
		menu->addChild(createMenuLabel("(None)"));
	}
};


void ModuleWidget::createContextMenu() {
	ui::Menu* menu = createMenu();
	assert(model);

	WeakPtr<ModuleWidget> weakThis = this;

	// Brand and module name
	menu->addChild(createMenuLabel(model->name));
	menu->addChild(createMenuLabel(model->plugin->brand));

	// Info
	menu->addChild(createSubmenuItem("Info", "", [=](ui::Menu* menu) {
		model->appendContextMenu(menu);
	}));

	// Preset
	menu->addChild(createSubmenuItem("Preset", "", [=](ui::Menu* menu) {
		menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [=]() {
			if (!weakThis)
				return;
			weakThis->copyClipboard();
		}));

		menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [=]() {
			if (!weakThis)
				return;
			weakThis->pasteClipboardAction();
		}));

		menu->addChild(createMenuItem("Open", "", [=]() {
			if (!weakThis)
				return;
			weakThis->loadDialog();
		}));

		menu->addChild(createMenuItem("Save as", "", [=]() {
			if (!weakThis)
				return;
			weakThis->saveDialog();
		}));

		menu->addChild(createMenuItem("Save default", "", [=]() {
			if (!weakThis)
				return;
			weakThis->saveTemplateDialog();
		}));

		menu->addChild(createMenuItem("Clear default", "", [=]() {
			if (!weakThis)
				return;
			weakThis->clearTemplateDialog();
		}, !weakThis->hasTemplate()));

		// Scan `<user dir>/presets/<plugin slug>/<module slug>` for presets.
		menu->addChild(new ui::MenuSeparator);
		menu->addChild(createMenuLabel("User presets"));
		appendPresetItems(menu, weakThis, weakThis->model->getUserPresetDirectory());

		// Scan `<plugin dir>/presets/<module slug>` for presets.
		menu->addChild(new ui::MenuSeparator);
		menu->addChild(createMenuLabel("Factory presets"));
		appendPresetItems(menu, weakThis, weakThis->model->getFactoryPresetDirectory());
	}));

	// Initialize
	menu->addChild(createMenuItem("Initialize", RACK_MOD_CTRL_NAME "+I", [=]() {
		if (!weakThis)
			return;
		weakThis->resetAction();
	}));

	// Randomize
	menu->addChild(createMenuItem("Randomize", RACK_MOD_CTRL_NAME "+R", [=]() {
		if (!weakThis)
			return;
		weakThis->randomizeAction();
	}));

	// Disconnect cables
	menu->addChild(createMenuItem("Disconnect cables", RACK_MOD_CTRL_NAME "+U", [=]() {
		if (!weakThis)
			return;
		weakThis->disconnectAction();
	}));

	// Bypass
	std::string bypassText = RACK_MOD_CTRL_NAME "+E";
	bool bypassed = module && module->isBypassed();
	if (bypassed)
		bypassText += " " CHECKMARK_STRING;
	menu->addChild(createMenuItem("Bypass", bypassText, [=]() {
		if (!weakThis)
			return;
		weakThis->bypassAction(!bypassed);
	}));

	// Duplicate
	menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() {
		if (!weakThis)
			return;
		weakThis->cloneAction(false);
	}));

	// Duplicate with cables
	menu->addChild(createMenuItem("└ with cables", RACK_MOD_SHIFT_NAME "+" RACK_MOD_CTRL_NAME "+D", [=]() {
		if (!weakThis)
			return;
		weakThis->cloneAction(true);
	}));

	// Delete
	menu->addChild(createMenuItem("Delete", "Backspace/Delete", [=]() {
		if (!weakThis)
			return;
		weakThis->removeAction();
	}, false, true));

	appendContextMenu(menu);
}

math::Vec ModuleWidget::getGridPosition() {
	return ((getPosition() - RACK_OFFSET) / RACK_GRID_SIZE).round();
}

void ModuleWidget::setGridPosition(math::Vec pos) {
	setPosition(pos * RACK_GRID_SIZE + RACK_OFFSET);
}

math::Vec ModuleWidget::getGridSize() {
	return (getSize() / RACK_GRID_SIZE).round();
}

math::Rect ModuleWidget::getGridBox() {
	return math::Rect(getGridPosition(), getGridSize());
}

math::Vec& ModuleWidget::dragOffset() {
	return internal->dragOffset;
}

bool& ModuleWidget::dragEnabled() {
	return internal->dragEnabled;
}

engine::Module* ModuleWidget::releaseModule() {
	engine::Module* module = this->module;
	this->module = NULL;
	return module;
}


} // namespace app
} // namespace rack
