local util = {httpMaskIndex = 0}

function util.loadVirtualCamera(config, path)
  local go = L_ResPool:syncGameObject(path or "Program/Camera/pre_worldVirtualCamera.prefab")
  if config then
    local virCam = go:GetComponent(typeof(CS.Cinemachine.CinemachineVirtualCamera))
    util.readVirtualCameraConfig(virCam, config)
  end
  return go
end

function util.getHeroModelConstId()
  return L_PlayerStore:getSex() == L_Const.sexType.male and 9999998 or 9999999
end

function util.readVirtualCameraConfig(virCam, config)
  local body = virCam:GetCinemachineComponent(0)
  local anim = virCam:GetCinemachineComponent(1)
  if not table.isEmpty(config) then
    body.m_ScreenX = config.screenX
    body.m_ScreenY = config.screenY
    body.m_TrackedObjectOffset = config.trackedObjectOffset
    local vertical = anim.m_VerticalAxis
    vertical.Value = config.vertical.value
    vertical.m_MinValue = config.vertical.minValue
    vertical.m_MaxValue = config.vertical.maxValue
    vertical.m_AccelTime = config.vertical.accelTime
    vertical.m_MaxSpeed = config.vertical.maxSpeed
    vertical.m_DecelTime = config.vertical.decelTime
    anim.m_VerticalAxis = vertical
    local horizontal = anim.m_HorizontalAxis
    horizontal.Value = config.horizontal.value
    horizontal.m_MinValue = config.horizontal.minValue
    horizontal.m_MaxValue = config.horizontal.maxValue
    horizontal.m_AccelTime = config.horizontal.accelTime
    horizontal.m_MaxSpeed = config.horizontal.maxSpeed
    horizontal.m_DecelTime = config.horizontal.decelTime
    anim.m_HorizontalAxis = horizontal
  end
end

function util.saveVirtualCameraConfig(virCam)
  local body = virCam:GetCinemachineComponent(0)
  local anim = virCam:GetCinemachineComponent(1)
  return {
    screenX = body.m_ScreenX,
    screenY = body.m_ScreenY,
    cameraDistance = body.m_CameraDistance,
    trackedObjectOffset = body.m_TrackedObjectOffset,
    vertical = {
      value = anim.m_VerticalAxis.Value,
      minValue = anim.m_VerticalAxis.m_MinValue,
      maxValue = anim.m_VerticalAxis.m_MaxValue,
      accelTime = anim.m_VerticalAxis.m_AccelTime,
      maxSpeed = anim.m_VerticalAxis.m_MaxSpeed,
      decelTime = anim.m_VerticalAxis.m_DecelTime
    },
    horizontal = {
      value = anim.m_HorizontalAxis.Value,
      minValue = anim.m_HorizontalAxis.m_MinValue,
      maxValue = anim.m_HorizontalAxis.m_MaxValue,
      accelTime = anim.m_HorizontalAxis.m_AccelTime,
      maxSpeed = anim.m_HorizontalAxis.m_MaxSpeed,
      decelTime = anim.m_HorizontalAxis.m_DecelTime
    }
  }
end

function util.getUnitIdByRole(id, type)
  if id == 0 then
    return
  end
  type = type or L_Const.roleType.hero
  local unitId = id
  if type == L_Const.roleType.hero then
    if id == util.getDefaultHeroId(L_Const.sexType.female) then
      unitId = L_GameConstTpl:getFemaleDefaultHeroUnitId()
    elseif id == util.getDefaultHeroId(L_Const.sexType.male) then
      unitId = L_GameConstTpl:getMaleDefaultHeroUnitId()
    else
      local heroTpl = L_GameTpl:getHeroTpl()
      local tpl = heroTpl:getTplById(id)
      unitId = heroTpl:getUnitId(tpl)
    end
  elseif type == L_Const.roleType.boss then
    local bossTpl = L_GameTpl:getBossTpl()
    local tpl = bossTpl:getTplById(id)
    unitId = bossTpl:getUnitId(tpl)
  elseif type == L_Const.roleType.pet then
    local petInfoTpl = L_GameTpl:getPetTpl()
    local tpl = petInfoTpl:getTplById(id)
    unitId = petInfoTpl:getUnitID(tpl)
  end
  return unitId
end

function util.fillColor(key, colorKey)
  return string.format("<color=%s>%s</color>", colorKey, tostring(key))
end

function util.removeXmlTags(str, tag)
  str = string.gsub(str, "</?" .. tag .. "[^%a>]+[^>]*>", "")
  return string.gsub(str, "</?" .. tag .. ">", "")
end

function util.setJoyStick(joystick)
  C_JoystickManager.SetPlayerJoystick(joystick)
end

function util.getJoyStick()
  return C_JoystickManager.GetPlayerJoystick()
end

function util.openMask(data)
  L_UI:open("pageMask", data)
end

function util.httpMask(isOpen, desc)
  printf("httpMask", isOpen, desc)
  if isOpen == true then
    if util.httpMaskIndex == 0 then
      L_UI:open("pageHttpWaiting")
    end
    util.httpMaskIndex = util.httpMaskIndex + 1
  else
    util.httpMaskIndex = util.httpMaskIndex - 1
    if util.httpMaskIndex == 0 then
      L_UI:close("pageHttpWaiting")
    end
  end
end

function util.getMainHeroUnitId(sexType)
  if sexType == nil then
    sexType = L_PlayerStore:getSex()
  end
  if sexType == L_Const.sexType.male then
    id = L_GameConstTpl:getValue("AVATAR_HERO_ID_MALE")
  else
    id = L_GameConstTpl:getValue("AVATAR_HERO_ID_FEMALE")
  end
end

function util.getSexTypeByHeroId(heroId)
  if heroId == L_GameConstTpl:getValue("AVATAR_HERO_ID_MALE") then
    return L_Const.sexType.male
  elseif heroId == L_GameConstTpl:getValue("AVATAR_HERO_ID_FEMALE") then
    return L_Const.sexType.female
  end
  return nil
end

function util.getDefaultHeroId(sexType)
  if sexType == L_Const.sexType.female then
    return L_GameConstTpl:getFemaleDefaultHeroConfigId()
  elseif sexType == L_Const.sexType.male then
    return L_GameConstTpl:getMaleDefaultHeroConfigId()
  end
  local defaultHeroGuid = L_HeroStore:getDefaultHeroGuid()
  local serverData = L_HeroStore:getHero(defaultHeroGuid)
  return serverData == nil and L_GameConstTpl:getFemaleDefaultHeroConfigId() or L_HeroStore:getHeroConfigId(serverData)
end

function util.getMainHeroPath(sexType)
  if sexType == nil then
    sexType = L_PlayerStore:getSex()
  end
  local path = ""
  if sexType == L_Const.sexType.male then
    path = L_GameConstTpl:getValue(L_Const.playerPath.avatarPrefabMalePath)
  else
    path = L_GameConstTpl:getValue(L_Const.playerPath.avatarPrefabFemalePath)
  end
  return path
end

function util.getWorldTriggerId(_worldMapTpl, worldMap_tpl)
  local worldTriggerId = _worldMapTpl:getTriggerId(worldMap_tpl)
  if worldTriggerId == 0 then
    local spawnerTPl = L_GameTpl:getWorldSpawnerTpl()
    local spawnerTplData = spawnerTPl:getTplById(_worldMapTpl:getSpawnerId(worldMap_tpl))
    worldTriggerId = spawnerTPl:getTrigger(spawnerTplData)
  end
  return worldTriggerId
end

function util.getInitStatus(_worldMapTpl, worldMap_tpl)
  local worldTriggerId = util.getWorldTriggerId(_worldMapTpl, worldMap_tpl)
  if worldTriggerId ~= 0 then
    local _worldTriggerTpl = L_GameTpl:getWorldTriggerTpl()
    local worldTrigger_tpl = _worldTriggerTpl:getTpl(worldTriggerId, 1)
    if not table.isEmpty(worldTrigger_tpl) then
      local state = _worldTriggerTpl:getState(worldTrigger_tpl)
      return state
    end
  end
  return _worldMapTpl:getInitStatus(worldMap_tpl)
end

function util.getAvatarTexturePath(heroId, index)
  local heroTpl = L_GameTpl:getHeroTpl()
  local tpl = heroTpl:getTplById(heroId)
  local imgPath = heroTpl:getAvatarTexture(tpl, index)
  return imgPath
end

function util.getAvatarModelPathByUnitId(unitId, index)
  if unitId == L_GameConstTpl:getValue("AVATAR_UNIT_ID_FEMALE") then
    return L_GameConstTpl:getValue(L_Const.playerPath.avatarPrefabFemalePath)
  elseif unitId == L_GameConstTpl:getValue("AVATAR_UNIT_ID_MALE") then
    return L_GameConstTpl:getValue(L_Const.playerPath.avatarPrefabMalePath)
  end
  local unitTpl = L_GameTpl:getUnitTpl()
  return unitTpl:getModel(unitTpl:getTplById(unitId), index)
end

function util.getAnimatorControllerPathByUnitId(unitId, index)
  local unitTpl = L_GameTpl:getUnitTpl()
  return unitTpl:getAnimatorConfig(unitTpl:getTplById(unitId), index)
end

function util.getStrByKey(key)
  local output = L_WordsTpl:getValue(key)
  if string.isEmpty(output) then
    return key
  else
    return output
  end
end

function util.getComponentListByType(tran, type)
  local list = {}
  
  local function _find(t)
    local target = t:GetComponent(type)
    if not L_CommonUtil.isValid(target) then
      local count = t.childCount - 1
      for i = 0, count do
        local child = t:GetChild(i)
        _find(child)
      end
    else
      table.insert(list, target)
    end
  end
  
  _find(tran)
  return list
end

function util.getComponentListAllByType(tran, type)
  local list = {}
  
  local function _find(t)
    local target = t:GetComponent(type)
    if not L_CommonUtil.isValid(target) then
    else
      table.insert(list, target)
    end
    local count = t.childCount - 1
    for i = 0, count do
      local child = t:GetChild(i)
      _find(child)
    end
  end
  
  _find(tran)
  return list
end

function util.findTranByName(gameObject, name)
  local modleControl = gameObject:GetComponent(typeof(C_ModelControl))
  local bindTran
  if modleControl then
    bindTran = modleControl:GetTransByName(name)
  end
  if bindTran == nil then
    bindTran = L_CommonUtil.findTranByName(name, gameObject.transform)
  end
  return bindTran
end

function util.getEntityForwardNavMeshPos(entity, index, is_random)
  local entityPos = entity:getTransform().position
  local forward = entity:getTransform().forward
  local randomPoint = {
    [1] = {angle = 0, dis = 1},
    [2] = {angle = 45, dis = 1},
    [3] = {angle = -45, dis = 1},
    [4] = {angle = 30, dis = 2},
    [5] = {angle = -30, dis = 2}
  }
  local forwardPos
  if is_random then
    local radius = randomPoint[index].dis
    local randomAngle = randomPoint[index].angle
    forward = C_Quaternion.Euler(0, randomAngle, 0) * forward
    forwardPos = C_Vector3(entityPos.x + forward.x * radius, entityPos.y + forward.y * radius + 1, entityPos.z + forward.z * radius)
  else
    local offset = randomPoint[index].dis
    forwardPos = C_Vector3(entityPos.x + forward.x * offset, entityPos.y + forward.y * offset + 1, entityPos.z + forward.z * offset)
  end
  local yScale = 0.1
  for i = 1, 30 do
    forwardPos.y = forwardPos.y - yScale
    local isBlock, navMeshHit = C_NavmeshHelper.SamplePosition(forwardPos, yScale, Unity.AI.NavMesh.AllAreas)
    if navMeshHit ~= nil then
      local hitPos = Unity.Vector3(navMeshHit.x, navMeshHit.y, navMeshHit.z)
      return hitPos
    end
  end
  return forwardPos
end

function util.getIsInRange(owner, target, dis, angle)
  local isInRange = true
  local sqrDis = dis * dis
  local abDis = util.getSqrDistance(owner, target)
  if angle and angle ~= 360 then
    local ag = util.getAngleByFwd(owner, target)
    isInRange = ag <= angle * 0.5
    if not isInRange then
      return false, abDis
    end
  end
  isInRange = isInRange and sqrDis >= abDis
  return isInRange, abDis
end

function util.inFov(owner, target, angle)
  local a = util.getAngleByFwd(owner, target)
  return a <= angle * 0.5
end

function util.getAngleByFwd(ownerTrans, targetTrans)
  local fwd = ownerTrans.forward
  local dir = L_Vector3.getPos(targetTrans) - L_Vector3.getPos(ownerTrans)
  fwd.y = 0
  dir.y = 0
  return C_Vector3.Angle(fwd, dir)
end

function util.getSqrDistance(t1, t2)
  local v1 = L_Vector3.getPos(t1)
  local v2 = L_Vector3.getPos(t2)
  local sqrDis = L_Vector3.sqrDistance(v1, v2)
  return sqrDis
end

function util.getDistance(t1, t2)
  local v1 = L_Vector3.getPos(t1)
  local v2 = L_Vector3.getPos(t2)
  local sqrDis = L_Vector3.distance(v1, v2)
  return sqrDis
end

function util.getSkillDesc(skillId, skillLevel)
  skillLevel = skillLevel or 1
  local skillLevelTpl = L_GameTpl:getSkillLevelTpl()
  local tpl_skillLevel = skillLevelTpl:getTplByIdAndLevel(skillId, skillLevel)
  local propertyValueCur_list = skillLevelTpl:getValue(tpl_skillLevel)
  local curDescribeTxt = string.gsub(skillLevelTpl:getSkillDescribe(tpl_skillLevel), " ", "\194\160")
  local propertyList_txt = {}
  for i = 0, #propertyValueCur_list - 1 do
    local value = propertyValueCur_list[i + 1]
    local startPos, endPos = string.find(curDescribeTxt, "{" .. i .. "}")
    if startPos then
      if not string.isEmpty(value) then
        propertyList_txt[i] = value
      end
    else
      errorf(L_WordsTpl:getValue("ui_gameUtil", {
        [0] = skillId,
        [1] = skillLevel
      }))
    end
  end
  return L_Lang:get(curDescribeTxt, propertyList_txt)
end

function util.getUnitCameraOffset(unitId)
  local unitTpl = L_GameTpl:getUnitTpl()
  local tpl = unitTpl:getTplById(unitId)
  local offset = unitTpl:getFallowCamShift(tpl)
  return offset
end

function util.getCameraEntityOffsetY(entity)
  local unitTpl = L_GameTpl:getUnitTpl()
  local tpl = unitTpl:getTplById(entity:getUnitId())
  local offset = unitTpl:getFallowCamShift(tpl)
  return offset
end

function util.getLvByLvPackId(lvPackId)
  local _enemyPackTpl = L_GameTpl:getEnemyPackTpl()
  local tpl = _enemyPackTpl:getTplById(lvPackId)
  local levelPolicy = _enemyPackTpl:getLevelPolicy(tpl)
  local lv
  if levelPolicy == 1 then
    local levelParam = _enemyPackTpl:getLevelParameter(tpl)
    local groupId = levelParam
    local worldLevel = L_WorldLevelStore:getNowLevel()
    local tpl = L_GameTpl:getWorldDifficultyObjLevelTpl():getTplByLvAndId(groupId, worldLevel)
    lv = L_GameTpl:getWorldDifficultyObjLevelTpl():getMonsterLevel(tpl)
  elseif levelPolicy == 2 then
    local levelParam = _enemyPackTpl:getLevelParameter(tpl)
    lv = levelParam
  end
  return lv
end

function util.getEntityInFly(cccState)
  return cccState == C_EFsmStateType.Fly or cccState == C_EFsmStateType.FlyIdle or cccState == C_EFsmStateType.FlyTurn
end

function util.getConfigIdByGuid(heroGuid, playerId)
  local hero = L_HeroStore:getHero(heroGuid)
  if hero then
    return L_HeroStore:getHeroConfigId(hero)
  end
  return heroGuid
end

function util.spToLpInRectangle(rectTrans, postion)
  local uiCamera = L_CameraManager:getUICamera()
  local _, pos = C_RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTrans, postion, uiCamera)
  return pos and L_Vector3.new(pos.x, pos.y, pos.z)
end

function util.forceRebuildLayout(trans)
  Unity.UI.LayoutRebuilder.ForceRebuildLayoutImmediate(trans)
  FrameScheduler.add(function()
    Unity.UI.LayoutRebuilder.ForceRebuildLayoutImmediate(trans)
  end)
end

function util.itemShowNum(n)
  if n < 1000000 then
    return tostring(n)
  elseif n < 1000000000 then
    n = math.floor(n / 100000) / 10
    return string.concat(n, "m")
  else
    n = math.floor(n / 100000000) / 10
    return string.concat(n, "b")
  end
end

function util.getBattleStaticPosList()
  local slots = L_GameConstTpl:getValue("MONSTER_BIRTH_LOCATION")
  local slotVector3 = {}
  for i, v in ipairs(slots) do
    local strarr = string.split(v, ",")
    table.insert(slotVector3, C_Vector3(tonumber(strarr[1]), 0, tonumber(strarr[2])))
  end
  return slotVector3
end

function util.getGameConstTableVal(key, default)
  local value = L_GameConstTpl:getValue(key, false, default)
  return value
end

function util.getSystemUnlock(_id)
  return L_SystemUnlockStore:getSystemIsOpen(_id)
end

function util.getIsDetectSkillOn()
  local value = L_GuideManager:getIsDetectSkill()
  return value
end

function util.checkCanMod()
  if L_BattleDataManager:checkPlayerBattle() then
    L_FlyMsgManager:showNormalMsg(L_BattleDataManager:getBattleBlockTips(L_Const.BattleBlockType.PlayerModChange))
    return false
  end
  if L_PlayerManager:isInMounting() then
    L_FlyMsgManager:showNormalMsg(L_GameTpl:getWordsTpl():getTplById("notice_mount_unable_change_image"))
    return false
  end
  return true
end

function util.logEvent(eventName, eventData)
  local clock = math.ceil(C_TimeUtility.NowMS)
  local device = C_MJSDKManager.GetDeviceInfo()
  local userInfo = C_MJSDKManager.userInfo
  local gameId = C_MJSDKManager.GetGameId()
  local roleId = L_PlayerStore:getPlayerId()
  local random = math.random(1, 1000)
  local base = {
    log_id = string.format("%s_%s_%s_%s", gameId, eventName, clock, random),
    log_timestamp = clock,
    device_id = device.device_id,
    open_id = userInfo ~= nil and userInfo.openId or 0,
    role_id = roleId,
    game_id = gameId,
    app_id = C_MJSDKManager.GetAppId(),
    platform = device.platform,
    server_id = string.empty,
    role_level = L_PlayerStore:getLv(),
    node_id = string.empty,
    related_event_log_id = string.format("%s_%s_%s_%s", gameId, roleId, clock, random)
  }
  local baseJson = L_Json.encode(base)
  local eventJson = L_Json.encode(eventData)
  C_MJSDKManager.LogEvent(eventName, baseJson, eventJson)
end

function util.showCommonTip(data)
  data = data or {}
  if data.showNoMoreType then
    if data.showNoMoreType == L_Const.ShowNoMoreType.curLogin then
      if not L_TipManager:getIsNeedOpen() then
        if data.confirmCallback then
          data.confirmCallback()
        end
        return
      end
    elseif data.showNoMoreType == L_Const.ShowNoMoreType.today then
      local lastTime = C_PlayerPrefsUtility.GetInt(data.showNoMoreKey) or 0
      if lastTime > L_TimeUtil.getServerTime() then
        if data.confirmCallback then
          data.confirmCallback()
        end
        return
      end
    end
  end
  data.closeCallbackTemp = data.closeCallback
  data.closeCallback = nil
  L_UI:open("pageCommonTip", data)
end

function util.showNetCommonTip(data)
  data.overrideSortingOrder = 32767
  data.closeCallbackTemp = data.closeCallback
  data.closeCallback = nil
  L_UI:open("pageNetCommonTip", data)
end

function util.showNormalTextBox(data)
  data = data or {}
  data.closeCallbackTemp = data.closeCallback
  data.closeCallback = nil
  L_UI:open("pageCommonTextBox", data)
end

function util.isUnder1080()
  return C_Screen.height <= 1080
end

return util
