using System;
using System.Collections.Generic;
using System.Linq;
using Net.Eagle0.Eagle.Api.Command.Util;
using Net.Eagle0.Eagle.Common;
using Net.Eagle0.Eagle.Views;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

namespace eagle {
    using HeroId = Int32;
    using BattalionId = Int32;
    using static SuitableBattalions.Types;

    public class EventBasedUnitSelector : MonoBehaviour {
        public GameObject availableHeroesContent;
        public GameObject availableBattalionsContent;
        public GameObject selectedUnitsContent;

        public GameObject availableHeroRow;
        public BattalionRowController availableBattalionsRow;
        public GameObject selectedUnitRow;
        public Button recommendedConfigButton;

        public UnityEvent selectionChanged;

        public HeroesAndBattalionsPanelController heroesAndBattalionsPanelController;

        public int BattalionCount => SelectedActingUnits.Where(u => u.BattalionId.HasValue).Count();

        private EagleCommonTextures textures =>
                gameObject.GetComponentInParent<EagleCommonTextures>();

        public struct MaybeHeroAndBattalion {
            public HeroId? heroId;
            public BattalionView battalion;

            public BattalionId? BattalionId =>
                    battalion == null ? (BattalionId?)null : battalion.Id;
        }
        public List<MaybeHeroAndBattalion> SelectedIds = new List<MaybeHeroAndBattalion>();

        private List<HeroId> _allHeroIds;
        private List<BattalionView> _orderedBattalions;

        public List<CombatUnit> SelectedActingUnits {
            get {
                var cus = new List<CombatUnit>();
                if (SelectedIds.Exists(sid => !sid.heroId.HasValue)) return cus;
                for (int i = 0; i < SelectedIds.Count; i++) {
                    cus.Add(new CombatUnit {
                        HeroId = SelectedIds[i].heroId.Value,
                        BattalionId = SelectedIds[i].BattalionId,
                        FactionId = _model.PlayerId.Value
                    });
                }
                return cus;
            }
        }
        public bool UnitsAreValid => SelectedIds.Count > 0 &&
                                     SelectedIds.TrueForAll(sid => sid.heroId.HasValue);
        public string InvalidUnitsReason => SelectedIds.Count < 1
                                                    ? "No units were selected"
                                                    : "Each battalion must have a hero";

        public List<HeroId> HeroIdsLeadingInappropriateUnits =>
                SelectedActingUnits
                        .Where(u => u.BattalionId.HasValue &&
                                    SuitableBattalions[u.HeroId]
                                                    .Find(bws => bws.BattalionId ==
                                                                 u.BattalionId.Value)
                                                    .Level == SuitabilityLevel.Restrictive)
                        .Select(u => u.HeroId)
                        .ToList();

        public bool HeroIsTargetable(HeroId heroId) =>
                AvailableHeroIds.Contains(heroId) ||
                SelectedActingUnits.Select(u => u.HeroId).Contains(heroId);
        public void AddTargetedHero(HeroId heroId) {
            var index = AvailableHeroIds.IndexOf(heroId);

            if (index != -1) { availableHeroButtonClicked(index); }
        }

        public void RemoveTargetedHero(HeroId heroId) =>
                selectedHeroClicked(SelectedIds.FindIndex(cu => cu.heroId == heroId));

        public bool BattalionIsTargetable(BattalionId bid) =>
                AvailableBattalions.Where(batt => batt.Id == bid).Count() > 0 ||
                SelectedActingUnits.Select(u => u.BattalionId).Contains(bid);
        public void AddTargetedBattalion(BattalionId bid) {
            var index = AvailableBattalions.FindIndex(batt => batt.Id == bid);

            if (index != -1) { availableBattalionButtonClicked(index); }
        }

        public void RemoveTargetedBattalion(BattalionId bid) =>
                selectedBattalionClicked(SelectedIds.FindIndex(cu => cu.BattalionId == bid));

        public void SetUpUnits(
                IGameModel model,
                List<HeroId> availableHeroIds,
                List<BattalionView> orderedBattalions,
                Dictionary<HeroId, List<BattalionIdWithSuitability>> suitableBattalions,
                List<CombatUnit> recommendedUnits) {
            _model = model;
            _allHeroIds = availableHeroIds;
            _orderedBattalions = orderedBattalions;
            AvailableHeroIds = availableHeroIds;
            AvailableBattalions = orderedBattalions;
            SuitableBattalions = suitableBattalions;
            RecommendedUnits = recommendedUnits;
            SelectedIds.Clear();

            var hasRecommendedConfig = RecommendedUnits.Count > 0;
            recommendedConfigButton.gameObject.SetActive(hasRecommendedConfig);

            SetUpTables();
        }

        private IGameModel _model;
        List<HeroId> AvailableHeroIds = new();
        List<BattalionView> AvailableBattalions = new();
        Dictionary<HeroId, List<BattalionIdWithSuitability>> SuitableBattalions;
        List<CombatUnit> RecommendedUnits;

        private GameObject instantiateInTable(GameObject prefab, Transform tableContent) {
            var newRow = Instantiate(
                    prefab,
                    new Vector3(0, 0, 0),
                    new Quaternion(0, 0, 0, 0),
                    tableContent);

            // FIXME: I don't know why this reset of the z-position is necessary
            var position = newRow.GetComponent<RectTransform>().anchoredPosition3D;
            position.z = 0;
            newRow.GetComponent<RectTransform>().anchoredPosition3D = position;

            return newRow;
        }

        private void SetUpTables() {
            AvailableHeroIds.OrderBy(hid => _allHeroIds.IndexOf(hid));
            AvailableBattalions.OrderBy(b => _orderedBattalions.IndexOf(b));
            var oddColor = availableHeroRow.GetComponent<Image>().color;
            oddColor.a = 0;

            foreach (Transform row in availableHeroesContent.transform) { Destroy(row.gameObject); }
            var parentTransform = availableHeroesContent.transform;
            for (int i = 0; i < AvailableHeroIds.Count; i++) {
                var hid = AvailableHeroIds[i];
                var hero = _model.Heroes[hid];

                var newRow = instantiateInTable(availableHeroRow, parentTransform)
                                     .GetComponent<UnitSelectorHeroRowController>();
                newRow.Hero = hero;

                var index = i;
                newRow.GetComponentInChildren<TableRowClickDetector>().onClick.AddListener(
                        () => availableHeroButtonClicked(index));
                newRow.GetComponentInChildren<TableRowClickDetector>().selectable = true;

                if (i % 2 == 1) { newRow.GetComponent<Image>().color = oddColor; }
            }

            foreach (Transform row in selectedUnitsContent.transform) { Destroy(row.gameObject); }
            parentTransform = selectedUnitsContent.transform;

            var idsCount = SelectedIds.Count;
            for (int i = 0; i < idsCount; i++) {
                var newRow = instantiateInTable(selectedUnitRow, parentTransform)
                                     .GetComponent<SelectedUnitRowController>();
                var buttons = newRow.GetComponentsInChildren<Button>();

                var heroId = SelectedIds[i].heroId;
                var battalion = SelectedIds[i].battalion;
                var appropriate = heroId.HasValue &&
                                  (battalion == null ||
                                   SuitableBattalions[heroId.Value]
                                                   .Find(bws => bws.BattalionId == battalion.Id)
                                                   .Level != SuitabilityLevel.Restrictive);

                var index = i;

                if (heroId is HeroId hid) {
                    var hero = _model.Heroes[hid];
                    newRow.Hero = hero;
                    newRow.Appropriate = appropriate;
                    newRow.HeroClickDetector = () => selectedHeroClicked(index);
                } else {
                    newRow.Hero = null;
                }

                newRow.Battalion = battalion;
                newRow.BattalionClickDetector = () => selectedBattalionClicked(index);

                if (i % 2 == 1) { newRow.GetComponent<Image>().color = oddColor; }
            }

            foreach (Transform row in availableBattalionsContent.transform) {
                Destroy(row.gameObject);
            }
            parentTransform = availableBattalionsContent.transform;
            for (int i = 0; i < AvailableBattalions.Count; i++) {
                var battalion = AvailableBattalions[i];

                var newRow = instantiateInTable(availableBattalionsRow.gameObject, parentTransform)
                                     .GetComponent<BattalionRowController>();
                newRow.Battalion = battalion;

                var index = i;

                newRow.GetComponent<TableRowClickDetector>().onClick.AddListener(
                        () => availableBattalionButtonClicked(index));
                newRow.GetComponentInChildren<TableRowClickDetector>().selectable = true;

                if (i % 2 == 1) { newRow.GetComponent<Image>().color = oddColor; }
            }

            heroesAndBattalionsPanelController.UpdateTables();

            selectionChanged.Invoke();
        }

        public void availableHeroButtonClicked(int index) {
            var hid = AvailableHeroIds[index];
            var chosenIndex = SelectedIds.FindIndex(sid => !sid.heroId.HasValue);
            if (chosenIndex == -1) {
                SelectedIds.Add(new MaybeHeroAndBattalion { heroId = hid, battalion = null });
            } else {
                var sid = SelectedIds[chosenIndex];
                sid.heroId = hid;
                SelectedIds[chosenIndex] = sid;
            }
            AvailableHeroIds.RemoveAt(index);

            heroesAndBattalionsPanelController.UpdateHeroSelections();
            SetUpTables();
        }

        public void availableBattalionButtonClicked(int index) {
            var batt = AvailableBattalions[index];
            var chosenIndex = SelectedIds.FindIndex(sid => sid.battalion == null);
            if (chosenIndex == -1) {
                SelectedIds.Add(new MaybeHeroAndBattalion { heroId = null, battalion = batt });
            } else {
                var sid = SelectedIds[chosenIndex];
                sid.battalion = batt;
                SelectedIds[chosenIndex] = sid;
            }
            AvailableBattalions.RemoveAt(index);

            heroesAndBattalionsPanelController.UpdateBattalionSelections();
            SetUpTables();
        }

        public void selectedHeroClicked(int index) {
            var heroId = SelectedIds[index].heroId;
            if (heroId.HasValue) {
                AvailableHeroIds.Add(heroId.Value);

                if (SelectedIds[index].battalion != null) {
                    SelectedIds[index] =
                            new MaybeHeroAndBattalion { battalion = SelectedIds[index].battalion };
                } else {
                    SelectedIds.RemoveAt(index);
                }

                heroesAndBattalionsPanelController.UpdateHeroSelections();
                SetUpTables();
            }
        }

        public void selectedBattalionClicked(int index) {
            var battalion = SelectedIds[index].battalion;
            if (battalion != null) {
                AvailableBattalions.Add(battalion);

                if (SelectedIds[index].heroId.HasValue) {
                    SelectedIds[index] =
                            new MaybeHeroAndBattalion { heroId = SelectedIds[index].heroId };
                } else {
                    SelectedIds.RemoveAt(index);
                }
            }

            heroesAndBattalionsPanelController.UpdateBattalionSelections();
            SetUpTables();
        }

        public void recommendedConfigClicked() {
            SelectedIds.Select(sid => sid.heroId)
                    .Where(hid => hid.HasValue)
                    .ToList()
                    .ForEach(hid => AvailableHeroIds.Add(hid.Value));
            SelectedIds.Select(sid => sid.battalion)
                    .Where(bid => bid != null)
                    .ToList()
                    .ForEach(batt => AvailableBattalions.Add(batt));
            SelectedIds.Clear();

            foreach (var cu in RecommendedUnits) {
                var recommendedBattalion =
                        cu.BattalionId.HasValue
                                ? _orderedBattalions.First(batt => batt.Id == cu.BattalionId)
                                : null;
                SelectedIds.Add(new MaybeHeroAndBattalion {
                    heroId = cu.HeroId,
                    battalion = recommendedBattalion
                });
                AvailableHeroIds.Remove(cu.HeroId);

                if (recommendedBattalion != null) {
                    AvailableBattalions.Remove(recommendedBattalion);
                }
            }

            heroesAndBattalionsPanelController.UpdateHeroSelections();
            heroesAndBattalionsPanelController.UpdateBattalionSelections();
            SetUpTables();
        }

        // Use this for initialization
        void Start() {}
    }
}