using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Handlers;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using VeloeMinecraftLauncher.Entity.Assets;
using VeloeMinecraftLauncher.Models.Entity;

namespace VeloeMinecraftLauncher.Utils;

internal static class Downloader
{
    static HttpClient _httpClient = new HttpClient();

    public static async Task<TaskStatus> StartDownload(
        Action<string> DownloadingFileName, //status text showing on ui
        Action<string> TaskStatus, //
        Action<bool> IsControlsEnabled, //value locking ui buttons
        Action<long> SetProgress, // progress bar
        Entity.Version.Version versionJson,
        CancellationToken cancellationToken,
        bool DownloadJava = false,
        bool InstallForge = false,
        bool InstallOptifine = false,
        bool InstallForgeOptifine = false,
        bool InstallFabric = false)
    {
        return await DownloadClient(DownloadingFileName, TaskStatus, IsControlsEnabled, SetProgress, versionJson, cancellationToken, 
                                    DownloadJava, InstallForge, InstallOptifine, InstallForgeOptifine, InstallFabric);
    }

    public static async Task<TaskStatus> StartDownloadModpack(
        Action<string> DownloadingFileName,
        Action<string> TaskStatus,
        Action<bool> IsControlsEnabled,
        Action<long> SetProgress,
        ObservableCollection<Entity.VersionManifest.Version> FilteredVersions,
        Modpack SelectedModpack,
        CancellationToken cancellationToken,
        bool DownloadJava = false,
        bool InstallForge = false,
        bool InstallOptifine = false,
        bool InstallForgeOptifine = false,
        bool InstallFabric = false)
    {
        return await DownloadClient(DownloadingFileName, TaskStatus, IsControlsEnabled, SetProgress, null, cancellationToken, DownloadJava,
                                    InstallForge, InstallOptifine, InstallForgeOptifine, InstallFabric,FilteredVersions,SelectedModpack,true);
    }

    private static async Task<TaskStatus> DownloadClient(
        Action<string> DownloadingFileName, //status text showing on ui
        Action<string> TasksStatus,
        Action<bool> IsControlsEnabled, //value locking ui buttons
        Action<long> SetProgress, // progress bar
        Entity.Version.Version? versionJson,
        CancellationToken cancellationToken,
        bool DownloadJava = false,
        bool InstallForge = false,
        bool InstallOptifine = false,
        bool InstallForgeOptifine = false,
        bool InstallFabric = false,
        ObservableCollection<Entity.VersionManifest.Version>? FilteredVersions = null,
        Modpack? SelectedModpack = null,
        bool downloadModpack = false)
    {
        try
        {
            //task 1 - download java
            //task 2 - download jar
            //task 3 - download libs
            //task 4 - download assets
            //task 5 - download addons
            //task 6 - download modpacks
            IsControlsEnabled(false);
            SetProgress( 0 );
            var tasksCount = 3;
            var tasksIterator = 0;

            if (DownloadJava) tasksCount++;
            if (InstallForge || InstallOptifine || InstallForgeOptifine || InstallFabric) tasksCount++;
            if (downloadModpack) tasksCount++;

            TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");

            var handler = new HttpClientHandler() { AllowAutoRedirect = true };
            var ph = new ProgressMessageHandler(handler);
            ph.HttpSendProgress += (_, args) =>
            {
                SetProgress(args.ProgressPercentage);
            };

            ph.HttpReceiveProgress += (_, args) =>
            {
                SetProgress(args.ProgressPercentage);
            };

            using var httpClient = new HttpClient(ph);

            Entity.VersionManifest.Version? filteredVersion;

            if (downloadModpack)
            {
                if (FilteredVersions is null || SelectedModpack is null)
                    return TaskStatus.Faulted;

                filteredVersion = FilteredVersions.Where(x => x.Id == SelectedModpack.Version).FirstOrDefault();

                if (filteredVersion is null)
                    return TaskStatus.Faulted;

                InstallForge = SelectedModpack.Forge;
                InstallForgeOptifine = SelectedModpack.ForgeOptifine;
                InstallOptifine = SelectedModpack.Optifine;
                InstallFabric = SelectedModpack.Fabric;

                Settings.logger.Debug("Downloading {0}.json", filteredVersion.Id);
                DownloadingFileName($"{filteredVersion.Id}.json");
                versionJson = await Downloader.DownloadAndDeserializeJsonData<Entity.Version.Version>(filteredVersion.Url, Settings.minecraftForlderPath + "versions/" + filteredVersion.Id + "/", filteredVersion.Id + ".json");
            }

            if (versionJson is null)
                return TaskStatus.Faulted;
            //SetMaxProgressBarValue(size);
            Settings.logger.Debug("Downloading minecraft {0}", versionJson.Id);

            //download java
            if (DownloadJava)
            {
                Settings.logger.Debug("Getting required Java version.");
                string javaUrl = "";
                if (versionJson.JavaVersion is not null && versionJson.JavaVersion.MajorVersion != null)
                {
                    switch (versionJson.JavaVersion.MajorVersion)
                    {
                        case 8:
                            if (Environment.Is64BitOperatingSystem && OperatingSystem.IsWindows())
                                javaUrl = "https://files.veloe.link/launcher/java/8/windows64/java.zip";
                            if (!Environment.Is64BitOperatingSystem && OperatingSystem.IsWindows())
                                javaUrl = "https://files.veloe.link/launcher/java/8/windows32/java.zip";

                            if (Environment.Is64BitOperatingSystem && OperatingSystem.IsLinux())
                                javaUrl = "https://files.veloe.link/launcher/java/8/linux64/java.zip";
                            if (!Environment.Is64BitOperatingSystem && OperatingSystem.IsLinux())
                                javaUrl = "https://files.veloe.link/launcher/java/8/linux32/java.zip";
                            break;
                        case 16:
                            if (Environment.Is64BitOperatingSystem && OperatingSystem.IsWindows())
                                javaUrl = "https://files.veloe.link/launcher/java/16/windows64/java.zip";
                            if (Environment.Is64BitOperatingSystem && OperatingSystem.IsLinux())
                                javaUrl = "https://files.veloe.link/launcher/java/16/linux64/java.zip";
                            break;
                        case 17:
                            if (Environment.Is64BitOperatingSystem && OperatingSystem.IsWindows())
                                javaUrl = "https://files.veloe.link/launcher/java/17/windows64/java.zip";
                            if (Environment.Is64BitOperatingSystem && OperatingSystem.IsLinux())
                                javaUrl = "https://files.veloe.link/launcher/java/17/linux64/java.zip";

                            break;
                        case 18:
                            if (Environment.Is64BitOperatingSystem && OperatingSystem.IsWindows())
                                javaUrl = "https://files.veloe.link/launcher/java/18/windows64/java.zip";
                            if (Environment.Is64BitOperatingSystem && OperatingSystem.IsLinux())
                                javaUrl = "https://files.veloe.link/launcher/java/18/linux64/java.zip";

                            break;
                    }
                }

                if (versionJson.Assets == "legacy")
                {
                    if (Environment.Is64BitOperatingSystem && OperatingSystem.IsWindows())
                        javaUrl = "https://files.veloe.link/launcher/java/8/windows64/java.zip";
                    if (!Environment.Is64BitOperatingSystem && OperatingSystem.IsWindows())
                        javaUrl = "https://files.veloe.link/launcher/java/8/windows32/java.zip";

                    if (Environment.Is64BitOperatingSystem && OperatingSystem.IsLinux())
                        javaUrl = "https://files.veloe.link/launcher/java/8/linux64/java.zip";
                    if (!Environment.Is64BitOperatingSystem && OperatingSystem.IsLinux())
                        javaUrl = "https://files.veloe.link/launcher/java/8/linux32/java.zip";
                }

                if (javaUrl != "")
                {
                    Settings.logger.Debug("Downloading Java");
                    DownloadingFileName("java.zip");
                    await DownloadFileAsync(javaUrl, Settings.minecraftForlderPath + "java.zip", cancellationToken, httpClient);
                     
                    Settings.logger.Debug("Unpacking Java");
                    DownloadingFileName("Unpacking java.zip");
                    ZipFile.ExtractToDirectory($"{Settings.minecraftForlderPath}java.zip", Settings.minecraftForlderPath, true);
                    File.Delete($"{Settings.minecraftForlderPath}java.zip");
                }
                else
                    Settings.logger.Debug("Required Java version don't found in json file."); //log that java was not downloaded;

                tasksIterator++;
                TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");
            }
            //download json
            var path = Settings.minecraftForlderPath + "/versions/" + versionJson.Id;
            if (!Directory.Exists(path))
            {
                Settings.logger.Debug("Creating path: {0}", path);
                Directory.CreateDirectory(path);
            }           

            //download jar
            if (!Directory.Exists(path))
            {
                Settings.logger.Debug("Creating path: {0}", path);
                Directory.CreateDirectory(path);
            }

            string chksum = String.Empty;
            //here hash check
            if (File.Exists(path + "/" + versionJson.Id + ".jar"))
            {
                DownloadingFileName($"Checking hash: {versionJson.Id}.jar");
                FileStream fop = File.OpenRead(path + "/" + versionJson.Id + ".jar");
                byte[] hash = System.Security.Cryptography.SHA1.Create().ComputeHash(fop);
                chksum = BitConverter.ToString(hash).Replace("-", String.Empty).ToLower();

                fop.Close();
                fop.Dispose();
            }

            if (chksum != versionJson.Downloads.Client.Sha1)
            {
                Settings.logger.Debug("Downloading {0}", $"{versionJson.Id}.jar");
                DownloadingFileName($"{versionJson.Id}.jar");
                await DownloadFileAsync(versionJson.Downloads.Client.Url, path + "/" + versionJson.Id + ".jar", cancellationToken, httpClient);
            }
            tasksIterator++;
            TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");
            //end download jar
            //download libraries
            Settings.logger.Debug("Downloading libraries.");

            foreach (var library in versionJson.Libraries)
            {
                var libPath = Settings.minecraftForlderPath + "libraries/";
                string libUrl = "";
                string sha1 = String.Empty;
                // getting path and url if universal lib
                if (library.Natives is null)
                {
                    libPath = Settings.minecraftForlderPath + "libraries/" + library.Downloads.Artifact.Path;
                    libUrl = library.Downloads.Artifact.Url;
                    sha1 = library.Downloads.Artifact.Sha1;
                }
                else
                {   // getting path if native
                    libPath = Settings.minecraftForlderPath + "libraries/";
                    libUrl = "";
                    if (library.Downloads.Classifiers is not null)
                    {
                        if (OperatingSystem.IsWindows() && library.Natives.Windows is not null)
                        {
                            if (library.Downloads.Classifiers.NativesWindows is not null)
                            {
                                libPath += library.Downloads.Classifiers.NativesWindows.Path;
                                libUrl = library.Downloads.Classifiers.NativesWindows.Url;
                                sha1 = library.Downloads.Classifiers.NativesWindows.Sha1;
                            }
                            else
                            {
                                if (Environment.Is64BitOperatingSystem && library.Downloads.Classifiers.NativesWindows64 is not null)
                                {
                                    libPath += library.Downloads.Classifiers.NativesWindows64.Path;
                                    libUrl = library.Downloads.Classifiers.NativesWindows64.Url;
                                    sha1 = library.Downloads.Classifiers.NativesWindows64.Sha1;
                                }
                                else if (library.Downloads.Classifiers.NativesWindows32 is not null)
                                {
                                    libPath += library.Downloads.Classifiers.NativesWindows32.Path;
                                    libUrl = library.Downloads.Classifiers.NativesWindows32.Url;
                                    sha1 = library.Downloads.Classifiers.NativesWindows32.Sha1;
                                }
                            }
                        }
                        if (OperatingSystem.IsLinux() && library.Natives.Linux is not null && library.Downloads.Classifiers.NativesLinux is not null)
                        {
                            libPath += library.Downloads.Classifiers.NativesLinux.Path;
                            libUrl = library.Downloads.Classifiers.NativesLinux.Url;
                            sha1 = library.Downloads.Classifiers.NativesLinux.Url;
                        }
                    }
                }

                //if no lib url
                if (libUrl == String.Empty)
                    continue;

                var libName = Path.GetFileName(libPath);
                var libDir = Path.GetDirectoryName(libPath);

                if (libName is null || libDir is null)
                {
                    if (libPath == string.Empty)
                        Settings.logger.Warning("Library dir, name are null, because libraryPath is empty! Skipping...");
                    else
                        Settings.logger.Warning("Library dir or name are null! Skipping...");
                    continue;
                }

                //checking rules
                if (library.Rules == null)
                {
                    if (!Directory.Exists(libDir))
                    {
                        Settings.logger.Debug("Creating path: {0}", libDir);
                        Directory.CreateDirectory(libDir);
                    }

                    chksum = String.Empty;

                    if (File.Exists(libPath))
                    {
                        DownloadingFileName($"Checking hash: {libName}");
                        FileStream fop = File.OpenRead(libPath);
                        byte[] hash = System.Security.Cryptography.SHA1.Create().ComputeHash(fop);
                        chksum = BitConverter.ToString(hash).Replace("-", String.Empty).ToLower();

                        fop.Close();
                        fop.Dispose();
                    }

                    if (chksum != sha1)
                    {
                        Settings.logger.Debug("Downloading: {0}", libName);
                        DownloadingFileName(libName);
                        await DownloadFileAsync(libUrl, libDir + "/" + libName, cancellationToken, httpClient);                        
                    }
                }
                else
                {
                    foreach (var rule in library.Rules)
                    {
                        if (rule.Action == "allow") // && rule.os is null
                        {
                            if (!Directory.Exists(libDir))
                            {
                                Settings.logger.Debug("Creating path: {0}", libDir);
                                Directory.CreateDirectory(libDir);
                            }

                            chksum = String.Empty;

                            if (File.Exists(libDir + "/" + libName) && sha1 != String.Empty)
                            {
                                DownloadingFileName($"Checking hash: {libName}");
                                FileStream fop = File.OpenRead(libDir + "/" + libName);
                                byte[] hash = System.Security.Cryptography.SHA1.Create().ComputeHash(fop);
                                chksum = BitConverter.ToString(hash).Replace("-", String.Empty).ToLower();

                                fop.Close();
                                fop.Dispose();
                            }

                            if (chksum != sha1)
                            {
                                Settings.logger.Debug("Downloading: {0}", libName);
                                DownloadingFileName(libName);
                                await DownloadFileAsync(libUrl, libDir + "/" + libName, cancellationToken, httpClient);
                            }
                            continue;
                        }
                        //fabic on start needs all libraries for all os, so os check removed
                        /*
                        if (rule.action == "allow" && (rule.os.name == "windows" || rule.os.name == "linux" || rule.os.name == "osx" ))
                        {
                            if (!Directory.Exists(libPath))
                            {
                                Settings.logger.Debug("Creating path: {0}", path);
                                Directory.CreateDirectory(libPath);
                            }
                            Settings.logger.Debug("Downloading: {0}", libName);
                            DownloadingFileName = libName;
                            webClient.DownloadFileAsync(new System.Uri(libUrl), libPath + "/" + libName);
                             
                        }
                        */
                    }
                }
                //unpacking native libs
                if ((library.Natives is not null || library.Downloads.Classifiers is not null) && File.Exists(libPath)/*&& chksum != sha1*/)
                {
                    try
                    {
                        if (!(library.Downloads.Classifiers is not null && (
                            library.Downloads.Classifiers.NativesWindows is not null ||
                            library.Downloads.Classifiers.NativesWindows64 is not null ||
                            (library.Downloads.Classifiers.NativesWindows32 is not null && OperatingSystem.IsWindows()) ||
                            (library.Downloads.Classifiers.NativesLinux is not null && OperatingSystem.IsLinux()))))
                            continue;

                        if (!Directory.Exists(Settings.minecraftForlderPath + "versions/" + versionJson.Id + "/natives/"))
                        {
                            Settings.logger.Debug("Creating path: {0}", Settings.minecraftForlderPath + "versions/" + versionJson.Id + "/natives/");
                            Directory.CreateDirectory(Settings.minecraftForlderPath + "versions/" + versionJson.Id + "/natives/");
                        }
                        Settings.logger.Debug("Extracting {0} to {1}", libName, Settings.minecraftForlderPath + "versions/" + versionJson.Id + "/natives/");
                        ZipFile.ExtractToDirectory(libDir + "/" + libName, Settings.minecraftForlderPath + "versions/" + versionJson.Id + "/natives/", true);
                    }
                    catch (IOException)
                    {
                        Settings.logger.Error("IOException on native lib extraction");
                    }
                    //TODO delete META-INF and sha1 files after
                }
            }

            tasksIterator++;
            TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");

            var assetsJson = await Downloader.DownloadAndDeserializeJsonData<AssetsManifest>(versionJson.AssetIndex.Url);
            var assetsPath = Settings.minecraftForlderPath + "assets/" + versionJson.Assets + "/objects";
            var assetsUrl = "https://resources.download.minecraft.net/";

            //download assets json
            Settings.logger.Debug("Downloading: {0}", versionJson.Assets + ".json");

            if (!Directory.Exists(Settings.minecraftForlderPath + "/assets/" + versionJson.Assets + "/indexes/"))
                Directory.CreateDirectory(Settings.minecraftForlderPath + "/assets/" + versionJson.Assets + "/indexes/");
            await DownloadFileAsync(versionJson.AssetIndex.Url, Settings.minecraftForlderPath + "/assets/" + versionJson.Assets + "/indexes/" + versionJson.Assets + ".json", cancellationToken);

            Settings.logger.Debug("Downloading assets.");
            SetProgress(0);
            var assetsCount = assetsJson?.Objects?.Count ?? 1;
            var assetsIterator = 1;

            List<Task> assetsDownloadTasks = new();
            using SemaphoreSlim concurrencySemaphore = new SemaphoreSlim(0,40);

            foreach (var asset in assetsJson?.Objects?.DistinctBy(a => a.Value.Hash) ?? new Dictionary<string,Asset>())
            {
                assetsDownloadTasks.Add(Task.Run(async () => {
                    
                    var folder = asset.Value.Hash.Substring(0, 2);

                    var chksum = String.Empty;
                    //here hash check
                    if (File.Exists(assetsPath + "/" + folder + "/" + asset.Value.Hash))
                    {
                        DownloadingFileName($"Checking hash {assetsIterator} of {assetsCount}");
                        using FileStream fop = File.OpenRead(assetsPath + "/" + folder + "/" + asset.Value.Hash);
                        byte[] hash = System.Security.Cryptography.SHA1.Create().ComputeHash(fop);
                        chksum = BitConverter.ToString(hash).Replace("-", String.Empty).ToLower();
                        fop.Close();
                    }
                
                    if (chksum != asset.Value.Hash)
                    {
                        concurrencySemaphore.Wait(cancellationToken);
                        if (!Directory.Exists(assetsPath + "/" + folder))
                            Directory.CreateDirectory(assetsPath + "/" + folder);

                        Settings.logger.Debug("Downloading asset: {0} ", asset.Value.Hash);
                        //DownloadingFileName($"Asset {assetsIterator} of {assetsCount}");
                        DownloadingFileName($"Asset {assetsIterator} of {assetsCount}");                                                      
                        await DownloadFileAsync(assetsUrl + folder + "/" + asset.Value.Hash, assetsPath + "/" + folder + "/" + asset.Value.Hash, cancellationToken);                                                  
                        concurrencySemaphore.Release();
                    }
                    SetProgress(assetsIterator * 100 / assetsCount);
                    Interlocked.Increment(ref assetsIterator);
                    
                }));
            }
            concurrencySemaphore.Release(40);
            Task.WaitAll(assetsDownloadTasks.ToArray());

            SetProgress(100);
            tasksIterator++;
            TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");

            if (InstallForge)
            {
                var forgePath = $"Forge{versionJson.Id}";
                var forgeUrl = $"https://files.veloe.link/launcher/forge/Forge{versionJson.Id}/";

                if (!Directory.Exists($"{Settings.minecraftForlderPath}versions/Forge{versionJson.Id}"))
                {
                    Settings.logger.Debug("Creating path: {0}", $"{Settings.minecraftForlderPath}versions/" + forgePath);
                    Directory.CreateDirectory($"{Settings.minecraftForlderPath}versions/" + forgePath);
                }
                 

                Settings.logger.Debug("Downloading: {0}", $"Forge{versionJson.Id}.json");
                DownloadingFileName($"Forge{versionJson.Id}.json");
                await DownloadFileAsync($"{forgeUrl}Forge{versionJson.Id}.json", Settings.minecraftForlderPath + "versions/" + forgePath + "/" + forgePath + ".json", cancellationToken, httpClient);
                 

                Settings.logger.Debug("Downloading: {0}", "libraries.zip");
                DownloadingFileName("libraries.zip");
                await DownloadFileAsync(forgeUrl + "libraries.zip", Settings.minecraftForlderPath + "versions/" + forgePath + "/libraries.zip", cancellationToken, httpClient);
                 

                Settings.logger.Debug("Extracting: {0}", "libraries.zip");
                DownloadingFileName("Unpacking libraries.zip");
                ZipFile.ExtractToDirectory($"{Settings.minecraftForlderPath}versions/Forge{versionJson.Id}/libraries.zip", Settings.minecraftForlderPath, true);
                File.Delete($"{Settings.minecraftForlderPath}versions/Forge{versionJson.Id}/libraries.zip");
            }

            if (InstallOptifine)
            {
                var optifinePath = $"Optifine{versionJson.Id}";
                var optifineUrl = $"https://files.veloe.link/launcher/optifine/Optifine{versionJson.Id}/";

                if (!Directory.Exists($"{Settings.minecraftForlderPath}versions/{optifinePath}"))
                {
                    Settings.logger.Debug("Creating path: {0}", $"{Settings.minecraftForlderPath}versions/" + optifinePath);
                    Directory.CreateDirectory($"{Settings.minecraftForlderPath}versions/" + optifinePath);
                }

                Settings.logger.Debug("Downloading: {0}", $"Optifine{versionJson.Id}.json");
                DownloadingFileName($"Optifine{versionJson.Id}.json");
                await DownloadFileAsync($"{optifineUrl}Optifine{versionJson.Id}.json", Settings.minecraftForlderPath + "versions/" + optifinePath + "/" + optifinePath + ".json", cancellationToken, httpClient);
                 

                Settings.logger.Debug("Downloading: {0}", $"Optifine{versionJson.Id}.jar");
                DownloadingFileName($"Optifine{versionJson.Id}.json");
                await DownloadFileAsync($"{optifineUrl}Optifine{versionJson.Id}.jar", Settings.minecraftForlderPath + "versions/" + optifinePath + "/" + optifinePath + ".jar", cancellationToken, httpClient);
                 

                Settings.logger.Debug("Downloading: {0}", "libraries.zip");
                DownloadingFileName("libraries.zip");
                await DownloadFileAsync(optifineUrl + "libraries.zip", Settings.minecraftForlderPath + "versions/" + optifinePath + "/libraries.zip", cancellationToken, httpClient);
                 

                Settings.logger.Debug("Extracting: {0}", "libraries.zip");
                DownloadingFileName("Unpacking libraries.zip");
                ZipFile.ExtractToDirectory($"{Settings.minecraftForlderPath}versions/Optifine{versionJson.Id}/libraries.zip", Settings.minecraftForlderPath, true);
                File.Delete($"{Settings.minecraftForlderPath}versions/Optifine{versionJson.Id}/libraries.zip");
            }

            if (InstallForgeOptifine)
            {
                if (!Directory.Exists($"{Settings.minecraftForlderPath}versions/Forge{versionJson.Id}/mods"))
                {
                    Settings.logger.Debug("Creating path: {0}", $"{Settings.minecraftForlderPath}versions/Forge" + versionJson.Id + "/mods");
                    Directory.CreateDirectory($"{Settings.minecraftForlderPath}versions/Forge" + versionJson.Id + "/mods");
                }

                Settings.logger.Debug("Downloading: {0}", $"Optifine{versionJson.Id}.jar");
                DownloadingFileName($"Optifine{versionJson.Id}.jar");
                await DownloadFileAsync($@"https://files.veloe.link/launcher/forge/Forge{versionJson.Id}/Optifine{versionJson.Id}.jar", Settings.minecraftForlderPath + "versions/Forge" + versionJson.Id + "/mods/" + "Optifine" + versionJson.Id + ".jar", cancellationToken, httpClient); 
            }

            if (InstallForge || InstallOptifine || InstallForgeOptifine || InstallFabric)
            {
                tasksIterator++;
                TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");
            }

            if (downloadModpack)
            {
                if (SelectedModpack is null)
                    return TaskStatus.Faulted;

                string modpackUrl = string.Empty;

                if (!string.IsNullOrWhiteSpace(SelectedModpack.Url))
                    modpackUrl = SelectedModpack.Url;
                else
                    modpackUrl = $"https://files.veloe.link/launcher/modpacks/{SelectedModpack.Name}.zip";

                if (!Directory.Exists($"{Settings.minecraftForlderPath}versions/{SelectedModpack.Name}"))
                {
                    Settings.logger.Debug("Creating path: {0}", $"{Settings.minecraftForlderPath}versions/{SelectedModpack.Name}");
                    Directory.CreateDirectory($"{Settings.minecraftForlderPath}versions/{SelectedModpack.Name}");
                }

                Settings.logger.Debug("Downloading: {0}", $"{SelectedModpack.Name}.zip");
                DownloadingFileName($"{SelectedModpack.Name}.zip");
                await DownloadFileAsync(modpackUrl, Settings.minecraftForlderPath + $"versions/{SelectedModpack.Name}.zip", cancellationToken, httpClient);

                FileStream? stream = null;

                if (Directory.Exists($"{Settings.minecraftForlderPath}versions/{SelectedModpack.Name}/mods"))
                {
                    foreach (var file in Directory.GetFiles($"{Settings.minecraftForlderPath}versions/{SelectedModpack.Name}/mods"))
                    {
                        try { stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.None); }
                        finally { stream?.Close(); }
                    }
                }
                if (Directory.Exists($"{Settings.minecraftForlderPath}versions/{SelectedModpack.Name}/config"))
                    foreach (var file in Directory.GetFiles($"{Settings.minecraftForlderPath}versions/{SelectedModpack.Name}/mods"))
                    {
                        try { stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.None); }
                        finally { stream?.Close(); }
                    }

                stream?.Dispose();

                if (Directory.Exists($"{Settings.minecraftForlderPath}versions/{SelectedModpack.Name}/mods"))
                    Directory.Delete($"{Settings.minecraftForlderPath}versions/{SelectedModpack.Name}/mods", true);
                if (Directory.Exists($"{Settings.minecraftForlderPath}versions/{SelectedModpack.Name}/config"))
                    Directory.Delete($"{Settings.minecraftForlderPath}versions/{SelectedModpack.Name}/config", true);

                Settings.logger.Debug("Extracting: {0}", $"{SelectedModpack.Name}.zip");
                DownloadingFileName($"Unpacking {SelectedModpack.Name}.zip");
                ZipFile.ExtractToDirectory($"{Settings.minecraftForlderPath}versions/{SelectedModpack.Name}.zip", Settings.minecraftForlderPath + $"versions/{SelectedModpack.Name}", true);
                File.Delete($"{Settings.minecraftForlderPath}versions/{SelectedModpack.Name}.zip");

                File.WriteAllText($"{Settings.minecraftForlderPath}versions/{SelectedModpack.Name}/revision.json", JsonSerializer.Serialize(SelectedModpack.Revision));
                tasksIterator++;
                TasksStatus($"Task {tasksIterator} of {tasksCount} complete.");
            }

            //RemoveProgressBar(webClient);
            //Progress = 100;
            Settings.logger.Debug("Downloading finished.");
            DownloadingFileName("Finished!");
            IsControlsEnabled(true);

            //webClient.Dispose();
        }
        catch (TaskCanceledException ex)
        {
            IsControlsEnabled(true);
            if (downloadModpack && SelectedModpack is not null)
                DownloadingFileName($"Downloading {SelectedModpack.Name} canceled.");
            else if (versionJson is not null)
                DownloadingFileName($"Downloading {versionJson.Id} canceled.");
            else
                DownloadingFileName("Downloading canceled: version config is null!");
            Settings.logger.Warning(ex.Message);
            return TaskStatus.Canceled;
        }
        catch (Exception ex)
        {
            IsControlsEnabled(true);
            if (downloadModpack && SelectedModpack is not null)
                DownloadingFileName($"Error occured while downloading {SelectedModpack.Name}.");
            else if (versionJson is not null)
                DownloadingFileName($"Error occured while downloading {versionJson.Id}.");
            else
                DownloadingFileName($"Error occured while downloading: version config is null!");
            Settings.logger.Error(ex.Message);
            if (ex.StackTrace is not null)
                Settings.logger.Error(ex.StackTrace);
            return TaskStatus.Faulted;
        }
        return TaskStatus.RanToCompletion;
    }

    public static async Task<T?> DownloadAndDeserializeJsonData<T>(string url, string path = "", string filename = "") where T : new()
    {
        var jsonData = string.Empty;

        try
        {
            Settings.logger.Debug($"Downloading: {url.Split('/').Last()}");
            jsonData = await _httpClient.GetStringAsync(url);
        }
        catch (Exception ex)
        {
            throw new WebException($"An error occured while downloading {url}. Check your internet connection.",ex);
        }
            
        if (string.IsNullOrEmpty(jsonData))
        {
            Settings.logger.Warning("Empty string!");
            return new T();
        }
        else
        {
            if (!string.IsNullOrEmpty(filename) && !string.IsNullOrEmpty(path))
            {
                if (!Directory.Exists(path))
                {
                    Settings.logger.Debug("Creating path: {0}", path);
                    Directory.CreateDirectory(path);
                }
                File.WriteAllText(path + filename, jsonData);
            }
            //Settings.logger.Debug("Return serialized string.");
            return JsonSerializer.Deserialize<T>(jsonData, new JsonSerializerOptions{PropertyNameCaseInsensitive = true});
        }       
    }

    public static async Task DownloadFileAsync(string url, string path, CancellationToken ct, HttpClient? client = null)
    {
        if (client == null) client = _httpClient;
        using HttpResponseMessage response = await client.GetAsync(new System.Uri(url),HttpCompletionOption.ResponseHeadersRead, ct);
        response.EnsureSuccessStatusCode();
        using Stream contentStream = await response.Content.ReadAsStreamAsync(ct);
        using FileStream fileStream = File.OpenWrite(path);
        await contentStream.CopyToAsync(fileStream,ct);
    }

    public static async Task<bool> IsFileAvaliable(string url, CancellationToken ct)
    {
        using HttpResponseMessage response = await _httpClient.GetAsync(new System.Uri(url), HttpCompletionOption.ResponseHeadersRead, ct);
        return response.IsSuccessStatusCode;
    }
}
