local CharacterFSMManager = require "app/module/character_fsm/character_fsm_manager"
local BaseObject = require "app/bf/unity/base_object"
local CharacterObject = class("CharacterObject", BaseObject)

local BF_CHARACTER_HELPER = GConst.TYPEOF_UNITY_CLASS.BF_CHARACTER_HELPER
local ANIMATOR = GConst.TYPEOF_UNITY_CLASS.ANIMATOR
local ANIMATION_FRAME_PER_SECOND = 30

local SUPPORT_SHADER = {
	["BF/Models/Character Battle"] = true,
}

local hash = GFunc.hash

local HashName = {}
local function getHashName(str)
	local intName = HashName[str]
	if intName == nil then
		intName = hash(str)
		HashName[str] = intName
	end
	return intName
end

local CHILDMAP_METATABLE = {
	__index = function(t, k)
		if rawget(t, k) == nil then
			local v = rawget(t, hash(k))
			if v then
				rawset(t, k, v)
			end
			return v
		end
		return rawget(t, k)
	end
}

local Vector3 = BF.Vector3(0, 0, 0)

local abs = math.abs

function CharacterObject:ctor()
	self.timeScale = 1
end

function CharacterObject:initWithPrefab(assetPath, prefab, modelId)
	BaseObject.initWithPrefab(self, assetPath, prefab)
	self.characterHelper = self:getComponent(BF_CHARACTER_HELPER)
	if self.characterHelper == nil then
		self.characterHelper = self:addComponent(BF_CHARACTER_HELPER)
	end
	self.mainModel = self.characterHelper:GetModelObject()
	self.objectIndex = -1
	self.modelId = modelId
	self:_genAllBones()
end

function CharacterObject:initWithCharacterHelper(characterHelper, index, gameObject, name, admin)
	self.characterHelper = characterHelper
	self.objectIndex = index
	self.gameObject = gameObject
	self.name = name
	self.admin = admin
end

function CharacterObject:_genAllBones()
	local childMap = {}
	if self.characterHelper then
		local childNum = self.characterHelper:GetListCount()
		for i = 0, childNum do
			local gameObject = self.characterHelper:GetGameObjectByIndex(i)
			local hashName = self.characterHelper:GetHashNameByIndex(i)
			local child = CharacterObject:create()
			child:initWithCharacterHelper(self.characterHelper, i, gameObject, hashName, self)
			if childMap[hashName] == nil then
				childMap[hashName] = child
			end
			table.insert(self.childList, child)
		end
	end
	setmetatable(childMap, CHILDMAP_METATABLE)
	self.childMap = childMap
end

function CharacterObject:getBoneByName(name)
	return self.childMap[name]
end

function CharacterObject:fastGetPosition()
	if self.characterHelper then
		self.characterHelper:CachePosition(self.objectIndex)
		return self.characterHelper.PositionX, self.characterHelper.PositionY, self.characterHelper.PositionZ
	else
		local position = self:getTransform().position
		return position.x, position.y, position.z
	end
end

function CharacterObject:getBonePositionByName(name)
	return self.childMap[name]:fastGetPosition()
end

function CharacterObject:getHashCode()
	local code = -1
	if self.characterHelper then
		code = self.characterHelper:GetModelHashCode()
	end
	return code
end

function CharacterObject:play(name, layer, normalizedTime)
	layer = layer or -1
	normalizedTime = normalizedTime or 0

	if self.weaponObj and not self.weaponObj:isDestroyed() then
		if self.weaponAniName then
			-- local weaponAniName = self:getWeaponSpecialAni(name, self.weaponAniName, true)
			-- if self.weaponObj:getStateTime(weaponAniName) > 0 then
			-- 	self.weaponObj:play(weaponAniName, layer, normalizedTime)
			-- end

			local newName = self:getWeaponSpecialAni(name, self.weaponAniName) or name
			if self:getStateTime(newName) > 0 then
				name = newName
			end
		end
	else
		self:setWeaponInfo()
	end

	if self.mainAnimator == nil then
		if self.mainModel then
			self.mainAnimator = self.mainModel:GetComponent(ANIMATOR)
			self.mainAnimator:Play(name, layer, normalizedTime)
		end
	else
		self.mainAnimator:Play(name, layer, normalizedTime)
	end
end

function CharacterObject:playHashName(hashName, layer, normalizedTime)
	layer = layer or -1
	normalizedTime = normalizedTime or 0

	if self.weaponObj and not self.weaponObj:isDestroyed() then
		if self.weaponAniName then
			-- local weaponHashName = self:getWeaponSpecialAniHash(hashName, self.weaponAniName, true)
			-- if self.weaponObj:getStateTimeHash(weaponHashName) > 0 then
			-- 	self.weaponObj:playHashName(weaponHashName, layer, normalizedTime)
			-- end

			local newHashName = self:getWeaponSpecialAniHash(hashName, self.weaponAniName, true) or hashName
			if self:getStateTimeHash(newHashName) > 0 then
				hashName = newHashName
			end
		end
	else
		self:setWeaponInfo()
	end

	if self.mainAnimator == nil then
		if self.mainModel then
			self.mainAnimator = self.mainModel:GetComponent(ANIMATOR)
			self.mainAnimator:Play(hashName, layer, normalizedTime)
		end
	else
		self.mainAnimator:Play(hashName, layer, normalizedTime)
	end
end

function CharacterObject:getWeaponSpecialAni(name, specialAniName, isWeapon)
	if not self.weaponSpecialAniMap then
		self.weaponSpecialAniMap = {}
	end

	if not self.weaponSpecialAniMap[name] then
		self.weaponSpecialAniMap[name] = {}
	end

	if not self.weaponSpecialAniMap[name][specialAniName] then
		self.weaponSpecialAniMap[name][specialAniName] = {}
	end

	if not isWeapon then
		if not self.weaponSpecialAniMap[name][specialAniName].heroAniName then
			self.weaponSpecialAniMap[name][specialAniName].heroAniName = name .. "_" .. specialAniName
		end
		return self.weaponSpecialAniMap[name][specialAniName].heroAniName
	else
		if not self.weaponSpecialAniMap[name][specialAniName].weaponAniName and self.modelId then
			self.weaponSpecialAniMap[name][specialAniName].weaponAniName = self.modelId .. "_" .. name .. "_" .. specialAniName
		end
		return self.weaponSpecialAniMap[name][specialAniName].weaponAniName
	end
end

function CharacterObject:getWeaponSpecialAniHash(hashName, specialAniName, isWeapon)
	if not self.weaponSpecialAniHashMap then
		self.weaponSpecialAniHashMap = {}
	end
	
	if not self.weaponSpecialAniHashMap[hashName] then
		self.weaponSpecialAniHashMap[hashName] = {}
	end

	if not self.weaponSpecialAniHashMap[hashName][specialAniName] then
		self.weaponSpecialAniHashMap[hashName][specialAniName] = {}
	end

	if not isWeapon then
		if not self.weaponSpecialAniHashMap[hashName][specialAniName].heroAniName then
			local name = GConst.HeroConst.HERO_ANI_HASH_MAP[hashName]
			if name then
				self.weaponSpecialAniHashMap[hashName][specialAniName].heroAniName = CS.UnityEngine.Animator.StringToHash(name .. "_" .. specialAniName)
			end
		end
		return self.weaponSpecialAniHashMap[hashName][specialAniName].heroAniName
	else
		if not self.weaponSpecialAniHashMap[hashName][specialAniName].weaponAniName and self.modelId then
			local name = GConst.HeroConst.HERO_ANI_HASH_MAP[hashName]
			if name then
				self.weaponSpecialAniHashMap[hashName][specialAniName].weaponAniName = CS.UnityEngine.Animator.StringToHash(self.modelId .. "_" .. name .. "_" .. specialAniName)
			end
		end
		return self.weaponSpecialAniHashMap[hashName][specialAniName].weaponAniName
	end
end

function CharacterObject:setWeaponInfo(weaponAniName, weaponObj)
	if not weaponObj then
		self.weaponAniName = nil
		self.weaponObj = nil
	end
	self.weaponAniName = weaponAniName
	self.weaponObj = weaponObj
end

function CharacterObject:setAnimatorBool(key, value)
	if self.mainAnimator == nil then
		if self.mainModel then
			self.mainAnimator = self.mainModel:GetComponent(ANIMATOR)
			self.mainAnimator:SetBool(key, value)
		end
	else
		self.mainAnimator:SetBool(key, value)
	end
end

function CharacterObject:getMainAnimator()
	if self.mainAnimator == nil then
		if self.mainModel then
			self.mainAnimator = self.mainModel:GetComponent(ANIMATOR)
		end
	end
	return self.mainAnimator
end

function CharacterObject:getMainModelTransform()
	if self.mainModelTransform == nil then
		self.mainModelTransform = self.mainModel and self.mainModel.transform or nil
	end
	return self.mainModelTransform
end

function CharacterObject:getMainModel()
	return self.mainModel
end

function CharacterObject:getCurrentAnimationHash()
	return CS.BF.Utils.GetCurrentAnimationHash(self:getMainAnimator())
end

function CharacterObject:setMainModelLocalScale(x, y, z)
	local transform = self:getMainModelTransform()
	if transform then
		Vector3.x = x
		Vector3.y = y
		Vector3.z = z
		transform.localScale = Vector3
	end
end

function CharacterObject:setMainModelLocalPosition(x, y, z)
	if self.characterHelper then
		self.characterHelper:SetMainModelLocalPosition(x, y, z)
	else
		local transform = self:getMainModelTransform()
		if transform then
			Vector3.x = x
			Vector3.y = y
			Vector3.z = z
			transform.localPosition = Vector3
		end
	end
end

function CharacterObject:getStateTime(name)
	local hashName = getHashName(name)
	return self.characterHelper:GetStateTime(hashName)
end

function CharacterObject:getStateTimeHash(hashName)
	return self.characterHelper:GetStateTime(hashName)
end

function CharacterObject:getStateKeyTime(name, index)
	local hashName = getHashName(name)
	local keyTime = self.characterHelper:GetStateKeyFrame(hashName, index) or 0
	return keyTime/ANIMATION_FRAME_PER_SECOND
end

function CharacterObject:containsState(name)
	return self:getStateTime(name) > 0.00001
end

function CharacterObject:setTimeScale(timeScale)
	if abs(self.timeScale - timeScale) < 0.00001 then
		return
	end
	self.timeScale = timeScale
	local mainAnimator = self:getMainAnimator()
	mainAnimator.speed = timeScale*(self.timeScaleAddition or 1)
end

function CharacterObject:setTimeScaleAddition(addition)
	self.timeScaleAddition = addition
	local timeScale = self.timeScale*addition
	local mainAnimator = self:getMainAnimator()
	mainAnimator.speed = timeScale
end

function CharacterObject:setCullingMode(cullingMode)
	local mainAnimator = self:getMainAnimator()
	if mainAnimator then
		mainAnimator.cullingMode = cullingMode
	end
end

function CharacterObject:getCharacterMaterials()
	if not self.characterMaterials then
		self.characterMaterials = {}
		for k, prefabObj in pairs(self.childMap) do
			local render = prefabObj:getComponent(GConst.TYPEOF_UNITY_CLASS.SKINNED_MESH_RENDERER)
			if not CS.BF.Utils.IsNull(render) and not CS.BF.Utils.IsNull(render.material) and SUPPORT_SHADER[render.material.shader.name] then
				table.insert(self.characterMaterials, render.material)
			end
		end
	end
	return self.characterMaterials
end

function CharacterObject:getCharacterMeshRenderer()
	if not self.characterMeshRenders then
		self.characterMeshRenders = {}
		for k, prefabObj in pairs(self.childMap) do
			local render = prefabObj:getComponent(GConst.TYPEOF_UNITY_CLASS.SKINNED_MESH_RENDERER)
			if not CS.BF.Utils.IsNull(render) and not CS.BF.Utils.IsNull(render.material) and SUPPORT_SHADER[render.material.shader.name] then
				table.insert(self.characterMeshRenders, render)
			end
		end
	end
	return self.characterMeshRenders
end

function CharacterObject:getMaterialPropertyBlock()
	if not self.mpb then
		self.mpb = CS.UnityEngine.MaterialPropertyBlock()
	end
	return self.mpb
end

function CharacterObject:setCustomShadowEnable(enabled)
	local materials = self:getCharacterMaterials()
	for _, material in ipairs(materials) do
		material:SetShaderPassEnabled("Always", enabled)
	end
end

function CharacterObject:setCharacterFSM(fsm)
	self.fsm = fsm
end

---- 默认
function CharacterObject:setDefault()
	if self.fsm:getCurStateKey() == CharacterFSMManager.STATE_TYPE.DEFAULT then
		return
	end
	self.fsm:changeState(CharacterFSMManager.STATE_TYPE.DEFAULT)
end

---- 石化
function CharacterObject:setStone(callback)
	if self.fsm:getCurStateKey() == CharacterFSMManager.STATE_TYPE.STONE then
		return
	end
	self.fsm:changeState(CharacterFSMManager.STATE_TYPE.STONE, {callback = callback})
end

---- 石化效果2
function CharacterObject:setStone2(callback)
	if self.fsm:getCurStateKey() == CharacterFSMManager.STATE_TYPE.STONE_2 then
		return
	end
	self.fsm:changeState(CharacterFSMManager.STATE_TYPE.STONE_2, {callback = callback})
end

---- 冰化
function CharacterObject:setIce()
	if self.fsm:getCurStateKey() == CharacterFSMManager.STATE_TYPE.ICE then
		return
	end
	self.fsm:changeState(CharacterFSMManager.STATE_TYPE.ICE)
end

---- 闪红
function CharacterObject:setGlow(time, callback)
	local state = self.fsm:getCurStateKey()
	if state == CharacterFSMManager.STATE_TYPE.DISSOLVE or state == CharacterFSMManager.STATE_TYPE.GLOW then
		return
	end

	if state == CharacterFSMManager.STATE_TYPE.ICE then
		self.fsm:changeState(CharacterFSMManager.STATE_TYPE.DEFAULT)
	end

	self.fsm:changeState(CharacterFSMManager.STATE_TYPE.GLOW, {time = time or 0.7, callback = function()
		if callback then
			callback()
		end
		self.fsm:changeState(state)
	end})
end

---- 消融
function CharacterObject:setDissolve(time, callback, needCallback)
	self.fsm:changeState(CharacterFSMManager.STATE_TYPE.DISSOLVE, {time = time,
																   callback = callback,
																   needCallback = needCallback})
end

function CharacterObject:setShadowColor(color)
	local renderers = self:getCharacterMeshRenderer()
	local mpBlock = self:getMaterialPropertyBlock()
	for i, renderer in ipairs(renderers) do
		renderer:GetPropertyBlock(mpBlock)
		mpBlock:SetColor("_shadow_color", color)
		renderer:SetPropertyBlock(mpBlock)
	end
end

function CharacterObject:setLocalEulerAngles(x, y, z)
	if self.characterHelper then
		self.characterHelper:SetLocalEulerAngles(self.objectIndex, x, y, z)
	else
		BaseObject.setLocalEulerAngles(self, x, y, z)
	end
end

function CharacterObject:setLocalPosition(x, y, z)
	if self.characterHelper then
		self.characterHelper:SetLocalPosition(self.objectIndex, x, y, z)
	else
		BaseObject.setLocalPosition(self, x, y, z)
	end
end

function CharacterObject:setPosition(x, y, z)
	if self.characterHelper then
		self.characterHelper:SetPosition(self.objectIndex, x, y, z)
	else
		BaseObject.setPosition(self, x, y, z)
	end
end

function CharacterObject:setLocalScale(x, y, z)
	if self.characterHelper then
		self.characterHelper:SetLocalScale(self.objectIndex, x, y, z)
	else
		BaseObject.setLocalScale(self, x, y, z)
	end
end

function CharacterObject:onDestroy()
	if self.fsm then
		CharacterFSMManager:stopFSM(self.fsm)
		self.fsm = nil
	end
	BaseObject.onDestroy(self)
	self.characterHelper = nil
	self.mainModel = nil
	self.mainAnimator = nil
	self.childMap = nil
	self.characterMaterials = nil
	self.characterMeshRenders = nil
	self.mpb = nil
end

return CharacterObject