########################################################################
#	Hello Worlds - Libre 3D RPG game.
#	Copyright (C) 2020  CYBERDEViL
#
#	This file is part of Hello Worlds.
#
#	Hello Worlds is free software: you can redistribute it and/or modify
#	it under the terms of the GNU General Public License as published by
#	the Free Software Foundation, either version 3 of the License, or
#	(at your option) any later version.
#
#	Hello Worlds is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#	GNU General Public License for more details.
#
#	You should have received a copy of the GNU General Public License
#	along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
########################################################################


from LUIFrame import LUIFrame
from LUILabel import LUILabel
from LUIProgressbar import LUIProgressbar
from LUILayouts import LUIHorizontalLayout
from LUIVerticalLayout import LUIVerticalLayout
from LUISprite import LUISprite
from LUIObject import LUIObject
from LUIInitialState import LUIInitialState

from core.models import SpellItemModel, SpellStatus

class UI_ActionMessages(LUIObject):
	def __init__(self, **kwargs):
		LUIObject.__init__(self, center_horizontal=True)

		self._layout = LUIVerticalLayout(parent=self)
		self._previousMessage = ""
		self._count = 0

		LUIInitialState.init(self, kwargs)

	def print(self, message, lifeTime=2):
		if message == self._previousMessage: return
		self._previousMessage = message
		self._count += 1
		label = LUILabel(message, parent=self._layout.cell(), margin=0, font_size=20, center_horizontal=True)
		taskMgr.doMethodLater(
			lifeTime,
			self.remove,
			'removeText',
			extraArgs=[label],
		)

	def remove(self, label):
		label.parent = None
		self._count -= 1

		if not self._count: self._previousMessage = ""

	def close(self): self.parent = None

class UI_ItemInfo(LUIFrame):
	def __init__(self, **kwargs):
		LUIFrame.__init__(self, style=LUIFrame.FS_raised, **kwargs)

		self.margin = 0
		self._model = None

		verticalLayout = LUIVerticalLayout(parent=self)
		horizontalLayout = LUIHorizontalLayout(parent=verticalLayout.cell())

		## Labels
		leftLayout = LUIVerticalLayout(parent=horizontalLayout.cell())

		LUILabel("Name: ", parent=leftLayout.cell(), margin=0)
		LUILabel("Points: ", parent=leftLayout.cell(), margin=0)
		LUILabel("Energy: ", parent=leftLayout.cell(), margin=0)
		LUILabel("Casting time: ", parent=leftLayout.cell(), margin=0)
		LUILabel("Cooling time: ", parent=leftLayout.cell(), margin=0)
		LUILabel("Range: ", parent=leftLayout.cell(), margin=0)

		## Values
		rightLayout = LUIVerticalLayout(parent=horizontalLayout.cell())

		self._nameValue = LUILabel("", parent=rightLayout.cell(), margin=0, color=(0.2, 0.8, 0.2))
		self._pointsValue = LUILabel("", parent=rightLayout.cell(), margin=0, color=(0.8, 0.8, 0.0))
		self._energyValue = LUILabel("", parent=rightLayout.cell(), margin=0, color=(0.8, 0.8, 0.0))
		self._castTimeValue = LUILabel("", parent=rightLayout.cell(), margin=0)
		self._coolTimeValue = LUILabel("", parent=rightLayout.cell(), margin=0)
		self._rangeValue = LUILabel("", parent=rightLayout.cell(), margin=0)

		self._descValue = LUILabel("", parent=verticalLayout.cell(), margin=0)

		self.hide()

	def setModel(self, model):
		"""
		@param model:
		@type model: core.model.SpellItemModel
		"""
		self._model = model

		if model:
			self._nameValue.set_text(model.name)
			self._pointsValue.set_text("{0}".format(model.data.impactPoints))
			self._energyValue.set_text("{0}".format(model.data.energyCost))
			self._castTimeValue.set_text("{0}s".format(model.data.castTime))
			self._coolTimeValue.set_text("{0}s".format(model.data.coolDown))
			self._rangeValue.set_text("{0}min {1}max".format(model.data.rangeStart, model.data.rangeEnd))

			if model.description:
				self._descValue.set_text(str(model.description))
				self._descValue.show()
			else:
				self._descValue.hide()

		else: self._modelRemoved()

	def close(self): self.parent = None

	def _modelRemoved(self):
		self._descLabel.set_text(self._descLabelFmt)

class UI_DragItem(LUIObject):
	icons = {
		'unknown'	: "assets/icons/unknown.png",
		'empty'		: "assets/icons/overlay_0.png"
	}
	def __init__(self, *args, model=None, **kwargs):
		LUIObject.__init__(self, *args)
		self.width = 48
		self.height = 48
		self._icon = LUISprite(self, self.icons['empty'], width=self.width, height=self.height)

		self.set_pos(100,100)
		self.setModel(model)

	def setModel(self, model=None):
		if model:
			self._icon.set_texture(model.iconPath, resize=False)

class UI_Item(LUIObject):
	## TODO use assets path
	icons = {
		'unknown'	: "assets/icons/unknown.png",
		'empty'		: "assets/icons/overlay_0.png"
	}
	## TODO use assets path
	overlays = {
		'highlite'	: "assets/icons/overlay_1.png",
		'disabled'	: "assets/icons/overlay_2_disabled.png"
	}
	def __init__(self, *args, **kwargs):
		# Without solid=True it doesn't receive mouse-events
		LUIObject.__init__(self, *args, solid=True)

		self.width = 48
		self.height = 48

		self._model = None
		self._isDragging = False
		self._dragStartPos = None
		self._backupParent = self.parent
		self._dragItem = None
		self.__enabled = False
		self._backupModel = None

		self._allowedDropTypes = [SpellItemModel]

		self._icon = LUISprite(self, self.icons['empty'], width=self.width, height=self.height)
		self._label = LUILabel("", parent=self, margin=0, left=0, bottom=0)

		self._overlay = None

		LUIInitialState.init(self, kwargs)

	@property
	def dropTypes(self): return self._allowedDropTypes

	def setEnabled(self, state=True):
		if state and not self._model: return False
		self.__enabled = state
		return True

	@property
	def enabled(self): return self.__enabled

	def mousePos(self):
		if base.mouseWatcherNode.hasMouse():
			mpos = base.mouseWatcherNode.getMouse()
			mousePos = (mpos.getX() * base.getAspectRatio(), mpos.getY())

			screenWidth = base.win.getProperties().getXSize()
			screenHeight = base.win.getProperties().getYSize()
			ratio = base.getAspectRatio()

			# https://stackoverflow.com/questions/929103/convert-a-number-range-to-another-range-maintaining-ratio
			left = (((mousePos[0] - -ratio) * (screenWidth - 0)) / (ratio - -ratio)) + 0
			top = (((mousePos[1] - 1) * (screenHeight - 0)) / (-1 - 1)) + 0

			return (left, top)
		return(0,0)

	def dragDistance(self):
		if self._dragStartPos:
			mouseX, mouseY = self.mousePos()
			dragX, dragY = self._dragStartPos
			return abs((dragX + dragY) - (mouseX + mouseY))
		return 0

	def _dragWatcher(self, event):
		if not self._dragItem and self.dragDistance() > 6:
			self._removeOverlay()
			self._dragItem = UI_DragItem(base.luiRegion.root, model=self.model)
			base.uiManager.dragModel = self.model
			self.unbind('mousemove')
			taskMgr.add(self._dragTask, 'dragWatcher')

	def _dragTask(self, task):
		self._dragItem.set_pos(self.mousePos())
		return task.cont

	def _stopDrag(self):
		if self._dragItem:
			taskMgr.remove('dragWatcher')
			self._dragItem.parent = None
			self._dragItem = None
			base.uiManager.dragModel = None

	def on_mouseover(self, event):
		## Replace model with the model behind the drag.
		if base.uiManager.dragModel:
			if type(base.uiManager.dragModel) in self.dropTypes:
				self._backupModel = self._model
				self.setModel(base.uiManager.dragModel)
				return

		## Show information box
		if self._model:
			pos = self.get_abs_pos()
			pos -= (-60,80)
			base.uiManager.spellInfoFrame.setModel(self._model)
			base.uiManager.spellInfoFrame.set_pos(pos)
			base.uiManager.spellInfoFrame.show()

	def on_mouseout(self, event):
		if self._model:
			base.uiManager.spellInfoFrame.hide()

		if base.uiManager.dragModel:
			if type(base.uiManager.dragModel) in self.dropTypes:
				self.setModel(self._backupModel)
		self._backupModel = None

	def on_mousedown(self, event=None):
		if self.enabled:
			if not base.uiManager.dragModel:
				self._dragStartPos = self.mousePos()
				self.bind('mousemove', self._dragWatcher) # uncomment to enable drag 'n drop (that isn't complete yet)
			self._addOverlay()

	def on_mouseup(self, event=None):
		self._stopDrag()
		self._removeOverlay()
		self._dragStartPos = None

	def keyDown(self):
		if self.enabled:
			self._addOverlay()
			self.trigger_event("click")

	def keyUp(self):
		if self.enabled: self._removeOverlay()

	def setOverlay(self, overlay='highlite'):
		self._removeOverlay()
		self._addOverlay(overlay=overlay)

	def _addOverlay(self, overlay='highlite'):
		if not self._overlay:
			overlayPath = self.overlays[overlay]
			self._overlay = LUISprite(self, overlayPath, width=self.width, height=self.height)

	def _removeOverlay(self):
		if self._overlay:
			self._overlay.parent = None
			self._overlay = None

	@property
	def model(self): return self._model

	def setModel(self, model):
		self._model = model

		if model:
			self._icon.set_texture(model.iconPath, resize=False)
			self._label.set_text(model.name)
			self.setEnabled(True)
		else:
			self._modelRemoved()
			self.setEnabled(False)

	def _modelRemoved(self):
		self._icon.set_texture(self.icons['empty'], resize=False)
		self._label.set_text("")

class UI_SpellItem(UI_Item):
	def __init__(self, *args, **kwargs):
		UI_Item.__init__(self, *args, **kwargs)
		self.castBar = None

	def _spellStatusChanged(self, status):
		"""
		@param status: SpellStatus.*
		@type status: int
		"""
		if status == SpellStatus.none or status == SpellStatus.done:
			self._removeOverlay()
			self.setEnabled(True)

		elif status == SpellStatus.casting:
			self.setEnabled(False)
			self.setOverlay('disabled')

			self.castBar = UI_CastingBar(self.model.data.castTime, parent=base.luiRegion.root)
			self.castBar.start()

		elif status == SpellStatus.cooling:
			self.setEnabled(False)
			self.setOverlay('disabled')

		elif status == SpellStatus.cancelled:
			if self.castBar: self.castBar.cancel()

	def setModel(self, model):
		"""
		@param model:
		@type model: core.models.SpellItemModel
		"""
		if self._model:
			self._model.statusChanged.disconnect(self._spellStatusChanged)

		UI_Item.setModel(self, model)

		if model:
			self._spellStatusChanged(model.status)
			model.statusChanged.connect(self._spellStatusChanged)

class UI_ProgressBar(LUIObject):
	def __init__(self, **kwargs):
		LUIObject.__init__(self)

		self.width = 200
		self.height = 10

		self._layout = LUIVerticalLayout(parent=self)

		self._bar = LUIProgressbar(parent=self._layout.cell(), width=200, label_fmt="casting")

		LUIInitialState.init(self, kwargs)

	def __bool__(self):
		return self.is_visible()

	def close(self, event=None): self.parent = None

class UI_CastingBar(UI_ProgressBar):
	def __init__(self, castingTime, **kwargs):
		UI_ProgressBar.__init__(self, **kwargs)
		self.center_horizontal = True
		self.bottom = 125
		self._cancelled = False
		self.castingTime = castingTime

		self._bar.set_max(castingTime)

	def start(self):
		self._bar.set_value(0)
		# TODO investigate where this weird 0.25 margin comes from.
		# it's only visual
		self._bar.set_max(self.castingTime - 0.25)

		## Start a task that updates the progress-bar over time.
		taskMgr.doMethodLater(
				0.1,
				self.updateProgress,
				'player_castBar'
			)

	def cancel(self):
		self._cancelled = True

	def updateProgress(self, task):
		""" Task method that updates the progress-bar over time.
		"""
		if not self._cancelled and self._bar.get_value() < self._bar.get_max():
			self._bar.set_value(self._bar.get_value() + 0.1)
			return task.again
		self.close()
		return task.done


class UI_SpellBar(LUIFrame):
	def __init__(self, **kwargs):
		LUIFrame.__init__(self, style=LUIFrame.FS_sunken, **kwargs)

		self.height = 68
		self.margin = 0

		screenHeight = base.win.getProperties().getYSize()

		self.top = screenHeight - 68 - 3
		self.center_horizontal = True

		self._layout = LUIHorizontalLayout(parent=self)
		self._layout.spacing = 3

		self.maxItems = 12
		self._items = []

		## List with spellId's; should be same ordered as self._items!
		for i in range(0, self.maxItems):
			item = UI_SpellItem(self._layout.cell())
			self._items.append(item)
			item.bind('click', self._spellClicked)

		# Assing shortcut keys 0 - 9
		keyMap = [
			["1", 0],
			["2", 1],
			["3", 2],
			["4", 3],
			["5", 4],
			["6", 5],
			["7", 6],
			["8", 7],
			["9", 8],
			["0", 9],
		]
		for key, index in keyMap:
			base.accept(key, self._items[index].keyDown)
			base.accept('{0}-up'.format(key), self._items[index].keyUp)

		base.accept('WINDOW_RESIZED', self.reposition)

	def clear(self):
		for item in self._items: item.setModel(None)

	def characterChanged(self):
		self.clear()

		# TODO
		if base.world.playerController.character:
			count = 0
			for spell in base.world.playerController.character.characterData.spells:
				model = SpellItemModel(spell)

				self._items[count].setModel(model)

				if count == self.maxItems: break
				count += 1

	def _spellClicked(self, event):
		index = self._items.index(event.sender)
		if self._items[index].model:
			selectedSpawnId = base.world.npcsManager.selectedNpcModel.selectedId
			base.world.playerController.character.startCast(
				self._items[index].model.data.id,
				selectedSpawnId
			)

	def reposition(self):
		self.top = base.win.getProperties().getYSize() - self.height - 10

	def close(self): self.parent = None

class UI_PlayerInfoBox(LUIFrame):
	def __init__(self, parent=None, top=10, left=10):
		if not parent: parent = base.luiRegion.root
		LUIFrame.__init__(self, parent=parent, style=LUIFrame.FS_sunken, width=175, height=80, margin=0, top=top, left=left)

		self._layout = LUIVerticalLayout(self)

		self.nameLabel = LUILabel(parent=self._layout.cell(), text="Name: ")
		self.healthBar = LUIProgressbar(parent=self._layout.cell(), width=145, label_fmt="Health: {value}/{max}")
		self.energyBar = LUIProgressbar(parent=self._layout.cell(), width=145, label_fmt="Energy: {value}/{max}")

		self._currentCharacterData = None

	def characterChanged(self):
		if self._currentCharacterData:
			self._currentCharacterData.stats.health.valueChanged.disconnect(self._healthChanged)
			self._currentCharacterData.stats.energy.valueChanged.disconnect(self._energyChanged)
			self._currentCharacterData = None

		if base.world.playerController.character:
			self._currentCharacterData = base.world.playerController.character.characterData

			self.nameLabel.set_text(self._currentCharacterData.name)

			self.healthBar.set_max(self._currentCharacterData.stats.health.max)
			self.healthBar.set_value(self._currentCharacterData.stats.health.value)

			self.energyBar.set_max(self._currentCharacterData.stats.energy.max)
			self.energyBar.set_value(self._currentCharacterData.stats.energy.value)

			self._currentCharacterData.stats.health.valueChanged.connect(self._healthChanged)
			self._currentCharacterData.stats.energy.valueChanged.connect(self._energyChanged)

	def _healthChanged(self, value): self.healthBar.set_value(value)
	def _energyChanged(self, value): self.energyBar.set_value(value)

	def close(self): self.parent = None

class UI_CreatureInfoBox:
	def __init__(self, parent=None, top=100, left=10):
		if not parent: parent = base.luiRegion.root

		self._currentSpawnId = -1

		self._container = LUIFrame(parent=parent, width=175, height=55, style=LUIFrame.FS_sunken, margin=0, top=top, left=left)

		self.nameLabel = LUILabel(parent=self._container, text="Name: ", top=0)
		self.healthBar = LUIProgressbar(parent=self._container, width=145, top=20, label_fmt="Health: {value}/{max}")

		base.world.npcsManager.selectedNpcModel.changed.connect(self._selectionChanged)

		self.hide()

	def hasMouse(self): return self._container.hasMouse()

	def _selectionChanged(self, spawnId):
		# Disconnect previously set connections
		if self._currentSpawnId != -1 and self._currentSpawnId != spawnId:
			oldNpc = base.world.npcsManager.getSpawn(self._currentSpawnId) # creaturesManager.Creature
			if oldNpc:
				oldNpc.characterData.stats.health.valueChanged.disconnect(self._healthChanged)
				oldNpc.removeSelectPlane()

		# Hide if selected id is 0 or smaller
		if spawnId < 1:
			self.hide()
			self._currentSpawnId = -1
			return

		if self._currentSpawnId != spawnId:

			self.show()

			npc = base.world.npcsManager.getSpawn(spawnId) # creaturesManager.Creature
			npc.addSelectPlane()

			# Name
			self.nameLabel.text = "{0} (id: {1} lvl: {2})".format(npc.characterData.name, npc.characterData.spawnData.id, npc.characterData.stats.level.value)

			# Health
			self.healthBar.set_max(npc.characterData.stats.health.max)
			self.healthBar.set_value(npc.characterData.stats.health.value)

			self._currentSpawnId = spawnId

			npc.characterData.stats.health.valueChanged.connect(self._healthChanged)

	def _healthChanged(self, value):
		npc = base.world.npcsManager.getSpawn(self._currentSpawnId)
		if npc: self.healthBar.set_value(npc.characterData.stats.health.value)

	def close(self):
		self._container.parent = None
		self._container = None

	def hide(self):
		self._container.hide()

	def show(self):
		self._container.show()

	def isVisible(self): return self._container.visible
