using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TechnologyAssembler.Core.Assets;
using TechnologyAssembler.Core.IO;
using TechnologyAssembler.Core.Language.Providers;

namespace HashCalculator.GUI
{
    [SuppressMessage("Design", "CA1001:具有可释放字段的类型应该是可释放的", Justification = "<挂起>")]
    internal class Controller
    {
        public const string SelectedFileSystemRootPath = "/selected";
        public const string ManifestExtension = ".manifest";

        private ViewModel ViewModel { get; }
        private BigFileSystemProvider? _currentBig = null;
        private readonly HashSet<AssetEntry> _loadedAssets = new HashSet<AssetEntry>();
        private Tuple<CancellationTokenSource, Task>? _lastXmlTask = null;

        public Controller(ViewModel viewModel)
        {
            ViewModel = viewModel;
        }

        public async Task OnMainInputDecided(InputEntry selected)
        {
            try
            {
                _currentBig?.Dispose();
                _currentBig = null;
                ClearEntries();
                ViewModel.ClearTraceText.Execute(null);
                ViewModel.BigEntryInput.AllManifests = null;
                ViewModel.IsXml = false;

                switch (selected.Type)
                {
                    case InputEntryType.BinaryFile:
                        ViewModel.StatusText = "请留意一下弹出的窗口(";
                        CopyableBox.ShowDialog("SAGE FastHash 计算器", token => CalculateBinaryHash(selected.Value, token));
                        ViewModel.StatusText = string.Empty;
                        return;
                    case InputEntryType.BigFile:
                        await LoadBig(selected.Value);
                        return;
                    case InputEntryType.ManifestFile:
                        await LoadManifestFromFile(selected.Value);
                        return;
                    case InputEntryType.XmlFile:
                    case InputEntryType.XsdFile:
                        await LoadXml(selected);
                        return;
                    default:
                        throw new NotSupportedException();
                }
            }
            catch (Exception error)
            {
                ViewModel.StatusText = "失败…";
                CopyableBox.ShowDialog("SAGE FastHash 计算器 - 失败!", _ =>
                {
                    return Task.FromResult($"在尝试加载 {selected.Type} `{selected.Value}` 时发生错误:\r\n{error}");
                });
                ViewModel.StatusText = string.Empty;
            }
        }

        public Task<string?> RequestOpenFile(string title, params (string Extension, string? Description)[] filters)
        {
            return ViewModel.RequestOpenFile(title, filters);
        }

        private static Task<string> CalculateBinaryHash(string filePath, CancellationToken cancel)
        {
            return Task.Run(() =>
            {
                try
                {
                    using var file = File.OpenRead(filePath);
                    var array = new byte[file.Length];
                    cancel.ThrowIfCancellationRequested();
                    var totalRead = 0;
                    const int ReadSize = 32 * 1024 * 1024;
                    while (totalRead < array.Length)
                    {
                        totalRead += file.Read(array, totalRead, Math.Min(array.Length - totalRead, ReadSize));
                        cancel.ThrowIfCancellationRequested();
                    }

                    var hashes = new Func<byte[], uint>[]
                    {
                        SageHash.CalculateBinaryHash,
                        SageHash.CalculateLauncherBinaryHash
                    }.AsParallel().Select(fn => fn(array)).ToArray();

                    return $"使用 SAGE FastHash 计算出的哈希值:{hashes[0]:x8} (十进制 {hashes[0]})\r\n"
                        + "注意这是以大小写敏感模式计算出的哈希值,与素材ID的哈希值(转换成小写后计算的哈希)一般是不一样的\r\n\r\n"
                        + "使用 SAGE Launcher FastHash(比如说 RA3.exe 用来校验更新补丁文件的哈希)计算出的哈希值:"
                        + $"{hashes[1]:x8} (十进制 {hashes[1]})";
                }
                catch (Exception exception)
                {
                    return exception.ToString();
                }
            });
        }

        private async Task LoadBig(string path)
        {
            ViewModel.StatusText = "正在尝试加载 big 文件…";
            var bigViewModel = ViewModel.BigEntryInput;
            bigViewModel.Text = string.Empty;
            bigViewModel.AllManifests = await Task.Run(() =>
            {
                using var big = new BigFile(path);
                _currentBig = new BigFileSystemProvider(SelectedFileSystemRootPath, big, null);
                return FindManifests();
            }).ConfigureAwait(true);

            var first = bigViewModel.AllManifests.FirstOrDefault();
            if (first != null)
            {
                var name = VirtualFileSystem.GetFileNameWithoutExtension(first.Value).ToLowerInvariant();
                if (new[] { "mod", "mapmetadata_mod", "static", "global" }.Any(name.StartsWith))
                {
                    bigViewModel.SelectedItem = first;
                    await bigViewModel.SelectCommand.ExecuteTask(ViewModel).ConfigureAwait(true);
                    ViewModel.StatusText = "big 已加载完毕,并自动选了一个看起来比较合适的 manifest 文件(";
                }
            }
            else
            {
                ViewModel.StatusText = "big 文件已加载完毕,请选择 big 文件里的 manifest 文件";
            }
        }

        private async Task LoadManifestFromFile(string path)
        {
            await ExceptionWrapepr(async () =>
            {
                try
                {
                    using var provider = new FileSystemProvider(SelectedFileSystemRootPath, null);
                    path = VirtualFileSystem.Combine(SelectedFileSystemRootPath, path);
                    await ProcessManifest(path).ConfigureAwait(true);
                }
                catch
                {
                    ViewModel.StatusText = "失败…";
                    throw;
                }

            }, "SAGE FastHash 计算器 - Manifest 加载失败…", "Manifest 文件加载失败,也许,你选择的 manifest 文件并不是红警3使用的那种……\r\n").ConfigureAwait(true);
        }

        public async Task ProcessManifest(string path)
        {
            ClearEntries();
            ViewModel.StatusText = "正在读取 manifest 文件…";
            var entries = await Task.Run(() =>
            {
                if (path.EndsWith(ManifestExtension, StringComparison.OrdinalIgnoreCase))
                {
                    path = path.Remove(path.Length - ManifestExtension.Length);
                }
                using var manifest = Manifest.Load(path, true);
                var assets = from asset in manifest.Assets.Values
                             select new AssetEntry(asset);
                return assets.ToArray();
            }).ConfigureAwait(true);
            AddEntries(entries);
            ViewModel.StatusText = $"Manifest文件读取完毕,加载了{_loadedAssets.Count}个素材";
        }

        public async Task CancelLoadingXml()
        {
            if (_lastXmlTask is null)
            {
                return;
            }

            var (tokenSource, task) = _lastXmlTask;
            tokenSource.Cancel();
            try
            {
                await task;
            }
            catch (OperationCanceledException)
            {
                ViewModel.StatusText = "已经取消了加载";
            }
        }

        private async Task LoadXml(InputEntry entry)
        {
            await CancelLoadingXml();
            var path = entry.Text;
            var type = entry.Type;
            using var tokenSource = new CancellationTokenSource();
            var modXml = type switch
            {
                InputEntryType.XmlFile => LoadModXml(path, tokenSource.Token),
                InputEntryType.XsdFile => LoadModXsd(path, tokenSource.Token),
                _ => throw new NotSupportedException(type.ToString())
            };
            ViewModel.IsXml = true;
            ViewModel.IsXsd = type == InputEntryType.XsdFile;
            var task = LoadXmlInternal(await modXml, tokenSource.Token);
            _lastXmlTask = (tokenSource, task).ToTuple();

            await ExceptionWrapepr(async () =>
            {
                try
                {
                    ViewModel.IsLoadingXml = true;
                    await task.ConfigureAwait(true);
                }
                catch (OperationCanceledException)
                {
                    ViewModel.StatusText = "已经取消了加载";
                }
                catch
                {
                    ViewModel.StatusText = "失败…";
                    throw;
                }
                finally
                {
                    ViewModel.IsLoadingXml = false;
                    _lastXmlTask = null;
                }

            }, "SAGE FastHash 计算器 - XML 加载失败…", "XML 文件加载失败,也许,你选择的 XML 文件并不是红警3使用的那种……\r\n").ConfigureAwait(true);
        }

        private async Task<IModXml> LoadModXml(string path, CancellationToken cancelToken)
        {
            if (ModXml.LocateSdkFromRegistry() is not { } sdkRoot)
            {
                var studio = await RequestOpenFile("选择 Mod Studio", ("EALAModStudio.exe", null));
                sdkRoot = Path.GetDirectoryName(studio);
            }
            var modXml = new ModXml(path, sdkRoot, cancelToken);
            try
            {
                await FindCsf(modXml.BaseDirectory, cancelToken);
            }
            catch (Exception exception)
            {
                TracerListener.WriteLine($"[ModXml] Failed to automatically find and load CSF: {exception}");
            }
            return modXml;
        }

        private Task<IModXml> LoadModXsd(string path, CancellationToken token)
        {
            return Task.FromResult<IModXml>(new ModXsd(path, token));
        }

        private async Task LoadXmlInternal(IModXml modXml, CancellationToken token)
        {
            token.ThrowIfCancellationRequested();

            var start = DateTimeOffset.UtcNow;
            var lastMoment = start;
            var previousCount = 0;
            var preivousSpeed = 0.0;
            await foreach (var entry in modXml.ProcessDocument())
            {
                var now = DateTimeOffset.Now;
                var total = modXml.TotalFilesProcessed;
                var time = now - start;
                var speed = total / time.TotalSeconds;

                var dt = (now - lastMoment).TotalSeconds;
                double instantSpeed;
                if (dt <= 0.1)
                {
                    instantSpeed = preivousSpeed;
                }
                else
                {
                    var dx = total - previousCount;
                    instantSpeed = dx / dt;
                    lastMoment = now;
                    previousCount = total;
                    preivousSpeed = instantSpeed;
                }
                ViewModel.StatusText = $"已读取{total}个文件,平均读取速度{speed,8:N2}文件/秒,瞬时速度{instantSpeed,8:N2}文件/秒";
                AddEntry(entry);
            }
            UpdateEntries();
            var totalTime = DateTimeOffset.UtcNow - start;
            var fileSpeed = modXml.TotalFilesProcessed / totalTime.TotalSeconds;
            var assetSpeed = modXml.TotalAssets / totalTime.TotalSeconds;
            var statistics = $"耗时{totalTime:mm\\:ss},共读取{modXml.TotalFilesProcessed}个文件({fileSpeed,1:N2}文件/秒;{assetSpeed,1:N2}素材/秒)";
            if (token.IsCancellationRequested)
            {
                ViewModel.StatusText = $"ModXml 的读取已被取消,共{statistics}";
                return;
            }

            ViewModel.StatusText = $"ModXml 读取完毕~ {statistics}";
            if (_loadedAssets.Count != modXml.TotalAssets)
            {
                var message = $"_loadedAssets.Count ({_loadedAssets.Count}) != modXml.TotalAssets ({modXml.TotalAssets})";
                throw new InvalidDataException(message);
            }
        }

        public static async Task LoadCsf(string filePath)
        {
            await ExceptionWrapepr(async () =>
            {
                switch (Path.GetExtension(filePath).ToUpperInvariant())
                {
                    case ".BIG":
                        await LoadCsfFromBig(filePath).ConfigureAwait(true);
                        return;
                    case ".CSF":
                        await Task.Run(() =>
                        {
                            using var stream = File.OpenRead(filePath);
                            Translator.Provider = new CsfTranslationProvider(stream);
                        }).ConfigureAwait(true);
                        return;
                }
            }, "SAGE FastHash 计算器 - CSF 加载失败…", "CSF 加载失败:").ConfigureAwait(true);
        }

        private static Task LoadCsfFromBig(string bigPath)
        {
            return Task.Run(() =>
            {
                using var big = new BigFile(bigPath);
                var files = big.GetFiles(string.Empty, "*.csf", VirtualSearchOptionType.AllDirectories);
                var file =
                    files.FirstOrDefault(x => x.Equals("gamestrings.csf", StringComparison.OrdinalIgnoreCase))
                    ?? files.FirstOrDefault();
                if (file == null)
                {
                    throw new FileNotFoundException();
                }
                using var stream = big.OpenStream(file);
                Translator.Provider = new CsfTranslationProvider(stream);
            });
        }

        private static Task FindCsf(DirectoryInfo baseDirectory, CancellationToken cancelToken)
        {
            return Task.Run(async () =>
            {
                if (!baseDirectory.Exists)
                {
                    throw new NotSupportedException();
                }

                var searchFrom = new List<DirectoryInfo> { baseDirectory };
                if (baseDirectory.Parent is { } parent)
                {
                    searchFrom.AddRange(parent.GetDirectories($"{baseDirectory.Name[0]}*"));
                    searchFrom.Add(parent);
                }
                var searchDirectories = new[] { "Additional", "Misc" }
                    .SelectMany(n => searchFrom.SelectMany(x => x.GetDirectories(n)))
                    .Concat(searchFrom);
                var csfs = from directories in searchDirectories
                           from data in directories.GetDirectories("Data")
                           from csf in data.GetFiles("*.csf")
                           select csf;
                csfs = csfs.AsParallel().WithCancellation(cancelToken);
                var result =
                    csfs.FirstOrDefault(x => x.Name.Equals("gamestrings.csf", StringComparison.OrdinalIgnoreCase))
                    ?? csfs.FirstOrDefault();
                if (result == null)
                {
                    throw new FileNotFoundException();
                }

                using var memoryStream = new MemoryStream();
                using (var fileStream = result.OpenRead())
                {
                    await fileStream.CopyToAsync(memoryStream, cancelToken);
                    memoryStream.Position = 0;
                }
                Translator.Provider = new CsfTranslationProvider(memoryStream);
                TracerListener.WriteLine($"[ModXml]: Automatically loaded csf from `{result.FullName}`");
            });
        }

        public void UpdateEntries()
        {
            ViewModel.FilterCollection(_loadedAssets);
        }

        private void ClearEntries()
        {
            _loadedAssets.Clear();
            UpdateEntries();
        }

        private void AddEntry(AssetEntry entry)
        {
            _loadedAssets.Add(entry);

            ViewModel.AddNewItems(new[] { entry });
        }

        private void AddEntries(IEnumerable<AssetEntry> entries)
        {
            _loadedAssets.UnionWith(entries);
            UpdateEntries();
        }

        public static IEnumerable<InputEntry> FindManifests()
        {
            var manifests = VirtualFileSystem.ListFiles(SelectedFileSystemRootPath, "*.manifest", VirtualSearchOptionType.AllDirectories);
            var modManifests = from manifest in manifests
                               where FileNameStartsWith(manifest, "mod")
                               select manifest;
            var globalDataManifests = from manifest in manifests
                                      where FileNameStartsWith(manifest, "mapmetadata")
                                      select manifest;
            var firstManifests = modManifests.Concat(globalDataManifests);
            var otherManifests = from manifest in manifests
                                 where !firstManifests.Contains(manifest)
                                 select manifest;

            return from manifest in firstManifests.Concat(otherManifests)
                   select new InputEntry(InputEntryType.ManifestFile, manifest, manifest);
        }

        private static bool FileNameStartsWith(string path, string what)
        {
            return VirtualFileSystem.GetFileName(path).StartsWith(what, StringComparison.OrdinalIgnoreCase);
        }

        private static async Task ExceptionWrapepr(Func<Task> action, string errorTitle, string preErrorMessage)
        {
            try
            {
                await action().ConfigureAwait(true);
            }
            catch (Exception exception)
            {
                CopyableBox.ShowDialog(errorTitle, _ =>
                {
                    return Task.FromResult(preErrorMessage + exception);
                });
            }
        }
    }
}