import sys
#sys.path.append("..") # why was needed?
from typing import List, Dict, Any
import frams
from .creature import Creature
from gui.framsutils.framsProperty import Property
import os
import glm
from .utils import parseNeuronDToOrient

class FramsLib:
	def __init__(self):
		self.frams = frams
		self.connected = False

	def initConnection(self, frams_path: str) -> None:
		self.frams.init(frams_path, "-t") #enable ExtValue thread synchronization with "-t"
		#self.messageCatcher = self.frams.MessageCatcher.new() # experiments with catching messages so that they could be later displayed in GUI... but let's rather print them on console
		#self.messageCatcher.store = 1
		self.connected = True
		
	def closeConnection(self) -> None:
		self.connected = False

	def getError(self) -> None or str:
		#if self.messageCatcher.error_count._value() > 0:
		#	return self.messageCatcher.messages._value()
		return None

	def writeCreatureFromString(self, groupNo: int, creature: str) -> None:
		if self.connected:
			self.frams.Populations[groupNo].add(creature)

	def step(self):
		if self.connected:
			self.frams.Simulator.step()

	def start(self):
		if self.connected:
			self.frams.Simulator.start()

	def stop(self):
		if self.connected:
			self.frams.Simulator.stop()

	def info(self, path:str = "/") -> List[Property]:
		properties: List[Property] = []

		if self.connected:
			if path.startswith('/'):
				path = path[1:]

			c = 0 if path == "" else path.count("/") + 1

			if path.endswith('/'):
				path = path[:-1]

			if c > 0:
				path += '/'

			current = self.frams.sim_params
			G = current._groupCount()
			for g in range(G):
				Id = current._groupName(g)
				name = Id.rsplit(': ', 1)[-1]
				Id = Id.replace(": ", "/")
				tmpId = Id + "/"
				prop = Property(Id=Id, Name=name, Type="o")
				if(Id.count('/') == c and tmpId.startswith(path)):
					prop.p["id"] = Id.split('/')[-1]
					properties.append(prop)

		return properties

	def infoList(self, path: str) -> List[Property]:
		properties: List[Property] = []
		raise Exception("unimplemented/unwanted")
		return properties

	def _VstyleParserColor(self, style: str):
		f = "color=0x"
		idx = style.find(f)
		if idx > -1:
			hx = style[idx+len(f):idx+len(f)+6]
			r = int(hx[0:2], 16) / 255.0
			g = int(hx[2:4], 16) / 255.0
			b = int(hx[4:6], 16) / 255.0
			return r, g, b
		return None, None, None

	def readCreatures(self, groupNo: int, creatureNo: int, colors):
		ret_creatures: List[Creature] = []
		if self.connected:
			groups = [self.frams.Populations[groupNo]] if groupNo != '+' else self.frams.Populations
			for group in groups:
				creatures = [group[creatureNo]] if groupNo != '+' else group
				for c in creatures:
					creature = Creature(c.population.index._int(), c.index._int())
					numparts = c.numparts._int()
					numjoints = c.numjoints._int()
					numneurons = c.numneurons._int()

					vstyle = c.model.Vstyle._value()
					modelR, modelG, modelB = self._VstyleParserColor(vstyle)

					mechPart = [0,0,0]
					for i in range(numparts):
						if i >= c.numparts._int():
							break
						mp = c.getMechPart(i)
						mechPart = [mp.x._double(), mp.y._double(), mp.z._double()]
						creature.mechParts.append(mechPart)
						if colors:
							vstyle = mp.part.Vstyle._value()
							r, g, b = self._VstyleParserColor(vstyle)

							tr = mp.part.vr._value()
							tg = mp.part.vg._value() 
							tb = mp.part.vb._value()

							if modelR is not None:
								tr = modelR
								tg = modelG
								tb = modelB

							if r is not None:
								tr = r
								tg = g
								tb = b
							
							creature.colorsPart.append(glm.vec3(tr, tg, tb))
							
						creature.stylePart.append(mp.part.Vstyle._value())
						orient = mp.orient.toVector.toString._value()
						orient = orient[orient.find('[')+1:orient.find(']')]
						R = glm.mat4(glm.mat3(*[float(x) for x in orient.split(',')]))
						creature.partOrient.append(R)
					for i in range(numjoints):
						if i >= c.numjoints._int():
							break
						j = c.getJoint(i)
						joint = [j.p1._int(), j.p2._int()]
						creature.joints.append(joint)
						if colors:
							vstyle = mp.part.Vstyle._value()
							r, g, b = self._VstyleParserColor(vstyle)

							tr = j.vr._value()
							tg = j.vg._value() 
							tb = j.vb._value()

							if r is not None:
								tr = r
								tg = g
								tb = b
							
							creature.colorsJoint.append(glm.vec3(tr, tg, tb))

						creature.styleJoint.append(j.Vstyle._value())

					for i in range(numneurons):
						if i >= c.numneurons._int():
							break
						n = c.getNeuroDef(i)
						d = n.d._value()
						creature.neurons.append((n.p._value(), n.j._value()))
						creature.styleNeuron.append(d.split(':')[0])
						
						rorient = parseNeuronDToOrient(d)

						creature.neuronRelativeOrient.append(rorient)
					ret_creatures.append(creature)
		return ret_creatures

	def readGenePoolsGroups(self) -> Dict[str, int]:
		genotypes = {}
		if self.connected:
			for gp in self.frams.GenePools:
				genotypes[getattr(gp, "name")._value()] = getattr(gp, "index")._value()
		return genotypes

	def readGenePools(self, props: List[str]) -> Dict[str, List[Dict[str, Any]]]:
		genotypes = {}
		if self.connected:
			for gp in self.frams.GenePools:
				group = gp.name._value()
				genotypes[group] = []
				for g in gp:
					genotype = {}
					for p in props:
						genotype[p] = getattr(g, p)._value()
					genotype["group"] = group
					genotypes[group].append(genotype)
		return genotypes

	def readPopulationsGroups(self) -> Dict[str, int]:
		creatures = {}
		if self.connected:
			for p in self.frams.Populations:
				creatures[getattr(p, "name")._value()] = getattr(p, "index")._value()

		return creatures

	def readPopulations(self, props: List[str]) -> Dict[str, List[Dict[str, Any]]]:
		creatures = {}
		if self.connected:
			for p in self.frams.Populations:
				group = p.name._value()
				creatures[group] = []
				for c in p:
					creature = {}
					for p in props:
						creature[p] = getattr(c, p)._value()
					creature["group"] = group
					creatures[group].append(creature)
		return creatures

	def readCreatureInfo(self, groupNo: int, creatureNo: str) -> List[Property]:
		properties: List[Property] = []
		if self.connected:
			creature = next(pop for group in self.frams.Populations for pop in group if pop.uid._string() == creatureNo)
			G = creature._groupCount()
			for g in range(G):
				groupName = creature._groupName(g)
				for m in range(creature._memberCount(g)):
					p = creature._groupMember(g, m)
					t = creature._propType(p)
					if t[0] and t[0] not in "xople":
						prop = Property(creature._propId(p), creature._propName(p), t)
						prop.p["flags"] = creature._propFlags(p)
						prop.p["value"] = getattr(creature, creature._propId(p))._string()
						prop.p["help"] = creature._propHelp(p)
						prop.p["group"] = groupName
						properties.append(prop)

		return properties

	def readGenotypeInfo(self, groupNo: int, genotypeNo: str) -> List[Property]:
		properties: List[Property] = []
		if self.connected:
			genotype = next(gen for group in self.frams.GenePools for gen in group if gen.uid._string() == genotypeNo)
			G = genotype._groupCount()
			for g in range(G):
				groupName = genotype._groupName(g)
				for m in range(genotype._memberCount(g)):
					p = genotype._groupMember(g, m)
					t = genotype._propType(p)
					if t[0] and t[0] not in "xople":
						prop = Property(genotype._propId(p), genotype._propName(p), t)
						prop.p["flags"] = genotype._propFlags(p)
						prop.p["value"] = getattr(genotype, genotype._propId(p))._string()
						prop.p["help"] = genotype._propHelp(p)
						prop.p["group"] = groupName
						properties.append(prop)

		return properties

	def readPropertyInfo(self, path: str) -> List[Property]:
		properties: List[Property] = []
		if self.connected:
			if path.startswith('/'):
				path = path[1:]

			c = path.count("/")

			if path.endswith('/'):
				path = path[:-1]

			current = self.frams.sim_params
			G = current._groupCount()
			for g in range(G):
				Id = current._groupName(g).replace(": ", "/")
				groupName = current._groupName(g)
				if(Id.count('/') == c and Id == path):
					for m in range(current._memberCount(g)):
						p = current._groupMember(g, m)
						t = current._propType(p)
						if t[0] and t[0] not in "xole":
							prop = Property(current._propId(p), current._propName(p), t)
							if not prop.p["id"].startswith("_"):
								prop.p["flags"] = current._propFlags(p)
								prop.p["value"] = getattr(current, current._propId(p)) if t[0] == 'p' else getattr(current, current._propId(p))._string()
								prop.p["help"] = current._propHelp(p)
								prop.p["v"] = current
								prop.p["group"] = groupName
								properties.append(prop)
					break

		return properties

	def writeCreatureInfo(self, uid: str, prop: str, value: str) -> bool:
		if self.connected:
			creature = next(pop for group in self.frams.Populations for pop in group if pop.uid._string() == uid)
			creature.__setattr__(prop, value)
			return True
		return False

	def writeGenotypeInfo(self, uid: str, prop: str, value: str) -> bool:
		if self.connected:
			genotype = next(gen for group in self.frams.GenePools for gen in group if gen.uid._string() == uid)
			genotype.__setattr__(prop, value)
			return True
		return False

	def writePropertyInfo(self, path: str, prop: str, value: str):
		if self.connected:
			current = self.frams.sim_params
			current.__setattr__(prop, value)
			return True
		return False

	def getSimulationStatus(self) -> bool:
		if self.connected:
			return True if self.frams.Simulator.running._int() == 1 else False
		return False

	def loadFile(self, path: str) -> None:
		self.frams.Simulator.load(path)

	def importFile(self, path: str, options: int) -> None:
		self.frams.Simulator.ximport(path, options)

	def saveFile(self, path: str, options: int) -> None:
		path = os.path.relpath(path, self.frams.res_dir + "\\scripts_output")
		self.frams.Simulator.export(path, options, -1, -1)

	def getWorldType(self) -> int:
		return self.frams.World.wrldtyp._value()

	def getWorldSize(self) -> float:
		return self.frams.World.wrldsiz._value()

	def getWorldWaterLevel(self) -> float:
		return self.frams.World.wrldwat._value()

	def getWorldBoundaries(self) -> int:
		return self.frams.World.wrldbnd._value()

	def getWorldMap(self) -> str:
		return self.frams.WorldMap.getAsString("", "")._value()

	def getSimtype(self) -> int:
		return self.frams.World.simtype._value()