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

namespace eagle {
    using static OrganizeTroopsSelectedCommand.Types;
    using BattalionId = Int32;
    using ProvinceId = Int32;

    public class OrganizeTroopsCommandSelector : CommandSelector {
        Dictionary<BattalionTypeId, double> costDict = new Dictionary<BattalionTypeId, double>();

        private OrganizeTroopsAvailableCommand OrganizeTroopsAvailableCommand =>
                _availableCommand.OrganizeTroopsCommand;
        private ProvinceId ActingProvinceId => OrganizeTroopsAvailableCommand.ActingProvinceId;
        private ProvinceView Province => _model.Provinces[ActingProvinceId];

        private class NewBattalionInfo : BattalionDisplayInfo {
            public NewBattalion newBattalion;

            private BattalionView _updated;
            private List<BattalionComponent> _components;

            public override int Count => _updated.Size;
            public override double? Armament => _updated.Armament;
            public override double? Training => _updated.Training;
            public override List<BattalionComponent> Components => _components;
            public override BattalionTypeId TypeId { get; }
            public override string Name => null;

            public BattalionView Update(List<ExistingBattalionInfo> existingBattalions) {
                _updated = new BattalionView { Type = newBattalion.Type };

                _updated.Size = newBattalion.NewTroops;

                foreach (TroopsFromOtherBattalion merged in newBattalion.TroopsFromOtherBattalion) {
                    var fromBatt =
                            existingBattalions.Find(x => x.Original.Id == merged.FromBattalionId)
                                    .Original;
                    var newSize = _updated.Size + merged.CountMoved;

                    _updated.Training = ((_updated.Size * _updated.Training) +
                                         (merged.CountMoved * fromBatt.Training)) /
                                        newSize;
                    _updated.Armament = ((_updated.Size * _updated.Armament) +
                                         (merged.CountMoved * fromBatt.Armament)) /
                                        newSize;
                    _updated.Size = newSize;
                }

                _components = MergedComponents(
                        newBattalion.TroopsFromOtherBattalion,
                        newBattalion.NewTroops,
                        existingBattalions);

                return _updated;
            }

            public NewBattalionInfo(
                    BattalionTypeId typeId,
                    Texture image,
                    double cost,
                    int capacity)
                : base(image, cost, capacity) {
                newBattalion = new NewBattalion { NewTroops = 0, Type = typeId };
                TypeId = typeId;
            }
        }

        private class ExistingBattalionInfo : BattalionDisplayInfo {
            public BattalionView Original { get; private set; }

            public override int Count => _updated.Size;
            public override double? Armament => _updated.Armament;
            public override double? Training => _updated.Training;
            public override List<BattalionComponent> Components => _components;
            public override BattalionTypeId TypeId => Original.Type;
            public override string Name => Original.Name;

            public ChangedBattalion changed;
            public int troopsRemoved;
            public bool dismissed = false;

            private BattalionView _updated;

            public BattalionId Id => Original.Id;

            private List<BattalionComponent> _components;

            public BattalionView Update(List<ExistingBattalionInfo> existingBattalions) {
                BattalionView bv = new BattalionView(Original);

                bv.Size -= troopsRemoved;

                if (changed != null) {
                    foreach (TroopsFromOtherBattalion merged in changed.TroopsFromOtherBattalion) {
                        var fromBatt = existingBattalions
                                               .Find(x => x.Original.Id == merged.FromBattalionId)
                                               .Original;
                        var newSize = bv.Size + merged.CountMoved;

                        bv.Training = ((bv.Size * bv.Training) +
                                       (merged.CountMoved * fromBatt.Training)) /
                                      newSize;
                        bv.Armament = ((bv.Size * bv.Armament) +
                                       (merged.CountMoved * fromBatt.Armament)) /
                                      newSize;
                        bv.Size = newSize;
                    }

                    var finalSize = bv.Size + changed.NewTroops;
                    bv.Training = (bv.Size * bv.Training) / finalSize;
                    bv.Armament = (bv.Size * bv.Armament) / finalSize;
                    bv.Size = finalSize;
                }

                _updated = bv;

                _components = new List<BattalionComponent>();
                if (Original.Size > troopsRemoved) {
                    _components.Add(new BattalionComponent {
                        armament = Original.Armament.Value,
                        training = Original.Training.Value,
                        count = Original.Size - troopsRemoved,
                        source = BattalionComponent.ComponentSource.originalTroops
                    });
                }

                if (changed != null) {
                    _components.AddRange(MergedComponents(
                            changed.TroopsFromOtherBattalion,
                            changed.NewTroops,
                            existingBattalions));
                }

                return _updated;
            }

            public ExistingBattalionInfo(
                    BattalionView orig,
                    Texture image,
                    double cost,
                    int capacity)
                : base(image, cost, capacity) {
                Original = orig;
            }
        }

        List<ExistingBattalionInfo> existingBattalions;
        List<NewBattalionInfo> newBattalions;

        public EventBasedTable battalionsTable;

        static List<BattalionComponent> MergedComponents(
                IEnumerable<TroopsFromOtherBattalion> tfob,
                int newCount,
                List<ExistingBattalionInfo> existingBattalions) {
            var list = tfob.Select(merged => {
                               var fromBatt =
                                       existingBattalions.Find(x => x.Id == merged.FromBattalionId)
                                               .Original;

                               return new BattalionComponent {
                                   armament = fromBatt.Armament.Value,
                                   training = fromBatt.Training.Value,
                                   count = merged.CountMoved,
                                   source = BattalionComponent.ComponentSource.mergedTroops
                               };
                           }).ToList();

            if (newCount > 0) {
                list.Add(new BattalionComponent {
                    armament = 0,
                    training = 0,
                    count = newCount,
                    source = BattalionComponent.ComponentSource.newTroops
                });
            }

            return list;
        }

        List<BattalionComponent> BattalionComponents(NewBattalion nb) {
            return MergedComponents(nb.TroopsFromOtherBattalion, nb.NewTroops, existingBattalions);
        }

        class TroopsFromBattalion {
            public BattalionId fromId;
            public BattalionTypeId type;
            public int count;
            public double training;
            public double armament;
        }

        List<TroopsFromBattalion> extraTroops = new List<TroopsFromBattalion>();

        public EventBasedTable lightInfantryTable;
        public EventBasedTable heavyInfantryTable;
        public EventBasedTable dragoonsTable;
        public EventBasedTable knightsTable;
        public EventBasedTable longbowmenTable;

        public Button maxAllButton;
        public Button resetAllButton;
        public Button mergeButton;

        int availableGold;

        public EagleCommonTextures textures;

        public TMP_Text costLabel;
        public TMP_Text troopCountLabel;

        private bool TypeIsAllowed(BattalionTypeId battalionTypeId) {
            return OrganizeTroopsAvailableCommand.AvailableBattalionTypes.Any(
                    tp => tp.TypeId == battalionTypeId && tp.MeetsRequirements);
        }

        private void MaybeActivateRow(EventBasedTable table, BattalionTypeId battalionTypeId) {
            var parent = table.gameObject.transform.parent;

            var allowed = TypeIsAllowed(battalionTypeId) ||
                          extraTroops.Where(tfb => tfb.type == battalionTypeId)
                                          .Select(tfb => tfb.count)
                                          .Sum() > 0;

            table.gameObject.GetComponent<OrganizeExtrasTable>().Set(
                    allowed,
                    OrganizeTroopsAvailableCommand.AvailableBattalionTypes.First(
                            tp => tp.TypeId == battalionTypeId),
                    ProvinceStatUtils.RoundedStat(Province.FullInfo.Agriculture),
                    ProvinceStatUtils.RoundedStat(Province.FullInfo.Economy));

            parent.GetComponentInChildren<Button>().interactable = allowed;

            var color = parent.GetComponentInChildren<RawImage>().color;
            if (allowed) {
                color.a = 255;
            } else {
                color.a = 70;
            }

            parent.GetComponentInChildren<RawImage>().color = color;
        }

        private void MaybeActivateRows() {
            MaybeActivateRow(lightInfantryTable, BattalionTypeId.LightInfantry);
            MaybeActivateRow(heavyInfantryTable, BattalionTypeId.HeavyInfantry);
            MaybeActivateRow(longbowmenTable, BattalionTypeId.Longbowmen);
            MaybeActivateRow(dragoonsTable, BattalionTypeId.LightCavalry);
            MaybeActivateRow(knightsTable, BattalionTypeId.HeavyCavalry);
        }

        protected override void SetUpUI() {
            foreach (var c in OrganizeTroopsAvailableCommand.TroopCosts) {
                costDict[c.Type] = c.CostPerTroop;
            }

            existingBattalions =
                    OrganizeTroopsAvailableCommand.ExistingBattalions
                            .Select(bid => Province.FullInfo.Battalions.First(b => b.Id == bid))
                            .OrderBy(b => b, BattalionUtils.BattalionComparer)
                            .Select(b => new ExistingBattalionInfo(
                                            b,
                                            textures.BattalionType(b.Type),
                                            costDict[b.Type],
                                            _model.BattalionTypes[b.Type].Capacity))
                            .ToList();

            availableGold = _model.Provinces[ActingProvinceId].FullInfo.Gold;
            newBattalions = new List<NewBattalionInfo>();

            UpdateTable();
        }

        private void PlusClickedImpl(BattalionDisplayInfo b) {
            if (b is ExistingBattalionInfo) {
                PlusClickedImpl(b as ExistingBattalionInfo);
            } else {
                PlusClickedImpl(b as NewBattalionInfo);
            }
        }

        private bool PlusClickedImpl(ExistingBattalionInfo eb) {
            var capacity = _model.BattalionTypes[eb.TypeId].Capacity;
            var updated = eb.Update(existingBattalions);
            var maxToAdd = Math.Min(KeyModifiedAmount.Amount(), capacity - updated.Size);

            // First try to pull troops that were previously removed
            var ownAvailable = extraTroops.Find(et => et.fromId == eb.Id);
            var otherAvailable = extraTroops.Find(et => et.type == eb.TypeId);
            if (ownAvailable != null) {
                var countToPull = Math.Min(maxToAdd, ownAvailable.count);
                ownAvailable.count -= countToPull;
                eb.troopsRemoved -= countToPull;
                if (ownAvailable.count == 0) { extraTroops.Remove(ownAvailable); }
            }
            // Then look for available troops of the same type
            else if (otherAvailable != null) {
                var countToPull = Math.Min(maxToAdd, otherAvailable.count);
                otherAvailable.count -= countToPull;
                if (otherAvailable.count == 0) { extraTroops.Remove(otherAvailable); }

                if (eb.changed == null)
                    eb.changed = new ChangedBattalion { Id = eb.Id, NewTroops = 0 };
                var fromThisBattalion = eb.changed?.TroopsFromOtherBattalion.ToList().Find(
                        x => x.FromBattalionId == otherAvailable.fromId);
                if (fromThisBattalion == null) {
                    fromThisBattalion = new TroopsFromOtherBattalion {
                        FromBattalionId = otherAvailable.fromId,
                        CountMoved = 0
                    };
                    eb.changed.TroopsFromOtherBattalion.Add(fromThisBattalion);
                }
                fromThisBattalion.CountMoved += countToPull;
            } else if (TypeIsAllowed(eb.TypeId)) {
                // Finally just add as new troops
                if (eb.changed == null) {
                    eb.changed = new ChangedBattalion { Id = eb.Id, NewTroops = 0 };
                }
                eb.changed.NewTroops += maxToAdd;
            } else
                return false;

            eb.Update(existingBattalions);
            return true;
        }

        private void PlusClicked(ExistingBattalionInfo eb) {
            PlusClickedImpl(eb);
            UpdateTable();
        }

        private void MinusClicked(ExistingBattalionInfo eb) {
            // First look for any newly-added troops
            if (eb.changed != null && eb.changed.NewTroops > 0) {
                eb.changed.NewTroops =
                        Math.Max(eb.changed.NewTroops - KeyModifiedAmount.Amount(), 0);
                if (eb.changed.NewTroops == 0 && eb.changed.TroopsFromOtherBattalion.Count == 0) {
                    eb.changed = null;
                }
            }
            // Next look for merged-in troops and remove those.
            else if (eb.changed != null && eb.changed.TroopsFromOtherBattalion.Count > 0) {
                var toRemoveFrom = eb.changed.TroopsFromOtherBattalion.Last();
                var countToRemove = Math.Min(KeyModifiedAmount.Amount(), toRemoveFrom.CountMoved);
                toRemoveFrom.CountMoved -= countToRemove;

                var extrasToMergeInto = ExtraTroopsForId(toRemoveFrom.FromBattalionId);
                extrasToMergeInto.count += countToRemove;

                if (toRemoveFrom.CountMoved == 0) {
                    eb.changed.TroopsFromOtherBattalion.Remove(toRemoveFrom);
                    if (eb.changed.TroopsFromOtherBattalion.Count == 0 &&
                        eb.changed.NewTroops == 0) {
                        eb.changed = null;
                    }
                }
            }
            // Remove original troops
            else {
                var updated = eb.Update(existingBattalions);
                var availableToRemove = eb.Original.Size - eb.troopsRemoved;
                var newlyRemovedCount = Math.Min(KeyModifiedAmount.Amount(), availableToRemove);

                eb.troopsRemoved += newlyRemovedCount;

                var extra = ExtraTroopsForId(eb.Id);
                extra.count += newlyRemovedCount;
            }

            UpdateTable();
        }

        private void MaxClickedImpl(ExistingBattalionInfo eb) {
            while (eb.Update(existingBattalions).Size < _model.BattalionTypes[eb.TypeId].Capacity &&
                   PlusClickedImpl(eb)) {}
        }

        private void MaxClicked(ExistingBattalionInfo eb) {
            MaxClickedImpl(eb);
            UpdateTable();
        }

        private TroopsFromBattalion ExtraTroopsForId(BattalionId bid) {
            var extra = extraTroops.Find(et => et.fromId == bid);
            if (extra == null) {
                var fromBattalion = existingBattalions.Find(batt => batt.Id == bid).Original;
                extra = new TroopsFromBattalion {
                    fromId = bid,
                    type = fromBattalion.Type,
                    count = 0,
                    armament = fromBattalion.Armament.Value,
                    training = fromBattalion.Training.Value
                };
                extraTroops.Add(extra);
            }

            return extra;
        }

        private void DismissClickedImpl(ExistingBattalionInfo eb) {
            // put any merged-in troops back into the pool
            if (eb.changed != null) {
                foreach (TroopsFromOtherBattalion merged in eb.changed.TroopsFromOtherBattalion) {
                    var extra = ExtraTroopsForId(merged.FromBattalionId);
                    extra.count += merged.CountMoved;
                }
                eb.changed = null;
            }

            // Put any troops remaining from this battalion into the pool
            var originalTroopsRemaining = eb.Original.Size - eb.troopsRemoved;
            if (originalTroopsRemaining > 0) {
                var myExtra = ExtraTroopsForId(eb.Id);
                myExtra.count += originalTroopsRemaining;
            }
            eb.troopsRemoved = eb.Original.Size;

            eb.dismissed = true;
        }

        private void DismissClicked(ExistingBattalionInfo eb) {
            DismissClickedImpl(eb);
            UpdateTable();
        }

        // Returns true if a change was made
        private bool PlusClickedImpl(NewBattalionInfo newB) {
            var capacity = _model.BattalionTypes[newB.TypeId].Capacity;
            var updated = newB.Update(existingBattalions);
            var maxToAdd = Math.Min(KeyModifiedAmount.Amount(), capacity - updated.Size);

            // First try to pull troops that were previously removed
            var otherAvailable = extraTroops.Find(et => et.type == newB.TypeId);
            // Then look for available troops of the same type
            if (otherAvailable != null) {
                var countToPull = Math.Min(maxToAdd, otherAvailable.count);
                otherAvailable.count -= countToPull;
                if (otherAvailable.count == 0) { extraTroops.Remove(otherAvailable); }

                var fromThisBattalion = newB.newBattalion.TroopsFromOtherBattalion.ToList().Find(
                        x => x.FromBattalionId == otherAvailable.fromId);
                if (fromThisBattalion == null) {
                    fromThisBattalion = new TroopsFromOtherBattalion {
                        FromBattalionId = otherAvailable.fromId,
                        CountMoved = 0
                    };
                    newB.newBattalion.TroopsFromOtherBattalion.Add(fromThisBattalion);
                }
                fromThisBattalion.CountMoved += countToPull;
            }
            // Finally just add as new troops
            else if (TypeIsAllowed(newB.TypeId)) {
                newB.newBattalion.NewTroops += maxToAdd;
            } else
                return false;

            newB.Update(existingBattalions);
            return true;
        }

        private void PlusClicked(NewBattalionInfo newB) {
            PlusClickedImpl(newB);
            UpdateTable();
        }

        private void MinusClicked(NewBattalionInfo newB) {
            // First look for any newly-added troops
            if (newB.newBattalion.NewTroops > 0) {
                newB.newBattalion.NewTroops =
                        Math.Max(newB.newBattalion.NewTroops - KeyModifiedAmount.Amount(), 0);
            }
            // Next look for merged-in troops and remove those.
            else if (newB.newBattalion.TroopsFromOtherBattalion.Count > 0) {
                var toRemoveFrom = newB.newBattalion.TroopsFromOtherBattalion.Last();
                var countToRemove = Math.Min(KeyModifiedAmount.Amount(), toRemoveFrom.CountMoved);
                toRemoveFrom.CountMoved -= countToRemove;

                var extrasToMergeInto = ExtraTroopsForId(toRemoveFrom.FromBattalionId);
                extrasToMergeInto.count += countToRemove;

                if (toRemoveFrom.CountMoved == 0) {
                    newB.newBattalion.TroopsFromOtherBattalion.Remove(toRemoveFrom);
                }
            }

            UpdateTable();
        }

        private void MaxClickedImpl(NewBattalionInfo newB) {
            while (newB.Update(existingBattalions).Size < newB.Capacity && PlusClickedImpl(newB)) {}
        }

        private void MaxClicked(NewBattalionInfo newB) {
            MaxClickedImpl(newB);
            UpdateTable();
        }

        private void DismissClickedImpl(NewBattalionInfo newB) {
            // put any merged-in troops back into the pool
            foreach (TroopsFromOtherBattalion merged in newB.newBattalion
                             .TroopsFromOtherBattalion) {
                var extra = ExtraTroopsForId(merged.FromBattalionId);
                extra.count += merged.CountMoved;
            }
        }

        private void DismissClicked(NewBattalionInfo newB) {
            DismissClickedImpl(newB);

            newBattalions.Remove(newB);
            UpdateTable();
        }

        public void MaxAllButtonClicked() {
            foreach (var eb in existingBattalions) {
                if (eb.dismissed) continue;
                MaxClickedImpl(eb);
            }
            foreach (var nb in newBattalions) { MaxClickedImpl(nb); }

            UpdateTable();
        }

        public void ResetAllButtonClicked() {
            foreach (var eb in existingBattalions) {
                eb.changed = null;
                eb.troopsRemoved = 0;
                eb.dismissed = false;
            }
            newBattalions.Clear();
            extraTroops.Clear();
            UpdateTable();
        }

        public void MergeButtonClicked() {
            var newTroopsByType = new Dictionary<BattalionTypeId, int> {
                { BattalionTypeId.LightInfantry, 0 },
                { BattalionTypeId.HeavyInfantry, 0 },
                { BattalionTypeId.LightCavalry, 0 },
                { BattalionTypeId.HeavyCavalry, 0 },
                { BattalionTypeId.Longbowmen, 0 },
            };

            foreach (var eb in existingBattalions) {
                if (eb.changed != null) { newTroopsByType[eb.TypeId] += eb.changed.NewTroops; }
                DismissClickedImpl(eb);
            }

            foreach (var nb in newBattalions) {
                newTroopsByType[nb.TypeId] += nb.newBattalion.NewTroops;
                DismissClickedImpl(nb);
            }
            newBattalions.Clear();

            while (extraTroops.Count > 0) {
                var head = extraTroops.First();
                BattalionDisplayInfo addTo = existingBattalions.Find(
                        eb => eb.TypeId == head.type && !eb.dismissed &&
                              eb.Update(existingBattalions).Size < eb.Capacity);
                if (addTo == null) {
                    var dismissed = existingBattalions.Find(
                            eb => eb.TypeId == head.type && eb.dismissed &&
                                  eb.Update(existingBattalions).Size < eb.Capacity);
                    dismissed.dismissed = false;

                    addTo = dismissed;
                }
                if (addTo == null) {
                    addTo = newBattalions.Find(
                            nb => nb.TypeId == head.type &&
                                  nb.Update(existingBattalions).Size < nb.Capacity);
                    if (addTo == null) {
                        var newB = new NewBattalionInfo(
                                head.type,
                                textures.BattalionType(head.type),
                                costDict[head.type],
                                _model.BattalionTypes[head.type].Capacity);
                        newBattalions.Add(newB);
                        addTo = newB;
                    }
                } else {
                    PlusClickedImpl(addTo);
                }
            }

            for (int typeIdx = 0; typeIdx < newTroopsByType.Count; typeIdx++) {
                BattalionTypeId typeId = (BattalionTypeId)typeIdx;
                var capacity = _model.BattalionTypes[typeId].Capacity;
                var image = textures.BattalionType(typeId);
                while (newTroopsByType[typeId] > 0) {
                    var existingAddTo = existingBattalions.Find(
                            eb => eb.TypeId == typeId && !eb.dismissed &&
                                  eb.Update(existingBattalions).Size < capacity);
                    if (existingAddTo == null) {
                        existingAddTo =
                                existingBattalions.Find(eb => eb.TypeId == typeId && eb.dismissed);
                        if (existingAddTo != null) existingAddTo.dismissed = false;
                    }

                    if (existingAddTo == null) {
                        var newAddTo = newBattalions.Find(
                                nb => nb.TypeId == typeId &&
                                      nb.Update(existingBattalions).Size < capacity);
                        if (newAddTo == null) {
                            newAddTo =
                                    new NewBattalionInfo(typeId, image, costDict[typeId], capacity);
                            newBattalions.Add(newAddTo);
                        }

                        var countToAdd = Math.Min(
                                capacity - newAddTo.Update(existingBattalions).Size,
                                newTroopsByType[typeId]);
                        newAddTo.newBattalion.NewTroops += countToAdd;
                        newTroopsByType[typeId] -= countToAdd;
                    } else {
                        var countToAdd = Math.Min(
                                capacity - existingAddTo.Update(existingBattalions).Size,
                                newTroopsByType[typeId]);
                        if (existingAddTo.changed == null) {
                            existingAddTo.changed = new ChangedBattalion { Id = existingAddTo.Id };
                        }
                        existingAddTo.changed.NewTroops += countToAdd;
                        newTroopsByType[typeId] -= countToAdd;
                    }
                }
            }

            UpdateTable();
        }

        public void UpdateTable() {
            battalionsTable.RowCount = 0;

            maxAllButton.gameObject.SetActive(false);
            mergeButton.gameObject.SetActive(false);

            Dictionary<BattalionTypeId, int> nonFullBattalionCount =
                    new Dictionary<BattalionTypeId, BattalionId> {
                        { BattalionTypeId.LightInfantry, 0 },
                        { BattalionTypeId.HeavyInfantry, 0 },
                        { BattalionTypeId.LightCavalry, 0 },
                        { BattalionTypeId.HeavyCavalry, 0 },
                        { BattalionTypeId.Longbowmen, 0 }
                    };

            maxAllButton.gameObject.SetActive(false);
            foreach (var eb in existingBattalions) {
                if (eb.dismissed) continue;

                var newRow = battalionsTable.AddRowWithComponent<OrganizeTroopsTableRow>();
                eb.Update(existingBattalions);
                newRow.BattalionInfo = eb;

                newRow.PlusButtonClickedCallback = () => PlusClicked(eb);
                newRow.MinusButtonClickedCallback = () => MinusClicked(eb);
                newRow.MaxButtonClickedCallback = () => MaxClicked(eb);
                newRow.DismissButtonClickedCallback = () => DismissClicked(eb);

                bool canAugment =
                        TypeIsAllowed(eb.TypeId) ||
                        extraTroops.Where(tfb => tfb.type == eb.TypeId).Sum(tfb => tfb.count) > 0;
                newRow.CanAugment = canAugment;

                if (eb.Count < eb.Capacity) {
                    // Enable MaxAll button if we could add new troops to this battalion type
                    if (canAugment) { maxAllButton.gameObject.SetActive(true); }

                    // Enable Merge button if there are other non-full battalions of this type
                    nonFullBattalionCount[eb.TypeId]++;
                    if (nonFullBattalionCount[eb.TypeId] > 1) {
                        mergeButton.gameObject.SetActive(true);
                    }
                }
            }

            foreach (var newB in newBattalions) {
                var newRow = battalionsTable.AddRowWithComponent<OrganizeTroopsTableRow>();

                newB.Update(existingBattalions);
                newRow.BattalionInfo = newB;

                newRow.PlusButtonClickedCallback = () => PlusClicked(newB);
                newRow.MinusButtonClickedCallback = () => MinusClicked(newB);
                newRow.MaxButtonClickedCallback = () => MaxClicked(newB);
                newRow.DismissButtonClickedCallback = () => DismissClicked(newB);

                if (newB.Count < newB.Capacity) {
                    maxAllButton.gameObject.SetActive(true);
                    nonFullBattalionCount[newB.TypeId]++;
                    if (nonFullBattalionCount[newB.TypeId] > 1) {
                        mergeButton.gameObject.SetActive(true);
                    }
                }
            }

            lightInfantryTable.RowCount = 0;
            heavyInfantryTable.RowCount = 0;
            dragoonsTable.RowCount = 0;
            knightsTable.RowCount = 0;
            longbowmenTable.RowCount = 0;
            foreach (var extra in extraTroops) {
                EventBasedTable table = null;
                switch (extra.type) {
                    case BattalionTypeId.LightInfantry: table = lightInfantryTable; break;

                    case BattalionTypeId.HeavyInfantry: table = heavyInfantryTable; break;

                    case BattalionTypeId.LightCavalry: table = dragoonsTable; break;

                    case BattalionTypeId.HeavyCavalry: table = knightsTable; break;

                    case BattalionTypeId.Longbowmen: table = longbowmenTable; break;
                }

                var row = table.AddRowWithComponent<ExtraTroopsRowController>();
                row.Size = extra.count;
                row.Armament = extra.armament;
                row.Training = extra.training;
            }

            var cost = Cost();
            bool sufficient = cost <= availableGold;
            var costText =
                    $"Total cost: {cost}\n({(sufficient ? "" : "only ")}{availableGold} available)";
            costLabel.text = costText;

            troopCountLabel.text = TroopCount().ToString();

            if (!sufficient) { _disabledReason = "Not enough gold"; }

            // Also check that something has changed
            bool somethingChanged =
                    (newBattalions.Exists(b => b.Update(existingBattalions).Size > 0) ||
                     existingBattalions.Exists(eb => eb.changed != null || eb.troopsRemoved > 0));
            if (!somethingChanged) { _disabledReason = "No battalions have changed"; }

            _enableCommit = sufficient && somethingChanged;
            resetAllButton.gameObject.SetActive(somethingChanged);

            MaybeActivateRows();
        }

        public override AvailableCommand.SealedValueOneofCase CommandType =>
                AvailableCommand.SealedValueOneofCase.OrganizeTroopsCommand;
        public override string HeaderString => "Organize Troops";
        public override string CommitButtonString => "Commit Battalions";

        public override bool WarnOnCommitButton {
            get {
                if (extraTroops.Exists(et => et.count > 0)) return true;
                return false;
            }
        }
        public override string CommitWarningText =>
                "This would dismiss some troops, and you won't recover any gold";

        public void NewLightInfantryButtonClicked() =>
                NewBattalionClicked(BattalionTypeId.LightInfantry);
        public void NewHeavyInfantryButtonClicked() =>
                NewBattalionClicked(BattalionTypeId.HeavyInfantry);
        public void NewDragoonsButtonClicked() => NewBattalionClicked(BattalionTypeId.LightCavalry);
        public void NewKnightsButtonClicked() => NewBattalionClicked(BattalionTypeId.HeavyCavalry);
        public void NewLongbowmenButtonClicked() => NewBattalionClicked(BattalionTypeId.Longbowmen);

        public void NewBattalionClicked(BattalionTypeId typeId) {
            var newBattalion = new NewBattalionInfo(
                    typeId,
                    textures.BattalionType(typeId),
                    costDict[typeId],
                    _model.BattalionTypes[typeId].Capacity);

            bool keepGoing = true;
            while (keepGoing &&
                   newBattalion.Update(existingBattalions).Size < newBattalion.Capacity) {
                keepGoing = PlusClickedImpl(newBattalion);
            }

            // I think if we're here we're guaranteed there were some troops available...
            if (newBattalion.Count <= 0) {
                throw new ArgumentException(
                        "We tried to create a new battalion but there are no troops");
            }
            newBattalions.Add(newBattalion);

            UpdateTable();
        }

        private bool _enableCommit;
        public override bool EnableCommitButton => _enableCommit;
        private string _disabledReason;
        public override string DisabledCommitButtonReason => _disabledReason;

        public override SelectedCommand Command {
            get {
                var otc = new OrganizeTroopsSelectedCommand();

                foreach (var eb in existingBattalions) {
                    var changed = eb.changed;
                    var extras = extraTroops.Find(x => x.fromId == eb.Id);

                    if (extras != null) {
                        if (changed == null) { changed = new ChangedBattalion { Id = eb.Id }; }
                        changed.DismissedTroops = extras.count;
                    }
                    if (changed != null &&
                        (changed.NewTroops > 0 || changed.TroopsFromOtherBattalion.Count > 0 ||
                         changed.DismissedTroops > 0)) {
                        otc.ChangedBattalions.Add(changed);
                    }
                }
                otc.NewBattalions.Add(
                        newBattalions
                                .Where(b => b.newBattalion.NewTroops > 0 ||
                                            b.newBattalion.TroopsFromOtherBattalion.Count > 0)
                                .Select(b => b.newBattalion));

                var cmd = new SelectedCommand { OrganizeTroopsCommand = otc };
                return cmd;
            }
        }

        private double? Cost(ExistingBattalionInfo eb) {
            if (eb.changed != null) return costDict[eb.TypeId] * eb.changed.NewTroops;
            return 0;
        }

        private double? Cost(NewBattalionInfo newBattalion) {
            if (newBattalion.newBattalion.NewTroops > newBattalion.Capacity) return null;
            if (newBattalion.newBattalion.NewTroops < 0) return null;

            return costDict[newBattalion.TypeId] * newBattalion.newBattalion.NewTroops;
        }

        private int? Cost() {
            int totalCost = 0;

            foreach (var eb in existingBattalions) {
                var cost = Cost(eb);
                if (!cost.HasValue) return null;

                totalCost += (int)Math.Ceiling(cost.Value);
            }

            foreach (var newBattalion in newBattalions) {
                var cost = Cost(newBattalion);
                if (!cost.HasValue) return null;

                totalCost += (int)Math.Ceiling(cost.Value);
            }

            return totalCost;
        }

        private int? TroopCount() {
            return existingBattalions.Select(eb => eb.Count).Sum() +
                   newBattalions.Select(nb => nb.Count).Sum();
        }
    }
}