from __future__ import annotations
from typing import List

from tcod.context import Context
from tcod.console import Console
from tcod.map import compute_fov

from entity import Entity
from message_log import Message, MessageLog
from floor_map import FloorMap #TODO: replace with "DungeonMap" or similar
from actions import BaseAction

from render_functions import render_hp_bar, render_names_at


class Engine:
	player: Entity
	floor_map: FloorMap

	def __init__(self, *, floor_map: FloorMap, initial_log: List[Message] = None, ui_width: int = None, ui_height: int = None):
		#events
		from event_handlers import GameplayHandler
		self.event_handler = GameplayHandler(self, None)
		self.mouse_position = (0, 0)

		#map
		self.floor_map = floor_map
		self.floor_map.engine = self #entities in maps can also reference the engine

		#messages
		self.message_log = MessageLog()
		if initial_log:
			self.message_log.push_messages(initial_log)

		#grab the player object (generated by the procgen, usually)
		self.player = self.floor_map.player

		#default values
		self.ui_width = floor_map.width if ui_width is None else ui_width
		self.ui_height = 0 if ui_height is None else ui_height

		#kick off the fov
		self.update_fov()

	def run_loop(self, context: Context, console: Console) -> None:
		while True:
			self.update_fov()

			if self.event_handler.handle_events(context):
				self.handle_entities() #TODO: what 'game state'?

			self.handle_rendering(context, console)

	def handle_entities(self) -> bool:
		"""
		Processes monster AI and other things.
		Returns `True` if the game state should be progressed.
		"""
		actions: List[BaseAction] = []

		#make the entities think and act
		for entity in set(self.floor_map.entities) - {self.player}:
			if entity.ai:
				actions.append(entity.ai.process())

		result = False

		for action in actions:
			result |= action.perform()

		return result

	def handle_rendering(self, context: Context, console: Console) -> None:
		#map and all entities within
		self.floor_map.render(console)

		#UI
		render_hp_bar(
			console = console,
			x = 0,
			y = self.floor_map.height,
			current_value = self.player.stats.current_hp,
			max_value = self.player.stats.maximum_hp,
			total_width = self.ui_width // 2,
		)
		render_names_at(
			console = console,
			x = 1,
			y = self.floor_map.height + 2,
			engine = self,
		)
		self.message_log.render(
			console=console,
			x=self.ui_width // 2,
			y=self.floor_map.height,
			width = self.ui_width // 2,
			height = self.ui_height,
		)

		self.event_handler.render(console)

		#send to the screen
		context.present(console)
		console.clear()

	#utils
	def update_fov(self):
		self.floor_map.visible[:] = compute_fov(
			self.floor_map.tiles["transparent"],
			(self.player.x, self.player.y),
			radius = 8,
		)

		#add the visible tiles to the explored list
		self.floor_map.explored |= self.floor_map.visible