#!/usr/bin/env python3
"""SC-Controller - Action Editor - common part of "DPAD or menu" and "Special Action",
two components with MenuAction selectable.
"""

import logging
import os
from typing import Self

from gi.repository import Gtk

from scc.actions import NoAction
from scc.constants import DEFAULT, LSTICK, SAME, SCButtons
from scc.gui.menu_editor import MenuEditor
from scc.gui.parser import GuiActionParser
from scc.gui.userdata_manager import UserDataManager
from scc.paths import get_menus_path
from scc.special_actions import (
	GridMenuAction,
	HorizontalMenuAction,
	MenuAction,
	PositionModifier,
	QuickMenuAction,
	RadialMenuAction,
)
from scc.tools import _, nameof

log = logging.getLogger("AE.Menu")

__all__ = ["MenuActionCofC"]


class MenuActionCofC(UserDataManager):
	# CofC - Component of Component
	def __init__(self):
		UserDataManager.__init__(self)
		self._current_menu = None
		self.parser = GuiActionParser()
		self.allow_globals = True
		self.allow_in_profile = True

	def allow_menus(self, allow_globals, allow_in_profile) -> Self:
		"""Sets which type of menu should be selectable.
		By default, both are enabled.

		Returns self.
		"""
		self.allow_globals = allow_globals
		self.allow_in_profile = allow_in_profile
		return self

	def set_selected_menu(self, menu):
		"""Sets menu selected in combobox.
		Returns self.
		"""
		self._current_menu = menu
		# TODO: This currently works only if menu list is not yet loaded

	@staticmethod
	def menu_class_to_key(action):
		"""For subclass of MenuAction, returns correct key to be used in ListStore.
		"""
		if isinstance(action, GridMenuAction):
			return "gridmenu"
		if isinstance(action, QuickMenuAction):
			return "quickmenu"
		if isinstance(action, HorizontalMenuAction):
			return "hmenu"
		if isinstance(action, RadialMenuAction):
			return "radialmenu"
		return "menu"

	def load_menu_data(self, action):
		if isinstance(action, PositionModifier):
			# Load menu position modifier, if used
			x, y = action.position
			self.builder.get_object("cbMenuPosX").set_active(0 if x >= 0 else 1)
			self.builder.get_object("cbMenuPosY").set_active(0 if y >= 0 else 1)
			self.builder.get_object("spMenuPosX").set_value(abs(x))
			self.builder.get_object("spMenuPosY").set_value(abs(y))
			action = action.action

		self._current_menu = action.menu_id
		cbm = self.builder.get_object("cbMenuType")
		self.set_cb(cbm, self.menu_class_to_key(action), 1)

		if self.builder.get_object("rvMenuSize"):
			spMenuSize = self.builder.get_object("spMenuSize")
			if self.update_size_display(action):
				size = spMenuSize.get_adjustment().set_value(action.size)

		cbControlWith = self.builder.get_object("cbControlWith")
		cbConfirmWith = self.builder.get_object("cbConfirmWith")
		cbCancelWith = self.builder.get_object("cbCancelWith")
		cbMenuAutoConfirm = self.builder.get_object("cbMenuAutoConfirm")
		cbMenuConfirmWithClick = self.builder.get_object("cbMenuConfirmWithClick")
		cbMenuAutoCancel = self.builder.get_object("cbMenuAutoCancel")
		if cbControlWith:
			self.set_cb(cbControlWith, nameof(action.control_with), 1)

		cow = action.confirm_with
		caw = action.cancel_with

		if cbConfirmWith:
			if cow == SAME and cbMenuAutoConfirm:
				cbMenuAutoConfirm.set_active(True)
				cbConfirmWith.set_sensitive(False)
			elif cbMenuConfirmWithClick and cow == self.get_default_confirm():
				cbMenuConfirmWithClick.set_active(True)
				cbConfirmWith.set_sensitive(False)
			else:
				if cbMenuAutoConfirm:
					cbMenuAutoConfirm.set_active(False)
				if cbMenuConfirmWithClick:
					cbMenuAutoConfirm.set_active(False)
				cbConfirmWith.set_sensitive(True)
				self.set_cb(cbConfirmWith, nameof(cow), 1)

		if cbCancelWith:
			if caw == SAME and cbMenuAutoCancel:
				cbMenuAutoCancel.set_active(True)
				cbCancelWith.set_sensitive(False)
			else:
				if cbMenuAutoCancel:
					cbMenuAutoCancel.set_active(False)
				self.set_cb(cbCancelWith, nameof(caw), 1)

		self.on_cbMenus_changed()

	def get_default_confirm(self):
		"""Returns DEFAULT, but may be overriden when default
		confirm button is different - specifically when used with pads.
		"""
		return DEFAULT

	def get_default_cancel(self):
		"""Returns DEFAULT, but may be overriden when default
		confirm button is different - specifically when used with pads.
		"""
		return DEFAULT

	def on_menu_changed(self, new_id):
		self._current_menu = new_id
		self.editor.set_action(MenuAction(new_id))
		self.load_menu_list()

	def on_btEditMenu_clicked(self, *a):
		name = self.get_selected_menu()
		if name:
			log.debug("Editing %s", name)
			me = MenuEditor(self.app, self.on_menu_changed)
			id = self.get_selected_menu()
			log.debug("Opening editor for menu ID '%s'", id)
			me.set_menu(id)
			me.allow_menus(self.allow_globals, self.allow_in_profile)
			me.show(self.editor.window)

	def on_cbMenus_button_press_event(self, trash, event):
		if event.button == 3:
			mnuMenu = self.builder.get_object("mnuMenu")
			mnuMenu.popup(None, None, None, None, 3, Gtk.get_current_event_time())

	def on_mnuMenuNew_activate(self, *a):
		self.on_new_menu_selected()

	def on_mnuMenuCopy_activate(self, *a):
		self.on_new_menu_selected(make_copy=True)

	def on_mnuMenuRename_activate(self, *a):
		self.on_btEditMenu_clicked()

	def on_mnuMenuDelete_activate(self, *a):
		id = self.get_selected_menu()
		if MenuEditor.menu_is_global(id):
			text = _("Really delete selected global menu?")
		else:
			text = _("Really delete selected menu?")

		d = Gtk.MessageDialog(
			parent=self.editor.window,
			flags=Gtk.DialogFlags.MODAL,
			type=Gtk.MessageType.WARNING,
			buttons=Gtk.ButtonsType.OK_CANCEL,
			message_format=text,
		)

		if MenuEditor.menu_is_global(id):
			d.format_secondary_text(_("This action is not undoable!"))

		if d.run() == -5:  # OK button, no idea where is this defined...
			if MenuEditor.menu_is_global(id):
				fname = os.path.join(get_menus_path(), id)
				try:
					os.unlink(fname)
				except Exception as e:
					log.error("Failed to remove %s: %s", fname, e)
			else:
				del self.app.current.menus[id]
				self.app.on_profile_modified()
			self.load_menu_list()
		d.destroy()

	def on_menus_loaded(self, menus):
		cb = self.builder.get_object("cbMenus")
		cb.set_row_separator_func(lambda model, iter: model.get_value(iter, 1) is None)
		model = cb.get_model()
		model.clear()
		i, current_index = 0, 0
		if self.allow_in_profile:
			# Add menus from profile
			for key in sorted(self.app.current.menus):
				model.append((key, key))
				if self._current_menu == key:
					current_index = i
				i += 1
			if i > 0:
				model.append((None, None))  # Separator
				i += 1
		if self.allow_globals:
			for f in menus:
				key = f.get_basename()
				name = key
				if name.startswith("."):
					continue
				if "." in name:
					name = _("%s (global)" % (name.split(".")[0]))
				model.append((name, key))
				if self._current_menu == key:
					current_index = i
				i += 1
		if i > 0:
			model.append((None, None))  # Separator
		model.append((_("New Menu..."), ""))

		self._recursing = True
		cb.set_active(current_index)
		self._recursing = False
		name = self.get_selected_menu()
		if name:
			self.builder.get_object("btEditMenu").set_sensitive(name not in MenuEditor.OPEN)

	def handles(self, mode, action):
		if isinstance(action, PositionModifier):
			return isinstance(action.action, MenuAction)
		return isinstance(action, MenuAction)

	def get_selected_menu(self):
		cb = self.builder.get_object("cbMenus")
		model = cb.get_model()
		iter = cb.get_active_iter()
		if iter is None:
			# Empty list
			return None
		return model.get_value(iter, 1)

	def prevent_confirm_cancel_nonsense(self, widget, *a):
		"""If 'confirm with click', 'confirm with release' and
		'cbMenuAutoCancel' are all present, this method prevents them from
		being checked in nonsensical way.
		"""
		cbMenuConfirmWithClick = self.builder.get_object("cbMenuConfirmWithClick")
		cbMenuAutoConfirm = self.builder.get_object("cbMenuAutoConfirm")
		cbMenuAutoCancel = self.builder.get_object("cbMenuAutoCancel")
		if widget.get_active():
			if widget == cbMenuConfirmWithClick:
				if cbMenuAutoConfirm:
					cbMenuAutoConfirm.set_active(False)
			elif widget == cbMenuAutoConfirm:
				if cbMenuConfirmWithClick:
					cbMenuConfirmWithClick.set_active(False)
				if cbMenuAutoCancel:
					cbMenuAutoCancel.set_active(False)
			elif widget == cbMenuAutoCancel:
				if cbMenuAutoConfirm:
					cbMenuAutoConfirm.set_active(False)

	def on_cbMenus_changed(self, *a):
		"""Called when user changes any menu settings"""
		if self._recursing:
			return None
		cbMenuConfirmWithClick = self.builder.get_object("cbMenuConfirmWithClick")
		cbMenuAutoConfirm = self.builder.get_object("cbMenuAutoConfirm")
		cbMenuAutoCancel = self.builder.get_object("cbMenuAutoCancel")
		lblControlWith = self.builder.get_object("lblControlWith")
		cbControlWith = self.builder.get_object("cbControlWith")
		lblConfirmWith = self.builder.get_object("lblConfirmWith")
		cbConfirmWith = self.builder.get_object("cbConfirmWith")
		lblCancelWith = self.builder.get_object("lblCancelWith")
		cbCancelWith = self.builder.get_object("cbCancelWith")

		cbm = self.builder.get_object("cbMenuType")
		menu_type = cbm.get_model().get_value(cbm.get_active_iter(), 1)

		if cbControlWith:
			sensitive = True
			if menu_type == "quickmenu":
				sensitive = False
			lblControlWith.set_sensitive(sensitive)
			cbControlWith.set_sensitive(sensitive)

		if cbConfirmWith:
			sensitive = True
			if cbMenuAutoConfirm and cbMenuAutoConfirm.get_active():
				sensitive = False
			if cbMenuConfirmWithClick and cbMenuConfirmWithClick.get_active():
				sensitive = False
			if menu_type == "quickmenu":
				sensitive = False
			lblConfirmWith.set_sensitive(sensitive)
			cbConfirmWith.set_sensitive(sensitive)

		if cbCancelWith:
			sensitive = True
			if cbMenuAutoCancel and cbMenuAutoCancel.get_active():
				sensitive = False
			if menu_type == "quickmenu":
				sensitive = False
			lblCancelWith.set_sensitive(sensitive)
			cbCancelWith.set_sensitive(sensitive)

		if cbMenuAutoConfirm:
			sensitive = True
			if menu_type == "quickmenu":
				sensitive = False
			cbMenuAutoConfirm.set_sensitive(sensitive)

		name = self.get_selected_menu()
		if name == "":
			return self.on_new_menu_selected()
		if name:
			# There is some menu choosen
			self.builder.get_object("btEditMenu").set_sensitive(name not in MenuEditor.OPEN)
			params = [name]

			cow = SAME
			if cbMenuAutoConfirm and cbMenuAutoConfirm.get_active():
				cow = SAME
			elif cbMenuConfirmWithClick and cbMenuConfirmWithClick.get_active():
				cow = DEFAULT
			elif cbConfirmWith:
				cow = cbConfirmWith.get_model().get_value(cbConfirmWith.get_active_iter(), 1)
				if cow != DEFAULT:
					cow = getattr(SCButtons, cow)

			caw = DEFAULT
			if cbMenuAutoCancel and cbMenuAutoCancel.get_active():
				caw = DEFAULT
			elif cbCancelWith:
				caw = cbCancelWith.get_model().get_value(cbCancelWith.get_active_iter(), 1)
				if caw != DEFAULT:
					caw = getattr(SCButtons, caw)

			params += [self.get_control_with(), cow, caw]

			# Hide / apply and display 'Items per row' selector if it exists in UI
			if self.builder.get_object("rvMenuSize"):
				spMenuSize = self.builder.get_object("spMenuSize")
				menu_type = cbm.get_model().get_value(cbm.get_active_iter(), 1)
				if menu_type == "gridmenu":
					self.update_size_display(GridMenuAction("dummy"))
					size = int(spMenuSize.get_adjustment().get_value())
					if size > 0:
						# size is 2nd parameter
						params += [False, size]
				elif menu_type == "radialmenu":
					self.update_size_display(RadialMenuAction("dummy"))
					size = int(spMenuSize.get_adjustment().get_value())
					if size > 0 and size < 100:
						# Both 0 and 100 means default here
						# size is 2nd parameter
						params += [False, size]
				elif menu_type == "hmenu":
					self.update_size_display(HorizontalMenuAction("dummy"))
					size = int(spMenuSize.get_adjustment().get_value())
					if size > 1:
						# Size 0 and 1 means default here
						# size is 2nd parameter
						params += [False, size]
				else:
					# , "radialmenu"):
					self.update_size_display(None)

			# Grab menu type and choose apropriate action
			action = NoAction()
			if cbm and menu_type == "gridmenu":
				# Grid menu
				action = GridMenuAction(*params)
			elif cbm and menu_type == "radialmenu":
				# Circular menu
				action = RadialMenuAction(*params)
			elif cbm and menu_type == "hmenu":
				# Horizontal menu
				action = HorizontalMenuAction(*params)
			elif cbm and menu_type == "quickmenu":
				# Horizontal menu
				action = QuickMenuAction(name)
			else:
				# Normal menu
				action = MenuAction(*params)

			# Apply Menu Position options, if such block exists in UI
			if self.builder.get_object("spMenuPosX"):
				cbMenuPosX = self.builder.get_object("cbMenuPosX")
				cbMenuPosY = self.builder.get_object("cbMenuPosY")
				x = int(self.builder.get_object("spMenuPosX").get_value())
				y = int(self.builder.get_object("spMenuPosY").get_value())
				x *= cbMenuPosX.get_model().get_value(cbMenuPosX.get_active_iter(), 0)
				y *= cbMenuPosY.get_model().get_value(cbMenuPosY.get_active_iter(), 0)
				if (x, y) != MenuAction.DEFAULT_POSITION:
					action = PositionModifier(x, y, action)

			self.editor.set_action(action)

	def on_new_menu_selected(self, make_copy=False):
		# 'New menu' selected
		self.load_menu_list()
		log.debug("Creating editor for new menu")
		me = MenuEditor(self.app, self.on_menu_changed)
		if make_copy:
			name = self.get_selected_menu()
			log.debug("Copying %s", name)
			me = MenuEditor(self.app, self.on_menu_changed)
			me.set_menu(name)
		me.set_new_menu()
		me.allow_menus(self.allow_globals, self.allow_in_profile)
		me.show(self.editor.window)

	def update_size_display(self, action):
		"""Displays or hides menu size area and upadates text displayed in it.
		Returns True if action is menuaction where changing size makes sense
		"""
		rvMenuSize = self.builder.get_object("rvMenuSize")
		lblMenuSize = self.builder.get_object("lblMenuSize")
		spMenuSize = self.builder.get_object("spMenuSize")
		sclMenuSize = self.builder.get_object("sclMenuSize")
		if isinstance(action, GridMenuAction):
			spMenuSize.set_visible(True)
			sclMenuSize.set_visible(False)
			lblMenuSize.set_text(_("Items per row"))
			rvMenuSize.set_reveal_child(True)
			return True
		if isinstance(action, (RadialMenuAction, HorizontalMenuAction)):
			spMenuSize.set_visible(False)
			sclMenuSize.set_visible(True)
			lblMenuSize.set_text(_("Size"))
			rvMenuSize.set_reveal_child(True)
			return True
		rvMenuSize.set_reveal_child(False)
		return False

	def get_control_with(self):
		"""Returns value of "Control With" combo or LSTICK if there is none"""
		cbControlWith = self.builder.get_object("cbControlWith")
		if cbControlWith:
			return cbControlWith.get_model().get_value(cbControlWith.get_active_iter(), 1)
		return LSTICK

	def on_spMenuSize_format_value(self, spinner):
		val = int(spinner.get_adjustment().get_value())
		if val < 1:
			spinner.get_buffer().set_text(_("auto"), -1)
		else:
			spinner.get_buffer().set_text(str(val), -1)
		return True

	def on_sclMenuSize_format_value(self, scale, val):
		cbm = self.builder.get_object("cbMenuType")
		menu_type = cbm.get_model().get_value(cbm.get_active_iter(), 1)
		if menu_type == "radialmenu":
			if val < 1:
				return _("default")
			return "%s%%" % (int(val),)
		# if menu_type == "hmenu"
		val = int(val)
		if val < 2:
			return _("default")
		return str(int(val))
