from __future__ import annotations
from typing import Optional, TYPE_CHECKING

import tcod

from actions import BaseAction, QuitAction, BumpAction, WaitAction

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,
}

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,
}

#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.apply()

		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 == tcod.event.KeySym.BACKQUOTE: #lowercase tilde
			self.engine.event_handler = LogHistoryViewer(self.engine, self)

	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)
		log_console.print_box(
			0, 0, log_console.width, 1, "Message History", alignment=tcod.constants.CENTER
		)
		self.engine.message_log.render_messages(
			log_console,
			1, 1,
			log_console.width - 2, log_console.height - 2,
			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)) #TODO: nicer scroll down

		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