// zlib open source license
//
// Copyright (c) 2018 to 2023 David Forsgren Piuva
// 
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// 
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 
//    1. The origin of this software must not be misrepresented; you must not
//    claim that you wrote the original software. If you use this software
//    in a product, an acknowledgment in the product documentation would be
//    appreciated but is not required.
// 
//    2. Altered source versions must be plainly marked as such, and must not be
//    misrepresented as being the original software.
// 
//    3. This notice may not be removed or altered from any source
//    distribution.

#include <cstdint>
#include "VisualComponent.h"

using namespace dsr;

PERSISTENT_DEFINITION(VisualComponent)

VisualComponent::VisualComponent() {}

VisualComponent::~VisualComponent() {
	this->callback_destroyEvent();
	// Let the children know that the parent component no longer exists.
	for (int i = 0; i < this->getChildCount(); i++) {
		this->children[i]->parent = nullptr;
	}
}

void VisualComponent::declareAttributes(StructureDefinition &target) const {
	target.declareAttribute(U"Name");
	target.declareAttribute(U"Index");
	target.declareAttribute(U"Visible");
	target.declareAttribute(U"Left");
	target.declareAttribute(U"Top");
	target.declareAttribute(U"Right");
	target.declareAttribute(U"Bottom");
}

Persistent* VisualComponent::findAttribute(const ReadableString &name) {
	if (string_caseInsensitiveMatch(name, U"Name")) {
		return &(this->name);
	} else if (string_caseInsensitiveMatch(name, U"Index")) {
		return &(this->index);
	} else if (string_caseInsensitiveMatch(name, U"Visible")) {
		return &(this->visible);
	} else if (string_caseInsensitiveMatch(name, U"Left")) {
		this->regionAccessed = true;
		return &(this->region.left);
	} else if (string_caseInsensitiveMatch(name, U"Top")) {
		this->regionAccessed = true;
		return &(this->region.top);
	} else if (string_caseInsensitiveMatch(name, U"Right")) {
		this->regionAccessed = true;
		return &(this->region.right);
	} else if (string_caseInsensitiveMatch(name, U"Bottom")) {
		this->regionAccessed = true;
		return &(this->region.bottom);
	} else {
		return nullptr;
	}
}

// Pre-condition: component != nullptr
// Post-condition: Returns the root of component
static Handle<VisualComponent> getRoot(VisualComponent *component) {
	assert(component != nullptr);
	while (component->parent != nullptr) {
		component = component->parent;
	}
	return component->getHandle();
}

IVector2D VisualComponent::getDesiredDimensions() {
	// Unless this virtual method is overridden, toolbars and such will try to give these dimensions to the component.
	return IVector2D(32, 32);
}

bool VisualComponent::isContainer() const {
	return true;
}

IRect VisualComponent::getLocation() {
	// If someone requested access to Left, Top, Right or Bottom, regionAccessed will be true
	if (this->regionAccessed) {
		// Now that a fixed location is requested, we need to recalculate the location from the flexible region based on parent dimensions
		this->updateLayout();
		this->regionAccessed = false;
	}
	return this->location;
}

void VisualComponent::changedAttribute(const ReadableString &name) {
	if (this->parent) {
		this->parent->childChanged = true;
	}
}

void VisualComponent::setRegion(const FlexRegion &newRegion) {
	this->region = newRegion;
	if (this->parent) {
		this->parent->childChanged = true;
	}
}

FlexRegion VisualComponent::getRegion() const {
	return this->region;
}

void VisualComponent::setVisible(bool visible) {
	this->visible.value = visible;
	if (this->parent) {
		this->parent->childChanged = true;
	}
}

bool VisualComponent::getVisible() const {
	return this->visible.value;
}

void VisualComponent::setName(const String& newName) {
	this->name.value = newName;
	if (this->parent) {
		this->parent->childChanged = true;
	}
}

String VisualComponent::getName() const {
	return this->name.value;
}

void VisualComponent::setIndex(int newIndex) {
	this->index.value = newIndex;
	if (this->parent) {
		this->parent->childChanged = true;
	}
}

int VisualComponent::getIndex() const {
	return this->index.value;
}

void VisualComponent::setLocation(const IRect &newLocation) {
	IRect oldLocation = this->location;
	this->location = newLocation;
	if (oldLocation != newLocation) {
		this->updateLocationEvent(oldLocation, newLocation);
	}
	this->changedLocation(oldLocation, newLocation);
}

void VisualComponent::updateLayout() {
	this->setLocation(this->region.getNewLocation(this->givenSpace));
}

void VisualComponent::applyLayout(const IRect& givenSpace) {
	this->givenSpace = givenSpace;
	this->updateLayout();
}

void VisualComponent::updateLocationEvent(const IRect& oldLocation, const IRect& newLocation) {
	// Place each child component
	for (int i = 0; i < this->getChildCount(); i++) {
		this->children[i]->applyLayout(IRect(0, 0, newLocation.width(), newLocation.height()));
	}
}

// Check if any change requires the child layout to update.
//   Used to realign members of toolbars after a desired dimension changed.
void VisualComponent::updateChildLocations() {
	if (this->childChanged) {
		this->updateLocationEvent(this->location, this->location);
		this->childChanged = false;
	}
}

// Overlays are only cropped by the entire canvas, so the offset is the upper left corner of component relative to the upper left corner of the canvas.
static void drawOverlays(ImageRgbaU8& targetImage, VisualComponent &component, const IVector2D& offset) {
	// Invisible components are not allowed to display overlays, because the component system is
	//   responsible for visibility settings that specific components are likely to forget about.
	if (component.getVisible() && component.ownsOverlay()) {
		// Check if the component has the overlay shown.
		if (component.showingOverlay()) {
			// Draw the component's own overlay below child overlays. 
			component.drawOverlay(targetImage, offset - component.location.upperLeft());
		}
		// Draw overlays in each child component on top.
		for (int i = 0; i < component.getChildCount(); i++) {
			drawOverlays(targetImage, component.children[i].getReference(), offset + component.children[i]->location.upperLeft());
		}
	}
}

// Offset may become non-zero when the origin is outside of targetImage from being clipped outside of the parent region
void VisualComponent::draw(ImageRgbaU8& targetImage, const IVector2D& offset) {
	// TODO: Any more good places to send notifications to make the GUI respond faster?
	// When about to start drawing from the root, check for state changes and handle events before drawing,
	//   so that anything needed for visuals is handled without further delay.
	if (this->parent == nullptr) {
		this->sendNotifications();
	}
	if (this->getVisible()) {
		this->updateChildLocations();
		IRect containerBound = this->getLocation() + offset;
		this->drawSelf(targetImage, containerBound);
		// Draw each child component
		if (!this->managesChildren()) {
			for (int i = 0; i < this->getChildCount(); i++) {
				this->children[i]->drawClipped(targetImage, containerBound.upperLeft(), containerBound);
			}
		}
		// When drawing the root, start recursive drawing of all overlays.
		if (this->parent == nullptr) {
			drawOverlays(targetImage, *this, this->location.upperLeft());
		}
	}
}

void VisualComponent::drawClipped(ImageRgbaU8& targetImage, const IVector2D& offset, const IRect& clipRegion) {
	IRect finalRegion = IRect::cut(clipRegion, IRect(0, 0, image_getWidth(targetImage), image_getHeight(targetImage)));
	if (finalRegion.hasArea()) {
		// TODO: Optimize allocation of sub-images
		ImageRgbaU8 target = image_getSubImage(targetImage, finalRegion);
		this->draw(target, offset - finalRegion.upperLeft());
	}
}

// A red rectangle is drawn as a placeholder if the class couldn't be found
// TODO: Should the type name be remembered in the base class for serializing missing components?
void VisualComponent::drawSelf(ImageRgbaU8& targetImage, const IRect &relativeLocation) {
	draw_rectangle(targetImage, relativeLocation, ColorRgbaI32(200, 50, 50, 255));
}

void VisualComponent::drawOverlay(ImageRgbaU8& targetImage, const IVector2D &absoluteOffset) {}

// Manual use with the correct type
void VisualComponent::addChildComponent(Handle<VisualComponent> child) {
	if (!this->isContainer()) {
		sendWarning(U"Cannot attach a child to a non-container parent component!\n");
	} else if (child.getUnsafe() == this) {
		sendWarning(U"Cannot attach a component to itself!\n");
	} else if (child->hasChild(this)) {
		sendWarning(U"Cannot attach to its own parent as a child component!\n");
	} else {
		// Remove from any previous parent
		child->detachFromParent();
		// Update layout based on the new parent size
		child->applyLayout(IRect(0, 0, this->location.width(), this->location.height()));
		// Connect to the new parent
		this->children.push(child);
		this->childChanged = true;
		child->parent = this;
		child->window = this->window;
	}
}

// Automatic insertion from loading
bool VisualComponent::addChild(Handle<Persistent> child) {
	// Try to cast from base class Persistent to derived class VisualComponent
	Handle<VisualComponent> visualComponent = handle_dynamicCast<VisualComponent>(child);
	if (visualComponent.isNull()) {
		return false; // Wrong type!
	} else {
		this->addChildComponent(visualComponent);
		return true; // Success!
	}
}

int VisualComponent::getChildCount() const {
	return this->children.length();
}

Handle<Persistent> VisualComponent::getChild(int index) const {
	if (index >= 0 && index < this->children.length()) {
		return this->children[index];
	} else {
		return Handle<Persistent>(); // Null handle for out of bound.
	}
}

static void detachFromWindow(Handle<VisualComponent> component) {
	component->window = Handle<BackendWindow>();
	for (int c = 0; c < component->children.length(); c++) {
		detachFromWindow(component->children[c]);
	}
}

void VisualComponent::detachFromParent() {
	// Check if there's a parent component
	VisualComponent *parent = this->parent;
	if (parent != nullptr) {
		parent->childChanged = true;
		// Find the component to detach among the child components.
		for (int i = 0; i < parent->getChildCount(); i++) {
			Handle<VisualComponent> current = parent->children[i];
			if (current.getUnsafe() == this) {
				// Disconnect child from backend window.
				detachFromWindow(parent->children[i]);
				// Disconnect parent from child.
				current->parent = nullptr;
				// Disconnect child from parent.
				parent->children.remove(i);
				return;
			}
		}
		// Update indirect states.
		getRoot(this)->updateIndirectStates();
		// Any ongoing drag action will allow the component to get the mouse up event to finish transactions safely before being deleted by reference counting.
		//   Otherwise it may break program logic or cause crashes.
	}
}

bool VisualComponent::hasChild(VisualComponent *child) const {
	for (int i = 0; i < this->getChildCount(); i++) {
		Handle<VisualComponent> current = this->children[i];
		if (current.getUnsafe() == child) {
			return true; // Found the component
		} else {
			if (current->hasChild(child)) {
				return true; // Found the component recursively
			}
		}
	}
	return false; // Could not find the component
}

bool VisualComponent::hasChild(Handle<VisualComponent> child) const {
	return this->hasChild(child.getUnsafe());
}

Handle<VisualComponent> VisualComponent::findChildByName(ReadableString name) const {
	for (int i = 0; i < this->getChildCount(); i++) {
		Handle<VisualComponent> current = this->children[i];
		if (string_match(current->getName(), name)) {
			return current; // Found the component
		} else {
			Handle<VisualComponent> searchResult = current->findChildByName(name);
			if (searchResult.isNotNull()) {
				return searchResult; // Found the component recursively
			}
		}
	}
	return Handle<VisualComponent>(); // Could not find the component
}

Handle<VisualComponent> VisualComponent::findChildByNameAndIndex(ReadableString name, int index) const {
	for (int i = 0; i < this->getChildCount(); i++) {
		Handle<VisualComponent> current = this->children[i];
		if (string_match(current->getName(), name) && current->getIndex() == index) {
			return current; // Found the component
		} else {
			Handle<VisualComponent> searchResult = current->findChildByNameAndIndex(name, index);
			if (searchResult.isNotNull()) {
				return searchResult; // Found the component recursively
			}
		}
	}
	return Handle<VisualComponent>(); // Could not find the component
}

bool VisualComponent::pointIsInside(const IVector2D& pixelPosition) {
	return pixelPosition.x > this->location.left() && pixelPosition.x < this->location.right()
	    && pixelPosition.y > this->location.top() && pixelPosition.y < this->location.bottom();
}

bool VisualComponent::pointIsInsideOfOverlay(const IVector2D& pixelPosition) {
	return false;
}

bool VisualComponent::pointIsInsideOfHover(const IVector2D& pixelPosition) {
	return this->pointIsInside(pixelPosition);
}

// Non-recursive top-down search
Handle<VisualComponent> VisualComponent::getDirectChild(const IVector2D& pixelPosition) {
	// Iterate child components in reverse drawing order
	for (int i = this->getChildCount() - 1; i >= 0; i--) {
		Handle<VisualComponent> currentChild = this->children[i];
		// Check if the point is inside the child component
		if (currentChild->getVisible() && currentChild->pointIsInside(pixelPosition)) {
			return currentChild;
		}
	}
	// Return nothing if the point missed all child components
	return Handle<VisualComponent>();
}

// Create a Handle to the component.
Handle<VisualComponent> VisualComponent::getHandle() {
	#ifdef SAFE_POINTER_CHECKS
		return Handle<VisualComponent>(this, heap_getHeader(this)->allocationIdentity);
	#else
		return Handle<VisualComponent>(this);
	#endif
}

void VisualComponent::updateStateEvent(ComponentState oldState, ComponentState newState) {}

void VisualComponent::updateIndirectStates() {
	// Call recursively for child components while checking what they contain.
	ComponentState childStates = 0;
	for (int i = this->getChildCount() - 1; i >= 0; i--) {
		this->children[i]->updateIndirectStates();
		childStates |= this->children[i]->currentState;
	}
	// Direct and indirect inheritance.
	ComponentState expectedIndirectStates = ((childStates & componentState_direct) << 1) | (childStates & componentState_indirect);
	this->currentState = (this->currentState & componentState_direct) | expectedIndirectStates;
}

void VisualComponent::sendNotifications() {
	// Call recursively for child components while checking what they contain.
	//   Run the loop backwards, so that no components are missed when once is detached.
	for (int i = this->getChildCount() - 1; i >= 0; i--) {
		// Use a reference counted pointer to the child, so that it can be removed safely outside of custom events.
		Handle<VisualComponent> child = this->children[i];
		if (child->detach) {
			child->detach = false;
			child->detachFromParent();
		} else {
			child->sendNotifications();
		}
	}
	// Detect differences for all flags at once using bits in the integers.
	if (this->currentState != this->previousState) {
		updateStateEvent(this->previousState, this->currentState);
		this->previousState = this->currentState;
	}
}

// Find the topmost overlay by searching backwards with the parent last and returning a pointer to the component.
// The point is relative to the upper left corner of component.
static VisualComponent *getTopmostOverlay(VisualComponent *component, const IVector2D &point) {
	// Only visible component may show its overlay or child components.
	if (component->getVisible()) {
		// Go through child components in reverse draw order to stop when reaching the one that is visible.
		for (int i = component->getChildCount() - 1; i >= 0; i--) {
			VisualComponent *result = getTopmostOverlay(component->children[i].getUnsafe(), point - component->children[i]->location.upperLeft());
			if (result != nullptr) return result;
		}
		// Check itself behind child overlays.
		if (component->showingOverlay() && component->pointIsInsideOfOverlay(point + component->location.upperLeft())) {
			return component;
		} else {
			return nullptr;
		}
	} else {
		return nullptr;
	}
}

// Get the upper left corner of child relative to the upper left corner of parent.
//   If parent is null or not a parent of child, then child's offset is relative to the window's canvas.
static IVector2D getTotalOffset(const VisualComponent *child, const VisualComponent *parent = nullptr) {
	IVector2D result;
	while ((child != nullptr) && (child != parent)) {
		result += child->location.upperLeft();
		child = child->parent;
	}
	return result;
}

// Remove its pointer to its child and the whole trail of focus.
void VisualComponent::defocusChildren() {
	for (int i = 0; i < this->getChildCount(); i++) {
		this->children[i]->applyStateAndMask(~componentState_focus);
	}
}

void VisualComponent::addStateBits(ComponentState directStates, bool unique) {
	Handle<VisualComponent> root = getRoot(this);
	// Remove all focus in the window if unique.
	if (unique) root->applyStateAndMask(~directStates);
	// Apply state directly to itself and indirectly to parents.
	this->currentState |= directStates;
	// Update indirect states, so that parent components know what happens to their child components.
	root->updateIndirectStates();
}

void VisualComponent::removeStateBits(ComponentState directStates) {
	Handle<VisualComponent> root = getRoot(this);
	// Remove state directly from itself and indirectly from parents.
	this->currentState &= ~directStates;
	// Update indirect states, so that parent components know what happens to their child components.
	root->updateIndirectStates();
}

// Create a chain of pointers from the root to this component
//   Any focus pointers that are not along the chain will not count but work as a memory for when one of its parents get focus again.
void VisualComponent::makeFocused() {
	this->addStateBits(componentState_focus, true);
}

void VisualComponent::hover() {
	this->addStateBits(componentState_hoverDirect, true);
}

void VisualComponent::leave() {
	this->removeStateBits(componentState_hoverDirect);
}

void VisualComponent::showOverlay() {
	this->addStateBits(componentState_showingOverlayDirect, false);
}

// When multiple components are allowed to have the direct flag set, one needs to clean it up like a tree.
void VisualComponent::hideOverlay() {
	this->removeStateBits(componentState_showingOverlayDirect);
}

void VisualComponent::applyStateAndMask(ComponentState keepMask) {
	this->currentState &= keepMask;
	for (int i = 0; i < this->getChildCount(); i++) {
		this->children[i]->applyStateAndMask(keepMask);
	}
}

// Takes events with points relative to the upper left corner of the called component.
void VisualComponent::sendMouseEvent(const MouseEvent& event, bool recursive) {
	if (this->parent == nullptr && !recursive) {
		// Use a combined bit mask for any state that needs to be reset at this time.
		this->applyStateAndMask(~(componentState_hover));
		// Update the layout if needed.
		this->updateChildLocations();
	}
	// Get the point of interaction within the component being sent to,
	//   so that it can be used to find direct child components expressed
	//   relative to their container's upper left corner.
	// If a button is pressed down, this method will try to grab a component to begin mouse interaction.
	//   Grabbing with the dragComponent pointer makes sure that move and up events can be given even if the cursor moves outside of the component.
	VisualComponent *childComponent = nullptr;
	// Find the component to interact with.
	if (event.mouseEventType == MouseEventType::MouseDown || this->dragComponent.isNull()) {
		// Check the overlays first when getting mouse events to the root component.
		if (this->parent == nullptr) {
			childComponent = getTopmostOverlay(this, event.position);
		}
		// Check for direct child components for passing on the event recursively.
		//   The sendMouseEvent method can be called recursively from a member of an overlay, so we can't know
		//   which component is at the top without asking the components that manage interaction with their children.
		if (childComponent == nullptr && !this->managesChildren()) {
			Handle<VisualComponent> nextContainer = this->getDirectChild(event.position);
			if (nextContainer.isNotNull()) {
				childComponent = nextContainer.getUnsafe();
			}
		}
	} else if (dragComponent.isNotNull()) {
		// If we're grabbing a component, keep sending events to it.
		childComponent = this->dragComponent.getUnsafe();
	}
	// Grab any detected component on mouse down events.
	if (event.mouseEventType == MouseEventType::MouseDown && childComponent != nullptr) {
		childComponent->makeFocused();
		this->dragComponent = childComponent->getHandle();
		this->holdCount++;
	}
	// Send the signal to a child component or itself.
	if (childComponent != nullptr) {		
		// Recalculate local offset through one or more levels of ownership.
		IVector2D offset = getTotalOffset(childComponent, this);
		MouseEvent localEvent = event;
		localEvent.position = event.position - offset;
		childComponent->sendMouseEvent(localEvent);
	} else {
		// If there is no child component found, interact directly with the parent.
		MouseEvent parentEvent = event;
		parentEvent.position += this->location.upperLeft();
		// Itself is directly hovered.
		if (pointIsInsideOfHover(parentEvent.position)) {
			this->hover();
		} else {
			this->leave();
		}
		// If the event receiver pass it on to child components, it can just reset the hover flags again.
		this->receiveMouseEvent(parentEvent);
	}
	// Release a component on mouse up.
	if (event.mouseEventType == MouseEventType::MouseUp) {
		this->holdCount--;
		if (this->holdCount <= 0) {
			this->dragComponent = Handle<VisualComponent>(); // Abort drag.
			// Reset when we had more up than down events, in case that the root panel was created with a button already pressed.
			this->holdCount = 0;
		}
	}
	// Once all focusing and defocusing with arbitrary callbacks is over, send the focus notifications to the components that actually changed focus.
	if (this->parent == nullptr && !recursive) {
		//Should not be needed if everything works. this->updateIndirectStates();
		this->sendNotifications();
	}
}

void VisualComponent::receiveMouseEvent(const MouseEvent& event) {
	if (event.mouseEventType == MouseEventType::MouseDown) {
		this->callback_mouseDownEvent(event);
	} else if (event.mouseEventType == MouseEventType::MouseUp) {
		this->callback_mouseUpEvent(event);
	} else if (event.mouseEventType == MouseEventType::MouseMove) {
		this->callback_mouseMoveEvent(event);
	} else if (event.mouseEventType == MouseEventType::Scroll) {
		this->callback_mouseScrollEvent(event);
	}
}

void VisualComponent::sendKeyboardEvent(const KeyboardEvent& event) {
	for (int i = 0; i < this->getChildCount(); i++) {
		ComponentState state = this->children[i]->currentState;
		if (state & componentState_focus) {
			if (state & componentState_focusDirect) {
				this->children[i]->receiveKeyboardEvent(event);
			} else if (state & componentState_focusIndirect) {
				this->children[i]->sendKeyboardEvent(event);
			}
		}
	}
	// Check for any state updates.
	if (this->parent == nullptr) {
		//Should not be needed if everything works. this->updateIndirectStates();
		this->sendNotifications();
	}
}

void VisualComponent::receiveKeyboardEvent(const KeyboardEvent& event) {
	if (event.keyboardEventType == KeyboardEventType::KeyDown) {
		this->callback_keyDownEvent(event);
	} else if (event.keyboardEventType == KeyboardEventType::KeyUp) {
		this->callback_keyUpEvent(event);
	} else if (event.keyboardEventType == KeyboardEventType::KeyType) {
		this->callback_keyTypeEvent(event);
	}
}

void VisualComponent::applyTheme(VisualTheme theme) {
	this->theme = theme;
	this->changedTheme(theme);
	for (int i = 0; i < this->getChildCount(); i++) {
		this->children[i]->applyTheme(theme);
	}
}

VisualTheme VisualComponent::getTheme() const {
	return this->theme;
}

void VisualComponent::changedTheme(VisualTheme newTheme) {}

String VisualComponent::call(const ReadableString &methodName, const ReadableString &arguments) {
	sendWarning("Unimplemented custom call received");
	return U"";
}

bool VisualComponent::managesChildren() {
	return false;
}

MediaResult dsr::component_generateImage(VisualTheme theme, MediaMethod &method, int width, int height, int red, int green, int blue, int pressed, int focused, int hovered) {
	return method.callUsingKeywords([&theme, &method, width, height, red, green, blue, pressed, focused, hovered](MediaMachine &machine, int methodIndex, int inputIndex, const ReadableString &argumentName) {
		if (string_caseInsensitiveMatch(argumentName, U"width")) {
			machine_setInputByIndex(machine, methodIndex, inputIndex, width);
		} else if (string_caseInsensitiveMatch(argumentName, U"height")) {
			machine_setInputByIndex(machine, methodIndex, inputIndex, height);
		} else if (string_caseInsensitiveMatch(argumentName, U"pressed")) {
			machine_setInputByIndex(machine, methodIndex, inputIndex, pressed);
		} else if (string_caseInsensitiveMatch(argumentName, U"focused")) {
			machine_setInputByIndex(machine, methodIndex, inputIndex, focused);
		} else if (string_caseInsensitiveMatch(argumentName, U"hovered")) {
			machine_setInputByIndex(machine, methodIndex, inputIndex, hovered);
		} else if (string_caseInsensitiveMatch(argumentName, U"red")) {
			machine_setInputByIndex(machine, methodIndex, inputIndex, red);
		} else if (string_caseInsensitiveMatch(argumentName, U"green")) {
			machine_setInputByIndex(machine, methodIndex, inputIndex, green);
		} else if (string_caseInsensitiveMatch(argumentName, U"blue")) {
			machine_setInputByIndex(machine, methodIndex, inputIndex, blue);
		} else if (theme_assignMediaMachineArguments(theme, method.contextIndex, machine, methodIndex, inputIndex, argumentName)) {
			// Assigned by theme_assignMediaMachineArguments.
		} else {
			// TODO: Ask the theme for the argument using a specified style class for variations between different types of buttons, checkboxes, panels, et cetera.
			//       Send a warning if the theme did not provide an input argument to its own media function.
			sendWarning(U"Unhandled setting \"", argumentName, U"\" requested by the media method \"", machine_getMethodName(machine, methodIndex), U"\" in the visual theme!\n");
		}
	});
}
