########################################################################
#	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/>.
#
########################################################################


import os
import json


global AssetsPath
global Spells
global NPCs
global Maps
global Players
global Classes

class GenericData: # dict
	empty = {}
	def __init__(self, _id=-1, data=None):
		self._id = _id
		if data: self._load(_id, data)
		else: self.setEmpty()

	def _load(self, _id, data):
		self._id = _id
		self._data = data

	@property
	def id(self): return self._id

	@id.setter
	def id(self, value): self._id = value

	def data(self): return self._data

	def columns(self): return len(self.data())

	def setEmpty(self):
		self._data = self.empty.copy()

class GenericDataList(GenericData): # list
	empty = []
	def __init__(self, _id=-1, data=None):
		GenericData.__init__(self, _id=_id, data=data)

"""Classes
"""
class ClassData(GenericDataList):
	empty = ['new',[]]

	def __init__(self, _id=-1, data=None):
		GenericData.__init__(self, _id, data=data)

		"""
		0	name
		1	default spells/actions
		"""

	@property
	def name(self): return self._data[0]
	@property
	def spells(self): return self._data[1] #[Spells[spellId] for spellId in self._data[1]]

	@name.setter
	def name(self, value): self._data[0] = value
	@spells.setter
	def spells(self, value):
		self._data[1] = value

"""CharacterData - base for PlayerData and NPCData
"""
class StatData:
	def __init__(self, data):
		self._data = data

	def data(self): return self._data

	@property
	def max(self): return self._data[0]
	@property
	def value(self): return self._data[1]

	@max.setter
	def max(self, value): self._data[0] = value
	@value.setter
	def value(self, value): self._data[1] = value

class StatsData(GenericData):
	empty = {
		'level'		: (10, 0),
		'health'	: (80, 80), # max, value
		'energy'	: (50, 50),
		'strength'	: (10, 1),
		'stamina'	: (10, 1),
		'resistance': (25, 2),
		'reflexes'	: (10, 0.5),
		'spitit'	: (10, 0)
	}
	def __init__(self, _id=-1, data=None):
		GenericData.__init__(self, _id, data=data)
		"""
		0	level
		1	health
		2	energy
		3	strength
		4	stamina
		5	resistance
		6	reflexes
		7	spitit
		"""

	@property
	def level(self): return StatData(self._data['level'])
	@property
	def health(self): return StatData(self._data['health'])
	@property
	def energy(self): return StatData(self._data['energy'])
	@property
	def strength(self): return StatData(self._data['strength'])
	@property
	def stamina(self): return StatData(self._data['stamina'])
	@property
	def resistance(self): return StatData(self._data['resistance'])
	@property
	def reflexes(self): return StatData(self._data['reflexes'])
	@property
	def spitit(self): return StatData(self._data['spirit'])

	@level.setter
	def level(self, value): self._data['level'] = value
	@health.setter
	def health(self, value): self._data['health'] = value
	@energy.setter
	def energy(self, value): self._data['energy'] = value
	@strength.setter
	def strength(self, value): self._data['strength'] = value
	@stamina.setter
	def stamina(self, value): self._data['stamina'] = value
	@resistance.setter
	def resistance(self, value): self._data['resistance'] = value
	@reflexes.setter
	def reflexes(self, value): self._data['reflexes'] = value
	@spitit.setter
	def spitit(self, value): self._data['spirit'] = value

class PlayerStatsData(StatsData):
	empty = {
		'level'		: (10, 0),
		'health'	: (80, 80), # max, value
		'energy'	: (50, 50),
		'strength'	: (10, 1),
		'stamina'	: (10, 1),
		'resistance': (25, 2),
		'reflexes'	: (10, 0.5),
		'spitit'	: (10, 0),
		'experience': (0,100)
	}
	def __init__(self, _id=-1, data=None):
		StatsData.__init__(self, _id, data)
		"""
		8	experience
		"""

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

	@experience.setter
	def experience(self, value): self._data['experience'] = value

class CharacterData(GenericDataList):
	"""
	0	name
	1	dirName
	2	file
	3	animations {}
	4	stats {}
	5	collisionShape {}
	6	speciesId
	"""
	empty = [
		'new',
		'dir-name',
		'.egg',
		{'idle':'.egg', 'walk':'.egg', 'run':'.egg', 'jump':'.egg'},
		StatsData.empty,
		[1,1],
		1
	]

	def __init__(self, _id=-1, data=None):
		GenericDataList.__init__(self, _id, data=data)

	@property
	def name(self): return self._data[0]
	@property
	def dirName(self): return self._data[1]
	@property
	def file(self): return self._data[2]
	@property
	def animations(self): return self._data[3]
	@property
	def stats(self): return StatsData(data = self._data[4])
	@property
	def collisionShape(self): return self._data[5]
	@property
	def speciesId(self): return self._data[6]

	@name.setter
	def name(self, value): self._data[0] = value
	@dirName.setter
	def dirName(self, value): self._data[1] = value
	@file.setter
	def file(self, value): self._data[2] = value
	@animations.setter
	def animations(self, value): self._data[3] = value
	@stats.setter
	def stats(self, value): self._data[4] = value
	@collisionShape.setter
	def collisionShape(self, value): self._data[5] = value
	@speciesId.setter
	def speciesId(self, value): self._data[6] = value

"""Players
"""
class PlayerData(CharacterData):
	empty = [
		'new',
		'dir-name',
		'.egg',
		{'idle':'.egg', 'walk':'.egg', 'run':'.egg', 'jump':'.egg'},
		PlayerStatsData.empty,
		[1,1],
		1
	]
	def __init__(self, _id=-1, data=None):
		CharacterData.__init__(self, _id, data)

	@property
	def filePath(self): return os.path.join(AssetsPath.players, self.dirName, self.file)

	@property
	def stats(self):
		return PlayerStatsData(data = self._data[4])


"""Maps
"""
class MapData(GenericDataList):
	empty = ['New map','map','.egg','.egg']

	def __init__(self, _id=-1, data=None):
		GenericData.__init__(self, _id, data)
		"""
		0	name
		1	dir-name
		2	file-name.egg
		3	ortho-file-name.egg
		"""

	@property
	def name(self): return self._data[0]
	@property
	def dirName(self): return self._data[1]
	@property
	def file(self): return self._data[2]
	@property
	def filePath(self): return os.path.join(AssetsPath.maps, self.dirName, self.file)
	@property
	def orthoFile(self): return self._data[3]
	@property
	def orthoFilePath(self): return os.path.join(AssetsPath.maps, self.dirName, self.orthoFile)

	@property
	def spawns(self): return Spawns(self.dirName)

	@name.setter
	def name(self, value): self._data[0] = value
	@dirName.setter
	def dirName(self, value): self._data[1] = value
	@file.setter
	def file(self, value): self._data[2] = value
	@orthoFile.setter
	def orthoFile(self, value): self._data[3] = value

"""Spawn data
"""
class GenericSpawnData(GenericDataList):
	empty = [1,0,0,0,0]
	def __init__(self, _id=-1, data=None):
		GenericDataList.__init__(self, _id, data=data)
		"""
		0	characterId (npc or player-character)
		1	x
		2	y
		3	z
		4	o
		"""

		#if data: self._load(_id, data)

	@property
	def characterId(self): return self._data[0]
	@property
	def x(self): return self._data[1]
	@property
	def y(self): return self._data[2]
	@property
	def z(self): return self._data[3]
	@property
	def orientation(self): return self._data[4]

	@property
	def pos(self): return (self.x, self.y, self.z)

	@characterId.setter
	def characterId(self, value): self._data[0] = value
	@x.setter
	def x(self, value): self._data[1] = value
	@y.setter
	def y(self, value): self._data[2] = value
	@z.setter
	def z(self, value): self._data[3] = value
	@orientation.setter
	def orientation(self, value): self._data[4] = value

class NPCSpawnData(GenericSpawnData):
	empty = GenericSpawnData.empty + [10]
	def __init__(self, _id=-1, data=None):
		GenericSpawnData.__init__(self, _id, data)
		"""
		5	respawnTime - in seconds
		"""

	@property
	def respawnTime(self): return self._data[5]

	@respawnTime.setter
	def respawnTime(self, value): self._data[5] = value

"""NPCs
"""

class NPCData(CharacterData):
	def __init__(self, _id=-1, data=None):
		CharacterData.__init__(self, _id, data)

	@property
	def filePath(self): return os.path.join(AssetsPath.npcs, self.dirName, self.file)

"""Spells
"""
class SpellData(GenericDataList):
	empty = [1,0,1,1,"new","unknown.png",""]
	def __init__(self, _id=-1, data=None):
		GenericDataList.__init__(self, _id, data=data)
		"""
		0	castTime
		1	coolDown
		2	damage
		3	energy
		4	name
		5	icon
		6	desc
		"""

	def iconPath(self):
		# return full icon path
		return os.path.join(str(AssetsPath), 'icons/', self.icon)

	@property
	def castTime(self): return self._data[0]
	@property
	def coolDown(self): return self._data[1]
	@property
	def damage(self): return self._data[2]
	@property
	def energy(self): return self._data[3]
	@property
	def name(self): return self._data[4]
	@property
	def icon(self): return self._data[5]
	@property
	def desc(self): return self._data[6]

	@castTime.setter
	def castTime(self, value): self._data[0] = value
	@coolDown.setter
	def coolDown(self, value): self._data[1] = value
	@damage.setter
	def damage(self, value): self._data[2] = value
	@energy.setter
	def energy(self, value): self._data[3] = value
	@name.setter
	def name(self, value): self._data[4] = value
	@icon.setter
	def icon(self, value): self._data[5] = value
	@desc.setter
	def desc(self, value): self._data[6] = value

class _AssetsPath:
	def __init__(self, path='assets/'):
		self._path = path
		self._callbacks = []

	def __str__(self): return self._path
	def __repr__(self): return self._path

	@property
	def icons(self): return os.path.join(str(self), 'icons/')

	@property
	def npcs(self): return os.path.join(str(self), 'creatures/')

	@property
	def maps(self): return os.path.join(str(self), 'maps/')

	@property
	def players(self): return os.path.join(str(self), 'characters/')

	@property
	def widgets(self): return os.path.join(str(self), 'widgets/')

	def joinPath(self, other):
		return os.path.join(str(self), other)

	def set(self, path):
		self._path = path
		for cb in self._callbacks: cb()

	def addReloadCallback(self, cb):
		self._callbacks.append(cb)


class Table:
	def __init__(self, _file, _type):
		self._file = _file
		self._filePath = self.filePath()
		self._fileValid = False
		self._data = {}
		self._type = _type
		AssetsPath.addReloadCallback(self.__load)

	def columns(self): return len(self._type.empty) + 1 # +1 for id

	def __iter__(self):
		for _id, item in self._data.items(): yield item

	def __getitem__(self, key):
		return self._data.get(str(key))

	def reload(self): self.__load()

	def __load(self):
		self._data = {}
		if os.path.isfile(self.filePath()):
			print(" [OK] Found {0}".format(self._file))
			with open(self.filePath()) as f:
				try:
					data = json.load(f)
					for _id, d in data.items():
						self._data.update({_id : self._type(_id, d)})
				except json.decoder.JSONDecodeError as err:
					print("\t! Empty or corrupt file! Error: {0}".format(err))
		else:
			print(" [XX] Not found {0}".format(self._file))

	def __save(self):
		data = {}
		for item in self:
			data.update({item.id : item.data()})
		with open(self.filePath(), 'w') as fp:
			json.dump(data, fp)

	def _getNewId(self):
		for i in range(1, 1024):
			if str(i) not in self._data: return str(i)

	def filePath(self): return os.path.join(str(AssetsPath), self._file)

	def new(self, data=[]):
		if not data: data = self._type.empty
		_id = self._getNewId()
		self.edit(_id, data)
		return _id

	def edit(self, _id, newData):
		self._data.update({_id : self._type(_id, newData)})

	def remove(self, _id):
		self._data.pop(_id)

	def save(self): self.__save()

class _Spells(Table):
	def __init__(self):
		Table.__init__(self, _file='spells.json', _type=SpellData)

class _NPCs(Table):
	def __init__(self):
		Table.__init__(self, _file='npcs.json', _type=NPCData)

class _Maps(Table):
	def __init__(self):
		Table.__init__(self, _file='maps.json', _type=MapData)

class _Players(Table):
	def __init__(self):
		Table.__init__(self, _file='players.json', _type=PlayerData)

class _Classes(Table):
	def __init__(self):
		Table.__init__(self, _file='classes.json', _type=ClassData)

class Spawns(Table):
	def __init__(self, mapDir):
		_file = 'spawns.json'
		self._subPath = os.path.join(mapDir, _file)
		Table.__init__(self, _file=_file, _type=NPCSpawnData)
		self.reload()

	def filePath(self): return os.path.join(AssetsPath.maps, self._subPath)


AssetsPath = _AssetsPath()
Spells = _Spells()
NPCs = _NPCs()
Maps = _Maps()
Players = _Players()
Classes = _Classes()
