#pragma once
#include <common.hpp>
#include <math.hpp>
#include <vector>
#include <set>



/** Remaps Ctrl to Cmd on Mac
Use this instead of GLFW_MOD_CONTROL, since Cmd should be used on Mac in place of Ctrl on Linux/Windows.
*/
#if defined ARCH_MAC
	#define RACK_MOD_CTRL GLFW_MOD_SUPER
	#define RACK_MOD_CTRL_NAME "Cmd"
#else
	#define RACK_MOD_CTRL GLFW_MOD_CONTROL
	#define RACK_MOD_CTRL_NAME "Ctrl"
#endif
#define RACK_MOD_SHIFT_NAME "Shift"
#define RACK_MOD_ALT_NAME "Alt"

/** Filters actual mod keys from the mod flags.
Use this if you don't care about GLFW_MOD_CAPS_LOCK and GLFW_MOD_NUM_LOCK.
Example usage:
	if ((e.mod & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) ...
*/
#define RACK_MOD_MASK (GLFW_MOD_SHIFT | GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER)


namespace rack {


namespace widget {
struct Widget;
}


/** Handles user interaction with Widget.
*/
namespace event {


/** A per-event state shared and writable by all widgets that recursively handle an event. */
struct Context {
	/** Whether the event should continue recursing to children Widgets. */
	bool propagating = true;
	/** Whether the event has been consumed by an event handler and no more handlers should consume the event. */
	bool consumed = false;
	/** The widget that responded to the event. */
	widget::Widget* target = NULL;
};


/** Base class for all events. */
struct Base {
	Context* context = NULL;

	/** Prevents the event from being handled by more Widgets.
	*/
	void stopPropagating() const {
		if (!context)
			return;
		context->propagating = false;
	}
	bool isPropagating() const {
		if (!context)
			return true;
		return context->propagating;
	}
	/** Tells the event handler that a particular Widget consumed the event.
	You usually want to stop propagation as well, so call consume() instead.
	*/
	void setTarget(widget::Widget* w) const {
		if (!context)
			return;
		context->target = w;
	}
	widget::Widget* getTarget() const {
		if (!context)
			return NULL;
		return context->target;
	}
	/** Sets the target Widget and stops propagating.
	A NULL Widget may be passed to consume but not set a target.
	*/
	void consume(widget::Widget* w) const {
		if (!context)
			return;
		context->propagating = false;
		context->consumed = true;
		context->target = w;
	}
	bool isConsumed() const {
		if (!context)
			return false;
		return context->consumed;
	}
};


/** An event prototype with a vector position. */
struct PositionBase {
	/** The pixel coordinate where the event occurred, relative to the Widget it is called on. */
	math::Vec pos;
};


#define RACK_HELD 3


/** An event prototype with a GLFW key. */
struct KeyBase {
	/** GLFW_KEY_* */
	int key;
	/** GLFW_KEY_*. You should usually use `key` instead. */
	int scancode;
	/** GLFW_RELEASE, GLFW_PRESS, GLFW_REPEAT, or RACK_HELD */
	int action;
	/** GLFW_MOD_* */
	int mods;
};


/** An event prototype with a Unicode character. */
struct TextBase {
	/** Unicode code point of the character */
	int codepoint;
};


/** Occurs every frame when the mouse is hovering over a Widget.
Recurses.
Consume this event to allow Enter and Leave to occur.
*/
struct Hover : Base, PositionBase {
	/** Change in mouse position since the last frame. Can be zero. */
	math::Vec mouseDelta;
};


/** Occurs each mouse button press or release.
Recurses.
Consume this event to allow DoubleClick, Select, Deselect, SelectKey, SelectText, DragStart, DragEnd, DragMove, and DragDrop to occur.
*/
struct Button : Base, PositionBase {
	/** GLFW_MOUSE_BUTTON_LEFT, GLFW_MOUSE_BUTTON_RIGHT, GLFW_MOUSE_BUTTON_MIDDLE, etc. */
	int button;
	/** GLFW_PRESS or GLFW_RELEASE */
	int action;
	/** GLFW_MOD_* */
	int mods;
};


/** Occurs when the left mouse button is pressed a second time on the same Widget within a time duration.
Must consume the Button event (on left button press) to receive this event.
*/
struct DoubleClick : Base {
};


/** Occurs when a key is pressed, released, or repeated while the mouse is hovering a Widget.
Recurses.
*/
struct HoverKey : Base, PositionBase, KeyBase {
};


/** Occurs when a character is typed while the mouse is hovering a Widget.
Recurses.
*/
struct HoverText : Base, PositionBase, TextBase {
};


/** Occurs when the mouse scroll wheel is moved while the mouse is hovering a Widget.
Recurses.
*/
struct HoverScroll : Base, PositionBase {
	/** Change of scroll wheel position. */
	math::Vec scrollDelta;
};


/** Occurs when a Widget begins consuming the Hover event.
Must consume the Hover event to receive this event.
The target sets `hoveredWidget`, which allows Leave to occur.
*/
struct Enter : Base {
};


/** Occurs when a different Widget is entered.
Must consume the Hover event (when a Widget is entered) to receive this event.
*/
struct Leave : Base {
};


/** Occurs when a Widget begins consuming the Button press event for the left mouse button.
Must consume the Button event (on left button press) to receive this event.
The target sets `selectedWidget`, which allows SelectText and SelectKey to occur.
*/
struct Select : Base {
};


/** Occurs when a different Widget is selected.
Must consume the Button event (on left button press, when the Widget is selected) to receive this event.
*/
struct Deselect : Base {
};


/** Occurs when a key is pressed, released, or repeated while a Widget is selected.
Must consume to prevent HoverKey from being triggered.
*/
struct SelectKey : Base, KeyBase {
};


/** Occurs when text is typed while a Widget is selected.
Must consume to prevent HoverKey from being triggered.
*/
struct SelectText : Base, TextBase {
};


struct DragBase : Base {
	/** The mouse button held while dragging. */
	int button;
};


/** Occurs when a Widget begins being dragged.
Must consume the Button event (on press) to receive this event.
The target sets `draggedWidget`, which allows DragEnd, DragMove, DragHover, DragEnter, and DragDrop to occur.
*/
struct DragStart : DragBase {
};


/** Occurs when a Widget stops being dragged by releasing the mouse button.
Must consume the Button event (on press, when the Widget drag begins) to receive this event.
*/
struct DragEnd : DragBase {
};


/** Occurs every frame on the dragged Widget.
Must consume the Button event (on press, when the Widget drag begins) to receive this event.
*/
struct DragMove : DragBase {
	/** Change in mouse position since the last frame. Can be zero. */
	math::Vec mouseDelta;
};


/** Occurs every frame when the mouse is hovering over a Widget while another Widget (possibly the same one) is being dragged.
Recurses.
Consume this event to allow DragEnter and DragLeave to occur.
*/
struct DragHover : DragBase, PositionBase {
	/** The dragged widget */
	widget::Widget* origin = NULL;
	/** Change in mouse position since the last frame. Can be zero. */
	math::Vec mouseDelta;
};


/** Occurs when the mouse enters a Widget while dragging.
Must consume the DragHover event to receive this event.
The target sets `draggedWidget`, which allows DragLeave to occur.
*/
struct DragEnter : DragBase {
	/** The dragged widget */
	widget::Widget* origin = NULL;
};


/** Occurs when the mouse leaves a Widget while dragging.
Must consume the DragHover event (when the Widget is entered) to receive this event.
*/
struct DragLeave : DragBase {
	/** The dragged widget */
	widget::Widget* origin = NULL;
};


/** Occurs when the mouse button is released over a Widget while dragging.
Must consume the Button event (on release) to receive this event.
*/
struct DragDrop : DragBase {
	/** The dragged widget */
	widget::Widget* origin = NULL;
};


/** Occurs when a selection of files from the operating system is dropped onto a Widget.
Recurses.
*/
struct PathDrop : Base, PositionBase {
	PathDrop(const std::vector<std::string>& paths) : paths(paths) {}

	/** List of file paths in the dropped selection */
	const std::vector<std::string>& paths;
};


/** Occurs after a certain action is triggered on a Widget.
The concept of an "action" is defined by the type of Widget.
*/
struct Action : Base {
};


/** Occurs after the value of a Widget changes.
The concept of a "value" is defined by the type of Widget.
*/
struct Change : Base {
};


/** Occurs after the zoom level of a Widget is changed.
Recurses.
*/
struct Zoom : Base {
};


/** Occurs after a Widget's position is set by Widget::setPosition().
*/
struct Reposition : Base {
};


/** Occurs after a Widget's size is set by Widget::setSize().
*/
struct Resize : Base {
};


/** Occurs after a Widget is added to a parent.
*/
struct Add : Base {
};


/** Occurs before a Widget is removed from its parent.
*/
struct Remove : Base {
};


/** Occurs after a Widget is shown with Widget::show().
Recurses.
*/
struct Show : Base {
};


/** Occurs after a Widget is hidden with Widget::hide().
Recurses.
*/
struct Hide : Base {
};


struct State {
	widget::Widget* rootWidget = NULL;
	/** State widgets
	Don't set these directly unless you know what you're doing. Use the set*() methods instead.
	*/
	widget::Widget* hoveredWidget = NULL;
	widget::Widget* draggedWidget = NULL;
	int dragButton = 0;
	widget::Widget* dragHoveredWidget = NULL;
	widget::Widget* selectedWidget = NULL;
	/** For double-clicking */
	double lastClickTime = -INFINITY;
	widget::Widget* lastClickedWidget = NULL;
	std::set<int> heldKeys;

	widget::Widget* getRootWidget() {
		return rootWidget;
	}
	widget::Widget* getHoveredWidget() {
		return hoveredWidget;
	}
	widget::Widget* getDraggedWidget() {
		return draggedWidget;
	}
	widget::Widget* getDragHoveredWidget() {
		return dragHoveredWidget;
	}
	widget::Widget* getSelectedWidget() {
		return selectedWidget;
	}

	void setHovered(widget::Widget* w);
	void setDragged(widget::Widget* w, int button);
	void setDragHovered(widget::Widget* w);
	void setSelected(widget::Widget* w);
	/** Prepares a widget for deletion */
	void finalizeWidget(widget::Widget* w);

	bool handleButton(math::Vec pos, int button, int action, int mods);
	bool handleHover(math::Vec pos, math::Vec mouseDelta);
	bool handleLeave();
	bool handleScroll(math::Vec pos, math::Vec scrollDelta);
	bool handleText(math::Vec pos, int codepoint);
	bool handleKey(math::Vec pos, int key, int scancode, int action, int mods);
	bool handleDrop(math::Vec pos, const std::vector<std::string>& paths);
	bool handleZoom();
};


} // namespace event
} // namespace rack
