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

namespace eagle {
    using ProvinceId = Int32;

    public class ArmTroopsCommandSelector : CommandSelector {
        private List<double> _costs;
        private List<int> _newArmaments;
        private List<BattalionView> _battalions;
        private int _availableGold;
        private int _maxArmament;
        private MaxAttainableEverywhereBattalions _maxAttainableEverywhereBattalions;

        public EagleCommonTextures textures;
        public EventBasedTable armTroopsTable;
        public TMP_Text totalCostText;
        public TMP_Text availableText;
        public TMP_Text maxLabel;
        public TMP_Text maxEvenLabel;
        public Button maxEvenButton;

        public override AvailableCommand.SealedValueOneofCase CommandType =>
                AvailableCommand.SealedValueOneofCase.ArmTroopsCommand;

        private ArmTroopsAvailableCommand ArmTroopsAvailableCommand =>
                _availableCommand.ArmTroopsCommand;
        private ProvinceId ActingProvinceId => ArmTroopsAvailableCommand.ActingProvinceId;

        private struct MaxAttainableEverywhereBattalions {
            public int Armament;
            public int Cost;
            public List<int> MaxedArmaments;
        }

        // Calculate the maximum armament that can be achieved by all battalions with available gold
        private MaxAttainableEverywhereBattalions SpendEvenlyOnly() {
            var maxCost = Cost(_battalions.Select(b => _maxArmament).ToList());
            if (maxCost <= _availableGold) {
                return new MaxAttainableEverywhereBattalions {
                    Armament = _maxArmament,
                    Cost = maxCost,
                    MaxedArmaments = _battalions.Select(b => (int)(b.Armament ?? 0)).ToList()
                };
            }

            int tooHigh = _maxArmament;
            int minPossibility = 0;

            while (tooHigh - minPossibility > 1) {
                int mid = (tooHigh + minPossibility) / 2;
                if (Cost(_battalions.Select(b => Math.Max(mid, (int)(b.Armament ?? 0))).ToList()) <=
                    _availableGold) {
                    minPossibility = mid;
                } else {
                    tooHigh = mid;
                }
            }

            var returnedBattalions =
                    _battalions.Select(b => (int)Math.Max(minPossibility, b.Armament ?? 0))
                            .ToList();

            return new MaxAttainableEverywhereBattalions {
                Armament = minPossibility,
                Cost = Cost(returnedBattalions),
                MaxedArmaments = _battalions
                                         .Select(b => (b.Armament ?? 0) > minPossibility
                                                              ? (int)(b.Armament ?? 0)
                                                              : minPossibility)
                                         .ToList()
            };
        }

        private MaxAttainableEverywhereBattalions SpendRemainingGold(
                MaxAttainableEverywhereBattalions startPosition) {
            var remainingGold = _availableGold - startPosition.Cost;

            int bestIndex = -1;
            int lowestArmament = _maxArmament;
            double currentCost = 0;
            for (int i = 0; i < startPosition.MaxedArmaments.Count; i++) {
                var currentArmament = startPosition.MaxedArmaments[i];
                if (currentArmament == _maxArmament) continue;
                if (currentArmament > lowestArmament) continue;
                var cost = _costs[i];
                if (cost > remainingGold) continue;
                if (cost > currentCost) {
                    currentCost = cost;
                    lowestArmament = currentArmament;
                    bestIndex = i;
                }
            }

            if (bestIndex == -1) return startPosition;
            startPosition.MaxedArmaments[bestIndex] += 1;
            startPosition.Cost = Cost(startPosition.MaxedArmaments);
            return SpendRemainingGold(startPosition);
        }

        protected override void SetUpUI() {
            var province = _model.Provinces[ActingProvinceId];
            _battalions =
                    ArmTroopsAvailableCommand.AvailableBattalions
                            .Select(bid => province.FullInfo.Battalions.First(b => b.Id == bid))
                            .OrderBy(b => b, BattalionUtils.BattalionComparer)
                            .ToList();
            _newArmaments = _battalions.Select(b => (int)(b.Armament ?? 0)).ToList();

            var costDict = new Dictionary<BattalionTypeId, double>();
            foreach (var t in ArmTroopsAvailableCommand.ArmamentCosts) {
                costDict[t.Type] = t.Cost;
            }

            _costs = _battalions.Select(b => costDict[b.Type] * b.Size).ToList();

            _availableGold = _model.Provinces[ActingProvinceId].FullInfo.Gold;
            _maxArmament = ArmTroopsAvailableCommand.MaxArmament;
            _maxAttainableEverywhereBattalions = SpendRemainingGold(SpendEvenlyOnly());

            if (_maxAttainableEverywhereBattalions.Armament == _maxArmament) {
                maxEvenButton.gameObject.SetActive(false);
            } else {
                maxEvenLabel.text = _maxAttainableEverywhereBattalions.Armament.ToString();
                maxEvenButton.gameObject.SetActive(true);
            }

            maxLabel.text = _maxArmament.ToString();
            availableText.text = _availableGold.ToString();

            UpdateTableAndCost();
        }

        public override string HeaderString => "Arm Troops";
        public override string CommitButtonString => "Commit Armament";

        private bool _enableCommit;
        public override bool EnableCommitButton => _enableCommit;

        private string _disabledCommitButtonReason;
        public override string DisabledCommitButtonReason => _disabledCommitButtonReason;

        private void PopulateRow(int index) {
            var row = armTroopsTable.ComponentAt<ArmTroopsRowController>(index);
            var battalion = _battalions[index];
            row.BattalionImage = textures.BattalionType(battalion.Type);
            row.Size = battalion.Size;
            row.Name = battalion.Name;

            row.PlusButtonClickedCallback = () => PlusClicked(index);
            row.MinusButtonClickedCallback = () => MinusClicked(index);
            row.ResetButtonClickedCallback = () => ResetClicked(index);
            row.MaxButtonClickedCallback = () => MaxClicked(index);

            SetValuesAndButtons(index);
        }

        private void SetValuesAndButtons(int index) {
            var row = armTroopsTable.ComponentAt<ArmTroopsRowController>(index);
            var battalion = _battalions[index];
            var newArmament = _newArmaments[index];

            row.OldArmament = battalion.Armament ?? 0;
            row.NewArmament = newArmament;
            row.Name = battalion.Name;

            row.AllowMax = battalion.Armament < _maxArmament;
            row.AllowPlus = battalion.Armament < _maxArmament;
            row.AllowMinus = battalion.Armament < newArmament;
            row.AllowReset = battalion.Armament != newArmament;

            UpdateCost();
        }

        private void UpdateCost() {
            _enableCommit = true;
            totalCostText.color = Color.black;
            int cost = Cost();

            totalCostText.text = cost.ToString();
            if (cost > _availableGold) {
                totalCostText.color = Color.red;
                _enableCommit = false;
                _disabledCommitButtonReason = "Not enough gold";
            }
            if (cost == 0) {
                _enableCommit = false;
                _disabledCommitButtonReason = "No battalions have changed";
            }
        }

        private void UpdateTableAndCost() {
            armTroopsTable.RowCount = _battalions.Count;

            for (int i = 0; i < _battalions.Count; i++) { PopulateRow(i); }

            UpdateCost();
        }

        public void PlusClicked(int index) {
            _newArmaments[index] =
                    Math.Min(_maxArmament, _newArmaments[index] + KeyModifiedAmount.Amount());
            SetValuesAndButtons(index);
        }

        public void MinusClicked(int index) {
            _newArmaments[index] = Math.Max(
                    (int)(_battalions[index].Armament ?? 0),
                    _newArmaments[index] - KeyModifiedAmount.Amount());
            SetValuesAndButtons(index);
        }

        public void ResetClicked(int index) {
            _newArmaments[index] = (int)_battalions[index].Armament;
            SetValuesAndButtons(index);
        }

        public void MaxClicked(int index) {
            _newArmaments[index] = _maxArmament;
            SetValuesAndButtons(index);
        }

        public void MaxAllClicked() {
            _newArmaments = _battalions.Select(b => _maxArmament).ToList();
            UpdateTableAndCost();
        }

        public void MaxAllEvenlyClicked() {
            _newArmaments = _maxAttainableEverywhereBattalions.MaxedArmaments;
            UpdateTableAndCost();
        }

        public void ResetAllClicked() {
            _newArmaments = _battalions.Select(b => (int)(b.Armament ?? 0)).ToList();
            UpdateTableAndCost();
        }

        public override SelectedCommand Command {
            get {
                var atc = new ArmTroopsSelectedCommand();
                for (int i = 0; i < _newArmaments.Count; i++) {
                    if (_newArmaments[i] > _battalions[i].Armament) {
                        var ab = new ArmedBattalion {
                            Id = _battalions[i].Id,
                            NewArmament = _newArmaments[i]
                        };
                        atc.ArmedBattalions.Add(ab);
                    }
                }

                var cmd = new SelectedCommand { ArmTroopsCommand = atc };

                return cmd;
            }
        }

        private int Cost(List<int> armaments) {
            double totalCost = 0.0;

            for (int i = 0; i < armaments.Count; i++) {
                var battalion = _battalions[i];

                totalCost += _costs[i] * Math.Max(armaments[i] - battalion.Armament.Value, 0.0);
            }

            return (int)Math.Ceiling(Math.Max(totalCost, 0.0));
        }

        private int Cost() { return Cost(_newArmaments); }
    }
}