from __future__ import annotations
from typing import List, Optional, TYPE_CHECKING

import tcod

import colors
from actions import (
	BaseAction,
	QuitAction,
	BumpAction,
	WaitAction,
	PickupAction,
)

if TYPE_CHECKING:
	from engine import Engine

#input options
MOVE_KEYS = {
	#arrow keys
	tcod.event.KeySym.UP: (0, -1),
	tcod.event.KeySym.DOWN: (0, 1),
	tcod.event.KeySym.LEFT: (-1, 0),
	tcod.event.KeySym.RIGHT: (1, 0),

	tcod.event.KeySym.HOME: (-1, -1),
	tcod.event.KeySym.END: (-1, 1),
	tcod.event.KeySym.PAGEUP: (1, -1),
	tcod.event.KeySym.PAGEDOWN: (1, 1),

	#numpad keys
	tcod.event.KeySym.KP_1: (-1, 1),
	tcod.event.KeySym.KP_2: (0, 1),
	tcod.event.KeySym.KP_3: (1, 1),
	tcod.event.KeySym.KP_4: (-1, 0),

	tcod.event.KeySym.KP_6: (1, 0),
	tcod.event.KeySym.KP_7: (-1, -1),
	tcod.event.KeySym.KP_8: (0, -1),
	tcod.event.KeySym.KP_9: (1, -1),

	#vi key mapping
	tcod.event.KeySym.h: (-1, 0),
	tcod.event.KeySym.j: (0, 1),
	tcod.event.KeySym.k: (0, -1),
	tcod.event.KeySym.l: (1, 0),

	tcod.event.KeySym.y: (-1, -1),
	tcod.event.KeySym.u: (1, -1),
	tcod.event.KeySym.b: (-1, 1),
	tcod.event.KeySym.n: (1, 1),
}

WAIT_KEYS = {
	tcod.event.KeySym.PERIOD,
	tcod.event.KeySym.KP_5,
	tcod.event.KeySym.CLEAR,
}

PICKUP_KEYS = {
	tcod.event.KeySym.COMMA,
}

CURSOR_SCROLL_KEYS = {
	tcod.event.KeySym.UP: -1,
	tcod.event.KeySym.DOWN: 1,
	tcod.event.KeySym.PAGEUP: -10,
	tcod.event.KeySym.PAGEDOWN: 10,

	tcod.event.KeySym.KP_2: 1,
	tcod.event.KeySym.KP_8: -1,
}

CURSOR_CONFIRM_KEYS = {
	tcod.event.KeySym.RETURN,
	tcod.event.KeySym.SPACE,
}

#the event handlers are one part of the engine
class EventHandler(tcod.event.EventDispatch[BaseAction]):
	engine: Engine

	def __init__(self, engine: Engine):
		super().__init__()
		self.engine = engine

	def render(self, console: tcod.console.Console) -> None:
		pass #no-op

	#callbacks
	def ev_quit(self, event: tcod.event.Quit) -> Optional[BaseAction]:
		return QuitAction()

	def handle_events(self, context: tcod.context.Context) -> bool:
		"""If any Action signals True, then the game state should be progressed after this"""
		result = False

		for event in tcod.event.wait():
			context.convert_event(event) #adds mouse position info
			action = self.dispatch(event)

			if action is None:
				continue

			result |= action.perform()

		return result


class GameplayHandler(EventHandler):
	def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[BaseAction]:
		key = event.sym #SDL stuff, neat.

		player = self.engine.player

		#player input
		if key == tcod.event.KeySym.ESCAPE:
			return QuitAction()

		if key in MOVE_KEYS:
			xdir, ydir = MOVE_KEYS[key]
			return BumpAction(player, xdir = xdir, ydir = ydir)

		if key in WAIT_KEYS:
			return WaitAction(player)

		if key in PICKUP_KEYS:
			return PickupAction(player)

		if key == tcod.event.KeySym.o: #TODO: temove this
			self.engine.event_handler = OptionSelector(self.engine, self, ["zero", "one", "two", "three"], lambda x: print("You chose", x)) #TODO: remove this

		if key == tcod.event.KeySym.BACKQUOTE: #lowercase tilde
			self.engine.event_handler = LogHistoryViewer(self.engine, self)

		# if key == tcod.event.KeySym.TAB:
		# 	self.engine.event_handler = InventoryViewer(self.engine, self) #TODO: deal with this

	def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None:
		if self.engine.floor_map.in_bounds(event.tile.x, event.tile.y):
			self.engine.mouse_location = event.tile.x, event.tile.y


class GameOverHandler(EventHandler):
	"""Game over, man, GAME OVER!"""
	def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[BaseAction]:
		key = event.sym #SDL stuff, neat.

		#player input
		if key == tcod.event.KeySym.ESCAPE:
			return QuitAction()

		if key == tcod.event.KeySym.BACKQUOTE: #lowercase tilde
			self.engine.event_handler = LogHistoryViewer(self.engine, self)

		return None


class LogHistoryViewer(EventHandler):
	baseEventHandler: EventHandler

	def __init__(self, engine: Engine, baseEventHandler: EventHandler):
		super().__init__(engine)
		self.baseEventHandler = baseEventHandler
		self.log_length = len(engine.message_log.messages)
		self.cursor = self.log_length - 1

	def render(self, console: tcod.console.Console) -> None:
		super().render(console)

		log_console = tcod.console.Console(console.width - 6, console.height - 6)

		#rendering a nice log window
		log_console.draw_frame(
			0,0, log_console.width, log_console.height,
			# "╔═╗║ ║╚═╝"
			decoration="\\x/x x/x\\",
			fg=colors.terminal_dark, bg=colors.black
		)
		log_console.print_box(
			0, 0, log_console.width, log_console.height,
			string = "Message History",
			alignment=tcod.constants.CENTER,
			fg=colors.terminal_light, bg=colors.black
		)
		self.engine.message_log.render_messages(
			log_console,
			2, 2,
			log_console.width - 4, log_console.height - 4,
			self.engine.message_log.messages[:self.cursor + 1]
		)
		log_console.blit(console, 3, 3) #into the middle

	def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[BaseAction]:
		if event.sym in CURSOR_SCROLL_KEYS:
			adjust = CURSOR_SCROLL_KEYS[event.sym]
			if adjust < 0 and self.cursor == 0:
				pass #do nothing
			elif adjust > 0 and self.cursor == self.log_length - 1:
				pass #do nothing
			else:
				self.cursor = max(0, min(self.log_length - 1, self.cursor + adjust))

		elif event.sym == tcod.event.KeySym.HOME:
			self.cursor = 0
		elif event.sym == tcod.event.KeySym.END:
			self.cursor = self.log_length - 1
		else:
			#return to the game
			self.engine.event_handler = self.baseEventHandler

#generic tool
class OptionSelector(EventHandler):
	baseEventHandler: EventHandler

	def __init__(self, engine: Engine, baseEventHandler: EventHandler, options: List[str], callback: function):
		super().__init__(engine)
		self.baseEventHandler = baseEventHandler
		self.options = options
		self.callback = callback
		self.length = len(options)
		self.cursor = 0

	def render(self, console: tcod.console.Console) -> None:
		super().render(console)

		select_console = tcod.console.Console(console.width - 20, console.height - 16)

		#rendering a nice list window
		select_console.draw_frame(
			0,0, select_console.width, select_console.height,
			# "╔═╗║ ║╚═╝"
			decoration="\\x/x x/x\\",
			fg=colors.terminal_dark, bg=colors.black
		)
		select_console.print_box(
			0, 0, select_console.width, select_console.height,
			string = "Select One",
			alignment=tcod.constants.CENTER,
			fg=colors.terminal_light, bg=colors.black
		)

		#render the cursor & options
		offset = 0
		for option in self.options:
			select_console.print(
				4, 2 + offset,
				string = option,
				fg=colors.terminal_light, bg=colors.black,
			)
			offset += 1

		select_console.print(2, 2 + self.cursor, string = ">", fg=colors.terminal_light, bg=colors.black)

		select_console.blit(console, 10, 8)

	def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[BaseAction]:
		if event.sym in CURSOR_SCROLL_KEYS:
			adjust = CURSOR_SCROLL_KEYS[event.sym]
			if adjust < 0 and self.cursor == 0:
				pass #do nothing
			elif adjust > 0 and self.cursor == self.length - 1:
				pass #do nothing
			else:
				self.cursor = max(0, min(self.length - 1, self.cursor + adjust))

		elif event.sym in CURSOR_CONFIRM_KEYS:
			#got the answer
			self.callback(self.cursor)
			self.engine.event_handler = self.baseEventHandler

		elif event.sym == tcod.event.KeySym.HOME:
			self.cursor = 0
		elif event.sym == tcod.event.KeySym.END:
			self.cursor = self.length - 1
		else:
			#return to the game
			self.engine.event_handler = self.baseEventHandler