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

namespace Jovian.InGameLogging {
    public sealed class GameLogStore : IGameLogStore {
        private readonly LogEntry[] buffer;
        private readonly HashSet<LogChannel> disabledChannels = new();
        private int head;
        private int count;

        public int Count => count;
        public int Capacity => buffer.Length;

        public event Action<LogEntry> OnEntryAdded;
        public event Action OnCleared;

        public GameLogStore(int capacity = 500) {
            buffer = new LogEntry[capacity];
            head = 0;
            count = 0;
        }

        public void Add(LogChannel channel, string message) {
            if(disabledChannels.Contains(channel)) {
                return;
            }
            var entry = new LogEntry(message, channel, Time.time);
            buffer[head] = entry;
            head = (head + 1) % buffer.Length;
            if(count < buffer.Length) {
                count++;
            }
            OnEntryAdded?.Invoke(entry);
        }

        public void EnableChannel(LogChannel channel) {
            disabledChannels.Remove(channel);
        }

        public void DisableChannel(LogChannel channel) {
            disabledChannels.Add(channel);
        }

        public bool IsChannelEnabled(LogChannel channel) {
            return !disabledChannels.Contains(channel);
        }

        public void Clear() {
            head = 0;
            count = 0;
            OnCleared?.Invoke();
        }

        public void Clear(LogChannel channel) {
            var kept = new List<LogEntry>(count);
            var entries = GetEntries();
            for(var i = 0; i < entries.Length; i++) {
                if(entries[i].channel != channel) {
                    kept.Add(entries[i]);
                }
            }

            head = 0;
            count = 0;
            for(var i = 0; i < kept.Count; i++) {
                buffer[i] = kept[i];
                count++;
            }
            head = count % buffer.Length;
            OnCleared?.Invoke();
        }

        public ReadOnlySpan<LogEntry> GetEntries() {
            if(count < buffer.Length) {
                return new ReadOnlySpan<LogEntry>(buffer, 0, count);
            }
            var result = new LogEntry[count];
            var start = head;
            for(var i = 0; i < count; i++) {
                result[i] = buffer[(start + i) % buffer.Length];
            }
            return result;
        }

        public int GetEntries(LogChannel channel, List<LogEntry> results) {
            results.Clear();
            var entries = GetEntries();
            for(var i = 0; i < entries.Length; i++) {
                if(entries[i].channel == channel) {
                    results.Add(entries[i]);
                }
            }
            return results.Count;
        }

        public GameLogSaveData GetSaveData() {
            var entries = GetEntries();
            var list = new List<LogEntry>(entries.Length);
            foreach(var t in entries) {
                list.Add(t);
            }
            return new GameLogSaveData(list);
        }

        public void RestoreFromSaveData(GameLogSaveData data) {
            head = 0;
            count = 0;
            if(data?.entries == null) {
                OnCleared?.Invoke();
                return;
            }
            var startIndex = Math.Max(0, data.entries.Count - buffer.Length);
            for(var i = startIndex; i < data.entries.Count; i++) {
                buffer[count] = data.entries[i];
                count++;
            }
            head = count % buffer.Length;
            OnCleared?.Invoke();
        }
    }
}
