using System;
using System.Collections.Generic;

namespace Jovian.EncounterSystem {
    /// <summary>
    /// Gated quest progression. Encounter E is gated iff some QuestKind encounter P has
    /// <c>P.nextEncounter == E</c> and P hasn't been resolved. Predecessor map is built once at
    /// construction; rolling and advancement are O(1).
    /// </summary>
    public class QuestProgress {
        private readonly HashSet<string> resolvedIds;
        private readonly Dictionary<string, IEncounter> predecessorOf;

        public event Action<IEncounter> QuestStarted;
        public event Action<IEncounter, IEncounter> QuestAdvanced;
        public event Action<IEncounter> QuestCompleted;

        public IReadOnlyCollection<string> ResolvedIds => resolvedIds;

        public QuestProgress(EncountersCollection encountersCollection) {
            var capacity = EstimateEncounterCount(encountersCollection);
            resolvedIds = new HashSet<string>(capacity);
            predecessorOf = new Dictionary<string, IEncounter>(capacity);
            IndexQuests(encountersCollection);
        }

        private static int EstimateEncounterCount(EncountersCollection collection) {
            if(collection?.encounterTables == null) {
                return 0;
            }
            var total = 0;
            for(int i = 0; i < collection.encounterTables.Length; i++) {
                var table = collection.encounterTables[i];
                if(table?.encounters != null) {
                    total += table.encounters.Count;
                }
            }
            return total;
        }

        public bool IsGated(IEncounter encounter) {
            var id = encounter?.EncounterDefinition?.internalId;
            if(string.IsNullOrEmpty(id)) {
                return false;
            }

            if(!predecessorOf.TryGetValue(id, out var predecessor)) {
                return false;
            }

            return !resolvedIds.Contains(predecessor.EncounterDefinition.internalId);
        }

        public void OnEncounterTriggered(IEncounter encounter) {
            if(encounter?.EncounterDefinition.Kind is not QuestKind questKind) {
                return;
            }

            var id = encounter.EncounterDefinition?.internalId;
            if(string.IsNullOrEmpty(id) || !resolvedIds.Add(id)) {
                return;
            }

            var next = questKind.nextEncounter.Resolve();
            var hasPredecessor = predecessorOf.TryGetValue(id, out var predecessor);

            if(!hasPredecessor) {
                QuestStarted?.Invoke(encounter);
            }
            else {
                QuestAdvanced?.Invoke(predecessor, encounter);
            }

            if(next == null) {
                QuestCompleted?.Invoke(encounter);
            }
        }

        public void LoadResolvedIds(IEnumerable<string> ids) {
            resolvedIds.Clear();
            if(ids == null) {
                return;
            }

            foreach(var id in ids) {
                if(!string.IsNullOrEmpty(id)) {
                    resolvedIds.Add(id);
                }
            }
        }

        private void IndexQuests(EncountersCollection collection) {
            if(collection?.encounterTables == null) {
                return;
            }

            for(int t = 0; t < collection.encounterTables.Length; t++) {
                var table = collection.encounterTables[t];
                if(table?.encounters == null) {
                    continue;
                }

                for(int i = 0; i < table.encounters.Count; i++) {
                    var encounter = table.encounters[i];
                    if(encounter?.EncounterDefinition?.Kind is not QuestKind questKind) {
                        continue;
                    }

                    var next = questKind.nextEncounter.Resolve();
                    if(next == null) {
                        continue;
                    }

                    var nextId = next.EncounterDefinition?.internalId;
                    if(!string.IsNullOrEmpty(nextId)) {
                        predecessorOf[nextId] = encounter;
                    }
                }
            }
        }
    }
}
