using AnotherReplayReader.ReplayFile;
using AnotherReplayReader.Utils;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;

namespace AnotherReplayReader
{
    internal sealed class MainWindowProperties : INotifyPropertyChanged
    {
        public MainWindowProperties()
        {
            const RegistryHive hklm = RegistryHive.LocalMachine;
            RA3Directory = RegistryUtils.RetrieveInRa3(hklm, "Install Dir");
            string? userDataLeafName = RegistryUtils.RetrieveInRa3(hklm, "UserDataLeafName");
            string? replayFolderName = RegistryUtils.RetrieveInRa3(hklm, "ReplayFolderName");
            if (string.IsNullOrWhiteSpace(userDataLeafName))
            {
                userDataLeafName = "Red Alert 3";
            }
            if (string.IsNullOrWhiteSpace(replayFolderName))
            {
                replayFolderName = "Replays";
            }

            RA3ReplayFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), userDataLeafName, replayFolderName);
            ReplayFolderPath = RA3ReplayFolderPath;
            CustomMapsDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), userDataLeafName, "Maps");
            ModsDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), userDataLeafName, "Mods");
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        // This method is called by the Set accessor of each property.  
        // The CallerMemberName attribute that is applied to the optional propertyName  
        // parameter causes the property name of the caller to be substituted as an argument.  
        private void SetAndNotifyPropertyChanged<T>(ref T target, T newValue, [CallerMemberName] string propertyName = "")
        {
            if (!Equals(target, newValue))
            {
                target = newValue;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public string? RA3Directory { get; }
        public string RA3ReplayFolderPath { get; }
        public string RA3Exe => Path.Combine(RA3Directory, "RA3.exe");
        public string CustomMapsDirectory { get; }
        public string ModsDirectory { get; }

        public string ReplayFolderPath
        {
            get => _replayFolderPath;
            set => SetAndNotifyPropertyChanged(ref _replayFolderPath, value);
        }

        public string? ReplayDetails
        {
            get => _replayDetails;
            set => SetAndNotifyPropertyChanged(ref _replayDetails, value);
        }

        public bool ReplaySelected => CurrentReplay != null;

        public bool ReplayPlayable => CurrentReplay?.HasFooter == true && CurrentReplay?.HasCommentator == true && (RA3Directory != null) && File.Exists(RA3Exe);

        public bool ReplayDamaged => CurrentReplay?.HasFooter == false;

        public Replay? CurrentReplay
        {
            get => _currentReplay;
            set
            {
                SetAndNotifyPropertyChanged(ref _currentReplay, value);
                ReplayDetails = "";
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ReplayPlayable)));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ReplaySelected)));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ReplayDamaged)));
            }
        }

        private string _replayFolderPath = null!;
        private string? _replayDetails;
        private Replay? _currentReplay;
    }

    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly TaskQueue _taskQueue;
        private readonly MainWindowProperties _properties = new();
        private readonly Cache _cache = new();
        private readonly BigMinimapCache _minimapCache;
        private readonly MinimapReader _minimapReader;
        private readonly CancelManager _cancelLoadReplays = new();
        private readonly CancelManager _cancelFilterReplays = new();
        private readonly CancelManager _cancelDisplayReplays = new();
        private ReplayPinyinList _replayList;
        private ImmutableArray<string> _filterStrings = ImmutableArray<string>.Empty;
        private ImmutableArray<Replay> _filteredReplays = ImmutableArray<Replay>.Empty;

        public MainWindow()
        {
            _taskQueue = new(Dispatcher);
            _minimapCache = new BigMinimapCache(_properties.RA3Directory);
            _minimapReader = new MinimapReader(_minimapCache, _properties.CustomMapsDirectory, _properties.ModsDirectory);
            _replayList = new();

            DataContext = _properties;
            InitializeComponent();
            Closing += (sender, eventArgs) =>
            {
                _cache.Save().Wait();
                Application.Current.Shutdown();
            };
        }

        private async void OnMainWindowLoaded(object sender, EventArgs eventArgs)
        {
            Debug.Initialize();
            await _cache.Initialization;
            ReplayAutoSaver.SpawnAutoSaveReplaysTask(_properties.RA3ReplayFolderPath);
            var token = _cancelLoadReplays.ResetAndGetToken(CancellationToken.None);
            _ = _taskQueue.Enqueue(() => LoadReplays(null, token), token);

            const string permissionKey = "questionAsked";
            if (_cache.GetOrDefault(permissionKey, false) is not true)
            {
                _cache.Set(permissionKey, true);
                var sb = new StringWriter();
                sb.WriteLine("要不要自动检查更新呢?");
                sb.WriteLine("之后也可以在“关于”窗口里,设置自动更新的选项");
                var choice = MessageBox.Show(this, sb.ToString(), App.Name, MessageBoxButton.YesNo);
                _cache.Set(UpdateChecker.CheckForUpdatesKey, choice is MessageBoxResult.Yes);
                await _cache.Save();
            }

            _ = UpdateChecker.CheckForUpdates(_cache).ContinueWith(t => Dispatcher.InvokeAsync(() =>
            {
                var updateData = t.Result;
                if (updateData.IsNewVersion())
                {
                    var about = new About(_cache, updateData)
                    {
                        Owner = this
                    };
                    about.ShowDialog();
                }
            }));
        }

        private async Task LoadReplays(string? nextSelected, CancellationToken cancelToken)
        {
            if (!IsLoaded)
            {
                return;
            }
            cancelToken.ThrowIfCancellationRequested();
            var filterToken = _cancelFilterReplays.ResetAndGetToken(cancelToken);
            _cancelDisplayReplays.Reset(_cancelFilterReplays.Token);

            _properties.CurrentReplay = null;
            _image.Source = null;
            if (_dataGrid.Items.Count > 0)
            {
                _dataGrid.ItemsSource = Array.Empty<Replay>();
                _dataGrid.Items.Refresh();
            }

            cancelToken.ThrowIfCancellationRequested();
            _replayList = new();

            var path = _properties.ReplayFolderPath;
            if (!Directory.Exists(path))
            {
                await FilterReplays("这个文件夹并不存在。", nextSelected, filterToken);
                return;
            }

            var result = await Task.Run(() =>
            {
                var list = new List<Replay>();
                var clock = new Stopwatch();
                clock.Start();
                foreach (var replayPath in Directory.EnumerateFiles(path, "*.RA3Replay"))
                {
                    cancelToken.ThrowIfCancellationRequested();
                    try
                    {
                        var replay = new Replay(replayPath);
                        list.Add(replay);
                    }
                    catch (Exception exception)
                    {
                        Debug.Instance.DebugMessage += $"Uncaught exception when loading replay list: \r\n{exception}\r\n";
                        continue;
                    }
                    if (clock.ElapsedMilliseconds > 300)
                    {
                        var text = $"正在加载录像列表,请稍候… 已加载 {list.Count} 个录像";
                        Dispatcher.Invoke(() => _properties.ReplayDetails = text);
                        clock.Restart();
                    }
                }
                return new ReplayPinyinList(list.ToImmutableArray());
            }, cancelToken);
            cancelToken.ThrowIfCancellationRequested();
            _replayList = result;
            await FilterReplays(string.Empty, nextSelected, filterToken);
        }

        private async Task FilterReplays(string message, string? nextSelected, CancellationToken cancelToken)
        {
            if (!IsLoaded)
            {
                return;
            }
            cancelToken.ThrowIfCancellationRequested();
            _cancelDisplayReplays.Reset(cancelToken);
            _filteredReplays = _replayList.Replays;

            _properties.CurrentReplay = null;
            _dataGrid.SelectedItem = null;
            _image.Source = null;

            if (_filterStrings.Any())
            {
                _properties.ReplayDetails = "正在筛选符合条件的录像…";
                if (_dataGrid.Items.Count > 0)
                {
                    _dataGrid.ItemsSource = Array.Empty<Replay>();
                    _dataGrid.Items.Refresh();
                }

                var (pinyins, list) = (_filterStrings, _replayList.Pinyins);
                var result = await Task.Run(() =>
                {
                    var query = from replay in list.AsParallel().WithCancellation(cancelToken)
                                where pinyins.Any(pinyin => replay.MatchPinyin(pinyin))
                                select replay.Replay;
                    return query.ToImmutableArray();
                }, cancelToken);
                cancelToken.ThrowIfCancellationRequested();
                _filteredReplays = result;
            }

            _properties.CurrentReplay = null;
            _dataGrid.SelectedItem = null;
            _dataGrid.ItemsSource = _filteredReplays;
            _dataGrid.Items.Refresh();
            _properties.ReplayDetails = message;

            if (nextSelected is not null)
            {
                for (var i = 0; i < _dataGrid.Items.Count; ++i)
                {

                    if (_dataGrid.Items[i] is Replay replay && replay.PathEquals(nextSelected))
                    {
                        _dataGrid.SelectedIndex = i;
                        break;
                    }
                }
            }
        }

        private async Task DisplayReplayDetail(Replay replay, string replayDetails, CancellationToken cancelToken)
        {
            if (!IsLoaded)
            {
                return;
            }
            cancelToken.ThrowIfCancellationRequested();
            _properties.CurrentReplay = null;
            _image.Source = null;
            _properties.ReplayDetails = replayDetails;

            // 开始获取小地图
            var mapPath = replay.MapPath;
            var minimapTask = _minimapReader.TryReadTargaAsync(replay);

            // 解析录像内容
            try
            {
                replay = await Task.Run(() => new Replay(replay.Path, true));
                cancelToken.ThrowIfCancellationRequested();
            }
            catch (Exception e) when (e is not OperationCanceledException)
            {
                Debug.Instance.DebugMessage += $"Uncaught exception when loading replay body: \r\n{e}\r\n";
                return;
            }

            // 假如小地图变了(这……),那么重新加载小地图
            if (replay.MapPath != mapPath)
            {
                minimapTask.Forget();
                minimapTask = _minimapReader.TryReadTargaAsync(replay);
            }
            var newDetails = replay.GetDetails();
            if (_properties.ReplayDetails != newDetails)
            {
                if (_replayList.Replays.FindIndex(r => r.PathEquals(replay)) is int index)
                {
                    _replayList = _replayList.SetItem(index, replay.CloneHeader());
                    var token = _cancelFilterReplays.ResetAndGetToken(_cancelLoadReplays.Token);
                    await FilterReplays(newDetails, replay.Path, token);
                    return;
                }
            }
            _properties.CurrentReplay = replay;
            _properties.ReplayDetails = newDetails;

            try
            {
                var newSource = await minimapTask;
                cancelToken.ThrowIfCancellationRequested();
                _image.Source = newSource;
                /* _image.Width = source.Width;
                   _image.Height = source.Height; */
            }
            catch (Exception e) when (e is not OperationCanceledException)
            {
                Debug.Instance.DebugMessage += $"Uncaught exception when loading minimap: \r\n{e}\r\n";
            }
        }

        private async void OnReplayFolderPathBoxTextChanged(object sender, EventArgs e)
        {
            var token = _cancelLoadReplays.ResetAndGetToken(CancellationToken.None);
            await _taskQueue.Enqueue(() => LoadReplays(null, token), token);
            var text = _replayFolderPathBox.Text;
            const string assemblyMagic = "!DreamSign";
            const string jsonMagic = "!FantasySeal";
            switch (_replayFolderPathBox.Text)
            {
                case "!SpellCard":
                    _replayDetailsBox.Text = $"{assemblyMagic}\r\n";
                    _replayDetailsBox.Text += $"{jsonMagic}\r\n";
                    break;
                case assemblyMagic:
                    break;
                case jsonMagic:
                    UpdateChecker.Sign();
                    break;
            }
        }

        private async void OnReplaySelectionChanged(object sender, EventArgs e)
        {
            if (_dataGrid.SelectedItem is not Replay replay)
            {
                return;
            }

            var token = _cancelDisplayReplays.ResetAndGetToken(_cancelFilterReplays.Token);
            _properties.ReplayDetails = replay.GetDetails();
            await _taskQueue.Enqueue(() => DisplayReplayDetail(replay, _properties.ReplayDetails, token), token);
        }

        private void OnAboutButtonClick(object sender, RoutedEventArgs e)
        {
            var aboutWindow = new About(_cache)
            {
                Owner = this
            };
            aboutWindow.ShowDialog();
        }

        private void OnBrowseButtonClick(object sender, RoutedEventArgs e)
        {
            try
            {
                var openFileDialog = new OpenFileDialog
                {
                    Filter = "红警3录像文件 (*.RA3Replay)|*.RA3Replay|所有文件 (*.*)|*.*",
                    InitialDirectory = _properties.ReplayFolderPath,
                };

                var result = openFileDialog.ShowDialog(this);
                if (result == true)
                {
                    var fileName = openFileDialog.FileName;
                    var directoryName = Path.GetDirectoryName(fileName);
                    _properties.ReplayFolderPath = directoryName;
                }
            }
            catch (Exception exception)
            {
                Debug.Instance.DebugMessage += $"Cannot set replay folder: \r\n{exception}\r\n";
            }
        }

        private void OnDetailsButtonClick(object sender, RoutedEventArgs e)
        {
            var detailsWindow = new ApmWindow(_properties.CurrentReplay!)
            {
                Owner = this
            };
            detailsWindow.ShowDialog();
        }

        private void OnDebugButtonClick(object sender, RoutedEventArgs e) => Debug.ShowDialog();

        private void OnPlayReplayButtonClick(object sender, RoutedEventArgs e)
        {
            Process.Start(_properties.RA3Exe, $" -replayGame \"{_properties.CurrentReplay}\"  ");
        }

        private async void OnFixReplayButtonClick(object sender, RoutedEventArgs e)
        {
            var replay = _properties.CurrentReplay ?? throw new InvalidOperationException("Trying to fix a null replay");

            try
            {
                var saveFileDialog = new SaveFileDialog
                {
                    Filter = "红警3录像文件 (*.RA3Replay)|*.RA3Replay|所有文件 (*.*)|*.*",
                    InitialDirectory = Path.GetDirectoryName(replay.Path),
                    OverwritePrompt = true,
                    Title = "保存已被修复的录像"
                };

                var result = saveFileDialog.ShowDialog(this);
                if (result == true)
                {
                    using var file = saveFileDialog.OpenFile();
                    using var writer = new BinaryWriter(file);
                    writer.Write(replay);
                }
            }
            catch (Exception exception)
            {
                MessageBox.Show(this, $"无法修复录像:\r\n{exception}");
            }

            var token = _cancelLoadReplays.ResetAndGetToken(CancellationToken.None);
            await _taskQueue.Enqueue(() => LoadReplays(replay.Path, token), token);
        }

        private async void OnReplayFilterBoxTextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
            _filterStrings = _replayFilterBox.Text
                .Split(',', ' ', ',')
                .Select(x => x.ToPinyin())
                .Where(x => !string.IsNullOrEmpty(x))
                .ToImmutableArray()!;
            var currentReplayPath = _properties?.CurrentReplay?.Path;

            var token = _cancelFilterReplays.ResetAndGetToken(_cancelLoadReplays.Token);
            await _taskQueue.Enqueue(() => FilterReplays(string.Empty, currentReplayPath, token), token);
        }
    }
}