using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using GraphicsManager.Enums;
using GraphicsManager.Interfaces;
using GraphicsManager.Objects;
using GraphicsManager.Objects.Core;
using Luski.GUI.MainScreen.UI;
using Luski.GUI.MainScreen.UI.LuskiControls;
using Luski.GUI.MainScreen.UI.LuskiSettings;
using Luski.GUI.MainScreen.UI.PublicServers;
using Luski.net;
using Luski.net.Structures.Public;
using Luski.Shared.PublicServers.V1.Enums;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
using DebugProc = OpenTK.Graphics.OpenGL4.DebugProc;
using DebugSeverity = OpenTK.Graphics.OpenGL4.DebugSeverity;
using DebugSource = OpenTK.Graphics.OpenGL4.DebugSource;
using DebugType = OpenTK.Graphics.OpenGL4.DebugType;
using ErrorCode = OpenTK.Windowing.GraphicsLibraryFramework.ErrorCode;
using Window = GraphicsManager.Window;

namespace Luski.GUI;

public class MainScreenWindow : Window
{
    public static readonly NativeWindowSettings Settings = new()
    {
        Title = "Luski",
        WindowBorder = WindowBorder.Resizable,
        APIVersion = new Version(3, 2),
        API = ContextAPI.OpenGL,
        StartFocused = true,
        Size = new Vector2i(624, 1090),
        Icon = Globals.Icon,
        SharedContext = null,
    };

    public TabControl? tc;
    private FlowLayout? channelpicker, friends, friend_request;
    private RoundedButton? FriendManagerBtn;
    private static DebugProc DebugMessageDelegate = OnDebugMessage;
    private static GLFWCallbacks.ErrorCallback GLFW_Error = OnGLFW_Error;

    private static void OnGLFW_Error(ErrorCode e, string d)
    {
        if ((Globals.Settings.Logs & ConsoleLog.ShowErrorsForGLFW) != ConsoleLog.ShowErrorsForGLFW) return;
        Console.ForegroundColor = ConsoleColor.DarkRed;
        Console.WriteLine("[GLFW Error:{0}] {1}", e, d);
        Console.ResetColor();
    }

    private static void OnDebugMessage(
        DebugSource source, // Source of the debugging message.
        DebugType type, // Type of the debugging message.
        int id, // ID associated with the message.
        DebugSeverity severity, // Severity of the message.
        int length, // Length of the string in pMessage.
        IntPtr pMessage, // Pointer to message string.
        IntPtr pUserParam) // The pointer you gave to OpenGL, explained later.
    {
        string message = string.Empty;

        switch (severity)
        {
            case DebugSeverity.DebugSeverityHigh:
                if ((Globals.Settings.Logs & ConsoleLog.BigErrosForOpenGL) == ConsoleLog.BigErrosForOpenGL)
                {
                    message = Marshal.PtrToStringAnsi(pMessage, length);
                    Console.ForegroundColor = ConsoleColor.DarkRed;
                    Console.WriteLine("[{0} source={1} type={2} id={3}] {4}", severity, source, type, id, message);
                    Console.ResetColor();
                }

                break;
            case DebugSeverity.DebugSeverityMedium:
                if ((Globals.Settings.Logs & ConsoleLog.MediumErrosForOpenGL) == ConsoleLog.MediumErrosForOpenGL)
                {
                    message = Marshal.PtrToStringAnsi(pMessage, length);
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.WriteLine("[{0} source={1} type={2} id={3}] {4}", severity, source, type, id, message);
                    Console.ResetColor();
                }
                break;
            case DebugSeverity.DebugSeverityLow:
                if ((Globals.Settings.Logs & ConsoleLog.LowErrosForOpenGL) == ConsoleLog.LowErrosForOpenGL)
                {
                    message = Marshal.PtrToStringAnsi(pMessage, length);
                    Console.ForegroundColor = ConsoleColor.DarkYellow;
                    Console.WriteLine("[{0} source={1} type={2} id={3}] {4}", severity, source, type, id, message);
                    Console.ResetColor();
                }
                break;
            default:
                if ((Globals.Settings.Logs & ConsoleLog.InfoForOpenGL) == ConsoleLog.InfoForOpenGL)
                {
                    message = Marshal.PtrToStringAnsi(pMessage, length);
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.WriteLine(message);
                    Console.ResetColor();
                }
                break;
        }
    }
    

    public MainScreenWindow() : base(Settings)
    {
        Globals.ms = this;
        Size = new(1332.ScaleInt(), 866.ScaleInt());
        ShowMissingChar = true;
        LogFrames = ((Globals.Settings.Logs & ConsoleLog.DrawFrames) == ConsoleLog.DrawFrames);
        VSync = VSyncMode.On;

        GL.DebugMessageCallback(DebugMessageDelegate, IntPtr.Zero);
        GL.Enable(EnableCap.DebugOutput);
        GLFW.SetErrorCallback(GLFW_Error);
        try
        {
            Globals.DefaultFontFamly = FontFamily.LoadFontFamily(Globals.GetResource("Fonts.OpenSans.zip"), "OpenSans");
            Globals.DefaultFont = FontInteraction.Load(Globals.DefaultFontFamly);
            string fams = Path.Combine(Globals.LuskiPath, "FontFamilies");
            if (!Directory.Exists(fams)) Directory.CreateDirectory(fams);
            DirectoryInfo di = new DirectoryInfo(fams);
            
            foreach (FileInfo fam in di.GetFiles())
            {
                try
                {
                    if (fam.FullName.ToLower().EndsWith(".zip"))
                        Globals.DefaultFont.AddFamily(FontFamily.LoadFontFamily(File.Open(fam.FullName, FileMode.Open), fam.Name));
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }


        Globals.DefaultFont.PixelHeight = Globals.Settings.DefaultFontPX.ScaleFont();
        Globals.DefaultFont.FontSize = FontSize.Regular;
        Globals.TopTimeFont = Globals.DefaultFont.Clone();
        Globals.TopTimeFont.PixelHeight = Globals.Settings.TopTimeFontPX.ScaleFont();
        Globals.TopTimeFont.FontSize = FontSize.Regular;
        Globals.ServerRoleFont = Globals.DefaultFont.Clone();
        Globals.ServerRoleFont.PixelHeight = Globals.Settings.RoleSettingsFontPX.ScaleFont();
        Globals.ServerRoleFont.FontSize = FontSize.Regular;
        Globals.MessageFont = Globals.DefaultFont.Clone();
        Globals.MessageFont.PixelHeight = Globals.Settings.MessageFontPX.ScaleFont();
        Globals.MessageFont.ExtraLinePixels = Globals.Settings.MessageFontLineSpacePX.ScaleFont();
        Globals.MessageFont.FontSize = FontSize.Regular;
        Globals.SmallTimeFont = Globals.DefaultFont.Clone();
        Globals.SmallTimeFont.PixelHeight = ((uint)11).ScaleFont();
        Globals.LuskiTexture = TextureManager.GetTextureResource("Luski.png");
        try
        {
            CenterWindow(Globals.Settings.Display);
        }
        catch (Exception e)
        {
            CenterWindow();
        }
        WindowLoaded += OnWindowLoaded;
    }

    private async Task OnWindowLoaded(Window arg)
    {
        if (Globals.UpdaterSettings.AutoUpdateCheck && new HttpClient()
                .GetAsync(
                    $"https://www.jacobtech.com/Updater/GetProgramVersion?directory=Luski&branch=main&selfcontained={Globals.UpdaterSettings.SelfContained.ToString().ToLower()}&platform={Globals.UpdaterSettings.Platform}")
                .Result.Content.ReadAsStringAsync().Result !=
            FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion)
        {
            var update = new UpdateWindow();
            var result = update.ShowDialogue(this);
            if (result == UpdateWindow.DialogueResult.Yes)
            {
                Globals.Download = true;
                Close();
            }
        }
        await LoginOnChangeToApp();
    }
    
    public FlowLayout ser;
    private UserControl? SerBox;
    public PublicChat pc;

    public async Task LoadPublicServer(PublicServer? Server)
    {
        GL.Enable(EnableCap.DepthTest);
        GL.Enable(EnableCap.Multisample);
        GL.DepthFunc(DepthFunction.Always);
        if (Server is null) return;
        if (!Server.IsLogedIn)
        {
            ServerLoginOverlay SLO = new(Server.Domain);
            Controls.Add(SLO);
            ForceUpdate(new(ClientSize));
            Globals.PrintParent(this);
            return;
        }
        BlockDraw = true;
        try
        {
            #region Box Init

            if (SerBox is null)
            {
                SerBox = new()
                {
                    Location = new(ser.Size.X, 0, 0),
                    Size = new(Size.X - ser.Size.X, ClientSize.Y),
                    Anchor = ObjectAnchor.All,
                    BackgroundColor = new(20, 20, 20, 255)
                };
                Controls.Add(SerBox);
                SerBox.LoadToParent(this, this);
            }

            SerBox.Controls.Clear();

            #endregion

            #region Server Title

            UserControl ServerTitle = new()
            {
                Size = new(307.ScaleInt(), 46.ScaleInt()),
                BackgroundColor = new(30, 30, 30, 255)
            };
            Label title = new(Globals.DefaultFont)
            {
                Text = Server.Name,
                IgnoreHover = true
            };
            if (await Server.User.HasPermissions(ServerPermission.ManageRoles))
            {
                ServerTitle.Clicked += o =>
                {
                    ServerSettings sm = new(Server);
                    Controls.Add(sm);
                    Globals.ms.DrawFrame();
                    Globals.ms.ForceUpdate(new(Globals.ms.CS));
                    Globals.ms.DrawFrame();
                    return Task.CompletedTask;
                };
                title.Color = Color4.Green;
            }
            title.Location = new(5.ScaleInt(), (int)((ServerTitle.Size.Y - title.LineHeight) / 2), 0);
            ServerTitle.Controls.Add(title);
            ServerTitle.ForceDistanceUpdate(SerBox);
            SerBox.Controls.Add(ServerTitle);

            #endregion
            
            #region Channel Selector Init

            SocketChannel current_channel = await Server.User.GetSelectedChannel(CancellationToken.None);
            List<SocketCategory> parents = new();
            SocketCategory? cur = await current_channel.GetParent();
            while (cur is not null)
            {
                parents.Add(cur);
                cur = await cur.GetParent();
            }

            parents.Reverse();
            ChannelSelector cs = await ChannelSelector.MakeSelector(parents[0]);
            cs.BackgroundColor = new(34, 34, 34, 255);
            cs.Location = new(0, ServerTitle.Size.Y +2.ScaleInt(), 0);
            cs.Size = new(ServerTitle.Size.X, SerBox.Size.Y - 54.ScaleInt() - cs.Location.Y);
            cs.Anchor = ObjectAnchor.Top | ObjectAnchor.Left | ObjectAnchor.Bottom;
            
            parents.RemoveAt(0);
            SerBox.Controls.Add(cs);
            cs.ForceDistanceUpdate(SerBox);

            #endregion

            #region Chat Init

            pc = new()
            {
                Anchor = ObjectAnchor.All,
                Location = new(cs.Size.X, 0, 0),
                Size = new(SerBox.Size.X - cs.Size.X, SerBox.Size.Y),
            };
            SerBox.Controls.Add(pc);
            pc.LoadToParent(SerBox, this);
            pc.ForceDistanceUpdate();
            pc.MessageFlow.ForceDistanceUpdate(pc);
            #endregion

            #region Channel Selector Loader

            _ = cs.Load(current_channel, parents);

            #endregion

            #region User Icon
            Role[] ra = await Server.User.GetRoles();
            Color c = ra[0].Color;
            Color4 c4 = new(c.R, c.G, c.B, c.A);
            ServerProfile DefaultProfile = await Server.GetProfile(Server.User.ServerProfile, CancellationToken.None);
            IRenderObject u = await DefaultProfile.MakeRct(Server.User, new(46.ScaleInt()));
            int ii = 4.ScaleInt();
            u.Location = new(ii, cs.Location.Y + cs.Size.Y + ii, 0);
            u.Anchor = ObjectAnchor.Bottom | ObjectAnchor.Left;
            SerBox.Controls.Add(u);
            u.LoadToParent(SerBox, this);
            u.ForceDistanceUpdate();
            Label ul = new Label(Globals.DefaultFont)
            {
                Anchor = u.Anchor,
                Text = DefaultProfile.DisplayName,
                Color = c4
            };

            ul.Location = new(u.Location.X + u.Size.X + 5.ScaleInt(),
                (u.Location.Y + ((u.Size.Y - ul.Size.Y) / 2)), 0);
            Rectangle Expand = new(TextureManager.GetTextureResource("Expand.png"))
            {
                Size = new(20.ScaleInt()),
                Shader = Rectangle.DefaultAlphaShader[Context],
                BackgroundColor = Color4.Gray,
                Tag = new Tuple<SocketAppUser, IRenderObject, Label>(Server.User, u, ul),
                Anchor = u.Anchor
            };
            Expand.Clicked += ExpandOnClicked;
            Expand.MouseEnter += _ =>
            {
                Expand.BackgroundColor = Color4.White;
                return Task.CompletedTask;
            };
            Expand.MouseLeave += _ =>
            {
                Expand.BackgroundColor = Color4.Gray;
                return Task.CompletedTask;
            };
            Expand.Location = new(ul.Location.X + ul.Size.X + 5.ScaleInt(),
                u.Location.Y + ((u.Size.Y - Expand.Size.Y) / 2), 0);
            Expand.ForceDistanceUpdate(SerBox);
            SerBox.Controls.Add(Expand);
            SerBox.Controls.Add(ul);
            Rectangle setting = new(TextureManager.GetTextureResource("settings.png"))
            {
                Location = new(cs.Size.X - 40.ScaleInt(), cs.Location.Y + cs.Size.Y + 11.ScaleInt(),0),
                Size = new(32.ScaleInt()),
                Shader = Rectangle.DefaultAlphaShader[Context],
                BackgroundColor = Color4.Gray,
                Anchor = ObjectAnchor.Bottom | ObjectAnchor.Left
            };
            setting.MouseEnter += o =>
            {
                setting.BackgroundColor = Color4.White;
                return Task.CompletedTask;
            };
            setting.MouseLeave += o =>
            {
                setting.BackgroundColor = Color4.Gray;
                return Task.CompletedTask;
            };
            setting.Clicked += SettingOnClicked;
            setting.ForceDistanceUpdate(SerBox);
            SerBox.Controls.Add(setting);
            ForceUpdate(new (Size));
            TryDraw();
            #endregion
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }

        BlockDraw = false;
    }

    private FlowLayout? ProfileFlow = null;

    private async Task ExpandOnClicked(IRenderObject arg)
    {
        if (ProfileFlow is null)
        {
            ProfileFlow = new();
            Tuple<SocketAppUser, IRenderObject, Label> s = (Tuple<SocketAppUser, IRenderObject, Label>)arg.Tag!;
            var role = (await s.Item1.GetRoles())[0];
            foreach (var prof in await s.Item1.GetProfiles(CancellationToken.None))
            {
                ProfileView con = await ProfileView.Make(s.Item1, prof, role);
                con.Clicked += async o =>
                {
                    IRenderObject iro = await prof.MakeRct(s.Item1, s.Item2.Size);
                    iro.Location = s.Item2.Location;
                    iro.Distance = s.Item2.Distance;
                    iro.Anchor = s.Item2.Anchor;
                    int oldx = s.Item3.Size.X;
                    s.Item2.Parent!.Controls.Add(iro);
                    s.Item2.Parent!.Controls.Remove(s.Item2);
                    s.Item3.Text = prof.DisplayName;
                    Controls.Remove(ProfileFlow);
                    ProfileFlow = null;
                    arg.Location = new(arg.Location.X - oldx + s.Item3.Size.X, arg.Location.Y, arg.Location.Z);
                    arg.ForceDistanceUpdate(arg.Parent!);
                    Globals.ServerProfile = prof;
                    arg.Tag = new Tuple<SocketAppUser, IRenderObject, Label>(s.Item1, iro, s.Item3);
                    TryDraw();
                };
                ProfileFlow.Controls.Add(con);
                ProfileFlow.Size = new((int)arg.SizeAsFloat.X, ProfileFlow.Size.Y + con.Size.Y);
            }

            ProfileFlow.Location = new(ser.Size.X + 5.ScaleInt(),
                CS.Y - 54.ScaleInt() - ProfileFlow.Size.Y,
                0);
            Controls.Add(ProfileFlow);
        }
        else
        {
            Controls.Remove(ProfileFlow);
            ProfileFlow = null;
        }
        DrawFrame();
    }

    private Task SettingOnClicked(IRenderObject arg)
    {
        SettingsMenu sm = new();
        Controls.Add(sm);
        Globals.ms.DrawFrame();
        return Task.CompletedTask;
    }

    public async Task LoadMainServer(MainServer Server)
    {
        if (SerBox is null)
            SerBox = new()
            {
                Location = new(ser.Size.X, 0, 0),
                Size = new(Size.X - ser.Size.X, Size.Y),
                Anchor = ObjectAnchor.All
            };
        Controls.Add(SerBox);
        SerBox.ForceDistanceUpdate();
        SerBox.Controls.Clear();
    }

    private async Task<Task> LoginOnChangeToApp()
    {
        Controls.Clear();
        ForceUpdate(new(ClientSize));
        BlockDraw = true;
        Title = "Luski";
        unsafe
        {
            GLFW.SetWindowSizeLimits(WindowPtr, 500.ScaleInt(), 250.ScaleInt(),GLFW.DontCare, GLFW.DontCare);
        }
        
        try
        {
            CenterWindow(Globals.Settings.Display);
        }
        catch (Exception e)
        {
            CenterWindow();
        }
        WindowBorder = WindowBorder.Resizable;
        BackgroundColor = new Color4(20, 20, 20, 255);

        Controls.Add(ser = new FlowLayout()
        {
            BackgroundColor = new(26, 26, 26, 255),
            Size = new(68.ScaleInt(), ClientSize.Y),
            Anchor = ObjectAnchor.Top | ObjectAnchor.Left | ObjectAnchor.Bottom,
            Location = new(0,0,0)
        });
        ser.LoadToParent(this,this);
        DrawFrame();
        DateTime utcNow = DateTime.UtcNow;
        Task.WhenAll(Globals.ServersLoading.ToArray()).Wait();
        foreach (PublicServer pser in Globals.Luski.LoadedServers)
        {
            ServerIcon<PublicServer> si = new ServerIcon<PublicServer>(pser);
            ser.Controls.Add(si);
            si.LoadToParent(ser, this);
        }

        foreach (PublicServer pser in Globals.Luski.FailedServers)
        {
            Console.WriteLine(pser.Name);
            ServerIcon<PublicServer> si = new ServerIcon<PublicServer>(pser, true);
            ser.Controls.Add(si);
            si.LoadToParent(ser, this);
        }

        AddServerIcon asi = new();
        asi.Clicked += AddButtonClicked;
        ser.Controls.Add(asi);


        
        
        if (ser.Controls.Length > 1) (ser.Controls[0] as ServerIcon<PublicServer>)!.LoadServer().Start();
        DrawFrame();
        MainShow += OnMainShow;
        return Task.CompletedTask;
    }
    
    protected override void OnResize(ResizeEventArgs e)
    {
        base.OnResize(e);
       
    }
    
    private Task AddButtonClicked(IRenderObject arg)
    {
        AddServerOverlay aso = new();
        aso.Clicked += AsoOnClicked;
        Controls.Add(aso);
        
        TryDraw();
        OnResize(new(CS));
        return Task.CompletedTask;
        
    }

    private Task AsoOnClicked(IRenderObject arg)
    {
        Globals.ms.Controls.Remove(arg);
        Globals.ms.DrawFrame();
        AddServerOverlay aso = (arg as AddServerOverlay)!;
        aso.Clicked -= AsoOnClicked;
        aso.Clean();
        aso = null!;
        return Task.CompletedTask;
    }

    private Task OnMainShow()
    {
        if (Globals.Luski.MainServer is not null && Globals.Luski.MainServer.IsLogedIn)
        {
            Globals.Luski.MainServer.OnError += LuskiOnOnError;
        }

        return Task.CompletedTask;
    }

    public event Func<Task>? MainShow;

    private Task LuskiOnOnError(Exception arg)
    {
        Console.WriteLine(arg);
        return Task.CompletedTask;
    }

}