extends Node
class_name AvatarDollDataManager

static var instance: AvatarDollDataManager


var avatar_part_scope = {"Clothes": 0, "Bodies": 1, "Heads": 2, "Hairs": 3, "Faces": 4}
var default_texture: Texture2D = preload("res://Resources/UI/missing_image.png")
var blank_texture: Texture2D = preload("res://Resources/__blank__.png")

var initialed = false
var work_json_file = ""
var avatar_part_state = {}
var avatar_part_state_name_to_index = {}
var avatar_part_state_index_to_name = {}

var avatar_doll_data = null
var avatar_asset_id_max = -1
var avatar_asset = {}
var avatar_asset_name = {}

var char_to_index: Dictionary = {}
var index_to_char: Dictionary = {}


func _init():
	instance = self
	
	var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
	for i in range(chars.length()):
		var c = chars[i]
		char_to_index[c] = i
		index_to_char[i] = str(c)

func load_json(json_path) -> bool:
	var file = FileAccess.open(json_path, FileAccess.READ)
	var json_str = file.get_as_text()
	var json = JSON.new()
	var error = json.parse(json_str)
	if error != OK:
		push_error("load_avatar_doll_json ERROR!")
		initialed = false
		return false
		
	avatar_doll_data = _fix_avatar_dool_data(json.get_data())
	work_json_file = json_path
	
	# _load_setting
	reload_setting()
	
	# AssetMap
	reload_all_asset()
		
	initialed = true
	return true

func save_json(json_path=""):
	if json_path == "":
		json_path = work_json_file
	
	if json_path == "" or initialed == false:
		return
	
	var data = _fix_avatar_dool_data(avatar_doll_data)
	var json_str = JSON.stringify(data)
		
	var file = FileAccess.open(json_path, FileAccess.WRITE)
	file.store_string(json_str)
	file.close()

func reload_setting():
	avatar_part_state.clear()
	avatar_part_state_name_to_index.clear()
	avatar_part_state_index_to_name.clear()
	
	for part in avatar_part_scope:
		avatar_part_state[part] = []
		
		var scope_flag = avatar_part_scope[part]
		for state in avatar_doll_data["Settings"]["States"]:
			if 1 == 1 & (int(state["Scope"]) >> scope_flag):
				avatar_part_state[part].append(state)
		
		var result = {}
		_full_permutation_state(result, avatar_part_state[part])
		avatar_part_state_name_to_index[part] = result
		var swapped_dict = {}
		for key in result:
			var value = result[key]
			swapped_dict[value] = key
		avatar_part_state_index_to_name[part] = swapped_dict

func _fix_avatar_dool_data(data):
	# fix data
	data["FormatVersion"] = int(data["FormatVersion"])
	data["Settings"]["MaxBodySlotCost"] = int(data["Settings"]["MaxBodySlotCost"])
	for i in range(data["Settings"]["States"].size()):
		data["Settings"]["States"][i]["Id"] = int(data["Settings"]["States"][i]["Id"])
		data["Settings"]["States"][i]["Scope"] = int(data["Settings"]["States"][i]["Scope"])
		
	var new_map = {}
	for key in data["AssetMap"]:
		if key is String and not key.is_valid_int():
			continue
		elif key is int and key <= 0:
			continue
			
		var asset_id = int(key)
		if asset_id > 0:
			new_map[asset_id] = [
				data["AssetMap"][key][0].simplify_path(), 
				int(data["AssetMap"][key][1]), 
				int(data["AssetMap"][key][2])
			]
	data["AssetMap"] = new_map
	
	for p in avatar_part_scope.keys():
		if p not in data:
			continue
		for index in range(data[p].size()):
			#data[p][index]["Guid"] = data[p][index]["Guid"]
			data[p][index]["IconAssetId"] = int(data[p][index]["IconAssetId"])
			for i in range(data[p][index]["Palettes"].size()):
				#data[p][index]["Palettes"][i]["Guid"] = data[p][index]["Palettes"][i]["Guid"]
				for j in range(data[p][index]["Palettes"][i]["IconPaletteParam"].size()):
					data[p][index]["Palettes"][i]["IconPaletteParam"][j]["ElementIndex"] = int(data[p][index]["Palettes"][i]["IconPaletteParam"][j]["ElementIndex"])
					data[p][index]["Palettes"][i]["IconPaletteParam"][j]["BlendMode"] = int(data[p][index]["Palettes"][i]["IconPaletteParam"][j]["BlendMode"])
				for j in range(data[p][index]["Palettes"][i]["ElementPaletteParam"].size()):
					data[p][index]["Palettes"][i]["ElementPaletteParam"][j]["ElementIndex"] = int(data[p][index]["Palettes"][i]["ElementPaletteParam"][j]["ElementIndex"])
					data[p][index]["Palettes"][i]["ElementPaletteParam"][j]["BlendMode"] = int(data[p][index]["Palettes"][i]["ElementPaletteParam"][j]["BlendMode"])
			
			var elements = {}
			for key in data[p][index]["Elements"]:
				if data[p][index]["Elements"][key].size() > 0:
					elements[key] = data[p][index]["Elements"][key]
				for i in range(data[p][index]["Elements"][key].size()):
					data[p][index]["Elements"][key][i]["AssetId"] = int(data[p][index]["Elements"][key][i]["AssetId"])
					data[p][index]["Elements"][key][i]["Layer"] = int(data[p][index]["Elements"][key][i]["Layer"])
					data[p][index]["Elements"][key][i]["LayerOffset"] = int(data[p][index]["Elements"][key][i]["LayerOffset"])
			data[p][index]["Elements"] = elements
			
			if p == "Clothes":
				var ClothClasses = int(data[p][index]["ClothClasses"]["Hash"])
				data[p][index]["ClothClasses"] = { "Hash": ClothClasses }
				
				var BodySlotCost = {}
				for slot in data[p][index]["BodySlotCost"]:
					BodySlotCost[int(slot)] = int(data[p][index]["BodySlotCost"][slot])
				data[p][index]["BodySlotCost"] = BodySlotCost
	return data
		
func _full_permutation_state(result: Dictionary, states: Array, depth = 0, current_list = ["R"], current_code=""):
	if states.size() == depth:
		result["/".join(current_list)] = current_code
	else:
		var state = states[depth]
		for i in range(state["Values"].size()):
			var new_list = current_list + [state["Values"][i]]
			var new_code = current_code + str(index_to_char.get(i, "#"))
			if state["Required"][i] == false: 
				new_list[0] = "N"
			_full_permutation_state(result, states, depth + 1, new_list, new_code)

func load_texture(texture_path: String) -> ImageTexture:
	var image = Image.new()
	var err = image.load(texture_path)
	if err != OK:
		#push_error("load image fail: %s" % texture_path)
		return null
		
	var image_texture = ImageTexture.new()
	image_texture.set_image(image)
	return image_texture
	
func load_asset(texture_path: String):
	texture_path = texture_path.simplify_path()
	for asset_id in avatar_doll_data["AssetMap"]:
		if avatar_doll_data["AssetMap"][asset_id][0] == texture_path:
			return asset_id
	
	var texture = load_texture(texture_path)
	if texture != null:
		var asset_id = avatar_asset_id_max + 1
		avatar_asset[asset_id] = texture
		avatar_asset_name[asset_id] = texture_path.get_file()
		var asset = [texture_path, int(texture.get_size().x), int(texture.get_size().y)]
		avatar_doll_data["AssetMap"][asset_id] = asset
		avatar_asset_id_max += 1
		return asset_id
	return null
	
func replace_asset(asset_id, texture_path):
	if asset_id not in avatar_doll_data["AssetMap"]:
		return false
		
	texture_path = texture_path.simplify_path()
	
	var texture = load_texture(texture_path)
	if texture == null:
		return false
		
	avatar_asset[asset_id] = texture
	avatar_asset_name[asset_id] = texture_path.get_file()
	var asset = [texture_path, int(texture.get_size().x), int(texture.get_size().y)]
	avatar_doll_data["AssetMap"][asset_id] = asset
	avatar_asset_id_max += 1
	return true
	
func remove_asset(asset_id):
	if asset_id not in avatar_doll_data["AssetMap"]:
		return
	
	for scope in avatar_part_scope:
		if scope not in avatar_doll_data:
			continue
		
		for part_index in range(avatar_doll_data[scope].size()):
			var part = avatar_doll_data[scope][part_index]
			# icon
			if part["IconAssetId"] == asset_id:
				part["IconAssetId"] = 0
			
			# element
			for state_code in part["Elements"]:
				var new_elements = []
				for element_index in range(part["Elements"][state_code].size()):
					if part["Elements"][state_code][element_index]["AssetId"] != asset_id:
						new_elements.append(part["Elements"][state_code][element_index])
				part["Elements"][state_code] = new_elements
	
	avatar_doll_data["AssetMap"].erase(asset_id)
	avatar_asset.erase(asset_id)
	avatar_asset_name.erase(asset_id)
	
func reload_all_asset():
	avatar_asset_id_max = 0
	avatar_asset.clear()
	for asset_id in avatar_doll_data["AssetMap"]:
		avatar_asset_id_max = max(asset_id, avatar_asset_id_max)
		
		var asset = avatar_doll_data["AssetMap"][asset_id]
		var asset_path = asset[0]
		var asset_name = asset_path.get_file().get_basename()
		var texture = load_texture(asset_path)
		if texture != null:
			avatar_doll_data["AssetMap"][asset_id] = [asset_path, int(texture.get_size().x), int(texture.get_size().y)]
			avatar_asset[asset_id] = texture
			avatar_asset_name[asset_id] = asset_name
		else:
			var image: Image = default_texture.get_image()
			image.resize(asset[1], asset[2], Image.INTERPOLATE_LANCZOS)
			avatar_asset[asset_id] = ImageTexture.create_from_image(image)
			avatar_asset_name[asset_id] = asset_name
	
func get_asset(asset_id):
	if asset_id == 0:
		return blank_texture
	return avatar_asset[asset_id]

func get_asset_name(asset_id):
	asset_id = int(asset_id)
	if asset_id == 0:
		return "__blank__"
	elif asset_id in avatar_asset_name:
		return avatar_asset_name[asset_id]
	else:
		return ""
	 
func get_asset_id(asset_name):
	if asset_name == "__blank__":
		return 0
	for asset_id in avatar_asset_name:
		if asset_name == avatar_asset_name[asset_id]:
			return asset_id
	return -1
	
func get_asset_using(asset_id):
	if not initialed:
		return []
	
	var result = []
	for scope in avatar_part_scope:
		if scope not in avatar_doll_data:
			continue
		
		for part_index in range(avatar_doll_data[scope].size()):
			var part = avatar_doll_data[scope][part_index]
			# icon
			if part["IconAssetId"] == asset_id:
				result.append(["icon", scope, part_index])
			
			# element
			for state_code in part["Elements"]:
				for element_index in range(part["Elements"][state_code].size()):
					if part["Elements"][state_code][element_index]["AssetId"] == asset_id:
						result.append(["elem", scope, part_index, state_code, element_index])
				
	return result
	
func get_avatar_part_scope_flag(avatar_part):
	return avatar_part_scope.get(avatar_part, -1)
	
func get_states_hash_code(setting) -> int:
	var final_hash := 0	
	for state in setting["States"]:
		var list_hash := 0
		for value in state["Required"]:
			var value_hash := 1 if value else 0
			list_hash = _combine_hashes(list_hash, value_hash)
		final_hash = _combine_hashes(final_hash, list_hash)		
	return final_hash

func _combine_hashes(h1: int, h2: int) -> int:
	var _hash: int = 2166136261
	_hash = (_hash ^ (h1 & 0xFF)) * 16777619
	_hash = (_hash ^ ((h1 >> 8) & 0xFF)) * 16777619
	_hash = (_hash ^ ((h1 >> 16) & 0xFF)) * 16777619
	_hash = (_hash ^ ((h1 >> 24) & 0xFF)) * 16777619
	
	_hash = (_hash ^ (h2 & 0xFF)) * 16777619
	_hash = (_hash ^ ((h2 >> 8) & 0xFF)) * 16777619
	_hash = (_hash ^ ((h2 >> 16) & 0xFF)) * 16777619
	_hash = (_hash ^ ((h2 >> 24) & 0xFF)) * 16777619
	return _hash & 0x7FFFFFFF

func get_state_index(c: String) -> int:
	return char_to_index.get(c, -1)
	
func get_state_char(index: int) -> String:
	return index_to_char.get(index, "#")

func get_state_names(avatar_type, state_code):
	var state_names = []
	var state = avatar_part_state[avatar_type]
	for i in range(state_code.length()):
		var index = get_state_index(state_code[i])
		state_names.append(state[i]["Values"][index])
	return state_names

func get_render_palettes(part: String, avatar_index: int, state_code: String, palette_id: int, is_icon: bool = false):
	var palettes = {}
	var target = avatar_doll_data[part][avatar_index]
	var elements = target["Elements"][state_code]
	
	var palette_setting
	if palette_id < 0:
		palette_setting = []
	elif is_icon:
		palette_setting = target["Palettes"][palette_id]["IconPaletteParam"]
	else:
		palette_setting = target["Palettes"][palette_id]["ElementPaletteParam"]
	
	for i in range(elements.size()):
		palettes[i] = []
	
	for setting in palette_setting:
		var element_index = setting["ElementIndex"]
		if element_index == -1:
			for i in palettes:
				palettes[i].append(setting)
		else:
			palettes[element_index].append(setting)
	return palettes
	
