########################################################################
#	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 core.signals import Signal

class Model:
	""" A model is used for data and data only. Other components can use
	a model to get data. A model has signals to which others can connect
	a callback; so they get notified when the data is updated or the
	model is destroyed.

	So this has nothing to do with a 3d model!
	"""
	def __init__(self):
		## Universal Signal emitted when this model is destroyed.
		self.destroyed = Signal()
		## Universal Signal emitted when this model has been changed.
		self.changed = Signal()

	def destroy(self):
		""" This should always be called before deletion of this object,
		so that instances using this object know it is going to be
		deleted.
		"""
		self.destroyed.emit()

class ItemModel(Model):
	""" Basic item model
	Game items and spells will extend this.
	"""
	def __init__(self, name="", iconPath="assets/icons/unknown.png", desc=""):
		"""
		@param name: Item name
		@type name: str

		@param iconPath: Item path
		@type iconPath: str

		@param desc: Item description
		@type desc: str
		"""
		Model.__init__(self)

		# TODO check if iconPath exists, else set assets/icons/unknown.png

		self._name = name
		self._iconPath = iconPath
		self._desc = desc

	@property
	def name(self): return self._name
	@property
	def iconPath(self): return self._iconPath
	@property
	def description(self): return self._desc

	@name.setter
	def name(self, value): self._name = value
	@iconPath.setter
	def iconPath(self, value): self._iconPath = value
	@description.setter
	def description(self, value): self._desc = value

from core.db import Spells
from core.spells import SpellStatus

class SpellModel(Model):
	""" Spell model
	"""
	def __init__(self, spellId):
		"""
		@param spellId: Spell ID
		@type spellId: str
		"""
		## statusChange emits it's status
		self.statusChanged = Signal(int)

		Model.__init__(self)
		self._data = Spells[spellId]

		self._status = SpellStatus.none

	@property
	def data(self): return self._data

	@property
	def status(self): return self._status

	def isCooling(self): return bool(self._status == SpellStatus.cooling)
	def isCasting(self): return bool(self._status == SpellStatus.casting)

	def setStatus(self, status):
		"""
		@param status: SpellStatus.*
		@type status: int
		"""
		self._status = status
		self.statusChanged.emit(status)

class SpellItemModel(ItemModel):
	""" Sort of proxy for the UI
	"""
	def __init__(self, spellModel):
		"""
		@param spellModel:
		@type spellModel: SpellModel
		"""
		## statusChange emits it's status
		self.statusChanged = Signal(int)

		ItemModel.__init__(self)
		self._model = spellModel

		self._model.statusChanged.connect(self._statusChanged)
		self._modelChanged()

	def _statusChanged(self, status):
		self.statusChanged.emit(status)

	def _modelChanged(self):
		self.name = self._model.data.name
		self.iconPath = self._model.data.iconPath()
		self.description = self._model.data.desc

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

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

class SpellsModel(Model):
	""" Spells container for NPC's and PlayerCharacter
	"""
	def __init__(self, spells=[]):
		"""
		@param spells: List with spell-id's
		@type spells: list
		"""
		Model.__init__(self)
		## Protected list with SpellModel's
		self._spells = {}

		self.isCasting = False

		if spells: self.setSpellIds(spells)

	def __iter__(self):
		""" Iterating over this class will yield SpellModel's

		@rtype: SpellModel's
		@return: 
		"""
		for spell in self._spells.values(): yield spell

	def __getitem__(self, key):
		""" Get SpellModel for a spellId
		@param key: spellId
		@type key: str

		@rtype: SpellModel
		@return: 
		"""
		return self._spells[key]

	def __list__(self): return self._spells.values()

	def __len__(self): return len(self._spells)

	def addSpellId(self, spellId):
		""" Adds a spell by id
		@param spellId: Spell id
		@type spellId: str
		"""
		self._spells.update({str(spellId) : SpellModel(spellId)})

	def setSpellIds(self, spellIds):
		""" Sets spells from list with spell-id's
		@param spellIds: List with spell-id's
		@type spellIds: list
		"""
		for spellId in spellIds: self.addSpellId(spellId)

class StatsModel(Model):
	def __init__(self, rawData={}):
		Model.__init__(self)

		self.setRawData(rawData)

	def setRawData(self, rawData):
		self._data = {
			'level'		: IntModel(
							max=rawData.level.max,
							value=rawData.level.value),

			'health'	: IntModel(
							max=rawData.health.max,
							value=rawData.health.value),

			'energy'	: IntModel(
							max=rawData.energy.max,
							value=rawData.energy.value),
			'strength'	: 1,
			'stamina'	: 1,
			'resistance': 2,
			'reflexes'	: 0.5,
			'spitit'	: 0,
		}

	@property
	def health(self): return self._data['health']

	@property
	def level(self): return self._data['level']

	@property
	def energy(self): return self._data['energy']

class PlayerStatsModel(StatsModel):
	def __init__(self, rawData={}):
		StatsModel.__init__(self, rawData=rawData)

	@property
	def experience(self): return self._data['experience']

	def setRawData(self, rawData):
		StatsModel.setRawData(self, rawData)
		self._data.update({
			'experience'	: IntModel(
							max=rawData.experience.max,
							value=rawData.experience.value)
		})

class SelectedNpcModel(Model):
	def __init__(self):
		""" Shared by a mouse-handler and the ui
		"""
		## Emits selected spawn id
		self.changed = Signal(int)
		self._selectedId = -1

	@property
	def selectedId(self): return self._selectedId

	@selectedId.setter
	def selectedId(self, id):
		if self.selectedNPC:
			self.selectedNPC.hasDied.disconnect(self._removeSelection)

		self._selectedId = id

		if id > 0:
			self.selectedNPC.hasDied.connect(self._removeSelection)

		self.changed.emit(id)

	@property
	def selectedNPC(self):
		if self.selectedId:
			return base.world.npcsManager.getSpawn(self._selectedId)

	def _removeSelection(self, spawnId):
		self.selectedId = -1

class IntModel(Model):
	def __init__(self, min=0, max=100, value=0, stepSize=1):
		""" Used for example: character / npc -level
		"""
		self.valueChanged = Signal(int)
		Model.__init__(self)
		self.__min = min
		self.__max = max
		self.__value = value
		self.__stepSize = stepSize

	@property
	def min(self): return self.__min

	@property
	def max(self): return self.__max

	@property
	def value(self): return self.__value

	@value.setter
	def value(self, value):
		self.__value = max(min(value, self.max), self.min)
		self.valueChanged.emit(self.value)

class FloatModel(IntModel):
	def __init__(self, min=0, max=100, value=0, stepSize=.01):
		""" Used for example: character / npc -stats
		"""
		self.valueChanged = Signal(float)
		IntModel.__init__(self, min=min, max=max, value=value, stepSize=stepSize)
