extends Characters_

onready var female = preload("res://characters/vroid1-female.tscn")
onready var male = preload("res://characters/vroid1-man.tscn")
onready var face_ctrl = preload("res://scenes/face/head_comtrol.tscn")
onready var modules = {
#	"physics": load("res://scripts/modules/character_physics.gd"),
	"cmdq": preload("res://scripts/modules/cmdq.gd"),
	"marker": preload("res://scripts/modules/npc_marker.gd"),
	"sacrifice": preload("res://scripts/modules/npc_sacrifice.gd"),
	"nun": preload("res://scripts/modules/npc_nun.gd"),
	"mystress": preload("res://scripts/modules/npc_mystress.gd"),
	"player": preload("res://scripts/modules/player_controls.gd"),
	"player_clothes": preload("res://scripts/modules/player_clothes.gd"),
	"hurtboxes": preload("res://scripts/modules/character_hurtboxes.gd"),
	"student": preload("res://scripts/modules/npc_student.gd"),
	"bandit": preload("res://scripts/modules/npc_bandit.gd")
}

var face_data_path = "res://scenes/face/"
var hair_data_path = "res://scenes/hair/"
var female_faces = []
var mesh_female_faces = {}
var male_faces = []
var mesh_male_faces = {}
var female_hairs = []
var mesh_female_hairs = {}
var male_hairs = []
var mesh_male_hairs = {}
var hair_materials = []
var data_hair_materials = {}

var name_data = {}
var rnd

var roommates = {}

#var _crowd: DetourCrowdManager

func _ready():
	var capsule_male = CapsuleShape.new()
	capsule_male.radius = 0.4
	capsule_male.height = 1.12
	capsule_male.margin = 0.08
	var capsule_female = CapsuleShape.new()
	capsule_female.radius = 0.35
	capsule_female.height = 0.9
	capsule_female.margin = 0.08
	CharacterSystemWorld.add_character_scene(female, {}, "female")
	CharacterSystemWorld.add_character_scene(male, {}, "male")
	for k in modules.keys():
		CharacterSystemWorld.add_module(k, modules[k])
	CharacterSystemWorld.set_face_ctrl_scene(face_ctrl)
	CharacterSystemWorld.add_character_shape(capsule_female, Transform(Basis().rotated(Vector3(1, 0, 0), -PI/2.0), Vector3(0, 0.805, 0)), "female")
	CharacterSystemWorld.add_character_shape(capsule_male, Transform(Basis().rotated(Vector3(1, 0, 0), -PI/2.0), Vector3(0, 0.965, 0)), "male")
	set_root_motion_mod(Transform())
	var fd = File.new()
	fd.open("res://data/names.json", File.READ)
	var names = fd.get_as_text()
	var result = JSON.parse(names)
	name_data = result.result
	rnd = RandomNumberGenerator.new()
	var data_fd: = File.new()
	for g in ["male", "female"]:
		var fp = face_data_path + g + "-face.tscn"
		if data_fd.file_exists(fp):
			match g:
				"female":
					female_faces.push_back(fp)
					mesh_female_faces[fp] = load(fp)
					CharacterSystemWorld.add_face_scene(mesh_female_faces[fp], "female")
					
				"male":
					male_faces.push_back(fp)
					mesh_male_faces[fp] = load(fp)
					CharacterSystemWorld.add_face_scene(mesh_male_faces[fp], "male")
	for id in range(10000):
		var fp_m = face_data_path + "male-face" + str(id) + ".tscn"
		var fp_f = face_data_path + "female-face" + str(id) + ".tscn"
		var hp_m = hair_data_path + "male-hair" + str(id) + ".tscn"
		var hp_f = hair_data_path + "female-hair" + str(id) + ".tscn"
		var mat = hair_data_path + "hair" + str(id) + ".tres"
		if data_fd.file_exists(fp_m):
			male_faces.push_back(fp_m)
			mesh_male_faces[fp_m] = load(fp_m)
			CharacterSystemWorld.add_face_scene(mesh_male_faces[fp_m], "male")
		if data_fd.file_exists(fp_f):
			female_faces.push_back(fp_f)
			mesh_female_faces[fp_f] = load(fp_f)
			CharacterSystemWorld.add_face_scene(mesh_female_faces[fp_f], "female")
		if data_fd.file_exists(hp_m):
			male_hairs.push_back(hp_m)
			mesh_male_hairs[hp_m] = load(hp_m)
			CharacterSystemWorld.add_hair_scene(mesh_male_hairs[hp_m], "male")
		if data_fd.file_exists(hp_f):
			female_hairs.push_back(hp_f)
			mesh_female_hairs[hp_f] = load(hp_f)
			CharacterSystemWorld.add_hair_scene(mesh_female_hairs[hp_f], "female")
		if data_fd.file_exists(mat):
			hair_materials.push_back(mat)
			data_hair_materials[mat] = load(mat)
			CharacterSystemWorld.add_hair_material(data_hair_materials[mat], "male")
			CharacterSystemWorld.add_hair_material(data_hair_materials[mat], "female")
	CharacterSystemWorld.create_character_pool(1024)
	assert(male_faces.size() > 0)
	assert(female_faces.size() > 0)
	assert(male_hairs.size() > 0)
	assert(female_hairs.size() > 0)
	assert(hair_materials.size() > 0)

func get_relationship(obj1, obj2, s) -> int:
	var stats1 = {}
	var stats2 = {}
	if obj1.has_meta("stats"):
		stats1 = obj1.get_meta("stats")
	if obj2.has_meta("stats"):
		stats2 = obj2.get_meta("stats")
	if !stats2.has("relationships"):
		return 0
	var rel = stats2.relationships
	var cid = stats1.firstname + stats1.lastname
	if !rel.has(cid):
		return 0
	var rel_data = rel[cid]
	if rel_data.has(s):
		return rel_data[s]
	return 0
func set_relationships(obj1, obj2, s, v: int) -> void:
	assert(obj1)
	assert(obj2)
	var stats1 = {}
	var stats2 = {}
	if obj1.has_meta("stats"):
		stats1 = obj1.get_meta("stats")
	if obj2.has_meta("stats"):
		stats2 = obj2.get_meta("stats")
	if !stats2.has("relationships"):
		stats2.relationships = {}
	var rel = stats2.relationships
	var cid = stats1.firstname + stats1.lastname
	if !rel.has(cid):
		rel[cid] = {}
	rel[cid][s] = v
	print(stats2)
	obj2.set_meta("stats", stats2)
func get_face_node(sc: Node) -> Node:
	var face_node_paths = ["skeleton/Skeleton/head/face", "Skeleton/head/face"]
	for e in face_node_paths:
		if sc.has_node(e):
			print("face node: ", e)
			return sc.get_node(e)
	assert(0)
	return null

func get_hair_node(sc: Node) -> Node:
	var hair_node_paths = ["skeleton/Skeleton/head/hair", "Skeleton/head/hair"]
	for e in hair_node_paths:
		if sc.has_node(e):
			print("hair node: ", e)
			return sc.get_node(e)
	assert(0)
	return null
#func compose_kinematic_character(g, enable_modules = [], face = -1, hair = -1, hair_mat = -1):
#	var id = create_character_in_pool(
#	return  CharacterSystemWorld.create_character(g, enable_modules, face, hair, hair_mat)

func replace_player_character(obj, g, enable_modules = [], face = -1, hair = -1, hair_mat = -1):
	assert(obj)
	var xform = obj.global_transform
	var p = obj.get_parent()
	obj.queue_free()
	CharacterSystemWorld.create_player_character_in_pool(enable_modules, xform)
	var body = CharacterSystemWorld.create_character_visual(0)

#	var body = compose_kinematic_character(g, enable_modules, face, hair, hair_mat)
#	var body = CharacterSystemWorld.create_character(g, enable_modules, face, hair, hair_mat)
#	p.add_child(body)
#	body.global_transform = xform
#	var orientation = Transform()
#	orientation.basis = xform.basis
#	body.set_meta("orientation", orientation)
	return body
const basedir = "res://scenes/clothes/"
#func prepare_extra_skeleton(obj, g):
#	var queue = [obj]
#	while queue.size() > 0:
#		var item = queue.pop_front()
#		if item is Skeleton:
#			item.add_to_group(g)
#			break
#		for g in item.get_children():
#			queue.push_back(g)
func set_hair_material(hair, mat: Material):
	assert(mat)
	var queue = [hair]
	while queue.size() > 0:
		var item = queue.pop_front()
		if item is MeshInstance:
			item.set_surface_material(0, mat)
			break
		for g in item.get_children():
			queue.push_back(g)
func setup_garments(obj, garments, garments_head, material):
	var skel = obj.get_meta("skeleton")
	assert(skel)
	var hair_skel = obj.get_meta("hair_skeleton")
	assert(hair_skel)
	
	if obj.has_meta("garments"):
		print("Can remove garments")
		var d = obj.get_meta("garments")
		var db = d.body_data
		var dh = d.head_data
		for e in db + dh:
			print("remove: ", e)
#			obj.remove_child(e)
			if e.get_parent() == obj:
				obj.remove_child(e)
			e.queue_free()
	var gdata_body = []
	for g in garments:
		var m: MeshInstance = MeshInstance.new()
		m.name = g
		print("loading: ", basedir + g + ".mesh")
		var geo: ArrayMesh = load(basedir + g + ".mesh")
		assert(geo)
		print("mesh: ", geo, "mat: ", material)
		geo.surface_set_material(0, material)
		m.mesh = geo
#		m.skeleton = m.get_path_to(root.get_meta("skeleton"))
#		 = root.get_meta("skeleton")
		skel.add_child(m)
		m.transform = Transform()
		gdata_body.push_back(m)
	var gdata_head = []
	for g in garments_head:
		var m: MeshInstance = MeshInstance.new()
		m.name = g
		var geo: ArrayMesh = load(basedir + g + ".mesh")
		print("mesh: ", geo, "mat: ", material)
		geo.surface_set_material(0, material)
		m.mesh = geo
#		m.skeleton = m.get_path_to(root.get_meta("skeleton"))
#		 = root.get_meta("skeleton")
		hair_skel.add_child(m)
		m.transform = Transform()
		gdata_head.push_back(m)
	var gdata = {
		"body": garments,
		"head": garments_head,
		"material": material,
		"body_data": gdata_body,
		"head_data": gdata_head
	}
	obj.set_meta("garments", gdata)

func lastname():
	var lastnames = name_data.lastname
	var d = lastnames.size()
	return lastnames[rnd.randi() % d].to_lower().capitalize()
	
func firstname(g):
	var firstnames = name_data[g].firstname
	var d = firstnames.size()
	return firstnames[rnd.randi() % d].to_lower().capitalize()

func generate_stats(npc: Node):
	var stats = npc.get_meta("stats")
	var strength = 10 + rnd.randi() % 100
	var max_health = 10 + rnd.randi() % 100
	var max_stamina = 10 + rnd.randi() % 100
	stats.tiredness = 0.0
	stats.hunger = 0.0
	stats.thirst = 0.0
	stats.libido = 0.0
	stats.toilet = 0.0
	stats.stress = 0.0
	stats.health = max_health
	stats.stamina = max_stamina
	stats.max_health = max_health
	stats.max_stamina = max_stamina
	stats.violence = rnd.randi() % (int(strength * 0.5))
	stats.xp = 0
	npc.set_meta("stats", stats)
var stat_changes = {
	"sleep":{
		"tiredness": -0.3,
		"stress": -0.03
	},
	"eating": {
		"hunger": -0.9,
		"stress": -0.01
	},
	"drinking": {
		"thirst": -2.8,
		"stress": -0.01
	},
	"use_tap": {
		"thirst": -2.8,
		"stress": -0.01
	},
	"locomotion": {
		"tiredness": 0.0015,
		"hunger": 0.025,
		"thirst": 0.045,
		"stress": 0.0001,
		"libido": 0.00015,
		"toilet": 0.0002
	}
}
func update_stats(npc: Node):
	var stats = npc.get_meta("stats")
	if stats.tiredness < 10.0:
		if stats.stamina < stats.max_stamina:
			stats.stamina += 0.1
	stats.stamina = clamp(stats.stamina, 0, stats.max_stamina)
	var animtree = npc.get_meta("animation_tree")
	var state = animtree["parameters/state/playback"].get_current_node()
	if state in stat_changes.keys():
		for s in stat_changes[state].keys():
			stats[s] += stat_changes[state][s]
			if stats[s] < 0:
				stats[s] = 0.0
	npc.set_meta("stats", stats)

# Action part should be integrated into SmartObjectManager
func check_leave_smart(npc: Node):
	return
	if !npc.has_meta("smart_object"):
		return
	var sm = npc.get_meta("smart_object")
	var new_goal = calculate_goal(npc)
	if !sm.is_in_group(new_goal):
#		var animtree = npc.get_meta("animation_tree")
#		animtree["parameters/state/playback"].travel("locomotion")
		npc.remove_meta("smart_object")

func calculate_goal(npc: Node):
	var utility = {
		"sleep": ["tiredness", 900],
		"hungry": ["hunger", 1000],
		"thirsty": ["thirst", 1300],
		"relieve_stress": ["stress", 800],
		"have_sex": ["libido", 500],
		"idle": ["", 600]
	}
	var stats = npc.get_meta("stats")
	var rutility = -10000
	var goal = "idle"
	var last_goal = ""
	var last_utility = -1
	if npc.has_meta("last_goal"):
		last_goal = npc.get_meta("last_goal")
		goal = npc.get_meta("last_goal")
	if npc.has_meta("last_utility"):
		last_utility = int(npc.get_meta("last_utility"))
		rutility = int(npc.get_meta("last_utility") * 2.0)
	for k in utility.keys():
		var st = 1.0
		if utility[k][0] != "":
			st = stats[utility[k][0]]
		var mul = utility[k][1]
		var cutil = int(st * mul)
		if rutility < cutil:
			rutility = cutil
			goal = k
	npc.set_meta("last_goal", goal)
	if goal == last_goal:
		npc.set_meta("last_utility", last_utility)
	else:
		npc.set_meta("last_utility", rutility)

	return goal

var cooldown = 0.0

var close_groups = {}

func walkto_(npc, target):
	assert(npc.has_meta("agent_id"))
	if npc.has_mete("agent_id"):
		walkto_agent(npc, target)
#	if npc.has_meta("_target"):
#		var otarget = npc.get_meta("_target")
#		if target == otarget:
#			return
#	assert(npc.has_meta("agent_id"))
#	npc.set_meta("_target", target)
##	print("walk to ", target)
##	print("walk to ", target)
##	print("walk to ", target)
#	var agent_id = npc.get_meta("agent_id")
#	if target is Spatial:
#		get_crowd().set_agent_target_position(agent_id, target.global_transform.origin)
#	elif target is Vector3:
#		get_crowd().set_agent_target_position(agent_id, target)

#func update_to_agent(root):
#	if !_crowd:
#		return
#	if !root.has_meta("agent_id"):
#		_crowd.add_agent(root, 0, false, PoolRealArray([0.2, 1.5, 0.3, 3.0]))
#	if !root.has_meta("agent_id"):
#		return
#	if !root.has_meta("_target"):
#		return
#	var velocity = root.get_meta("agent_velocity")
#	if velocity.length() == 0:
#		characters.set_walk_speed(root, 0, 0)
#	else:
##		print("agent_velocity=", velocity)
#		var vel1 = velocity
#		vel1.y = 0
#		var xform: = Transform().looking_at(vel1, Vector3.UP)
#		xform = xform.orthonormalized()
#		var orientation = root.get_meta("orientation")
#		orientation.basis = orientation.basis.slerp(xform.basis, get_physics_process_delta_time())
#		root.set_meta("orientation", orientation)
#		characters.set_walk_speed(root, clamp(vel1.length() / 6.5, 0.0, 1.0), 0)
#
#func character_physics(root):
#	var delta = get_physics_process_delta_time()
#	var animtree = root.get_meta("animation_tree")
#	var orientation = root.get_meta("orientation")
#	var root_motion = animtree.get_root_motion_transform()
#	orientation *= root_motion
#	var h_velocity = orientation.origin / delta
#	var velocity: = Vector3()
#	velocity.x = h_velocity.x
#	velocity.z = h_velocity.z
#	velocity.y = h_velocity.y
##	if root.has_meta("climb"):
##		print("has climb meta")
##		if root.has_meta("cmdqueue"):
##			print("+has cmdqueue")
#	if !root.has_meta("vehicle") && !root.has_meta("cmdqueue"):
#		if root is KinematicBody:
#			if !root.is_on_floor():
#				velocity += Vector3(0, -9.8, 0)
#			velocity = root.move_and_slide(velocity, Vector3.UP, true, 4, 0.785, false)
#	elif root.has_meta("cmdqueue") && root.has_meta("cmdq_walk"):
#		if root is KinematicBody:
#			if !root.is_on_floor() && !root.has_meta("climb"):
#				velocity += Vector3(0, -9.8, 0)
#			velocity = root.move_and_slide(velocity, Vector3.UP, true, 4, 0.785, false)
#	elif root.has_meta("cmdqueue") && root.has_meta("climb"):
#		if root is KinematicBody:
#			velocity.y = h_velocity.y
#			velocity = root.move_and_slide(velocity, Vector3.UP, true, 4, 0.785, false)
#	orientation.origin = Vector3()
#	orientation = orientation.orthonormalized()
#	root.global_transform.basis = orientation.basis
#	root.set_meta("orientation", orientation)
#	if root.has_meta("vehicle"):
#		var vehicle: VehicleBody = get_meta("vehicle")
#		if has_meta("vehicle_offset"):
#			var xform = get_meta("vehicle_offset")
#			root.global_transform = vehicle.global_transform * xform
#	if root.has_node("blackboard"):
#		var bb = root.get_node("blackboard")
#		bb.set("velocity", velocity)
#	update_to_agent(root)

func check_main_animtree(item):
	if !item is AnimationTree:
		return false
	for g in ["hair", "face"]:
		if item.is_in_group(g):
			return false
	return true

func check_main_skeleton(item):
	if !item is Skeleton:
		return false
	for g in ["hair", "face"]:
		if item.is_in_group(g):
			return false
	return true

func setup_character_physics(root):
	var queue = [root]
	while queue.size() > 0:
		var item = queue.pop_front()
		if check_main_animtree(item):
			root.set_meta("animation_tree", item)
		if check_main_skeleton(item):
			root.set_meta("skeleton", item)
		if item is Skeleton && item.is_in_group("hair"):
			root.set_meta("hair_skeleton", item)
		if item is Skeleton && item.is_in_group("face"):
			root.set_meta("face_skeleton", item)
		for e in item.get_children():
			queue.push_back(e)
#	var velocity = Vector3()
	var orientation = Transform()
	if root.is_inside_tree():
		orientation = root.global_transform
	orientation.origin = Vector3()
	root.set_meta("orientation", orientation)

func _process(delta):
	if cooldown > 0.0:
		cooldown -= delta
		return
	for e in get_tree().get_nodes_in_group("character"):
		var gr = []
		for o in get_tree().get_nodes_in_group("character"):
			if e == o:
				continue
			var e_org = e.global_transform.origin
			var o_org = o.global_transform.origin
			if e_org.distance_to(o_org) < 1.2:
				gr.push_back(o)
		close_groups[e] = gr
		if e.has_meta("animation_tree"):
			var animtree = e.get_meta("animation_tree")
			var state = animtree["parameters/state/playback"].get_current_node()
			var face_playback = e.get_meta("face_playback")
			var face_ctrl = e.get_meta("face_control")
			var p = face_ctrl[face_playback]
			if state == "sleeping":
				if p.get_current_node() != "sleeping":
					p.travel("sleeping")
			else:
				if p.get_current_node() != "locomotion":
					p.travel("locomotion")
	cooldown = 0.2
func get_player():
	var c = get_viewport().get_camera()
	if !c:
		return null
	if !c.has_meta("player"):
		return null
	var player = c.get_meta("player")
	if !player:
		return null
	return player

func _physics_process(delta):
	var player = get_player()
	if !player:
		return
#	if player.has_meta("animation_tree") && !player.has_meta("vehicle"):
#		if streaming.can_spawn:
#			character_physics(player)
