using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using GraphicsManager;
using GraphicsManager.Enums;
using GraphicsManager.Objects;
using GraphicsManager.Objects.Core;
using OpenTK.Mathematics;
using OpenTK.Windowing.Desktop;

namespace Updater;

public class NewUpdater : FPSWindow
{
    private readonly ConfigFile cf;
    private ProgressBar<ulong> TotalProgress;
    private Argumets Argumets;
    private List<Tuple<ProgressBar<ulong>, Label, int>> ProgressThreads = new();
    private readonly BackgroundWorker Download = new();
    private Label? Total_Downloaded, Transfer_Speed;
    private readonly System.Timers.Timer Speed;
    private List<double> speeds = new();
    
    public NewUpdater(NativeWindowSettings nws, GameWindowSettings gws, Argumets args)
    :base(nws, gws)
    {
        ShowMissingChar = false;
        LogFrames = false;
        Speed = new()
        {
            Interval = 500
        };
        Speed.Elapsed += Speed_Elapsed;
        CanControleUpdate = false;
        FontFamily r = FontFamily.LoadFontFamily(Tools.GetResourceStream(Assembly.GetExecutingAssembly(), "Updater.Resource.OpenSans.zip"), "OpenSans");
        FontInteraction fi = FontInteraction.Load(r);
        cf = Config.GetConfig();
        Size = new(cf.Scale(Size.X), cf.Scale(Size.Y));
        Argumets = args;
        int WallDistance = cf.Scale(15);
        BackgroundColor = cf.Colors.Background.ToColor4();
        Texture ProgressbarTexture = TextureManager.AddTexture(Tools.GetResourceStream(Assembly.GetExecutingAssembly(), "Updater.Resource.Progress.png"));
        Controls.Add(TotalProgress = new(ProgressbarTexture)
        {
            Location = new(WallDistance, cf.Scale(70)),
            Size = new(Size.X - WallDistance - WallDistance, cf.Scale(20)),
            BackgroundColor = cf.Colors.Progress_bars.Backcolor.ToColor4(),
            ProgressColor = cf.Colors.Progress_bars.Fillcolor.ToColor4(),
            ProgressValue = 5,
            DrawingGap = new(cf.Format.DrawingGaps.X, cf.Format.DrawingGaps.Y, cf.Format.DrawingGaps.Z, cf.Format.DrawingGaps.W),
            Shader = Rectangle.DefaultAlphaShader[Context],
            TextureDisplay = TextureDisplay.ProgressHorizontalCenter,
            UpdateOnDraw = true,
            InnerShader = Rectangle.DefaultAlphaShader[Context]
        });
        
        fi.PixelHeight = (uint)WallDistance;
        TotalProgress.Controls.Add(Total_Downloaded = new(fi)
        {
            Color = Color4.Black,
            Text = "Downloading ...."
        });
        Total_Downloaded.Location = new(cf.Scale(5), (TotalProgress.Size.Y - Total_Downloaded.Size.Y) / 2);
        FontInteraction tmp = fi.Clone();
        tmp.FontSize = FontSize.Bold;
        tmp.PixelHeight += 10;
        Label Information;
        Controls.Add(Information = new(tmp)
        {
            Text = "Downloading " + Argumets.RemoteDirectory,
            Location = new(WallDistance + Total_Downloaded.Location.X, cf.Scale(5))
        });
        Controls.Add(Transfer_Speed = new(fi)
        {
            Text = "Adverage Speed: ....",
            Location = new(Information.Location.X, + Information.Size.Y + cf.Scale(7))
        });

        int gap = cf.Scale(10);
        for (int i = 0; i < cf.Format.DownloadThreads; i++)
        {
            ProgressBar<ulong> temp;
            Size = new(Size.X, Size.Y + gap + TotalProgress.Size.Y);
            Label l = new(fi)
            {
                Color = Color4.Black,
                Text = "Downloading ...."
            };
            l.Location = new(Total_Downloaded.Location.X, (TotalProgress.Size.Y - l.Size.Y) / 2);
            Controls.Add(temp = new(ProgressbarTexture)
            {
                Location = new(WallDistance, cf.Scale(70) + (TotalProgress.Size.Y * (i + 1)) + (gap * (i + 1))),
                Size = TotalProgress.Size,
                BackgroundColor = TotalProgress.BackgroundColor,
                ProgressColor = TotalProgress.ProgressColor,
                ProgressValue = 100,
                DrawingGap = TotalProgress.DrawingGap,
                Shader = TotalProgress.Shader,
                TextureDisplay = TotalProgress.TextureDisplay,
                UpdateOnDraw = true,
                InnerShader = TotalProgress.InnerShader
            });
            temp.Controls.Add(l);
            ProgressThreads.Add(new(temp,l, i));
        }
        
        Download.DoWork += Download_DoWork;
        Download.RunWorkerCompleted += Download_RunWorkerCompleted;
        Download.WorkerReportsProgress = true;
        Invoke(Download.RunWorkerAsync);
        Speed.Start();
    }

    private ulong last = 1;
    private string end = "";
    
    private void Speed_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
    {
        ulong BytesPertenthSec;
            if (last > TotalProgress.ProgressValue) BytesPertenthSec= 0;
            else BytesPertenthSec= (TotalProgress.ProgressValue) - last;
        last = TotalProgress.ProgressValue;
        speeds.Add(BytesPertenthSec * 2);
        if (speeds.Count > 25) speeds.RemoveAt(0);
        if (Transfer_Speed is not null && speeds.Average() != 0)
        {
            Transfer_Speed.Text = Download_Speed_Neat(speeds.Average());
            Set_Downloaded_Amount_Neat();
        }
    }
    
    private void Download_DoWork(object? sender, DoWorkEventArgs e)
    {
        try
        {
            void TryDeleteDir(string? dir)
            {
                if (string.IsNullOrEmpty(dir) || string.IsNullOrWhiteSpace(dir)) return;
                try
                {
                    if (Directory.Exists(dir)) Directory.Delete(dir, true);
                }
                catch
                {
                    DirectoryInfo dirInfo = new(dir);
                    foreach (var file in dirInfo.GetFiles())
                    {
                        try
                        {
                            file.Delete();
                        }
                        catch
                        {
                            // ignored
                        }
                    }
                    foreach (var subdir in dirInfo.GetDirectories())
                    {
                        TryDeleteDir(subdir.FullName);
                    }
                }
            }
            TryDeleteDir(Argumets.LocalDirectory);
            Directory.CreateDirectory(Argumets.LocalDirectory);
            
            try
            {
                HttpClient client = new();
            
                Task<string> files_pre = client.GetStringAsync($"https://{Handler.Domain}/Updater/Files?directory={Argumets.RemoteDirectory}{Argumets.UriBranch}{Argumets.UriSeflContaind}{Argumets.UriPlatform}{Argumets.UriVersion}");
                Task<string> max_pre = client.GetStringAsync(
                    $"https://{Handler.Domain}/Updater/GetSize?directory={Argumets.RemoteDirectory}{Argumets.UriBranch}{Argumets.UriSeflContaind}{Argumets.UriPlatform}{Argumets.UriVersion}");
                max_pre.Wait();
                TotalProgress.MaxProgressValue = ulong.Parse(max_pre.Result);
                end = Downloaded_Amount_Neat(TotalProgress.MaxProgressValue);
                files_pre.Wait();
                string[] files = files_pre.Result.Split('\n');
                Queue<int> IndexesWaiting = new();
                for (int i = 0; i < cf.Format.DownloadThreads; i++)
                {
                    IndexesWaiting.Enqueue(i);
                }
                _ = Parallel.ForEach(files, new ParallelOptions() { MaxDegreeOfParallelism = cf.Format.DownloadThreads },
                    file =>
                    {
                        try
                        {
                            int v;
                            lock (IndexesWaiting)
                            {
                                v = IndexesWaiting.Dequeue();
                            }
                            Tuple<ProgressBar<ulong>, Label, int> pb = ProgressThreads[v];
                            StartDownloadForFile(file, pb.Item1, pb.Item2).Wait();
                            IndexesWaiting.Enqueue(v);
                        }
                        catch (Exception exception)
                        {
                            HandelError(exception);
                        }
                    }
                );
            }
            catch (Exception ex)
            {
                HandelError(ex);
            }
        }
        catch (Exception exception)
        {
            HandelError(exception);
            throw;
        }
    }

    public void HandelError(Exception ex)
    {
        StackTrace st = new(ex, true);
        StackFrame? frame = st.GetFrame(0);
        if (frame is null) return;
        int line = frame.GetFileLineNumber();
        string msg = $"Error in Updater.exe in function Download_DoWork on line '{line}' through error '{ex.Message}'\nERROR:\n'{ex}' FRAM:\n '{frame}'";
        MessageBox.Show(ex.ToString());
        _ = new HttpClient().PostAsync($"https://{Handler.Domain}/Updater/Error/updater", new StringContent(msg));
    }

    private async Task StartDownloadForFile(string file, ProgressBar<ulong> pb, Label pbLabel)
    {
        if (string.IsNullOrWhiteSpace(file)) return;
        string[] temp = file.Split('/');
        if (file.Contains('/'))
        {
            string cur = "";
            for (int i = 0; i < temp.Length - 1; i++)
            {
                cur = Path.Join(cur, temp[i]);
                if (!Directory.Exists(Path.Join(Argumets.LocalDirectory, cur)))
                    Directory.CreateDirectory(Path.Join(Argumets.LocalDirectory, cur));
            }
        }
        using HttpClient client = new();
        string max = await client.GetStringAsync($"https://{Handler.Domain}/Updater/GetFileSize?directory={Argumets.RemoteDirectory}{Argumets.UriBranch}{Argumets.UriVersion}{Argumets.UriSeflContaind}{Argumets.UriPlatform}&file={file}");
        Invoke(() =>
        {
            pb.ProgressValue = 0;
            pb.MaxProgressValue = ulong.Parse(max);
            string s = $"Downloading {temp[^1]}";
            try
            {
                pbLabel.Text = s;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            } 
        });
        await DownloadFileAsync(pb,
            $"https://{Handler.Domain}/Updater/GetFile?directory={Argumets.RemoteDirectory}{Argumets.UriBranch}{Argumets.UriVersion}{Argumets.UriSeflContaind}{Argumets.UriPlatform}&file={file}",
            file.Replace("/", Handler.Slash));
    }
    
    private async Task DownloadFileAsync(ProgressBar<ulong> progressBar, string url, string destinationPath)
    {
        using (HttpClient client = new HttpClient())
        using (HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
        {
            response.EnsureSuccessStatusCode();


            using (var contentStream = await response.Content.ReadAsStreamAsync())
            using (var fileStream = new FileStream(Path.Join(Argumets.LocalDirectory, destinationPath), FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
            {
                var buffer = new byte[8192];
                long totalRead = 0;
                int bytesRead;

                while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                {
                    await fileStream.WriteAsync(buffer, 0, bytesRead);
                    totalRead += bytesRead;
                    TotalProgress.ProgressValue += (ulong)bytesRead;
                    progressBar.ProgressValue = (ulong)totalRead;
                }
            }
        }
    }
    
    private void Download_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e)
    {
        try
        {
            Invoke(Close);
        }
        catch (Exception exception)
        {
            Console.WriteLine(exception);
        }
    }
    
    #region Neat
    private string Download_Speed_Neat(double Number)
    {
        ConfigFileSize cfs = cf.Format.DownloadSpeed;
        string Size = SizeName(Number, cfs);
        int bytes = 1024;
        if (cfs == ConfigFileSize.MebiBit || cfs == ConfigFileSize.MebiByte) bytes = 1000;
        int Exponet;
        if (Number / bytes >= 1)
            if (Number / Math.Pow(bytes, 2) >= 1)
                if (Number / Math.Pow(bytes, 3) >= 1) Exponet = 3;
                else Exponet = 2;
            else Exponet = 1;
        else Exponet = 0;
        if (cfs == ConfigFileSize.MebiBit || cfs == ConfigFileSize.MegaBit) Number *= 8;
        Number = Math.Round(Number / Math.Pow(bytes, Exponet), 2, MidpointRounding.ToEven);
        if (!Number.ToString().Contains('.'))
        {
            return $"Adverage Speed: {Number}.00{Size}/s";
        }
        else
        {
            if (Number.ToString().Remove(0, Number.ToString().IndexOf('.')).Length == 1)
            {
                return $"Adverage Speed: {Number}0{Size}/s";
            }
            else
            {
                return $"Adverage Speed: {Number}{Size}/s";
            }
        }
    }

    private void Set_Downloaded_Amount_Neat()
    {
        Total_Downloaded!.Text = $"Downloaded {Downloaded_Amount_Neat(TotalProgress.ProgressValue)} of {end}";
    }

    private string Downloaded_Amount_Neat(double Number)
    {
        int Exponet = 0;
        ConfigFileSize cfs = cf.Format.DownloadAmount;
        int bytes = 1024;
        if (cfs == ConfigFileSize.MebiBit || cfs == ConfigFileSize.MebiByte) bytes = 1000;
        string Size = SizeName(Number, cfs);
        if (Size.ToLower().StartsWith('g')) Exponet = 3;
        else if (Size.ToLower().StartsWith('m')) Exponet = 2;
        else if (Size.ToLower().StartsWith('k')) Exponet = 1;
        Number = Math.Round(Number / Math.Pow(bytes, Exponet), 2, MidpointRounding.ToEven);
        if (cfs == ConfigFileSize.MebiBit || cfs == ConfigFileSize.MegaBit) Number *= 8;
        string text = "";
        if (!Number.ToString().Contains('.'))
        {
            text = $"{Number}.00{Size}{text}";
        }
        else
        {
            text = Number.ToString().Remove(0, Number.ToString().IndexOf('.')).Length != 1 ? $"{Number}{Size}{text}" : $"{Number}0{Size}{text}";
        }

        return text;
    }
    
    private static string SizeName(double Number, ConfigFileSize format)
    {
        string Size;
        switch (format)
        {
            default:
            case ConfigFileSize.MegaByte:
                if (Number / 1000 >= 1)
                    if (Number / Math.Pow(1000, 2) >= 1)
                        if (Number / Math.Pow(1000, 3) >= 1) Size = "GB";
                        else Size = "MB";
                    else Size = "KB";
                else Size = "B";
                break;
            case ConfigFileSize.MebiByte:
                if (Number / 1024 >= 1)
                    if (Number / Math.Pow(1024, 2) >= 1)
                        if (Number / Math.Pow(1024, 3) >= 1) Size = "GiB";
                        else Size = "MiB";
                    else Size = "KiB";
                else Size = "B";
                break;
            case ConfigFileSize.MegaBit:
                Number *= 8;
                if (Number / 1000 >= 1)
                    if (Number / Math.Pow(1000, 2) >= 1)
                        if (Number / Math.Pow(1000, 3) >= 1) Size = "Gb";
                        else Size = "Mb";
                    else Size = "Kb";
                else Size = "b";
                break;
            case ConfigFileSize.MebiBit:
                Number *= 8;
                if (Number / 1024 >= 1)
                    if (Number / Math.Pow(1024, 2) >= 1)
                        if (Number / Math.Pow(1024, 3) >= 1) Size = "Gib";
                        else Size = "Mib";
                    else Size = "Kib";
                else Size = "b";
                break;
        }
        return Size;
    }
    #endregion
}