#include <deque>

#include "ppsspp_config.h"

#include "Common/Input/InputState.h"
#include "Common/Log.h"
#include "Common/TimeUtil.h"
#include "Common/UI/Root.h"
#include "Common/UI/ViewGroup.h"

namespace UI {

static std::vector<int> focusMoves;
extern bool focusForced;

static View *focusedView;
static bool focusMovementEnabled;
bool focusForced;

static std::function<void(UISound)> soundCallback;

struct DispatchQueueItem {
	Event *e;
	EventParams params;
};

std::deque<DispatchQueueItem> g_dispatchQueue;

void EventTriggered(Event *e, EventParams params) {
	DispatchQueueItem item{ e, params };
	g_dispatchQueue.push_front(item);
}

DialogResult DispatchEvents() {
	DialogResult result = DR_NONE;
	while (!g_dispatchQueue.empty()) {
		DispatchQueueItem item;
		if (g_dispatchQueue.empty())
			break;
		item = g_dispatchQueue.back();
		g_dispatchQueue.pop_back();
		if (item.e) {
			item.e->Dispatch(item.params);
			if (item.params.bubbleResult != DR_NONE) {
				result = item.params.bubbleResult;
			}
		}
	}
	return result;
}

void RemoveQueuedEventsByView(View *view) {
	for (auto it = g_dispatchQueue.begin(); it != g_dispatchQueue.end(); ) {
		if (it->params.v == view) {
			it = g_dispatchQueue.erase(it);
		} else {
			++it;
		}
	}
}

void RemoveQueuedEventsByEvent(Event *event) {
	for (auto it = g_dispatchQueue.begin(); it != g_dispatchQueue.end(); ) {
		if (it->e == event) {
			it = g_dispatchQueue.erase(it);
		} else {
			++it;
		}
	}
}

View *GetFocusedView() {
	return focusedView;
}

void SetFocusedView(View *view, bool force) {
	if (focusedView) {
		focusedView->FocusChanged(FF_LOSTFOCUS);
	}
	focusedView = view;
	if (focusedView) {
		focusedView->FocusChanged(FF_GOTFOCUS);
		if (force) {
			focusForced = true;
		}
	}
}

void EnableFocusMovement(bool enable) {
	// INFO_LOG(Log::UI, "EnableFocusMovement: %s", enable ? "true" : "false");
	focusMovementEnabled = enable;
	if (!enable) {
		if (focusedView) {
			focusedView->FocusChanged(FF_LOSTFOCUS);
		}
		focusedView = nullptr;
	}
}

bool IsFocusMovementEnabled() {
	return focusMovementEnabled;
}

void LayoutViewHierarchy(const UIContext &dc, const UI::Margins &rootMargins, ViewGroup *root, bool ignoreInsets, bool ignoreBottomInset) {
	Bounds rootBounds = ignoreInsets ? dc.GetBounds() : dc.GetLayoutBounds(ignoreBottomInset);

	MeasureSpec horiz(EXACTLY, rootBounds.w - (rootMargins.left + rootMargins.right));
	MeasureSpec vert(EXACTLY, rootBounds.h - (rootMargins.top + rootMargins.bottom));

	// Two phases - measure contents, layout.
	root->Measure(dc, horiz, vert);
	// Root has a specified size. Set it, then let root layout all its children.
	rootBounds.x += rootMargins.left;
	rootBounds.y += rootMargins.top;
	rootBounds.w -= rootMargins.left + rootMargins.right;
	rootBounds.h -= rootMargins.top + rootMargins.bottom;
	root->SetBounds(rootBounds);
	root->Layout();
}

static void MoveFocus(ViewGroup *root, FocusDirection direction) {
	View *focusedView = GetFocusedView();
	if (!focusedView) {
		// Nothing was focused when we got in here. Focus the first non-group in the hierarchy.
		root->SetFocus();
		return;
	}

	NeighborResult neigh = root->FindNeighbor(focusedView, direction, NeighborResult());
	if (neigh.view) {
		neigh.view->SetFocus();
		root->SubviewFocused(neigh.view);

		PlayUISound(UISound::SELECT);
	}
}

void SetSoundCallback(std::function<void(UISound)> func) {
	soundCallback = func;
}

void PlayUISound(UISound sound) {
	if (soundCallback) {
		soundCallback(sound);
	}
}

// TODO: Figure out where this should really live.
// Simple simulation of key repeat on platforms and for gamepads where we don't
// automatically get it.

static int frameCount;

// Ignore deviceId when checking for matches. Turns out that Ouya for example sends
// completely broken input where the original keypresses have deviceId = 10 and the repeats
// have deviceId = 0.
struct HeldKey {
	InputKeyCode key;
	InputDeviceID deviceId;
	double triggerTime;

	// Ignores startTime
	bool operator <(const HeldKey &other) const {
		if (key < other.key) return true;
		return false;
	}
	bool operator ==(const HeldKey &other) const { return key == other.key; }
};

static std::set<HeldKey> heldKeys;

const double repeatDelay = 15 * (1.0 / 60.0f);  // 15 frames like before.
const double repeatInterval = 5 * (1.0 / 60.0f);  // 5 frames like before.

bool IsScrollKey(const KeyInput &input) {
	switch (input.keyCode) {
	case NKCODE_PAGE_UP:
	case NKCODE_PAGE_DOWN:
	case NKCODE_MOVE_HOME:
	case NKCODE_MOVE_END:
		return true;
	default:
		return false;
	}
}

static KeyEventResult KeyEventToFocusMoves(const KeyInput &key) {
	KeyEventResult retval = KeyEventResult::PASS_THROUGH;
	// Ignore repeats for focus moves.
	if ((key.flags & KeyInputFlags::DOWN) && !(key.flags & KeyInputFlags::IS_REPEAT)) {
		if (IsDPadKey(key) || IsScrollKey(key)) {
			// Let's only repeat DPAD initially.
			HeldKey hk;
			hk.key = key.keyCode;
			hk.deviceId = key.deviceId;
			hk.triggerTime = time_now_d() + repeatDelay;

			// Check if the key is already held. If it is, ignore it. This is to avoid
			// multiple key repeat mechanisms colliding.
			if (heldKeys.find(hk) != heldKeys.end()) {
				return KeyEventResult::IGNORE_KEY;
			}

			heldKeys.insert(hk);
			focusMoves.push_back(key.keyCode);
			retval = KeyEventResult::ACCEPT;
		}
	}
	if (key.flags & KeyInputFlags::UP) {
		// We ignore the device ID here (in the comparator for HeldKey), due to the Ouya quirk mentioned above.
		if (!heldKeys.empty()) {
			HeldKey hk;
			hk.key = key.keyCode;
			hk.deviceId = key.deviceId;
			hk.triggerTime = 0.0; // irrelevant
			if (heldKeys.find(hk) != heldKeys.end()) {
				heldKeys.erase(hk);
				retval = KeyEventResult::ACCEPT;
			}
		}
	}
	return retval;
}

KeyEventResult UnsyncKeyEvent(const KeyInput &key, ViewGroup *root) {
	KeyEventResult retval = KeyEventToFocusMoves(key);

	// Ignore volume keys and stuff here. Not elegant but need to propagate bools through the view hierarchy as well...
	switch (key.keyCode) {
	case NKCODE_VOLUME_DOWN:
	case NKCODE_VOLUME_UP:
	case NKCODE_VOLUME_MUTE:
		retval = KeyEventResult::PASS_THROUGH;
		break;
	default:
		if (!(key.flags & KeyInputFlags::IS_REPEAT)) {
			// If a repeat, we follow what KeyEventToFocusMoves set it to.
			// Otherwise we signal that we used the key, always.
			retval = KeyEventResult::ACCEPT;
		}
		break;
	}
	return retval;
}

bool KeyEvent(const KeyInput &key, ViewGroup *root) {
	return root->Key(key);
}

void TouchEvent(const TouchInput &touch, ViewGroup *root) {
	focusForced = false;
	root->Touch(touch);
	if ((touch.flags & TouchInputFlags::DOWN) && !focusForced) {
		EnableFocusMovement(false);
	}
}

static void FakeKeyEvent(const KeyInput &key, ViewGroup *root) {
	KeyEventToFocusMoves(key);
	KeyEvent(key, root);
}

void AxisEvent(const AxisInput &axis, ViewGroup *root) {
	enum class DirState {
		NONE = 0,
		POS = 1,
		NEG = 2,
	};
	struct PrevState {
		PrevState() : x(DirState::NONE), y(DirState::NONE) {}
		DirState x;
		DirState y;
	};
	struct StateKey {
		int deviceId;
		int axisId;

		bool operator <(const StateKey &other) const {
			return std::tie(deviceId, axisId) < std::tie(other.deviceId, other.axisId);
		}
	};
	static std::map<StateKey, PrevState> state;
	StateKey stateKey{ axis.deviceId, axis.axisId };

	const float THRESHOLD = 0.75;

	// Cannot use the remapper since this is for the menu, so we provide our own
	// axis->button emulation here.
	auto GenerateKeyFromAxis = [=](DirState old, DirState cur, InputKeyCode neg_key, InputKeyCode pos_key) {
		if (old == cur)
			return;
		if (old == DirState::POS) {
			FakeKeyEvent(KeyInput{ DEVICE_ID_KEYBOARD, pos_key, KeyInputFlags::UP }, root);
		} else if (old == DirState::NEG) {
			FakeKeyEvent(KeyInput{ DEVICE_ID_KEYBOARD, neg_key, KeyInputFlags::UP }, root);
		}
		if (cur == DirState::POS) {
			FakeKeyEvent(KeyInput{ DEVICE_ID_KEYBOARD, pos_key, KeyInputFlags::DOWN }, root);
		} else if (cur == DirState::NEG) {
			FakeKeyEvent(KeyInput{ DEVICE_ID_KEYBOARD, neg_key, KeyInputFlags::DOWN }, root);
		}
	};

	switch (axis.deviceId) {
	case DEVICE_ID_PAD_0:
	case DEVICE_ID_PAD_1:
	case DEVICE_ID_PAD_2:
	case DEVICE_ID_PAD_3:
	case DEVICE_ID_XINPUT_0:
	case DEVICE_ID_XINPUT_1:
	case DEVICE_ID_XINPUT_2:
	case DEVICE_ID_XINPUT_3:
	{
		PrevState &old = state[stateKey];
		DirState dir = DirState::NONE;
		if (axis.value < -THRESHOLD)
			dir = DirState::NEG;
		else if (axis.value > THRESHOLD)
			dir = DirState::POS;

		if (axis.axisId == JOYSTICK_AXIS_X || axis.axisId == JOYSTICK_AXIS_HAT_X) {
			GenerateKeyFromAxis(old.x, dir, NKCODE_DPAD_LEFT, NKCODE_DPAD_RIGHT);
			old.x = dir;
		}
		if (axis.axisId == JOYSTICK_AXIS_Y || axis.axisId == JOYSTICK_AXIS_HAT_Y) {
			int direction = GetAnalogYDirection(axis.deviceId);
			if (direction == 0) {
				// We stupidly interpret the joystick Y axis backwards on Android and Linux instead of reversing
				// it early (see keymaps...). Too late to fix without invalidating a lot of config files, so we
				// reverse it here too.
#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(LINUX) || PPSSPP_PLATFORM(SWITCH)
				GenerateKeyFromAxis(old.y, dir, NKCODE_DPAD_UP, NKCODE_DPAD_DOWN);
#else
				GenerateKeyFromAxis(old.y, dir, NKCODE_DPAD_DOWN, NKCODE_DPAD_UP);
#endif
			} else if (direction == -1) {
				GenerateKeyFromAxis(old.y, dir, NKCODE_DPAD_UP, NKCODE_DPAD_DOWN);
			} else {
				GenerateKeyFromAxis(old.y, dir, NKCODE_DPAD_DOWN, NKCODE_DPAD_UP);
			}
			old.y = dir;
		}
		break;
	}
	default:
		break;
	}

	root->Axis(axis);
}

static void ProcessHeldKeys(ViewGroup *root) {
	double now = time_now_d();

restart:
	for (std::set<HeldKey>::iterator iter = heldKeys.begin(); iter != heldKeys.end(); ++iter) {
		if (iter->triggerTime < now) {
			KeyInput key;
			key.keyCode = iter->key;
			key.deviceId = iter->deviceId;
			key.flags = KeyInputFlags::DOWN;
			KeyEvent(key, root);

			focusMoves.push_back(key.keyCode);

			// Cannot modify the current item when looping over a set, so let's do this instead.
			HeldKey hk = *iter;
			heldKeys.erase(hk);
			hk.triggerTime = now + repeatInterval;
			heldKeys.insert(hk);
			goto restart;
		}
	}
}

DialogResult UpdateViewHierarchy(ViewGroup *root) {
	ProcessHeldKeys(root);
	frameCount++;

	if (!root) {
		ERROR_LOG(Log::UI, "Tried to update a view hierarchy from a zero pointer root");
		return DR_NONE;
	}

	if (focusMoves.size()) {
		EnableFocusMovement(true);
		if (!GetFocusedView()) {
			// Find a view to focus.
			View *defaultView = root->GetDefaultFocusView();
			// Can't focus what you can't see.
			if (defaultView && defaultView->GetVisibility() == V_VISIBLE) {
				root->GetDefaultFocusView()->SetFocus();
			} else {
				root->SetFocus();
			}
			root->SubviewFocused(GetFocusedView());
		} else {
			for (size_t i = 0; i < focusMoves.size(); i++) {
				switch (focusMoves[i]) {
				case NKCODE_DPAD_LEFT: MoveFocus(root, FOCUS_LEFT); break;
				case NKCODE_DPAD_RIGHT: MoveFocus(root, FOCUS_RIGHT); break;
				case NKCODE_DPAD_UP: MoveFocus(root, FOCUS_UP); break;
				case NKCODE_DPAD_DOWN: MoveFocus(root, FOCUS_DOWN); break;
				case NKCODE_PAGE_UP: MoveFocus(root, FOCUS_PREV_PAGE); break;
				case NKCODE_PAGE_DOWN: MoveFocus(root, FOCUS_NEXT_PAGE); break;
				case NKCODE_MOVE_HOME: MoveFocus(root, FOCUS_FIRST); break;
				case NKCODE_MOVE_END: MoveFocus(root, FOCUS_LAST); break;
				}
			}
		}
		focusMoves.clear();
	}

	root->Update();
	return DispatchEvents();
}

}  // namespace UI
