using DynamicData;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using VeloeMinecraftLauncher.Entity.LauncherProfiles;
using VeloeMinecraftLauncher.Entity.VersionManifest;
using VeloeMinecraftLauncher.Utils;
using VeloeMinecraftLauncher.Models.Entity;
using VeloeMinecraftLauncher.Entity.Version;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Globalization;
using VeloeMinecraftLauncher.Utils.Downloader;
using VeloeMinecraftLauncher.Utils.Starter;
using System.Windows.Input;

namespace VeloeMinecraftLauncher.ViewModels;

public class VersionsDownloaderViewModel : ViewModelBase, IDisposable
{
    private string _downloadButtonText = "Download";
    private bool _showOld = false;
    private bool _showSnaps = false;
    private bool _installFabric = false;
    private bool _installForge = false;
    private bool _installOptifine = false;
    private bool _installForgeOptifine = false;
    private bool _installFabricVisible = false;
    private bool _installForgeVisible = false;
    private bool _installOptifineVisible = false;
    private bool _installForgeOptifineVisible = false;
    private bool _downloadJava = false;
    private bool _isDownloaderTabControlsEnabled = true;
    private bool _isManagerTabControlsEnabled = true;
    private long _progress = 0;
    private long _maxprogressvalue = 100;
    private string _downloadingFileName = "No active downloads";
    private string _tasksStatusLine = "No tasks started yet";

    /// <summary>
    /// Token source for downloading tasks
    /// </summary>
    private CancellationTokenSource _tokenSource = new();

    /// <summary>
    /// Token source for checking downloading options on selection from verions available for downloading
    /// </summary>
    private CancellationTokenSource _filteredVersionTokenSource = new();

    ObservableCollection<Entity.VersionManifest.Version> _filteredVersions;
    ObservableCollection<DownloadedVersion> _downloadedVersions;
    ObservableCollection<Modpack> _modpackVersions;

    ObservableCollection<TreeNode> _downloadedVersionTree;
    TreeNode _libraries;

    List<Entity.VersionManifest.Version> _modpackVersionsAsVersion;
    Entity.VersionManifest.Version? _filteredVersion;
    DownloadedVersion? _downloadedVersion;
    Modpack? _selectedModpack;
    VersionManifest _versionManifest;
    public VersionsDownloaderViewModel() : this(new Serilog.LoggerConfiguration().CreateLogger(), null)
    { }
    public VersionsDownloaderViewModel(Serilog.ILogger logger,ICommand update)
    {
        IsDownloaderTabControlsEnabled = false;
        IsManagerTabControlsEnabled = false;

        this.PropertyChanged += OnFilteredVersionPropertyChanged;
        this.PropertyChanged += OnDownloadedVersionPropertyChanged;
        _logger = logger;

        ArgumentNullException.ThrowIfNull(_logger);

        _versionManifest ??= new();

        _filteredVersions ??= new();
        _modpackVersions ??= new();
        _modpackVersionsAsVersion ??= _modpackVersions
                                        .Select(v => new Entity.VersionManifest.Version() 
                                        { 
                                            Id = v.Name, 
                                            Type = "modpack", 
                                            ComplianceLevel = v.Revision 
                                        })
                                        .ToList();

        _libraries ??= new();
        _downloadedVersions ??= new();
        _downloadedVersionTree ??= new();
        DownloadedVersionsDictionary ??= new();

        Task.Run(async () =>
        {
            try
            {
                    _logger.Debug("Getting versionManifest.json");
                    _versionManifest = await Downloader.DownloadAndDeserializeJsonData<VersionManifest>("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json") ?? new();
                    _modpackVersions.AddRange(await Downloader.DownloadAndDeserializeJsonData<List<Modpack>>("https://files.veloe.link/launcher/modpacks.json") ?? new());
                    _modpackVersionsAsVersion = _modpackVersions.Select(v=> new Entity.VersionManifest.Version() { Id = v.Name, Type = "modpack", ComplianceLevel = v.Revision}).ToList();
                    _logger.Debug("Updating available versions to download.");
                    UpdateList();
                    IsDownloaderTabControlsEnabled = true;
            }
            catch (Exception ex)
            {
                OpenErrorWindow(ex);
            }
        });

        Task.Run(() =>
        {
            try
            {
                SearchGameFolderForVersions();
                IsManagerTabControlsEnabled = true;
            }
            catch (Exception ex)
            {
                OpenErrorWindow(ex);
            }
        });
        Update = update;
    }

    public Entity.VersionManifest.Version? FilteredVersion
    {
        get { return _filteredVersion; }
        set 
        {
            InstallFabric = false;
            InstallForge = false;
            InstallOptifine = false;
            InstallForgeOptifine = false;
            this.RaiseAndSetIfChanged(ref _filteredVersion, value);
        }
    }

    public DownloadedVersion? DownloadedVersion
    {
        get => _downloadedVersion;
        set 
        { 
            this.RaiseAndSetIfChanged(ref _downloadedVersion, value);
        }
    }

    public Dictionary<string,Entity.Version.Version?> DownloadedVersionsDictionary { get; set; }

    public ObservableCollection<Entity.VersionManifest.Version> FilteredVersions
    {
        get => _filteredVersions; 
        set => this.RaiseAndSetIfChanged(ref _filteredVersions, value);
    }

    public ObservableCollection<DownloadedVersion> DownloadedVersions
    {
        get => _downloadedVersions;
        set => this.RaiseAndSetIfChanged(ref _downloadedVersions, value);
    }

    public ObservableCollection<TreeNode> DownloadedVersionTree
    {
        get => _downloadedVersionTree;
        set => this.RaiseAndSetIfChanged(ref _downloadedVersionTree, value);
    }

    public string DownloadButtonText
    {
        get => _downloadButtonText; 
        set => this.RaiseAndSetIfChanged(ref _downloadButtonText, value);
    }

    public bool ShowOld
    {
        get => _showOld;
        set { 
            this.RaiseAndSetIfChanged(ref _showOld, value);
            UpdateList();
        }
    }

    public bool ShowSnaps
    {
        get => _showSnaps;
        set { 
            this.RaiseAndSetIfChanged(ref _showSnaps, value);
            UpdateList();
        }
    }

    public long Progress
    {
        get => _progress;
        set => this.RaiseAndSetIfChanged(ref _progress, value);
    }

    public long MaxProgressValue
    {
        get => _maxprogressvalue;
        set => this.RaiseAndSetIfChanged(ref _maxprogressvalue, value);
    }

    public string DownloadingFileName
    {
        get => _downloadingFileName;
        set => this.RaiseAndSetIfChanged(ref _downloadingFileName, value);
    }

    public string TasksStatusLine
    {
        get => _tasksStatusLine;
        set => this.RaiseAndSetIfChanged(ref _tasksStatusLine, value);
    }

    public bool InstallForge
    {
        get => _installForge;
        set
        {
            this.RaiseAndSetIfChanged(ref _installForge, value);
            if (InstallFabric)
                InstallFabric = false;
        }
    }

    public bool InstallFabric
    {
        get => _installFabric;
        set
        {
            this.RaiseAndSetIfChanged(ref _installFabric, value);
            if (InstallForge)
                InstallForge = false;
        }
    }

    public bool InstallOptifine
    {
        get => _installOptifine;
        set => this.RaiseAndSetIfChanged(ref _installOptifine, value);
    }

    public bool InstallForgeOptifine
    {
        get { return _installForgeOptifine; }
        set
        {
            this.RaiseAndSetIfChanged(ref _installForgeOptifine, value);
            if (value)
                InstallForge = true;
        }
    }

    public bool InstallForgeVisible
    {
        get => _installForgeVisible;
        set => this.RaiseAndSetIfChanged(ref _installForgeVisible, value);
    }

    public bool InstallFabricVisible
    {
        get => _installFabricVisible;
        set => this.RaiseAndSetIfChanged(ref _installFabricVisible, value);
    }

    public bool InstallOptifineVisible
    {
        get => _installOptifineVisible;
        set => this.RaiseAndSetIfChanged(ref _installOptifineVisible, value);
    }

    public bool InstallForgeOptifineVisible
    {
        get => _installForgeOptifineVisible;
        set => this.RaiseAndSetIfChanged(ref _installForgeOptifineVisible, value);
    }

    public bool DownloadJava
    {
        get => _downloadJava;
        set => this.RaiseAndSetIfChanged(ref _downloadJava, value);
    }

    public bool IsDownloaderTabControlsEnabled
    {
        get => _isDownloaderTabControlsEnabled;
        set => this.RaiseAndSetIfChanged(ref _isDownloaderTabControlsEnabled, value);
    }

    public bool IsManagerTabControlsEnabled
    {
        get => _isManagerTabControlsEnabled;
        set => this.RaiseAndSetIfChanged(ref _isManagerTabControlsEnabled, value);
    }
    public ICommand Update { get; }

    private async void OnFilteredVersionPropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName is nameof(FilteredVersion) && FilteredVersion is not null)
        { 
            await DownloadButtonTextUpdateAsync(FilteredVersion);
            await GetFilteredVersionDownloadableFeaturesAsync(FilteredVersion);
        }
    }

    private async void OnDownloadedVersionPropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName is nameof(DownloadedVersion) && DownloadedVersion is not null)
        {
            await CreateNewTreeListAsync(DownloadedVersion);
        }
    }

    private Task DownloadButtonTextUpdateAsync(Entity.VersionManifest.Version value)
    {
        if (value is null)
            return Task.CompletedTask;

        if (value.Type == "modpack")
        {
            try
            {
                if (System.IO.File.Exists(SettingsService.Instance.MinecraftForlderPath + $"versions/{value.Id}/revision.json"))
                {
                    if (value.ComplianceLevel > JsonSerializer.Deserialize<int>(System.IO.File.ReadAllText(SettingsService.Instance.MinecraftForlderPath + $"versions/{value.Id}/revision.json")))
                        DownloadButtonText = "Update Modpack";
                    else
                        DownloadButtonText = "Reinstall Modpack";
                }
                else
                    if (System.IO.Directory.Exists($"{SettingsService.Instance.MinecraftForlderPath}versions/{value.Id}") && System.IO.File.Exists($"{SettingsService.Instance.MinecraftForlderPath}versions/{value.Id}/{value.Id}.json"))
                    DownloadButtonText = "Update Modpack";
                else
                    DownloadButtonText = "Download Modpack";
            }
            catch (Exception)
            {
                DownloadButtonText = "Update Modpack";
            }
        }
        else
        {
            if (System.IO.File.Exists(SettingsService.Instance.MinecraftForlderPath + $"versions/{value.Id}/{value.Id}.json"))
                DownloadButtonText = "Reinstall";
            else
                DownloadButtonText = "Download";
        }
        return Task.CompletedTask;
    }

    private async Task GetFilteredVersionDownloadableFeaturesAsync(Entity.VersionManifest.Version value)
    {
        _filteredVersionTokenSource.Cancel();
        _filteredVersionTokenSource.Dispose();
        _filteredVersionTokenSource = new();
        try
        {
            if (await Downloader.IsFileAvaliable(@$"https://files.veloe.link/launcher/forge/Forge{value.Id}/Forge{value.Id}.json", _filteredVersionTokenSource.Token))
            {
                if (await Downloader.IsFileAvaliable(@$"https://files.veloe.link/launcher/forge/Forge{value.Id}/Optifine{value.Id}.jar", _filteredVersionTokenSource.Token))
                    InstallForgeOptifineVisible = true;
                InstallForgeVisible = true;
            }
            else
            {
                InstallForgeVisible = false;
                InstallForgeOptifineVisible = false;
            }

            if (await Downloader.IsFileAvaliable(@$"https://files.veloe.link/launcher/fabric/Fabric{value.Id}/Fabric{value.Id}.json", _filteredVersionTokenSource.Token))
                InstallFabricVisible = true;
            else
                InstallFabricVisible = false;

            if (await Downloader.IsFileAvaliable(@$"https://files.veloe.link/launcher/optifine/Optifine{value.Id}/Optifine{value.Id}.json", _filteredVersionTokenSource.Token))
                InstallOptifineVisible = true;
            else
                InstallOptifineVisible = false;
        }
        catch (OperationCanceledException)
        {
            InstallForgeVisible = false;
            InstallForgeOptifineVisible = false;
            InstallFabricVisible = false;
            InstallOptifineVisible = false;
        }  
        catch (Exception ex)
        {
            OpenErrorWindow(ex);
        }
    }

    public async Task OnStartBunttonClick()
    {
        await Task.Run(async () => {

            if (FilteredVersion is null)
                return TaskStatus.Faulted;

            var parameters = new DownloaderParameters();
            parameters.Action.DownloadingFileName = value => DownloadingFileName = value;
            parameters.Action.TasksStatus = value => TasksStatusLine = value;
            parameters.Action.IsControlsEnabled = value => IsDownloaderTabControlsEnabled = value;
            parameters.Action.SetProgress = value => Progress = value;

            parameters.Parameter.DownloadJava = DownloadJava;

            if (FilteredVersion.Type == "modpack")
            {
                _selectedModpack = _modpackVersions.Where(m => m.Name == FilteredVersion.Id).FirstOrDefault();
                if (_selectedModpack is null)
                    return TaskStatus.Faulted;


                var selectedVersion = FilteredVersions.Where(x => x.Id == _selectedModpack.Version).FirstOrDefault();
                if (selectedVersion is null)
                    return TaskStatus.Faulted;

                parameters.Data.SelectedVersion = selectedVersion;
                parameters.Data.SelectedModpack = _selectedModpack;          
            }
            else
            {
                parameters.Data.SelectedVersion = FilteredVersion;

                parameters.Parameter.InstallForge = InstallForge;
                parameters.Parameter.InstallOptifine = InstallOptifine;
                parameters.Parameter.InstallForgeOptifine = InstallForgeOptifine;
                parameters.Parameter.InstallFabric = InstallFabric;
            }
            await new Downloader(parameters, _logger).DownloadClient(_tokenSource.Token);

            SearchGameFolderForVersions();
            Update.Execute(null);
            return TaskStatus.RanToCompletion; 
        });
    }

    private async Task CreateNewTreeListAsync(DownloadedVersion value)
    {
        await Task.Run(async() =>
        {
            CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
            try
            {
                IsManagerTabControlsEnabled = false;
                DownloadedVersionTree.Clear();
                DownloadedVersionTree = new();
                this.RaisePropertyChanged(nameof(DownloadedVersionTree));
                DownloadedVersionsDictionary.Clear();

                if (value is null)
                {
                    IsManagerTabControlsEnabled = true;
                    return;
                }

                Entity.Version.Version? version = null;

                foreach (var dversion in DownloadedVersions)
                {
                    using StreamReader reader = new(dversion.Path);

                    Entity.Version.Version versionObject = await JsonSerializer.DeserializeAsync<Entity.Version.Version>(reader.BaseStream, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
                    DownloadedVersionsDictionary.Add(dversion.Version, versionObject);

                    if (dversion == _downloadedVersion)
                    {
                        version = versionObject;
                    }
                }

                if (version is null)
                {
                    OpenErrorWindow("Json file is invalid!");
                    return;
                }

                var versionDir = new FileInfo(value.Path)?.Directory?.FullName;

                if (versionDir is null)
                {
                    OpenErrorWindow("Version folder is invalid!");
                    return;
                }

                //get version

                DownloadedVersionTree.Add(new TreeNode() { Title = "Version: " + (version.InheritsFrom ?? version.Id) });

                //calc Size

                var allSize = 0L;
                var dirSize = 0L;
                var dirInfo = new DirectoryInfo(versionDir);
                var sizeTreeNode = new TreeNode();
                var dirTreeNode = new TreeNode();

                dirTreeNode.SubNode.AddRange(DirectoryToTreeNode(dirInfo, out dirSize));
                dirTreeNode.Title = "versions/" + dirInfo.Name + " (" + BytesToString(dirSize) + ")";
                sizeTreeNode.SubNode.Add(dirTreeNode);
                allSize += dirSize;

                if (version.InheritsFrom is null)
                {
                    dirInfo = new DirectoryInfo(SettingsService.Instance.MinecraftForlderPath);
                    sizeTreeNode.SubNode.AddRange(
                        DirectoryToTreeNode(
                            dirInfo,
                            out dirSize,
                            (dir) => dir != null && (dir.Name.Equals(".mixin.out",StringComparison.Ordinal) ||
                                dir.Name.Equals("assets",StringComparison.Ordinal) ||
                                dir.Name.Equals("javaruntime", StringComparison.Ordinal) ||
                                dir.Name.Equals("libraries", StringComparison.Ordinal) ||
                                dir.Name.Equals("logs", StringComparison.Ordinal) ||
                                dir.Name.Equals("versions", StringComparison.Ordinal)),
                            (file) => file != null && (file.Name.Contains("Veloe",StringComparison.Ordinal) ||
                                file.Name.Contains("log", StringComparison.Ordinal) ||
                                file.Name.Contains("exe", StringComparison.Ordinal) ||
                                file.Name.Equals("launcher_profiles.json", StringComparison.Ordinal) ||
                                file.Name.Equals("libHarfBuzzSharp.dll", StringComparison.Ordinal) ||
                                file.Name.Equals("libSkiaSharp.dll", StringComparison.Ordinal) ||
                                file.Name.Equals("settings.json", StringComparison.Ordinal)))
                        );
                    allSize += dirSize;
                }

                sizeTreeNode.Title = "Size: " + BytesToString(allSize);
                DownloadedVersionTree.Add(sizeTreeNode);

                //calc Worlds

                dirInfo = null;

                if (version.InheritsFrom is null && Directory.Exists(SettingsService.Instance.MinecraftForlderPath + "saves"))
                    dirInfo = new DirectoryInfo(SettingsService.Instance.MinecraftForlderPath + "saves");
                else
                    if (Directory.Exists(versionDir + "/saves"))
                    dirInfo = new DirectoryInfo(versionDir + "/saves");

                if (dirInfo is not null)
                {
                    var worldsTreeNode = new TreeNode() { Title = "Worlds: " + dirInfo.GetDirectories().Length };

                    foreach (var world in dirInfo.GetDirectories())
                    { worldsTreeNode.SubNode.Add(new TreeNode() { Title = world.Name }); }

                    DownloadedVersionTree.Add(worldsTreeNode);
                }

                //check modloader

                var modsTreeNode = new TreeNode() { Title = "Unknown" };

            if (version.InheritsFrom is null)
                modsTreeNode.Title = "Modloader: No";
            else if (version.Libraries.Any(l => l.Downloads?.Artifact?.Url?.Contains("forge", StringComparison.OrdinalIgnoreCase) ?? false))
            {
                modsTreeNode.Title = "Modloader: Forge";
                OpenErrorWindow("This version contains forge modloader, it installs some libraries that can't be displayed and deleted in current launcher versions manager.");
            }
            else if (version.Libraries.Any(l => l.Name.Contains("fabric", StringComparison.OrdinalIgnoreCase)))
                modsTreeNode.Title = "Modloader: Fabric";
            else if (version.Libraries.Any(l => l.Name.Contains("optifine",StringComparison.OrdinalIgnoreCase)))
                    modsTreeNode.Title = "Modloader: Optifine modified";
                //get mods list

                if (modsTreeNode.Title != "No" && Directory.Exists(versionDir + "/mods"))
                {
                    dirInfo = new DirectoryInfo(versionDir + "/mods");

                    modsTreeNode.Title += " (" + dirInfo.EnumerateFiles("*.jar", SearchOption.TopDirectoryOnly).Count() + ")";
                    foreach (var mod in dirInfo.EnumerateFiles("*.jar", SearchOption.TopDirectoryOnly))
                    { modsTreeNode.SubNode.Add(new TreeNode() { Title = mod.Name }); }

                }

                DownloadedVersionTree.Add(modsTreeNode);

                //calc resourcepacks

                dirInfo = null;

                if (version.InheritsFrom is null && Directory.Exists(SettingsService.Instance.MinecraftForlderPath + "resourcepacks"))
                    dirInfo = new DirectoryInfo(SettingsService.Instance.MinecraftForlderPath + "resourcepacks");
                else
                    if (Directory.Exists(versionDir + "/resourcepacks"))
                    dirInfo = new DirectoryInfo(versionDir + "/resourcepacks");

                if (dirInfo is not null)
                {
                    var resourcepacksTreeNode = new TreeNode
                    {
                        Title = "Resoursepacks: " + (dirInfo?.GetDirectories()?.Length + dirInfo?.GetFiles().Length ?? 0).ToString()
                    };
                    foreach (var resourcepack in dirInfo?.GetFileSystemInfos())
                    { resourcepacksTreeNode.SubNode.Add(new TreeNode() { Title = resourcepack.Name }); }

                    DownloadedVersionTree.Add(resourcepacksTreeNode);
                }

                //calc assets

                var assetsFolderName = version.Assets;

                if (version.Assets is null &&
                    version.InheritsFrom is not null &&
                    DownloadedVersionsDictionary.TryGetValue(version.InheritsFrom, out var outVersion) &&
                    outVersion?.Assets is not null)
                {
                    assetsFolderName = outVersion.Assets;
                }

                if (Directory.Exists(SettingsService.Instance.MinecraftForlderPath + "assets/" + assetsFolderName))
                {
                    dirInfo = new DirectoryInfo(SettingsService.Instance.MinecraftForlderPath + "assets/" + assetsFolderName);
                    allSize = 0;
                    foreach (var file in dirInfo.EnumerateFiles("*", SearchOption.AllDirectories))
                    { allSize += file.Length; }

                    var assetsTreeNode = new TreeNode();
                    var usedByVersionCount = 0;

                    foreach (var dictionaryVersion in DownloadedVersionsDictionary)
                    {
                        if ((dictionaryVersion.Value?.Assets == assetsFolderName &&
                             dictionaryVersion.Value?.InheritsFrom is null) ||
                            (dictionaryVersion.Value?.InheritsFrom is not null &&
                             DownloadedVersionsDictionary.TryGetValue(dictionaryVersion.Value.InheritsFrom, out outVersion) &&
                             outVersion?.Assets == assetsFolderName))
                        {
                            usedByVersionCount++;
                            assetsTreeNode.SubNode.Add(new TreeNode() { Title = dictionaryVersion.Key });
                        }
                    }

                    assetsTreeNode.Title = "Assets: " + version.Assets + " (" + BytesToString(allSize) + ") (" + usedByVersionCount + ")";
                    DownloadedVersionTree.Add(assetsTreeNode);
                }

                // calc libraries

                var usedLibraries = version.Libraries;

                var libTreeNode = new TreeNode
                {
                    Title = "Libraries"
                };

                if (version.InheritsFrom is not null && DownloadedVersionsDictionary.TryGetValue(version.InheritsFrom, out outVersion) && outVersion is not null)
                {
                    usedLibraries.AddRange(outVersion.Libraries);
                }

                usedLibraries =
                    usedLibraries
                        .Where(l =>
                            File.Exists(Path.Combine(SettingsService.Instance.MinecraftForlderPath + "libraries/" + l.Downloads?.Artifact?.Path)) ||
                            File.Exists(Path.Combine(SettingsService.Instance.MinecraftForlderPath + "libraries/" + l.Downloads?.Classifiers?.NativesLinux?.Path)) ||
                            File.Exists(Path.Combine(SettingsService.Instance.MinecraftForlderPath + "libraries/" + l.Downloads?.Classifiers?.NativesWindows?.Path)) ||
                            File.Exists(Path.Combine(SettingsService.Instance.MinecraftForlderPath + "libraries/" + l.Downloads?.Classifiers?.NativesWindows64?.Path)) ||
                            File.Exists(Path.Combine(SettingsService.Instance.MinecraftForlderPath + "libraries/" + l.Downloads?.Classifiers?.NativesWindows32?.Path)) ||
                            File.Exists(Path.Combine(SettingsService.Instance.MinecraftForlderPath + "libraries/" + StartCommandBuilder.GetLibPathFromName(l.Name)))
                        )
                        .ToList();

                var libraries = GetLibrariesFromVersions(DownloadedVersionsDictionary)
                    .Where(v => usedLibraries.Any(u => v.Value.Name == u.Name))
                    .GroupBy(a => a.Value.Name)
                    .Select(g =>
                    {
                        return new
                        {
                            Name = g.Key,
                            Count = g.Select(a => a.Key).Distinct().Count(),
                            Versions = g.Select(a => a.Key).Distinct(),
                            LibraryUniqueInstances = g.Select(a => a.Value).Distinct() //IEnumerable cause there can be several lib blocks with different rules in one version json
                        };
                    });

                foreach (var lib in libraries)
                {
                    var subLibTreeNode = new TreeNode()
                    {
                        Title = lib.Name + " (" + lib.Count + ")",
                    };

                    //if only one version uses it (selected version), then add path to lib in Tag for deletion
                    if (lib.Count == 1)
                    {
                        subLibTreeNode.Tag = lib.LibraryUniqueInstances.Where(l => l.Downloads?.Artifact?.Path is not null).Select(l => l.Downloads?.Artifact?.Path).FirstOrDefault() ?? string.Empty;
                        //if fabric library
                        if (File.Exists(SettingsService.Instance.MinecraftForlderPath + "libraries/" + StartCommandBuilder.GetLibPathFromName(lib.Name)))
                            subLibTreeNode.Tag = StartCommandBuilder.GetLibPathFromName(lib.Name);
                    }

                    var subLibUsedVersionsTreeNodeHeader = new TreeNode() { Title = "Using in versions:" };

                    foreach (var ver in lib.Versions)
                        subLibUsedVersionsTreeNodeHeader.SubNode.Add(new TreeNode() { Title = ver });

                    if (subLibUsedVersionsTreeNodeHeader.SubNode.Count > 0)
                        subLibTreeNode.SubNode.Add(subLibUsedVersionsTreeNodeHeader);

                    if (lib.LibraryUniqueInstances.Any(l => l.Natives is not null) || lib.LibraryUniqueInstances.Any(l => l.Downloads?.Classifiers is not null))
                    {
                        var subLibNativesTreeNodeHeader = new TreeNode() { Title = "Natives:" };

                        //check classifiers
                        var libraryNativeInstance = lib.LibraryUniqueInstances.Where(l => l.Natives is not null || l.Downloads?.Classifiers is not null).First();

                        if (libraryNativeInstance.Downloads?.Classifiers?.NativesWindows is not null)
                        {
                            subLibNativesTreeNodeHeader.SubNode.Add(new TreeNode()
                            {
                                Title = Path.GetFileName(libraryNativeInstance.Downloads?.Classifiers?.NativesWindows.Path) ?? "No lib name",
                                Tag = lib.Count == 1 ? libraryNativeInstance.Downloads?.Classifiers?.NativesWindows?.Path ?? string.Empty : string.Empty
                            });
                        }

                        if (libraryNativeInstance.Downloads?.Classifiers?.NativesWindows32 is not null)
                        {
                            subLibNativesTreeNodeHeader.SubNode.Add(new TreeNode()
                            {
                                Title = Path.GetFileName(libraryNativeInstance.Downloads?.Classifiers?.NativesWindows32.Path) ?? "No lib name",
                                Tag = lib.Count == 1 ? libraryNativeInstance.Downloads?.Classifiers?.NativesWindows32?.Path ?? string.Empty : string.Empty
                            });
                        }

                        if (libraryNativeInstance.Downloads?.Classifiers?.NativesWindows64 is not null)
                        {
                            subLibNativesTreeNodeHeader.SubNode.Add(new TreeNode()
                            {
                                Title = Path.GetFileName(libraryNativeInstance.Downloads?.Classifiers?.NativesWindows64.Path) ?? "No lib name",
                                Tag = lib.Count == 1 ? libraryNativeInstance.Downloads?.Classifiers?.NativesWindows64?.Path ?? string.Empty : string.Empty
                            });
                        }

                        if (libraryNativeInstance.Downloads?.Classifiers?.NativesLinux is not null)
                        {
                            subLibNativesTreeNodeHeader.SubNode.Add(new TreeNode()
                            {
                                Title = Path.GetFileName(libraryNativeInstance.Downloads?.Classifiers?.NativesLinux.Path) ?? "No lib name",
                                Tag = lib.Count == 1 ? libraryNativeInstance.Downloads?.Classifiers?.NativesLinux?.Path ?? string.Empty : string.Empty
                            });
                        }

                        if (subLibNativesTreeNodeHeader.SubNode.Count > 0)
                            subLibTreeNode.SubNode.Add(subLibNativesTreeNodeHeader);

                    }

                    libTreeNode.SubNode.Add(subLibTreeNode);
                }

                _libraries = libTreeNode;
                DownloadedVersionTree.Add(libTreeNode);
            }
            finally
            {
                this.RaisePropertyChanged(nameof(DownloadedVersionTree));
                IsManagerTabControlsEnabled = true;
            }
        });
    }

    public async Task OnDeleteButtonClick()
    {
        await Task.Run(() => {
            try
            {
                IsManagerTabControlsEnabled = false;
                DownloadedVersionTree.Clear();
                DownloadedVersionTree = new();
                this.RaisePropertyChanged(nameof(DownloadedVersionTree));
                DownloadedVersionsDictionary.Clear();
                DownloadedVersionsDictionary = new();

                Entity.Version.Version? version = null;

                foreach (var dversion in DownloadedVersions)
                {
                    string json;
                    using (StreamReader reader = new(dversion.Path))
                    {
                        json = reader.ReadToEnd();
                    }

                    var versionObject = JsonSerializer.Deserialize<Entity.Version.Version>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
                    DownloadedVersionsDictionary.Add(dversion.Version, versionObject);

                    if (dversion == _downloadedVersion)
                    {
                        version = versionObject;
                    }
                }

                if (version is null)
                {
                    OpenErrorWindow("Json file is invalid!");
                    return;
                }

                var versionDir = new FileInfo(DownloadedVersion.Path)?.Directory?.FullName;

                if (versionDir is null)
                {
                    OpenErrorWindow("Version folder is invalid!");
                    return;
                }

                var dirInfo = new DirectoryInfo(versionDir);

                

                if (version.InheritsFrom is null && DownloadedVersionsDictionary.Where(v=>v.Value.InheritsFrom is null).Count() == 1)
                {
                    //if vanilla
                    Debug.WriteLine("Vanilla root directories deleted!");
                    
                    if (Directory.Exists(SettingsService.Instance.MinecraftForlderPath + "saves"))
                        Directory.Delete(SettingsService.Instance.MinecraftForlderPath + "saves", true);

                    if (Directory.Exists(SettingsService.Instance.MinecraftForlderPath + "resourcepacks"))
                        Directory.Delete(SettingsService.Instance.MinecraftForlderPath + "resourcepacks", true);

                    if (Directory.Exists(SettingsService.Instance.MinecraftForlderPath + "shaderpacks"))
                        Directory.Delete(SettingsService.Instance.MinecraftForlderPath + "shaderpacks", true);
                }

                //calc assets

                var assetsFolderName = version.Assets;

                if (version.Assets is null &&
                    version.InheritsFrom is not null &&
                    DownloadedVersionsDictionary.TryGetValue(version.InheritsFrom, out var outVersion) &&
                    outVersion?.Assets is not null)
                {
                    assetsFolderName = outVersion.Assets;
                }

                if (Directory.Exists(SettingsService.Instance.MinecraftForlderPath + "assets/" + assetsFolderName))
                {
                    var usedByVersionCount = 0;

                    foreach (var dictionaryVersion in DownloadedVersionsDictionary)
                    {
                        if ((dictionaryVersion.Value?.Assets == assetsFolderName &&
                             dictionaryVersion.Value?.InheritsFrom is null) ||
                            (dictionaryVersion.Value?.InheritsFrom is not null &&
                             DownloadedVersionsDictionary.TryGetValue(dictionaryVersion.Value.InheritsFrom, out outVersion) &&
                             outVersion?.Assets == assetsFolderName))
                        {
                            usedByVersionCount++;
                        }
                    }

                    //delete assets if only deleting version used it
                    if (usedByVersionCount == 1)
                    {
                        Debug.WriteLine(SettingsService.Instance.MinecraftForlderPath + "assets/" + assetsFolderName);
                        Directory.Delete(SettingsService.Instance.MinecraftForlderPath + "assets/" + assetsFolderName,true);
                    }
                }

                // calc libraries
                DeleteLibrariesFromTreeNode(_libraries);

                // delete version dir
                Debug.WriteLine(versionDir);
                Directory.Delete(versionDir, true);
            }
            catch (Exception ex)
            {
                OpenErrorWindow(ex);
            }
            finally
            {
                SearchGameFolderForVersions();
                IsManagerTabControlsEnabled = true;
            }
        });
        Update.Execute(null);
    }

    static string[] _size_label = { "bytes", "KB", "MB", "GB" };

    private string BytesToString(long size)
    {
        var j = 0;
        var result = (double)size;
        while (j < _size_label.Length && result > 1024)
        {
            result /= 1024;
            j++;
        }
        return string.Format("{0:N2}", result) + " " + _size_label[j];
    }

    private List<TreeNode> DirectoryToTreeNode(DirectoryInfo? dirInfo, out long size, Expression<Func<DirectoryInfo?, bool>>? dirExpression = null, Expression<Func<FileInfo?,bool>>? fileExpression = null)
    {
        size = 0L;
        var nodes = new List<TreeNode>();

        if (!(dirInfo is not null && dirInfo.Exists)) return nodes;

        dirExpression ??= (x) => false;

        fileExpression ??= (x) => false;

        var dirDelegate = dirExpression.Compile();
        var fileDelegate = fileExpression.Compile();

        foreach (var dir in dirInfo.EnumerateDirectories("*", SearchOption.TopDirectoryOnly))
        {
            if (dirDelegate(dir)) continue;

            var subDirSize = 0L;
            var subDirTreeNode = new TreeNode();
            subDirTreeNode.SubNode.AddRange(DirectoryToTreeNode(dir,out subDirSize));
            subDirTreeNode.Title = dir.Name + " (" + BytesToString(subDirSize) + ")";
            nodes.Add(subDirTreeNode);
            size += subDirSize;
        }

        foreach (var file in dirInfo.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
        {
            if (fileDelegate(file)) continue;
            nodes.Add(new TreeNode() { Title = file.Name + " (" + BytesToString(file.Length) + ")" });
            size += file.Length;
        }

        return nodes;
    }

    private void DeleteLibrariesFromTreeNode(TreeNode library)
    {
        foreach (var node in library.SubNode)
            DeleteLibrariesFromTreeNode(node);
        
        if (!string.IsNullOrEmpty(library.Tag))
        {
            var libFileInfo = new FileInfo(SettingsService.Instance.MinecraftForlderPath + "libraries/" + library.Tag);
            var libDir = libFileInfo.Directory;

            if(libDir?.Exists == false)
            {
                Debug.WriteLine($"Directory {libDir.FullName} does not exists! Subnode: {library.Title}");
                _logger.Warning($"Directory {libDir.FullName} does not exists!");
                return;
            }

            if (libDir?.GetFileSystemInfos().Length > 1)
            {
                Debug.WriteLine(libFileInfo.FullName);
                libFileInfo.Delete();
            }
            else
            {
                Debug.WriteLine(libDir?.FullName);
                libDir?.Delete(true);
            }
        }
    }

    private void UpdateList()
    {
        try
        {
            FilteredVersions.Clear();

            if (_versionManifest.Versions is null)
                return;

            FilteredVersions.AddRange(_modpackVersionsAsVersion);
            FilteredVersions.AddRange(_versionManifest.Versions.Where(version => ShowSnaps && version.Type == "snapshot" || ShowOld && version.Type is ("old_alpha" or "old_beta") || version.Type == "release"));
        }
        catch (Exception ex)
        {
            OpenErrorWindow(ex);
        }
    }

    private void SearchGameFolderForVersions()
    {
        if (DownloadedVersions is null)
            DownloadedVersions = new();
        else
        {
            DownloadedVersions.Clear();
            DownloadedVersions = new();
        }

        DirectoryInfo versions = new(SettingsService.Instance.MinecraftForlderPath + "versions");
        try
        {
            var dirs = versions.GetDirectories("*", SearchOption.TopDirectoryOnly);
            LauncherProfiles profiles = new()
            {
                SelectedProfile = "NotImplemented"
            };

            foreach (var dir in dirs)
            {
                _logger.Debug("Checking folder {0}", dir.Name);
                string checkedPath;
                if (File.Exists(checkedPath = dir.FullName + "/" + dir.Name + ".json"))
                {
                    _logger.Debug("Found version {0}", dir.Name);
                    DownloadedVersions.Add(new DownloadedVersion(checkedPath, dir.Name));
                    profiles.Profiles.Add($"Version {dir.Name}", new Profile() { Name = dir.Name, LastVersionId = dir.Name, LauncherVisibilityOnGameClose = "keep the launcher open" });
                }
            }
        }
        catch (DirectoryNotFoundException)
        {
            Directory.CreateDirectory(SettingsService.Instance.MinecraftForlderPath + "versions");
            return;
        }

    }

    public void OnOpenForlder()
    {       
        if (DownloadedVersion?.Version is not null && Path.Exists(SettingsService.Instance.MinecraftForlderPath + "versions/" + DownloadedVersion.Version))
            Process.Start(new System.Diagnostics.ProcessStartInfo() { FileName = SettingsService.Instance.MinecraftForlderPath + "versions/" + DownloadedVersion.Version, UseShellExecute = true });   
    }

    public void OnClosing(object sender, CancelEventArgs args)
    {
        _tokenSource.Cancel();
        _tokenSource.Dispose();
        _filteredVersionTokenSource.Cancel();
        _filteredVersionTokenSource.Dispose();
    }

    private IEnumerable<KeyValuePair<string,Library>> GetLibrariesFromVersions(Dictionary<string, Entity.Version.Version?> versions)
    {
        foreach (var version in versions)
        {
            if (version.Value is null) continue;

            if (version.Value.InheritsFrom is not null && versions.TryGetValue(version.Value.InheritsFrom,out var inheritVersion) && inheritVersion?.Libraries is not null)
            {
                foreach (var lib in inheritVersion.Libraries)
                    yield return new KeyValuePair<string, Library>(version.Key, lib);
            }

            foreach (var lib in version.Value.Libraries)
                yield return new KeyValuePair<string, Library>(version.Key,lib);
        }
    }

    public void Dispose()
    {
        this.PropertyChanged -= OnFilteredVersionPropertyChanged;
        this.PropertyChanged -= OnDownloadedVersionPropertyChanged;
        GC.SuppressFinalize(this);
    }
}

public class TreeNode
{
    public TreeNode()
    {
        Title = "Default";
        SubNode = new();
        Tag = string.Empty;
    }

    public string Title { get; set; }
    public string Tag { get; set; }

    public List<TreeNode> SubNode { get; set; }

    public override string ToString()
    {
        return Title;
    }
}
