import bpy
import os
import random
import mathutils
import enum
import math
import time
from . import utils
from .commun import *

class RouteLogique:
	def __init__(self, réseauRoutier, posX, posY, direction4):
		self.direction4 = direction4
		self.positionActuelleX = math.floor(posX)
		self.positionActuelleY = math.floor(posY)
		self.longueurActuelleMètres = 0
		self.pasEncoreDémarré = True
		self.réseauRoutier = réseauRoutier
		
		if direction4 == DIRECTION4.EST:
			self.déplacementX = +1
			self.déplacementY = 0
		elif direction4 == DIRECTION4.OUEST:
			self.déplacementX = -1
			self.déplacementY = 0
		elif direction4 == DIRECTION4.SUD:
			self.déplacementX = 0
			self.déplacementY = -1
		elif direction4 == DIRECTION4.NORD:
			self.déplacementX = 0
			self.déplacementY = +1
		
		if self.réseauRoutier.peutPlacerRouteIci(self.positionActuelleX, self.positionActuelleY, -1, -1):
			self.placerPortionSurLot(self.réseauRoutier.grille_de_lots.lots[self.positionActuelleX][self.positionActuelleY])
	#--------------------------------------------------------------------------------------------------------------------
	def placerPortionSurLot(self, lot):
		lot.routeConstruiteDessus = self
		# lot.bétonner()
	#--------------------------------------------------------------------------------------------------------------------
	def grandirUneEtapeEtDireSiTerminé(self, listeOuAjouterNllesRoutes):
		if self.longueurActuelleMètres >= self.réseauRoutier.ruesLongueurMaxMètres:
			return True
		
		prochainX = self.positionActuelleX + self.déplacementX
		prochainY = self.positionActuelleY + self.déplacementY
		# attention à la différence de hauteur entre la location actuelle, et la suivante
		if not self.réseauRoutier.peutPlacerRouteIci(prochainX, prochainY, self.positionActuelleX, self.positionActuelleY):
			return True
		
		self.positionActuelleX = prochainX
		self.positionActuelleY = prochainY
		self.placerPortionSurLot(self.réseauRoutier.grille_de_lots.lots[self.positionActuelleX][self.positionActuelleY])
		self.longueurActuelleMètres += 10
		pasEncoreDémarré = False
		# décider si doit spawner une nouvelle route perpendiculaire, et de quel coté
		if random.random() <= self.réseauRoutier.probaEmbranchements:
			# TODO attention si on est en bordure de grille, ne pas lancer la nouvelle route dans l'extérieur
			if self.direction4 == DIRECTION4.NORD or self.direction4 == DIRECTION4.SUD:
				if random.random() <= .5:
					directionNlleRoute = DIRECTION4.EST
				else:
					directionNlleRoute = DIRECTION4.OUEST
			else:
				if random.random() <= .5:
					directionNlleRoute = DIRECTION4.SUD
				else:
					directionNlleRoute = DIRECTION4.NORD
					
			listeOuAjouterNllesRoutes.append(RouteLogique(self.réseauRoutier, self.positionActuelleX, self.positionActuelleY, directionNlleRoute))
		
		return False

class RéseauRoutier:
	def __init__(self, 
			grille_de_lots, 
			maxItérations=999999, 
			échelle=.01,
			seed=None, 
			probaEmbranchements=.1, 
			ruesLongueurMaxMètres=1000, 
			image_placement_rues=None):
		self.routesLogiques = []
		self.maxItérations = maxItérations
		self.seed = seed
		self.échelle = échelle
		self.probaEmbranchements = probaEmbranchements
		self.ruesLongueurMaxMètres = ruesLongueurMaxMètres
		self.grille_de_lots = grille_de_lots
		self.image_placement_rues = image_placement_rues
		
	# return la nlle image
	def dessiner_rues_dans_image(self):
		img = bpy.data.images.new(name='SceneCity road network', width=self.grille_de_lots.nbLotsCôté, height=self.grille_de_lots.nbLotsCôté)
		pixels = [0 for i in range(self.grille_de_lots.nbLotsCôté**2 * 4)]
		for i in range(self.grille_de_lots.nbLotsCôté):
			for j in range(self.grille_de_lots.nbLotsCôté):
				lot = self.grille_de_lots.lots[i][j]
					
				couleur = [0,.5,0,1]
				# route
				if lot.routeConstruiteDessus:
					couleur = [1,1,1,1]
				# sous l'eau
				if not lot.estEmergé():
					couleur = [0,0,.5,1]
				# montagne
				elif not lot.estPlat():
					couleur = [0.2,0.1,0,1]
				utils.définirPixel(pixels, img.size[0], i, j, couleur)
				
		img.pixels = pixels
		return img
		
	'''def placer_rues_sur_lots_selon_image(self):
		for i in range(self.grid.nbLotsCôté):
			if i >= self.image_placement_rues.size[0]: break
			for j in range(self.grid.nbLotsCôté):
				lot = self.grid.lots[i][j]
				if j >= self.image_placement_rues.size[1]: break
					
				try:
					pixel_rgb = utils.avoir_rgb_pixel(self.image_placement_rues, i, j)
					pixel_couleur = mathutils.Color(pixel_rgb)
					
					lot.contientUneRoute = pixel_couleur.r == pixel_couleur.b and pixel_couleur.r == pixel_couleur.g and pixel_couleur.r == 1 and lot.estPlat()
					
				except:
					pass'''
					
	def nettoyer_ancien_réseau(self):
		utils.definir_blender_mode('OBJECT')
		objetsASupprimer = [objet for objet in bpy.context.scene.objects if objet.name.startswith('SceneCity')]
		for objet in objetsASupprimer:
			# bpy.context.scene.objects.unlink(objet)
			# try: bpy.context.scene.collection.objects.unlink(objet)
			# except: pass
			objet.user_clear()
			bpy.data.objects.remove(objet)
		meshesASupprimer = [mesh for mesh in bpy.data.meshes if mesh.name.startswith('SceneCity') and mesh.users <= 0]
		for mesh in meshesASupprimer:
			bpy.data.meshes.remove(mesh)
		'''matériauxASupprimer = [matériau for matériau in bpy.data.materials if matériau.name.startswith('SceneCity')]
		for matériau in matériauxASupprimer:
			if hasattr(matériau.node_tree, 'nodes'):
				matériau.node_tree.nodes.clear()
		for matériau in matériauxASupprimer:
			bpy.data.materials.remove(matériau)
		texturesASupprimer = [texture for texture in bpy.data.textures if texture.name.startswith('SceneCity')]
		for texture in texturesASupprimer:
			bpy.data.textures.remove(texture)
		# supprime toutes les images
		imagesASupprimer = [image for image in bpy.data.images if image.name.startswith('SceneCity')]
		for image in imagesASupprimer:
			image.user_clear()
			bpy.data.images.remove(image)'''
	
	def placer_portions_dans_scene_3d(self):
		print('SceneCity | Starting to create and to location the road objects in the scene. This operation will take a few minutes for large cities')
		
		tempsDébut = time.time()
		utils.definir_blender_mode('OBJECT')
		random.seed(self.seed)

		# créé les collections Blender pour organiser les futures instances
		if 'SceneCity output roads' not in bpy.data.collections:
			c = bpy.data.collections.new(name='SceneCity output roads')
			bpy.context.scene.collection.children.link(c)

		# préparer objet parent
		objParent = bpy.data.objects.new(name='SceneCity roads', object_data=None)
		bpy.data.collections['SceneCity output roads'].objects.link(objParent)

		# compte total objets à créer
		total_objets_à_créer = 0
		for i in range(self.grille_de_lots.nbLotsCôté):
			for j in range(self.grille_de_lots.nbLotsCôté):
				if self.grille_de_lots.lots[i][j].routeConstruiteDessus:
					total_objets_à_créer += 1

		# trouver toutes les rues, et les organiser par type de portion
		dico_datablocks_rues_par_type = {}
		# for datablock in bpy.data.meshes.values() + bpy.data.groups.values():
		for datablock in bpy.data.meshes.values() + bpy.data.collections.values():
			if 'SceneCity' not in datablock or not datablock.SceneCity.utiliser_comme_rue:
				continue
			
			type_portion = datablock.SceneCity.rue_type
			
			if type_portion not in dico_datablocks_rues_par_type:
				dico_datablocks_rues_par_type[type_portion] = []
			dico_datablocks_rues_par_type[type_portion].append((datablock, datablock.SceneCity.rue_probabilité_relative))
			
		# créé objets
		objNb = 1
		liste_rues_à_créer = []
		for i in range(self.grille_de_lots.nbLotsCôté):
			for j in range(self.grille_de_lots.nbLotsCôté):
				lot = self.grille_de_lots.lots[i][j]
				
				# pas une route ? Ignore
				if not lot.routeConstruiteDessus:
					continue
				
				routeNord = lot.voisin(DIRECTION8.NORD) and lot.voisin(DIRECTION8.NORD).routeConstruiteDessus
				routeSud = lot.voisin(DIRECTION8.SUD) and lot.voisin(DIRECTION8.SUD).routeConstruiteDessus
				routeOuest = lot.voisin(DIRECTION8.OUEST) and lot.voisin(DIRECTION8.OUEST).routeConstruiteDessus
				routeEst = lot.voisin(DIRECTION8.EST) and lot.voisin(DIRECTION8.EST).routeConstruiteDessus
				
				rotation = 0
				
				# carrefour 4
				if routeNord and routeSud and routeOuest and routeEst:
					# nom_groupe_à_instancier = 'road_1x1_1_intersect4'
					type_rue_à_instancier = 'intersection 4'
					
				
				# carrefour 3
				elif routeNord and routeSud and routeOuest and not routeEst:
					# nom_groupe_à_instancier = 'road_1x1_1_intersect3'
					type_rue_à_instancier = 'intersection 3'
					rotation = 180
				elif routeNord and routeSud and not routeOuest and routeEst:
					# nom_groupe_à_instancier = 'road_1x1_1_intersect3'
					type_rue_à_instancier = 'intersection 3'
				elif not routeNord and routeSud and routeOuest and routeEst:
					# nom_groupe_à_instancier = 'road_1x1_1_intersect3'
					type_rue_à_instancier = 'intersection 3'
					rotation = 90
				elif routeNord and not routeSud and routeOuest and routeEst:
					# nom_groupe_à_instancier = 'road_1x1_1_intersect3'
					type_rue_à_instancier = 'intersection 3'
					rotation = -90
				
				# route droite
				elif routeNord and routeSud and not routeOuest and not routeEst or not routeNord and not routeSud and routeOuest and routeEst:
					# nom_groupe_à_instancier = 'road_1x1_1_straight'
					type_rue_à_instancier = 'STRAIGHT'
					if routeNord and routeSud and not routeOuest and not routeEst:
						rotation = 90
						
				# virage
				elif routeNord and not routeSud and not routeOuest and routeEst:
					# nom_groupe_à_instancier = 'road_1x1_1_turn'
					type_rue_à_instancier = 'turn'
					rotation = -90
				elif routeNord and not routeSud and routeOuest and not routeEst:
					# nom_groupe_à_instancier = 'road_1x1_1_turn'
					type_rue_à_instancier = 'turn'
					rotation = 180
				elif not routeNord and routeSud and routeOuest and not routeEst:
					# nom_groupe_à_instancier = 'road_1x1_1_turn'
					type_rue_à_instancier = 'turn'
					rotation = 90
				elif not routeNord and routeSud and not routeOuest and routeEst:
					# nom_groupe_à_instancier = 'road_1x1_1_turn'
					type_rue_à_instancier = 'turn'
				
				
				# cul de sac
				elif not routeNord and routeSud and not routeOuest and not routeEst:
					# nom_groupe_à_instancier = 'road_1x1_1_deadend'
					type_rue_à_instancier = 'dead end'
					rotation = 90
				elif not routeNord and not routeSud and not routeOuest and routeEst:
					# nom_groupe_à_instancier = 'road_1x1_1_deadend'
					type_rue_à_instancier = 'dead end'
				elif not routeNord and not routeSud and routeOuest and not routeEst:
					# nom_groupe_à_instancier = 'road_1x1_1_deadend'
					type_rue_à_instancier = 'dead end'
					rotation = 180
				elif routeNord and not routeSud and not routeOuest and not routeEst:
					# nom_groupe_à_instancier = 'road_1x1_1_deadend'
					type_rue_à_instancier = 'dead end'
					rotation = -90
					
				datablock_rue_à_instancier = None
				try: datablock_rue_à_instancier = choisir_aléatoire_pondéré(dico_datablocks_rues_par_type[type_rue_à_instancier])
				except: pass
					
				# on doit créer une instance de groupe
				if datablock_rue_à_instancier:
					liste_rues_à_créer.append({
						'datablock' : datablock_rue_à_instancier,
						'name' : 'SceneCity road instance ' + datablock_rue_à_instancier.name + ' ' + str(objNb),
						'location' : lot.positionDuCentre(),
						'rotation_euler_radians' : (0,0,-math.radians(rotation)),
						# 'noms_groupes' : ['SceneCity', 'SceneCity roads only'],
						'noms_groupes' : ['SceneCity output roads'],
					})
					
					objNb += 1
					
		placer_objets(
			liste_dict_choses_à_placer=liste_rues_à_créer, 
			parent=objParent,
			prefixe_nom_dupli_placeur='SceneCity roads',
			prefixe_nom_dupli_placé='SceneCity road',
			couches = [0,2],
		)
		
		objParent.scale = self.échelle, self.échelle, self.échelle
		objParent.empty_display_size = 100
		objParent.empty_display_type = 'SINGLE_ARROW'
		
		print('SceneCity | Finished to create and to location '+str(objNb - 1) + ' road objects in the scene, in ' + str(round((time.time() - tempsDébut)/60, 1)) + 'm\n')
	
	def peutPlacerRouteIci(self, x, y, précédentX, précédentY):
		# en dehors de la ville ? impossible de construire
		if x < 0 or x >= self.grille_de_lots.nbLotsCôté or y < 0 or y >= len(self.grille_de_lots.lots[0]):
			return False
		
		lot = self.grille_de_lots.lots[x][y]
		
		# lot doit être constructible
		if not lot.estPlat() or not lot.estEmergé() or lot.routeConstruiteDessus:
			# print(str(lot.estPlat())+","+str(lot.estEmergé())+","+str(lot.contientUneRoute)+",")
			return False
		
		lotPrécédent = None
		if précédentX >= 0 and précédentX < self.grille_de_lots.nbLotsCôté and précédentY >= 0 and précédentY < len(self.grille_de_lots.lots[0]):
			lotPrécédent = self.grille_de_lots.lots[précédentX][précédentY]
		
		# les 2 lots doivent être à la même hauteur
		if lotPrécédent and abs(lot.positionDuCentre()[2] - lotPrécédent.positionDuCentre()[2]) > .0001:
			return False
		
		# vérifier si c'est logique de construire une route ici
		routeAuNord = lot.voisin(DIRECTION8.NORD) and lot.voisin(DIRECTION8.NORD).routeConstruiteDessus
		routeAuSud = lot.voisin(DIRECTION8.SUD) and lot.voisin(DIRECTION8.SUD).routeConstruiteDessus
		routeALOuest = lot.voisin(DIRECTION8.OUEST) and lot.voisin(DIRECTION8.OUEST).routeConstruiteDessus
		routeALEst = lot.voisin(DIRECTION8.EST) and lot.voisin(DIRECTION8.EST).routeConstruiteDessus
		routeAuNordOuest = lot.voisin(DIRECTION8.NORD_OUEST) and lot.voisin(DIRECTION8.NORD_OUEST).routeConstruiteDessus
		routeAuNordEst = lot.voisin(DIRECTION8.NORD_EST) and lot.voisin(DIRECTION8.NORD_EST).routeConstruiteDessus
		routeAuSudOuest = lot.voisin(DIRECTION8.SUD_OUEST) and lot.voisin(DIRECTION8.SUD_OUEST).routeConstruiteDessus
		routeAuSudEst = lot.voisin(DIRECTION8.SUD_EST) and lot.voisin(DIRECTION8.SUD_EST).routeConstruiteDessus
		
		totalContactsDiagonales = 0
		if routeAuNordEst: totalContactsDiagonales += 1
		if routeAuNordOuest: totalContactsDiagonales += 1
		if routeAuSudEst: totalContactsDiagonales += 1
		if routeAuSudOuest: totalContactsDiagonales += 1
		
		contactAvecDiagonalesOpposees = totalContactsDiagonales == 2 and (routeAuNordEst and routeAuSudOuest or routeAuNordOuest and routeAuSudEst)
		contactAvecUneLigneHoriOuVert = (routeAuNord and routeAuSud and not routeALOuest and not routeALEst) or (not routeAuNord and not routeAuSud and routeALOuest and routeALEst)
		
		# en contact avec 4 ou 3 diagonales ? Doit alors avoir nord et sud ou est ou ouest également
		if (totalContactsDiagonales >= 3 or contactAvecDiagonalesOpposees) and not contactAvecUneLigneHoriOuVert:
			return False
			
		# en contact avec 2 diagonales non opposées ? Doit avoir une route entre les deux diagonales et aucune sur les cotées
		if totalContactsDiagonales == 2 and not contactAvecDiagonalesOpposees:
			if routeAuNordEst and routeAuNordOuest and (not routeAuNord or routeALOuest or routeALEst): return False
			if routeAuSudEst and routeAuSudOuest and (not routeAuSud or routeALOuest or routeALEst): return False
			if routeAuNordOuest and routeAuSudOuest and (not routeALOuest or routeAuSud or routeAuNord): return False
			if routeAuNordEst and routeAuSudEst and (not routeALEst or routeAuSud or routeAuNord): return False
			
		# en contact avec une seule diagonale ? Doit avoir une seule route en contact avec la diagonale et le lot
		if totalContactsDiagonales == 1:
			if routeAuNordEst and (routeAuNord and routeALEst or not routeAuNord and not routeALEst): return False
			if routeAuSudEst and (routeAuSud and routeALEst or not routeAuSud and not routeALEst): return False
			if routeAuNordOuest and (routeAuNord and routeALOuest or not routeAuNord and not routeALOuest): return False
			if routeAuSudOuest and (routeAuSud and routeALOuest or not routeAuSud and not routeALOuest): return False
			
		return True
	
	def générer_rues_organiques(self):
		print('SceneCity | Starting road network generation')
		tempsDébut = time.time()
		random.seed(self.seed)
		
		# routes de départ, tenter des emplacements au hasard
		routes_début_sont_placées = False
		for no_essai in range(0, 100):
			i = random.randint(1, self.grille_de_lots.nbLotsCôté-2)
			j = random.randint(1, self.grille_de_lots.nbLotsCôté-2)
			
			lotVersEst = self.grille_de_lots.lots[i][j]
			lotVersOuest = self.grille_de_lots.lots[i-1][j]
			lotVersNord = self.grille_de_lots.lots[i][j+1]
			lotVersSud = self.grille_de_lots.lots[i][j-1]
			routesPlacées = 0
			
			if lotVersOuest.estPlat() and lotVersOuest.estEmergé():
				self.routesLogiques.append(RouteLogique(self, i-1, j, DIRECTION4.OUEST))
				routesPlacées += 1
				
			if lotVersEst.estPlat() and lotVersEst.estEmergé():
				self.routesLogiques.append(RouteLogique(self, i, j, DIRECTION4.EST))
				routesPlacées += 1
				
			if lotVersNord.estPlat() and lotVersNord.estEmergé():
				self.routesLogiques.append(RouteLogique(self, i, j+1, DIRECTION4.NORD))
				routesPlacées += 1
				
			if lotVersSud.estPlat() and lotVersSud.estEmergé():
				self.routesLogiques.append(RouteLogique(self, i, j-1, DIRECTION4.SUD))
				routesPlacées += 1
				
			if routesPlacées > 0:
				routes_début_sont_placées = True
				break
		
		if not routes_début_sont_placées:
			print('SceneCity | Could not place roads: no valid area. Make sure you have enough flat and emerged land on your terrain for the city')
			return
		
		# on fait grandir les rues tant qu'on peut
		resteUneRouteAFaireGrandir = True
		nbItérations = 0
		temps_dernier_affichage = time.time()
		while resteUneRouteAFaireGrandir and nbItérations < self.maxItérations:
			resteUneRouteAFaireGrandir = False
			nllesRoutes = []
			for route in self.routesLogiques:
				resteUneRouteAFaireGrandir = resteUneRouteAFaireGrandir or not route.grandirUneEtapeEtDireSiTerminé(nllesRoutes)

			self.routesLogiques.extend(nllesRoutes)
			nbItérations += 1
			if time.time() - temps_dernier_affichage >= 1:
				temps_dernier_affichage = time.time()
				print('SceneCity | ...road network generation in progress. ' + str(nbItérations) + ' iterations complete so far...')
			
		print('SceneCity | Road network generation complete at '+str(nbItérations) + ' iterations in ' + str(round((time.time() - tempsDébut)/60, 1)) + 'm\n')
	
	def générer_rues_grille(self, grille_espacement):
		print('SceneCity | Starting road network generation')
		tempsDébut = time.time()
		random.seed(self.seed)
		
		for i in range(self.grille_de_lots.nbLotsCôté):
			for j in range(self.grille_de_lots.nbLotsCôté):
				if i % (grille_espacement+1) != 0 and j % (grille_espacement+1) != 0:
					continue
			
				lot = self.grille_de_lots.lots[i][j]
				lot.routeConstruiteDessus = lot.estPlat() and lot.estEmergé()
			
		print('SceneCity | Road network generation complete in ' + str(round((time.time() - tempsDébut)/60, 1)) + 'm\n')
	