tool
extends CharacterSkeleton3D

# Copyright Péter Magyar relintai@gmail.com
# MIT License, functionality from this class needs to be protable to the entity spell system

# Copyright (c) 2019-2021 Péter Magyar

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

var job_script = preload("res://player/CharacterSkeletonMeshJob.gd")

export(bool) var refresh_in_editor : bool = false setget editor_build
export(bool) var automatic_build : bool = false
export(bool) var use_lod : bool = true

export(NodePath) var mesh_instance_path : NodePath
var mesh_instance : MeshInstance = null

export(NodePath) var skeleton_path : NodePath
var skeleton : Skeleton

export(Array, ModelVisual) var viss : Array

var meshes : Array

var _current_lod_level : int = 0

var _generating : bool = false

var _mesh_job : ThreadPoolJob = null
var _material_cache : ESSMaterialCache = null

var bone_names = {
	0: "root",
	1: "pelvis",
	2: "spine",
	3: "spine_1",
	4: "spine_2",
	5: "neck",
	6: "head",
	
	7: "left_clavicle",
	8: "left_upper_arm",
	9: "left_forearm",
	10: "left_hand",
	11: "left_thunb_base",
	12: "left_thumb_end",
	13: "left_fingers_base",
	14: "left_fingers_end",
	
	15: "right_clavicle",
	16: "right_upper_arm",
	17: "right_forearm",
	18: "right_hand",
	19: "right_thumb_base",
	20: "right_thumb_head",
	21: "right_fingers_base",
	22: "right_fingers_head",
	
	23: "left_thigh",
	24: "left_calf",
	25: "left_foot",
	
	26: "right_thigh",
	27: "right_calf",
	28: "right_foot",
}


var _textures : Array
var _texture : Texture

var _editor_built : bool = false
var sheathed : bool = true

func _enter_tree():
	_mesh_job = job_script.new()
	_mesh_job.use_lod = use_lod
	_mesh_job.connect("finished", self, "job_finished")
	
	meshes.resize(3)
	
	skeleton = get_node(skeleton_path) as Skeleton
	mesh_instance = get_node(mesh_instance_path) as MeshInstance

	set_process(false)
	
	if Engine.editor_hint:
		return
			
#	if not Engine.is_editor_hint():
	for iv in viss:
		add_model_visual(iv as ModelVisual)

	call_deferred("sheath", sheathed)
	
	if automatic_build:
		call_deferred("build_model")

func _build_model():
	if _generating:
		return
		
	_generating = true
	
	if Engine.is_editor_hint() and not refresh_in_editor:
		set_process(false)
		return
	
	if not automatic_build:
		set_process(false)
		return
	
	build()
	set_process(false)
		
	model_dirty = false
	
func build():
	setup_build_mesh()
	
	sort_layers()
	
	var data : Array = Array()

	for skele_point in range(ESS.skeletons_bones_index_get(entity_type).count(',') + 1):
		var bone_name : String = get_bone_name(skele_point)

		if bone_name == "":
			print("Bone name error")
			continue
		
		var bone_idx : int = skeleton.find_bone(bone_name)
		
		var abi_dict : Dictionary = Dictionary()
		
		for abti in range(bone_additional_mesh_transform_count):
			var obi : int = bone_additional_mesh_transform_bone_index_get(abti)
			var bin = get_bone_name(obi)
			var bi : int = skeleton.find_bone(bin)
						
			abi_dict[bi] = bone_additional_mesh_transform_transform_get(abti) * bone_additional_mesh_transform_user_transform_get(abti)

		var ddict : Dictionary = Dictionary()
		for j in range(get_model_entry_count(skele_point)):
			var entry : SkeletonModelEntry = get_model_entry(skele_point, j)
			
			for k in range(entry.entry.size):
				if entry.entry.get_mesh(k):
					ddict["bone_name"] = bone_name
					ddict["bone_idx"] = bone_idx
					
					var global_pose = skeleton.get_bone_global_pose(bone_idx)
					
					ddict["transform"] = skeleton.get_bone_global_pose(bone_idx)
					
					if abi_dict.has(bone_idx):
						global_pose *= abi_dict[bone_idx]
					
#					for abti in range(bone_model_additional_mesh_transform_count):
#						var bin = get_bone_name(bone_model_additional_mesh_transform_bone_index_get(abti))
#						var bi : int = skeleton.find_bone(bin)
#
#						if bone_idx == bi:
#							global_pose *= bone_model_additional_mesh_transform_bone_transform_get(abti)
#							break
#
					ddict["transform"] = global_pose
									
					ddict["mesh"] = entry.entry.get_mesh(k)
		
		var texture_layer_array : Array = Array()
		texture_layer_array.resize(ESS.texture_layers.count(",") + 1)
		var texture_used : bool = false
		
		for j in range(get_model_entry_count(skele_point)):
			var entry : SkeletonModelEntry = get_model_entry(skele_point, j)
			
			var layer : int = entry.entry.override_layer
			
			if texture_layer_array.size() <= layer || texture_layer_array[layer]:
				continue
			
			for k in range(entry.entry.size):
				if entry.entry.get_texture(k):
					texture_layer_array[layer] = entry.entry.get_texture(k)
					texture_used = true
					break
		
		if texture_used:
			ddict["textures"] = texture_layer_array
			
		if !ddict.empty():
			data.append(ddict)
	
	_mesh_job.data = data
	_material_cache= ESS.material_cache_get(data.hash())
	 
	if _material_cache.material_get_num() == 0:
		#lock just in case
		_material_cache.mutex_lock()

		if _material_cache.material_get_num() == 0:
			#this has to be done on the main thread!
			_material_cache.initial_setup_default()
		
		_material_cache.mutex_unlock()
	
	_mesh_job.material_cache = _material_cache
	
	finish_build_mesh()
	
	ThreadPool.add_job(_mesh_job)
#	_mesh_job.execute()

func setup_build_mesh() -> void:
	if mesh_instance != null:
		mesh_instance.hide()
	
	if get_animation_tree() != null:
		get_animation_tree().active = false
	
	if get_animation_player() != null:
		get_animation_player().play("rest")
		get_animation_player().seek(0, true)
	
func finish_build_mesh() -> void:
	mesh_instance.mesh = null
#	mesh_instance.mesh = meshes[_current_lod_level]
		
	if get_animation_tree() != null:
		get_animation_tree().active = true
		
	if mesh_instance != null:
		mesh_instance.show()
	
	_generating = false	
	
func job_finished():
	meshes = _mesh_job.meshes
	mesh_instance.mesh = meshes[_current_lod_level]
	
	if !mesh_instance.is_software_skinning_enabled():
		mesh_instance.initialize_skinning(true, true)

func clear_mesh() -> void:
	meshes.clear()
	meshes.resize(3)
	
	if mesh_instance != null:
		mesh_instance.mesh = null

func editor_build(val : bool) -> void:
	if not is_inside_tree():
		return

	skeleton = get_node(skeleton_path) as Skeleton
	mesh_instance = get_node(mesh_instance_path) as MeshInstance
	
	if val:
		_editor_built = true
		build()
	else:
		clear_mesh()
		_editor_built = false
		
	refresh_in_editor = val

func get_bone_name(skele_point : int) -> String:
	if bone_names.has(skele_point):
		return bone_names[skele_point]
		
	return ""
	
func _common_attach_point_index_get(point):
	if point == EntityEnums.COMMON_SKELETON_POINT_LEFT_HAND:
		return 0
	elif point == EntityEnums.COMMON_SKELETON_POINT_ROOT:
		return 3
	elif point == EntityEnums.COMMON_SKELETON_POINT_SPINE_2:
		return 6
	elif point == EntityEnums.COMMON_SKELETON_POINT_RIGHT_HAND:
		return 1
	elif point == EntityEnums.COMMON_SKELETON_POINT_BACK:
		return 6
	elif point == EntityEnums.COMMON_SKELETON_POINT_RIGHT_HIP:
		return 4
	elif point == EntityEnums.COMMON_SKELETON_POINT_WEAPON_LEFT:
		return 7
	elif point == EntityEnums.COMMON_SKELETON_POINT_WEAPON_LEFT_BACK:
		return 9
	elif point == EntityEnums.COMMON_SKELETON_POINT_WEAPON_RIGHT:
		return 8
	elif point == EntityEnums.COMMON_SKELETON_POINT_WEAPON_RIGHT_BACK:
		return 10
	elif point == EntityEnums.COMMON_SKELETON_POINT_WEAPON_LEFT_SHIELD:
		return 11
		
	return 3
	
func set_lod_level(level : int) -> void:
	if _current_lod_level == level:
		return
		
	if meshes.size() == 0:
		return
	
	if level < 0:
		return
		
	if level >= meshes.size():
		level = meshes.size() - 1
		
	_current_lod_level = level
	
	mesh_instance.mesh = meshes[_current_lod_level]
	
	if !mesh_instance.is_software_skinning_enabled():
		mesh_instance.initialize_skinning(true, true)
	
		

func toggle_sheath():
	sheathed = not sheathed
	sheath(sheathed)
		

	
func sheath(on : bool) -> void:
	var pos = 0
	
	if not on:
		pos = 1
	
	attach_point_node_get(7).set_node_position(pos)
	attach_point_node_get(8).set_node_position(pos)
	attach_point_node_get(9).set_node_position(pos)
	attach_point_node_get(10).set_node_position(pos)
	attach_point_node_get(11).set_node_position(pos)