using System;
using System.Collections.Generic;
using Unity.Barracuda;
using Unity.MLAgents.Policies;
using UnityEngine;
using Random = UnityEngine.Random;

public class CharacterFactory : MonoBehaviour
{
    private static CharacterFactory instance;
    public static CharacterFactory Instance => instance;

    [SerializeField] private List<NavPoint> spawnPointsForDefendersTeam;
    [SerializeField] private List<NavPoint> spawnPointsForAttackersTeam;
    [SerializeField] private GameObject AIPrefab;
    [SerializeField] private GameObject PlayerPrefab;

    private List<GameObject> bots = new List<GameObject>();
    public GameObject Player { get; private set; }

    private void Awake()
    {
        if (instance == null)
            instance = this;
        else
        {
            Destroy(gameObject);
            Debug.LogError("Only 1 Instance");
        }
    }

    private void Start()
    {
        var attcNum = SettingsReader.Instance.GetSettings.NumOfAttackers;
        var defNum = SettingsReader.Instance.GetSettings.NumOfDefenders;
        var humanDef = SettingsReader.Instance.GetSettings.HasHumanDefender == true ? 1 : 0;
        var humanAtc = SettingsReader.Instance.GetSettings.HasHumanAttacker == true ? 1 : 0;

        if (humanAtc == 1 && humanDef == 1)
            throw new System.ArgumentException("Can be only one human player");
        
        if (humanAtc == 1)
            InstantiateEntity(Team.Attackers, TypeAI.HumanAI,
                spawnPointsForAttackersTeam[Random.Range(0, spawnPointsForAttackersTeam.Count)]);
        if (humanDef == 1)
            InstantiateEntity(Team.Defenders, TypeAI.HumanAI,
                spawnPointsForDefendersTeam[Random.Range(0, spawnPointsForDefendersTeam.Count)]);
        
        for (int i = 0; i < attcNum - humanAtc; i++)
            InstantiateEntity(Team.Attackers, TypeAI.D0DiskAI,
                spawnPointsForAttackersTeam[Random.Range(0, spawnPointsForAttackersTeam.Count)]);
        for (int i = 0; i < defNum - humanDef; i++)
            InstantiateEntity(Team.Defenders, TypeAI.D0DiskAI,
                spawnPointsForDefendersTeam[Random.Range(0, spawnPointsForDefendersTeam.Count)]);
        
        GameManager.OnResetScene += ResetCharacters;
    }

    private void InstantiateEntity(Team team, TypeAI typeAi, NavPoint spawnPoint)
    {
        var gameobject = GameObject.Instantiate(
            typeAi == TypeAI.HumanAI ? PlayerPrefab : AIPrefab,
            spawnPoint.Position,
            Quaternion.identity);
        ApplyMaterial(team, gameobject);
        gameobject.SetActive(true);
        gameobject.tag = team == Team.Attackers ? "Attacker" : "Defender";
        if (typeAi == TypeAI.HumanAI)
        {
            print("added player to list");
            gameobject.GetComponent<Player>().GetCharacter.Team = team;
            Player = gameobject;
        }
        else
        {
            var npc = gameobject.GetComponent<NPC>();
            if (team == Team.Attackers)
                GameManager._attackersTeam.RegisterAgent(npc);
            else
            {
                GameManager._defendersTeam.RegisterAgent(npc);
            }

            npc.GetCharacter.Team = team;
            npc.SetModel(team.ToString(), null, InferenceDevice.Default );
            gameobject.GetComponent<MovementController>().PointStartID = spawnPoint.PointId;
            bots.Add(gameobject);
        }
    }

    private void ApplyMaterial(Team team, GameObject go)
    {
        var mat = team == Team.Attackers
            ? SettingsReader.Instance.GetSettings.atcMaterial
            : SettingsReader.Instance.GetSettings.defMaterial;
        
        for (var i = 0; i < go.transform.childCount; i++)
        {
            if (go.transform.GetChild(i)
                .TryGetComponent<SkinnedMeshRenderer>(out var skin))
            {
                skin.material = mat;
            }
        }
    }

    public void ReSpawn(ICharacter character, GameObject go)
    {
        character.ResetCharacter();
        var team = character.GetCharacter.Team;
        var ai = character.GetCharacter.TypeAi;
        NavPoint navPoint;
        if (team == Team.Attackers)
            navPoint = spawnPointsForAttackersTeam[Random.Range(0, spawnPointsForAttackersTeam.Count)];
        else
            navPoint = spawnPointsForDefendersTeam[Random.Range(0, spawnPointsForDefendersTeam.Count)];

        go.transform.position = navPoint.Position;
        if (ai == TypeAI.D0DiskAI)
        {
            var mc =go.GetComponent<MovementController>();
            mc.PointStartID = navPoint.PointId;
        }
    }

    private void ResetCharacters()
    {
        foreach (var bot in bots)
        {
            var npc = bot.GetComponent<NPC>();
            npc.ResetCharacter();
            if (npc.GetCharacter.Team == Team.Attackers)
                bot.transform.position = spawnPointsForAttackersTeam[Random.Range(0, spawnPointsForAttackersTeam.Count)].Position;
            else
                bot.transform.position = spawnPointsForDefendersTeam[Random.Range(0, spawnPointsForDefendersTeam.Count)].Position;
        }
        Player player;
        if (TryGetComponent<Player>(out player))
        {
            player.ResetCharacter();
            if (player.GetCharacter.Team == Team.Attackers)
                this.Player.transform.position = spawnPointsForAttackersTeam[Random.Range(0, spawnPointsForAttackersTeam.Count)].Position;
            else
                this.Player.transform.position = spawnPointsForDefendersTeam[Random.Range(0, spawnPointsForDefendersTeam.Count)].Position;
        }
    }
}