using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public enum CommandType
{
    Wait,
    BuildColony,
    BuildProject,
    AttackNearest,
    BuildArmy,
    BuildUnit
}

public sealed class EnemyController : MonoBehaviour
{
    [SerializeField]
    private List<Command> commands = new List<Command>();
    [SerializeField]
    private FactionProvider factionProvider = null;
    [SerializeField]
    private BlueprintProvider blueprintProvider = null;
    [SerializeField]
    private UnitFactory unitsFactory = null;
    [SerializeField]
    private int startLoopId = 6;

    [Serializable]
    public sealed class Command {
        [SerializeField]
        public CommandType Type = CommandType.Wait;
        [SerializeField]
        public int Count = 0;
        [SerializeField]
        public string Id = String.Empty;
    }

    private Faction faction = null;
    private int commandId = 0;
    private int count = 0;
    private Command current = null;
    private ProjectBlueprint blueprint = null;

    // wait
    private float timer = 0;

    public void Init(Faction controlFaction) {
        faction = controlFaction;
        commandId = -1;
        StartNextCommand();
    }

    public void Update()
    {
        if (faction == null)
            return;
        if (current == null)
        {
            StartNextCommand();
        }
        else
        {
            ProcessCommand(current);
        }
    }

    private void StartNextCommand()
    {
        if (commands.Count == 0)
            return;
        commandId += 1;
        if (commandId >= commands.Count)
            commandId = startLoopId;
        current = commands[commandId];
        StartCommand(current);
    }

    private void ProcessCommand(Command current)
    {
        if (current == null)
            return;

        switch (current.Type)
        {
            case CommandType.Wait:
            case CommandType.AttackNearest:
                timer -= Time.deltaTime;
                if (timer < 0)
                    StartNextCommand();
                return;
            case CommandType.BuildProject:
                if (blueprint == null || count <= 0)
                {
                    StartNextCommand();
                    return;
                }
                if (faction.ResourceUnitCount <= blueprint.Cost) {
                    return; // wait for resources
                }
                faction.Bases[0].StartProject(blueprint);
                count -= 1;
                return;
            case CommandType.BuildUnit:
                if (blueprint == null || count <= 0)
                {
                    StartNextCommand();
                    return;
                }
                if (faction.ResourceUnitCount <= blueprint.Cost)
                {
                    return; // wait for resources
                }
                var army = FindRandomArmy();
                if (army != null)
                {
                    army.StartProject(blueprint);
                    count -= 1;
                }
                else
                {
                    Debug.Log("No army for unt build!");
                    count = 0;
                }
                return;
            case CommandType.BuildColony:
                if (blueprint == null || count <= 0)
                {
                    StartNextCommand();
                    return;
                }
                if (faction.ResourceUnitCount <= blueprint.Cost)
                {
                    return; // wait for resources
                }
                var mainBase = faction.Bases.FirstOrDefault();
                if (mainBase == null)
                {
                    Debug.LogError($"Can't build outpost - main base is empty for {blueprint.ID}");
                    StartNextCommand();
                    return;
                }
                var point = FindNearestPoint(mainBase.transform.position);
                if (point == null)
                {
                    Debug.LogError($"Can't build outpost - no clear points");
                    StartNextCommand();
                    return;
                }
                BuildProject project = new BuildProject(blueprint.BuildTime, blueprint.ID, blueprint.ID, (readyPtoject) => {
                    unitsFactory.CreateOutpostAssembler(faction, mainBase.transform.position, point);
                });

                faction.BuildProject(project, blueprint.Cost);
                count -= 1;
                return;
        }
    }

    private ControlPoint FindNearestPoint(Vector3 position)
    {
        var points = FindObjectsOfType<ControlPoint>().Where(o => o.Base == null).ToList();
        points.Sort((a, b) => {
            var delta = Vector3.Distance(a.transform.position, position) - Vector3.Distance(a.transform.position, position);
            if (delta > 0)
                return 1;
            return -1;
        });
        return points.FirstOrDefault();
    }

    private Officer FindRandomArmy()
    {
        var officers = FindObjectsOfType<Officer>();
        return officers.Where(o => o.FactionId == faction.ID).FirstOrDefault();
    }

    private void StartCommand(Command current)
    {
        if (current == null)
            return;
//        Debug.Log($"start command {current.Type} {current.Count} {current.Id}");

        switch (current.Type)
        {
            case CommandType.Wait:
                timer = current.Count;
                return;
            case CommandType.BuildProject:
                blueprint = FindBaseBlueprint(current.Id);
                count = current.Count;
                return;
            case CommandType.BuildUnit:
                blueprint = FindOfficerBlueprint(current.Id);
                count = current.Count;
                return;
            case CommandType.BuildColony:
                blueprint = blueprintProvider.ControlPointProjects[0];
                count = current.Count;
                return;
            case CommandType.AttackNearest:
                var army = FindRandomArmy();
                var enemyTarget = findNearestEnemy(army.transform.position);
                var direction = (army.transform.position - enemyTarget).normalized;
                army.MoveTo(enemyTarget + direction * 1.5f);
                timer = 1;
                return;
        }
    }

    private Vector3 findNearestEnemy(Vector3 position)
    {
        var enemyOfficers = FindObjectsOfType<Officer>().Where(o => o.FactionId != faction.ID).ToList();
        if (enemyOfficers.Count > 0)
        {
            enemyOfficers.Sort((a, b) => {
                var delta = Vector3.Distance(a.transform.position, position) - Vector3.Distance(a.transform.position, position);
                if (delta > 0)
                    return 1;
                return -1;
            });
            var target = enemyOfficers.First().transform.position;
            return target;
        }
        var enemyBases = FindObjectsOfType<Base>().Where(o => o.FactionId != faction.ID).ToList();
        if (enemyBases.Count > 0)
        {
            enemyBases.Sort((a, b) => {
                var delta = Vector3.Distance(a.transform.position, position) - Vector3.Distance(a.transform.position, position);
                if (delta > 0)
                    return 1;
                return -1;
            });
            var target = enemyBases.First().transform.position;
            return target;
        }

        return Vector3.zero;
    }

    private ProjectBlueprint FindBaseBlueprint(string id)
    {
        return blueprintProvider.BaseProjects.Where(b => b.ID == id).FirstOrDefault();
    }

    private ProjectBlueprint FindOfficerBlueprint(string id)
    {
        return blueprintProvider.OfficerProjects.Where(b => b.ID == id).FirstOrDefault();
    }
}
