using System;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public class TargetController : MonoBehaviour
{
    [SerializeField] private GameObject environmentObj;
    [SerializeField] private GameObject agentObj;
    [SerializeField] private GameObject HUDObj;
    [SerializeField] private GameObject sceneBlockContainerObj;
    [SerializeField] private GameObject enemyContainerObj;
    [SerializeField] private GameObject environmentUIObj;

    // area
    public GameObject edgeUp;

    public GameObject edgeDown;
    public GameObject edgeLeft;
    public GameObject edgeRight;
    public GameObject edgeAgent_Enemy;

    public float minEnemyAreaX;
    [System.NonSerialized] public float maxEnemyAreaX;
    [System.NonSerialized] public float minEnemyAreaZ;
    [System.NonSerialized] public float maxEnemyAreaZ;
    [System.NonSerialized] public float minAgentAreaX;
    [System.NonSerialized] public float maxAgentAreaX;
    [System.NonSerialized] public float minAgentAreaZ;
    [System.NonSerialized] public float maxAgentAreaZ;
    [System.NonSerialized] public float startTime = 0f;
    [System.NonSerialized] public float leftTime = 0f;

    [SerializeField, Range(0f, 1f)] public float attackProb = 0.2f;
    [SerializeField, Range(0f, 1f)] public float gotoProb = 0.2f;
    [SerializeField, Range(0f, 1f)] public float defenceProb = 0.2f;

    [System.NonSerialized] public Targets targetType;
    [System.NonSerialized] public int gotoLevelNum;
    [System.NonSerialized] public int attackLevelNum;
    public float[] targetState = new float[6];

    public enum EndType
    { Win, Lose, Running, Num };

    [System.NonSerialized] public int targetNum = 0;
    private Dictionary<int, float[]> oneHotRarget = new Dictionary<int, float[]>();

    private float freeProb;
    private float sceneBlockSize;
    private int randBlockType = 0;
    private int randLevel = 0;
    public int inArea = 0;
    public Vector3 targetPosition;
    private bool firstRewardFlag = true;
    public bool targetEnemySpawnFinish = false;

    private SceneBlockContainer sceneBlockCon;
    private EnemyContainer enemyCon;
    private EnvironmentUIControl envUICon;
    private CommonParameterContainer commonParamCon;
    private CharacterController agentCharaCon;
    private HUDController hudCon;
    private MessageBoxController messageBoxCon;

    // start scene datas 0=train 1=play
    private int gamemode;

    // Start is called before the first frame update
    private void Start()
    {
        commonParamCon = CommonParameterContainer.Instance;
        sceneBlockCon = sceneBlockContainerObj.GetComponent<SceneBlockContainer>();
        envUICon = environmentUIObj.GetComponent<EnvironmentUIControl>();
        enemyCon = enemyContainerObj.GetComponent<EnemyContainer>();
        agentCharaCon = agentObj.GetComponent<CharacterController>();
        hudCon = HUDObj.GetComponent<HUDController>();
        messageBoxCon = HUDObj.GetComponent<MessageBoxController>();

        // get parameter from ParameterContainer
        gamemode = commonParamCon.gameMode;
        attackProb = commonParamCon.attackProb;
        gotoProb = commonParamCon.gotoProb;
        defenceProb = commonParamCon.defenceProb;

        // initialize spawn area
        minEnemyAreaX = edgeLeft.transform.localPosition.x + 1.0f;
        maxEnemyAreaX = edgeRight.transform.localPosition.x - 1.0f;
        minEnemyAreaZ = edgeAgent_Enemy.transform.localPosition.z + 1.0f;
        maxEnemyAreaZ = edgeUp.transform.localPosition.z - 1.0f;

        minAgentAreaX = edgeLeft.transform.localPosition.x + 1.0f;
        maxAgentAreaX = edgeRight.transform.localPosition.x - 1.0f;
        minAgentAreaZ = edgeDown.transform.localPosition.z + 1.0f;
        maxAgentAreaZ = edgeAgent_Enemy.transform.localPosition.z - 1.0f;

        freeProb = 1 - attackProb - gotoProb - defenceProb;
        targetNum = (int)Targets.Num;
        gotoLevelNum = commonParamCon.scenePrefabSet.GetLevelNumber(Targets.Go);
        attackLevelNum = commonParamCon.scenePrefabSet.GetLevelNumber(Targets.Attack);
        if (freeProb < 0)
        {
            Debug.LogError("TargetController.Start: target percentage wrong");
        }

        // initialize a simple fake onehot encoder.
        for (int i = 0; i < targetNum; i++)
        {
            float[] onehotList = new float[targetNum];
            for (int j = 0; j < targetNum; j++)
            {
                onehotList[j] = 0;
            }
            onehotList[i] = 1;
            oneHotRarget.Add(i, onehotList);
        }
    }

    private void Update()
    {
        // if gamemode is play, then time will keep paramCon.timeLimit
        if (gamemode == 1)
        {
            leftTime = commonParamCon.timeLimit;
            // print out time
            // Debug.Log("Playing Time: " + leftTime);
        }
        else
        {
            leftTime = commonParamCon.timeLimit - Time.time + startTime;
        }
    }

    /// <summary>
    /// Generates a new scene configuration by selecting a random target type and spawning related scene blocks.
    /// </summary>
    /// <remarks>
    /// This method is responsible for creating a new scene configuration, which involves selecting a target type
    /// (Go, Attack, Defence, or Free) based on predefined probabilities. Depending on the chosen target type,
    /// the method spawns the associated scene blocks, updates various flags, and informs the user interface about
    /// the selected target type.
    /// </remarks>
    public void RollNewScene()
    {
        startTime = Time.time;// Reset StartTime as now time
        leftTime = commonParamCon.timeLimit - Time.time + startTime;
        float randTargetType = UnityEngine.Random.Range(0f, 1f);
        if (randTargetType <= gotoProb)
        {
            // goto target spawn
            Debug.Log("GOTO THIS TARGET!");
            targetType = Targets.Go;
            RandomSpawnSceneBlock(Targets.Go);
            // set startDistance
            firstRewardFlag = true;
        }
        else if (randTargetType > gotoProb && randTargetType <= gotoProb + attackProb)
        {
            // attack target spawn
            Debug.Log("ATTACK Mode Start");
            targetType = Targets.Attack;
            RandomSpawnSceneBlock(Targets.Attack);
            // set startDistance
            firstRewardFlag = true;
            targetEnemySpawnFinish = false;
        }
        else if (randTargetType > gotoProb + attackProb && randTargetType <= gotoProb + attackProb + defenceProb)
        {
            // defence target spawn
            Debug.Log("DEFENCE Mode Start");
            targetType = Targets.Defence;
            RandomSpawnSceneBlock(Targets.Defence);
            // set startDistance
            firstRewardFlag = true;
        }
        else
        {
            Debug.Log("Free Mode Start");
            targetType = Targets.Free;
            enemyCon.DestroyAllEnemys();
            enemyCon.RandomInitEnemys(hudCon.enemyNum);
            MoveAgentToSpwanArea();
            sceneBlockCon.DestroyBlock();
        }
        UpdateTargetStates();
        envUICon.UpdateTargetType(targetType);
    }

    #region Agent Move Method

    /// <summary>
    /// Move the agent to the spawn area.
    /// 将Agent移动到生成区域。
    /// </summary>
    private void MoveAgentToSpwanArea()
    {
        float randX = UnityEngine.Random.Range(minAgentAreaX, maxAgentAreaX); ;
        float randZ = 0f;
        if (commonParamCon.spawnAgentInAllMap)
        {
            // spawn agent in all around map
            randZ = UnityEngine.Random.Range(minAgentAreaZ, maxEnemyAreaZ);
        }
        else
        {
            // spawn agent in only agent spawn area
            randZ = UnityEngine.Random.Range(minAgentAreaZ, maxAgentAreaZ);
        }

        int Y = 1;
        Vector3 initAgentLoc = new Vector3(randX, Y, randZ);
        MoveAgentTo(initAgentLoc);
    }

    /// <summary>
    /// Move the agent to the specified position.
    /// 将代理移动到指定位置。
    /// </summary>
    /// <param name="position">要移动到的位置。</param>
    /// <remarks>
    /// When moving the character using transform.localPosition,
    /// must disable the character controller, or it won't work properly.
    /// 使用 transform.localPosition 移动角色时,
    /// 必须禁用角色控制器,否则它将无法正常工作。
    /// </remarks>
    public void MoveAgentTo(Vector3 position)
    {
        // while using transform.localPosition to move character
        // u should turn off character Controller or it won't work
        agentCharaCon.enabled = false;
        agentObj.transform.localPosition = position;
        agentCharaCon.enabled = true;
    }

    #endregion Agent Move Method

    #region Random SceneBlock Spawn Method

    /// <summary>
    /// Randomly spawns a scene block based on the target type.
    /// 根据目标类型随机生成场景块。
    /// </summary>
    /// <param name="targetType">要生成的场景块的目标类型。The target type of the scene block to be generated.</param>
    /// <remarks>
    /// This method generates a random scene block based on the target type and spawns enemies at the specified location.
    /// 此方法根据目标类型生成一个随机场景块,并在指定位置生成敌人。
    /// </remarks>
    private void RandomSpawnSceneBlock(Targets targetType)
    {
        randLevel = RollRandomLevelIndex(targetType);
        randBlockType = Random.Range(0, commonParamCon.scenePrefabSet.GetBlockNumber(randLevel,targetType));
        sceneBlockSize = commonParamCon.scenePrefabSet.GetBlockSize(randLevel, randBlockType, targetType);

        float randX = UnityEngine.Random.Range(minEnemyAreaX + sceneBlockSize / 2 + 1f, maxEnemyAreaX - sceneBlockSize / 2 - 1f);
        float randZ = UnityEngine.Random.Range(minEnemyAreaZ + sceneBlockSize / 2 + 1f, maxEnemyAreaZ - sceneBlockSize / 2 - 1f);
        targetPosition = new Vector3(randX, 0, randZ);

        // init scene block
        sceneBlockCon.DestroyBlock();
        sceneBlockCon.CreateNewBlock(targetType, randLevel, randBlockType, targetPosition, commonParamCon.group1Tag, commonParamCon.group2Tag);
        enemyCon.DestroyAllEnemys();
        enemyCon.RandomInitEnemysExcept(hudCon.enemyNum, targetPosition, sceneBlockSize);
        sceneBlockCon.nowBlock.InitBlock(environmentObj);
    }

    #endregion Random SceneBlock Spawn Method

    #region Play Mode Method

    /// <summary>
    /// Initializes the game in play mode. 
    /// 初始化游戏playMode。
    /// </summary>
    /// <remarks>
    /// This method is used to initialize the game in play mode, 
    /// including setting the target type, updating target states, 
    /// updating UI display, moving the agent to the spawn area, 
    /// destroying all enemies, and scene blocks.
    /// 该方法用于初始化游戏播放模式,包括设置目标类型、更新目标状态、更新UI显示、
    /// 将代理移动到生成区域、销毁所有敌人和场景块。
    /// </remarks>
    public void PlayInitialize()
    {
        targetType = Targets.Stay;
        UpdateTargetStates();
        envUICon.UpdateTargetType(targetType);
        MoveAgentToSpwanArea();
        enemyCon.DestroyAllEnemys();
        sceneBlockCon.DestroyBlock();
    }

    // change to attack mode
    public void AttackModeChange(Vector3 targetPosition)
    {
        targetType = Targets.Attack;
        UpdateTargetStates(targetPosition);
        envUICon.UpdateTargetType(targetType);
    }

    // change to free mode
    public void FreeModeChange()
    {
        targetType = (int)Targets.Free;
        UpdateTargetStates();
        envUICon.UpdateTargetType(targetType);
    }

    // change to goto mode
    public void GotoModeChange(Vector3 targetPosition)
    {
        targetType = Targets.Go;
        UpdateTargetStates(targetPosition);
        envUICon.UpdateTargetType(targetType);
    }

    // change to stay mode
    public void StayModeChange()
    {
        targetType = Targets.Stay;
        UpdateTargetStates();
        envUICon.UpdateTargetType(targetType);
    }

    #endregion Play Mode Method

    /// <summary>
    /// Gets the target observation states.
    /// 获取目标观测状态。
    /// </summary>
    /// <param name="targetPosition">The target position (optional).</param>
    private void UpdateTargetStates(Vector3? targetPosition = null)
    {
        // targettype, x,y,z, firebasesAreaDiameter
        targetState[0] = (int)targetType;
        if (targetPosition != null)
        {
            this.targetPosition = (Vector3)targetPosition;
        }
        if (targetType == (int)Targets.Free || targetType == Targets.Stay)
        {
            for (int i = 1; i < targetState.Length; i++)
                // set target position state to 0
                targetState[i] = 0f;
        }
        else
        {
            targetState[1] = this.targetPosition.x;
            targetState[2] = this.targetPosition.y;
            targetState[3] = this.targetPosition.z;
            targetState[4] = sceneBlockCon.nowBlock.firebasesAreaDiameter;
            targetState[5] = sceneBlockCon.nowBlock.belongRatio;
        }
    }

    /// <summary>
    /// Gets the in-area state.
    /// 获取是否在区域内的State
    /// </summary>
    /// <returns>The in-area state.</returns>
    public int GetInAreaState()
    {
        if (targetType == Targets.Go)
        {
            return inArea;
        }
        else
        {
            return 0;
        }
    }

    /// <summary>
    /// Gets a random level index based on the target type.
    /// 根据目标类型获取随机关卡索引。
    /// </summary>
    /// <param name="target">The target type.</param>
    /// <returns>A random level index.</returns>
    public int RollRandomLevelIndex(Targets target)
    {
        Debug.Log(target);
        List<float> targetProbs;
        targetProbs = commonParamCon.levelProbs[target];

        // sample random level depends on the target probabilities
        float randomNum = UnityEngine.Random.Range(0f, 1f);
        float sumProb = 0f;
        for (int i = 0; i < targetProbs.Count; i++)
        {
            sumProb += targetProbs[i];
            if (randomNum < sumProb)
            {
                return i;
            }
        }

        // If no level was returned, log an error and return -1
        messageBoxCon.PushMessage(
            new List<string> { "[ERROR]TargetController:RollRandomLevelIndex", "level index out of range" },
            new List<string> { "orange" });
        return -1;
    }
}