using System.ComponentModel;
using System.Diagnostics;
using System.Net;
using System.Reflection;
using System.Text.Json;
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)
    {
        base.ShowMissingChar = false;
        base.LogFrames = false;
        Speed = new()
        {
            Interval = 500
        };
        Speed.Elapsed += Speed_Elapsed;
        CanControleUpdate = false;
        //small = (uint)Math.Round(0.0656167979002625 * StartGUI.Height, 0);
        //RenderObjects.Add(Total_Downloaded = new RenderText("Download Amount", "OpenSans-Regular", small, -0.9381188f, 0.149797574f, 1.0f, new Vector2(1f, 0f), new Vector4(cf.Colors.Text.R, cf.Colors.Text.G, cf.Colors.Text.B, cf.Colors.Text.A)));
        //small = (uint)Math.Round(0.078740157480315 * StartGUI.Height, 0);
        //RenderObjects.Add(Transfer_Speed = new RenderText("Download Speed", "OpenSans-Regular", small, -0.9381188f, -0.05263158f, 1.2f, new Vector2(1f, 0f), new Vector4(cf.Colors.Text.R, cf.Colors.Text.G, cf.Colors.Text.B, cf.Colors.Text.A)));

        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(File.OpenRead("/home/jacob/Pictures/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,
            ProgressGap = new(cf.Scale(3)),
            Shader = Rectangle.DefaultAlphaShader[Context],
            TextureDisplay = TextureDisplay.ProgressHorizontalCenter,
            UpdateOnDraw = true,
            InnerShader = Rectangle.DefaultAlphaShader[Context]
        });
        
        
        fi.PixelHeight = (uint)WallDistance;
        Controls.Add(Total_Downloaded = new(fi)
        {
            Text = "D",
            Location = new((int)FloatToInt(-0.9381188f), (int)FloatToInt(0.149797574f + 0.14f, true))
        });
        Controls.Add(Transfer_Speed = new(fi)
        {
            Text = "abcdefghijklmnopqrstuvwxyz",
            Location = new(Total_Downloaded.Location.X, (int)FloatToInt(-0.05263158f + 0.09f, true))
        });

        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(cf.Scale(5), (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,
                ProgressGap = TotalProgress.ProgressGap,
                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(new Action(() => { Download.RunWorkerAsync(); }));
        Speed.Start();
        
        
        
        Controls.Add(new Label(fi)
        {
            Location = new(10,10),
            Text = FloatToInt(-0.11304348f, true).ToString() + " / " 
                + FloatToInt(-0.133858263f, true).ToString() + " / "
                + FloatToInt(-0.314960629f, true).ToString() + " / "
                + FloatToInt(-0.341389941f, true).ToString() + " / "
                + FloatToInt(-0.5748032f, true).ToString()
        });
    }

    private ulong last = 0;
    Queue<ulong> ProgressQuee = new();
    
    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);
        //Debug.WriteLine($"l: {last}");
        //Debug.WriteLine($"bps: {BytesPertenthSec}");
        for (int i = 0; i < ProgressQuee.Count; i++)
        {
            Console.WriteLine(TotalProgress.ProgressValue);
            TotalProgress.ProgressValue += ProgressQuee.Dequeue();
        }
        if (Transfer_Speed is not null && speeds.Average() != 0) Transfer_Speed.Text = Download_Speed_Neat(speeds.Average());
    }

    private bool ran = true;
    
    private void Download_DoWork(object? sender, DoWorkEventArgs e)
    {
        try
        {
            ChangeProcessText("Deleting old program files");
            
            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 { }
                    }
                    foreach (var subdir in dirInfo.GetDirectories())
                    {
                        TryDeleteDir(subdir.FullName);
                    }
                }
            }
            TryDeleteDir(Argumets.LocalDirectory);
            Directory.CreateDirectory(Argumets.LocalDirectory);
            if (Argumets.Updater is not null) TryDeleteDir(Argumets.Updater);
            if (Argumets.Updater is not null) Directory.CreateDirectory(Argumets.Updater);

    #pragma warning disable SYSLIB0014 // Type or member is obsolete
            using WebClient webPre = new();
    #pragma warning restore SYSLIB0014 // Type or member is obsolete
            ChangeProcessText("Getting update info");
            string pp =
                $"https://{Handler.Domain}/Updater/Files?directory={Argumets.RemoteDirectory}{Argumets.UriBranch}{Argumets.UriSeflContaind}{Argumets.UriPlatform}{Argumets.UriVersion}";
            string rem = webPre.DownloadString($"https://{Handler.Domain}/Updater/Files?directory={Argumets.RemoteDirectory}{Argumets.UriBranch}{Argumets.UriSeflContaind}{Argumets.UriPlatform}{Argumets.UriVersion}");
            //string uri;
            try
            {
                ulong realmax = ulong.Parse(webPre.DownloadString($"https://{Handler.Domain}/Updater/GetSize?directory={Argumets.RemoteDirectory}{Argumets.UriBranch}{Argumets.UriSeflContaind}{Argumets.UriPlatform}{Argumets.UriVersion}"));
                TotalProgress.MaxProgressValue = realmax;
                Console.WriteLine(TotalProgress.MaxProgressValue);
                string[] files = rem.Split('\n');
                int c = -1;

                Tuple<ProgressBar<ulong>, Label, int> getpb()
                {
                    c++;
                    if (c == ProgressThreads.Count) c = 0;
                    return ProgressThreads[c];
                }
                _ = Parallel.ForEach(files, new ParallelOptions() {MaxDegreeOfParallelism = cf.Format.DownloadThreads},file =>
                {
#pragma warning disable SYSLIB0014 // Type or member is obsolete
                    using WebClient web = new();
#pragma warning restore SYSLIB0014 // Type or member is obsolete
                    
                    if (string.IsNullOrEmpty(file)) return;
                    string uri = $"https://{Handler.Domain}/Updater/GetFileSize?directory={Argumets.RemoteDirectory}{Argumets.UriBranch}{Argumets.UriVersion}{Argumets.UriSeflContaind}{Argumets.UriPlatform}&file={file}";
                    Uri u = new($"https://{Handler.Domain}/Updater/GetFile?directory={Argumets.RemoteDirectory}{Argumets.UriBranch}{Argumets.UriVersion}{Argumets.UriSeflContaind}{Argumets.UriPlatform}&file={file}");
                    string[] temp = file.Split('/');
                    if (file.Contains('/'))
                    {

                        string cur = "";
                        for (int i = 0; i < temp.Length - 1; i++)
                        {
                            cur += temp[i] + "/";
                            if (Argumets.Updater is null)
                            {
                                if (!Directory.Exists(Argumets.LocalDirectory + cur))
                                    Directory.CreateDirectory(Argumets.LocalDirectory + cur);
                            }
                            else
                            {
                                if (!Directory.Exists(Argumets.Updater + cur))
                                    Directory.CreateDirectory(Argumets.Updater + cur);
                            }
                        }
                    }
                    
                   
                    DownloadFile(getpb(), web, uri, u, temp, file.Replace("/", Handler.Slash)).Wait();
                });
            }
            catch (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));
            }
        }
        catch (Exception exception)
        {
            Console.WriteLine(exception);
            throw;
        }
    }
    
    private readonly string m_Locker = "THREAD_LOCKER";
    
    
    
    private Task DownloadFile(Tuple<ProgressBar<ulong>, Label, int> pb, WebClient web, string uri, Uri u, string[] temp, string file)
    {
        while (!web.IsBusy)
        {
            
            string a = web.DownloadString(uri);
            pb.Item1.MaxProgressValue = ulong.Parse(a);
            pb.Item1.ProgressValue = 0;
            web.DownloadProgressChanged += (sender, args) =>
            {
                ulong tmp = (ulong)args.BytesReceived - pb.Item1.ProgressValue;
                ProgressQuee.Enqueue(tmp);
                
                pb.Item1.ProgressValue = (ulong)args.BytesReceived;
                if (ran)
                {
                    ran = false;
                    Invoke( () =>
                    {
                        Total_Downloaded!.Text =
                            $"Downloaded {TotalDownloadAmountNeat(TotalProgress.ProgressValue)}";
                        ran = true;
                    });
                }
            };
            string s = $"Downloading {temp[^1]}";
            ChangeProcessText(s);
            try
            {
                pb.Item2.Text = s;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            } 
            if (Argumets.Updater is null)
            {
                web.DownloadFileTaskAsync(u, Path.Combine(Argumets.LocalDirectory, file)).Wait();
            }
            else
            {
                web.DownloadFileTaskAsync(u, Path.Combine(Argumets.Updater, file)).Wait();
            }

            return Task.CompletedTask;
        }
        return Task.CompletedTask;
    }
    
    private void Download_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e)
    {
        try
        {
            Close();
        }
        catch (Exception exception)
        {
            Console.WriteLine(exception);
        }
    }
    
    private void ChangeProcessText(string Process)
    {
        Console.WriteLine(Process);
        //queue.Enqueue(new Action(() => { if (Current_Process is not null) Current_Process.Text = $"{Process}"; }));
    }
    
    #region Neat
    private string Download_Speed_Neat(double Number)
    {
        Debug.WriteLine($"a: {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 $"{Number}.00{Size}/s";
        }
        else
        {
            if (Number.ToString().Remove(0, Number.ToString().IndexOf('.')).Length == 1)
            {
                return $"{Number}0{Size}/s";
            }
            else
            {
                return $"{Number}{Size}/s";
            }
        }
    }

    private string Downloaded_Amount_Neat(double Number, string Old_Text)
    {
        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 = Old_Text.Remove(0, Old_Text.IndexOf(' '));
        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 string TotalDownloadAmountNeat(double Number)
    {
        string Output;
        int Exponet;
        ConfigFileSize cfs = cf.Format.DownloadAmount;
        int bytes = 1024;
        if (cfs == ConfigFileSize.MebiBit || cfs == ConfigFileSize.MebiByte) bytes = 1000;
        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;
        Output = $"{Math.Round(Number / Math.Pow(bytes, Exponet), 2, MidpointRounding.ToEven)}{SizeName(Number, cfs)}";
        return Output;
    }

    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
}