using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Win32;
using OpenSage.FileFormats.Big;

namespace AnotherReplayReader
{
    internal sealed class BigMinimapCache
    {
        private sealed class CacheAdapter
        {
            public List<string> Bigs { get; set; }
            public Dictionary<string, string> MapsToBigs { get; set; }

            public CacheAdapter()
            {
                Bigs = new List<string>();
                MapsToBigs = new Dictionary<string, string>();
            }
        }

        //private Cache _cache;
        private volatile IReadOnlyDictionary<string, string> _mapsToBigs = null;

        public BigMinimapCache(Cache cache, string ra3Directory)
        {
            //_cache = cache;

            Task.Run(() =>
            {
                try
                {
                    if (!Directory.Exists(ra3Directory))
                    {
                        Debug.Instance.DebugMessage += $"Will not initialize BigMinimapCache because RA3Directory {ra3Directory} does not exist.\r\n";
                        return;
                    }

                    var bigSet = ParseSkudefs(Directory.EnumerateFiles(ra3Directory, "*.SkuDef"));

                    //var cached = _cache.GetOrDefault("bigsCache", new CacheAdapter());
                    var mapsToBigs = new Dictionary<string, string>();

                    foreach (var bigPath in bigSet/*.Where(x => !cached.Bigs.Contains(x))*/)
                    {
                        if (!File.Exists(bigPath))
                        {
                            Debug.Instance.DebugMessage += $"Big {bigPath} does not exist.\r\n";
                            continue;
                        }

                        Debug.Instance.DebugMessage += $"Trying to add Big {bigPath} to big minimap cache...\r\n";
                        try
                        {
                            using (var big = new BigArchive(bigPath))
                            {
                                foreach (var entry in big.Entries)
                                {
                                    if (entry.FullName.EndsWith("_art.tga", StringComparison.OrdinalIgnoreCase))
                                    {
                                        mapsToBigs[entry.FullName] = bigPath;
                                    }

                                }
                            }
                        }
                        catch(Exception exception)
                        {
                            Debug.Instance.DebugMessage += $"Exception when reading big:\r\n {exception}\r\n";
                        }
                    }
                    //cached.Bigs = bigSet.ToList();

                    //_cache.Set("bigsCache", cached);
                    _mapsToBigs = mapsToBigs; //cached.MapsToBigs;
                }
                catch (Exception exception)
                {
                    Debug.Instance.DebugMessage += $"Exception during initialization of BigMinimapCache: \r\n{exception}\r\n";
                }
            });
        }

        public static HashSet<string> ParseSkudefs(IEnumerable<string> skudefs)
        {
            var skudefSet = new HashSet<string>(skudefs.Select(x => x.ToLowerInvariant()));
            var unreadSkudefs = new HashSet<string>();
            var bigSet = new HashSet<string>();

            void ReadSkudefLine(string baseDirectory, string line, string expectedCommand, Action<string> action)
            {
                try
                {
                    char[] separators = { ' ', '\t' };
                    line = line.ToLowerInvariant();
                    var splitted = line.Split(separators, 2, StringSplitOptions.RemoveEmptyEntries);
                    if (splitted[0].Equals(expectedCommand))
                    {
                        var path = splitted[1];
                        if (!Path.IsPathRooted(path))
                        {
                            path = Path.Combine(baseDirectory, path);
                        }
                        action(path);
                    }
                }
                catch (Exception exception)
                {
                    Debug.Instance.DebugMessage += $"Exception when parsing skudef line:\r\n {exception}\r\n";
                }
            }

            void ReadSkudef(string fileName, Action<string, string> onBaseDirectoryAndLine)
            {
                try
                {
                    var baseDirectory = Path.GetDirectoryName(fileName).ToLowerInvariant();
                    foreach (var line in File.ReadAllLines(fileName))
                    {
                        onBaseDirectoryAndLine(baseDirectory, line);
                    }
                }
                catch (Exception exception)
                {
                    Debug.Instance.DebugMessage += $"Exception when parsing skudef file:\r\n {exception}\r\n";
                }
            }

            foreach (var skudef in skudefSet)
            {
                ReadSkudef(skudef, (baseDirectory, line) =>
                {
                    ReadSkudefLine(baseDirectory, line, "add-config", x =>
                    {
                        if (!skudefSet.Contains(x))
                        {
                            unreadSkudefs.Add(x);
                        }
                    });

                    ReadSkudefLine(baseDirectory, line, "add-big", x => bigSet.Add(x));
                });
            }

            foreach (var skudef in unreadSkudefs)
            {
                ReadSkudef(skudef, (baseDirectory, line) =>
                {
                    ReadSkudefLine(baseDirectory, line, "add-big", x => bigSet.Add(x));
                });
            }

            return bigSet;
        }

        public bool TryGetBigByEntryPath(string path, out BigArchive big)
        {
            big = null;

            if (_mapsToBigs == null)
            {
                return false;
            }

            if (!_mapsToBigs.ContainsKey(path))
            {
                Debug.Instance.DebugMessage += $"Cannot find big entry [{path}].\r\n";
                return false;
            }

            try
            {
                var bigPath = _mapsToBigs[path];
                big = new BigArchive(bigPath);
                if(big.GetEntry(path) == null)
                {
                    //_cache.Remove("bigsCache");
                    big.Dispose();
                    big = null;
                    return false;
                }
            }
            catch (Exception exception)
            {
                Debug.Instance.DebugMessage += $"Exception during query (entryStream) of BigMinimapCache: \r\n{exception}\r\n";
                big = null;
                return false;
            }

            return true;
        }

        public byte[] TryReadBytesFromBig(string path)
        {
            if(_mapsToBigs == null)
            {
                return null;
            }

            if(!_mapsToBigs.ContainsKey(path))
            {
                return null;
            }

            try
            {
                var bigPath = _mapsToBigs[path];
                using (var big = new BigArchive(path))
                {
                    var entry = big.GetEntry(path);
                    using (var stream = entry.Open())
                    using (var reader = new BinaryReader(stream))
                    {
                        return reader.ReadBytes((int)entry.Length);
                    }
                }
            }
            catch (Exception exception)
            {
                Debug.Instance.DebugMessage += $"Exception during query (bytes) of BigMinimapCache: \r\n{exception}\r\n";
                //_cache.Remove("bigsCache");
            }

            return null;
        }
    }
}