using System.CodeDom.Compiler;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using GraphicsManager;
using GraphicsManager.Interfaces;
using GraphicsManager.Objects;
using GraphicsManager.Objects.Core;
using Luski.Classes;
using Luski.Classes.ThemeSub;
using Luski.GUI;
using Luski.net;
using Luski.net.Enums;
using Luski.net.Interfaces;
using Luski.net.Structures.Public;
using Luski.Shared.PublicServers.V1.Enums;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common.Input;
using OpenTK.Windowing.Desktop;

namespace Luski;

public static class Globals
{
#pragma warning disable CS8618
    public static List<Task<bool>> ServersLoading = new();
    public static ServerList ServerList;
    public static Dictionary<Window, List<IRenderObject>> AllowedBehindObjects = new();

    public static ThemeStart Theme
    {
        get
        {
            return LuskiThemes.LuskiThemeList.Where(s => s.Name == Settings.Theme).First();
        }
    }

    public static ServerThemeProperties GetTheme(this PublicServer ser)
    {
        IEnumerable<ServerTheme> l = Theme.ServerOverrides.Where(s => s.Address == ser.Domain);
        ServerTheme[] serverThemes = l as ServerTheme[] ?? l.ToArray();
        if (serverThemes.Any()) return serverThemes[0].Properties;
        return Theme.GlobalServerTemplate;
    }

    private static List<ExperimentInfo> AddedExperiments = new();
    private static List<ExperimentInfo> EnabledExperiments = new();
    public static List<ExperimentJson> MissingExperiments = new();
    private static Dictionary<ExperimentSelectorInfo, ExperimentInfo> ExperimentSelectorInfos = new();
    public static IReadOnlyList<ExperimentInfo> Experiments => AddedExperiments.AsReadOnly();

    private static int LastExpCount = 0;

    public static Color4 DodgerBlue = new Color4(30, 144, 255, 255);

    public static double GetScale()
    {
        if (Settings.Scale is not null) return Settings.Scale.Value;
        return Monitors.GetMonitorFromWindow(ms).HorizontalScale;
    }
    public static int ScaleInt(this int i)
    { 
        return (int)(GetScale() * i);
    }
    public static int ScaleInt(this double i)
    {
        return (int)(GetScale() * i);
    }
    public static double ScaleDouble(this int i)
    {
        return (GetScale() * i);
    }
    public static double ScaleDouble(this double i)
    {
       return (GetScale() * i);
    }
    public static uint ScaleFont(this uint i)
    {
        if (Globals.Settings.ScaleFonts) return (uint)(GetScale() * i);
        return i;
    }

    public static void PrintParent(IParent par)
    {
        void PrintP(int index, IParent p, Vector3i l)
        {
            string sp = "";
            for (int i = 0; i < index; i++)
            {
                sp += "    ";
            }

            for (int i = 0; i < p.Controls.Length; i++)
            {
                if (p.Controls[i].IgnoreHover) continue;
                Console.WriteLine(sp + p.Controls[i] + ": " + p.Controls[i].Location + " " + (p.Controls[i].Location + l) + " "+ p.Controls[i].Size);
                if (p.Controls[i] is IParent pp) PrintP(index + 1, pp, l + pp.Position);
            }
        }
        Console.WriteLine(par + ": " + par.Position + par.Size);
        PrintP(1,par, par.Position);
    }

    public static void RegisterExperiment(ExperimentInfo exp)
    {
        IEnumerable<ExperimentJson> found = MissingExperiments.Where(e => e.Name == exp.Name);
        ExperimentJson[] experimentInfos = found as ExperimentJson[] ?? found.ToArray();
        if (experimentInfos.Length > 0)
        {
            MissingExperiments.Remove(experimentInfos[0]);
            EnabledExperiments.Add(exp);
        }
        AddedExperiments.Add(exp);
        foreach (ExperimentSelectorInfo esi in exp.Options)
        {
            ExperimentSelectorInfos.Add(esi, exp);
        }
    }

    public static void Toggle(this ExperimentSelectorInfo exp)
    {
        if (!ExperimentSelectorInfos.TryGetValue(exp, out ExperimentInfo EI)) return;
        if (Settings.Experiments.Any(e => e.Name == EI.Name))
        {
            ExperimentJson ej = Settings.Experiments.Where(e => e.Name == EI.Name).First();
            if (EI.Selected == EI.Options.IndexOf(exp))
            {
                Settings.Experiments.Remove(ej);
                EnabledExperiments.Remove(EI);
                EI.Selected = null;
                LastExpCount--;
                exp.SendTog(false);
            }
            else
            {
                EI.Options[EI.Selected!.Value].SendTog(false);
                ej.Selected = EI.Options.IndexOf(exp);
                EI.Selected = ej.Selected;
                exp.SendTog(true);
            }
        }
        else
        {
            LastExpCount++;
            ExperimentJson ej = new()
            {
                Name = EI.Name,
                Selected = EI.Options.IndexOf(exp)
            };
            Settings.Experiments.Add(ej);
            EI.Selected = ej.Selected;
            if (!EnabledExperiments.Contains(EI)) EnabledExperiments.Add(EI);
            exp.SendTog(true);
        }
        Settings.SaveSettings(Path.Combine(Globals.LuskiPath, "Settings.json"), SettingsContext.Default.Settings);
    }
    
    public static bool IsEnabled(this ExperimentSelectorInfo exp)
    {
        if (!ExperimentSelectorInfos.TryGetValue(exp, out ExperimentInfo EI)) return false;
        if (LastExpCount != Settings.Experiments.Count)
        {
            //rescan
            foreach (ExperimentJson experiment in Settings.Experiments)
            {
                IEnumerable<ExperimentInfo> found = AddedExperiments.Where(e => e.Name == experiment.Name);
                ExperimentInfo[] experimentInfos = found as ExperimentInfo[] ?? found.ToArray();
                if (experimentInfos.Count() > 0)
                {
                    if (!EnabledExperiments.Contains(EI)) EnabledExperiments.Add(experimentInfos[0]);
                    experimentInfos[0].Selected = experiment.Selected;
                }
                else
                {
                    MissingExperiments.Add(experiment);
                }
            }

            LastExpCount = Settings.Experiments.Count;
        }
        
        //detect
        return EnabledExperiments.Contains(EI) && EI.Selected.HasValue && EI.Options.Count > EI.Selected &&
               EI.Options[EI.Selected.Value] == exp;
    }

    private static Dictionary<TextureManager, Dictionary<string, Texture>> TextureResources = new();

    public static Texture GetTextureResource(this TextureManager tm, string File)
    {
        if (!TextureResources.ContainsKey(tm)) TextureResources.Add(tm, new());
        if (TextureResources[tm].TryGetValue(File, out Texture? t)) return t;
        t = tm.AddTexture(GetResource($"Textures.{File}"));
        TextureResources[tm].Add(File,t);
        return t;
    }
    
    public static Stream GetResource(string File)
    {
        return Tools.GetResourceStream(Assembly.GetExecutingAssembly(),
            $"Luski.Resources.{File}");
    }
    
    public static void AddBehindObject(this Window w, IRenderObject obj)
    {
        if (!AllowedBehindObjects.ContainsKey(w)) AllowedBehindObjects.Add(w, new());
        if (AllowedBehindObjects[w].Count > 0)
        {
            obj.AllowHoverFromBehind = AllowedBehindObjects[w][0].AllowHoverFromBehind;
        }
        w.Controls.Add(obj);
        AllowedBehindObjects[w].Add(obj);
    }
    
    public static void ToggleBehindObjects(this Window w)
    {
        if (!AllowedBehindObjects.ContainsKey(w)) AllowedBehindObjects.Add(w, new());
        if (AllowedBehindObjects[w].Count > 0)
        {
            bool new_val = !AllowedBehindObjects[w][0].AllowHoverFromBehind;
            foreach (IRenderObject v in AllowedBehindObjects[w])
            {
                v.AllowHoverFromBehind = new_val;
            }
        }
    }
    
    public static void RemoveBehindObject(this Window w, IRenderObject obj, bool purge = true)
    {
        if (!AllowedBehindObjects.ContainsKey(w)) AllowedBehindObjects.Add(w, new());
        AllowedBehindObjects[w].Remove(obj);
        w.Controls.Remove(obj, purge);
        obj.Clean();
        obj = null!;
    }
    
    
    public static bool Download { get; set; } = false;
    public static API Luski { get; } = new();
    public static MainScreenWindow ms;

    public static Color4 ToColor4(this Color col)
    {
        return new(col.R, col.G, col.B, col.A);
    }
    
    public static Color ToColor(this Color4 col)
    {
        return new((byte)(col.R*byte.MaxValue), (byte)(col.G*byte.MaxValue), (byte)(col.B*byte.MaxValue), (byte)(col.A*byte.MaxValue));
    }

    public static Texture GetAlphaCircle(this TextureManager tm)
    {
        return tm.GetTextureResource("Status.png");
    }

    public static Dictionary<long, Texture> UserTextureMap = new();
    public static Dictionary<long, Texture> ProfileTextureMap = new();
    
    private static async Task<Texture> GetIcon(this SocketUser User)
    {
        if (UserTextureMap.TryGetValue(User.Id, out Texture? t)) return t;
        Stream UserStream = await User.GetAvatar(CancellationToken.None);
        t = Globals.ms.TextureManager.AddTexture(UserStream);
        UserTextureMap.Add(User.Id, t);
        UserStream.Dispose();
        t.Unit = TextureUnit.Texture1;
        return t;
    }

    public static async Task<IRenderObject> MakeRct<TUser>(this TUser User, Vector2i Size, bool IsProfile) where TUser : IUser
    {
        Texture t = ms.TextureManager.GetTextureResource("Status.png");
        if (User.PictureType == PictureType.none)
        {
            UserControl r = new(t);
            r.Size = Size;
            r.Shader = Rectangle.DefaultAlphaShader[ms.Context];
            Color c = await User.GetColor();
            r.BackgroundColor = new(25, 25, 25, 255);
            Label l = new(DefaultFont)
            {
                Color = c.ToColor4()
            };
            l.Text = User.DisplayName[0].ToString();
            var y = l.GetSizeOfChar(0);
            l.Location = new((r.Size.X - l.Size.X)/2,
                (int)(r.Size.Y - (l.Font.PixelHeight - y.Y) - (r.Size.Y / 2) - (y.Y/2)),
                0);
            r.Controls.Add(l);
            return r;
        }
        else
        {
            Rectangle r = new(t);
            r.Size = Size;
            r.Shader = Rectangle.DefaultAlphaTextureShader[ms.Context];
            r.Textures.Add(await User.GetIcon(IsProfile));
            return r;
        }
    }
    
    private static async Task<Texture> GetIcon(this IUser User, bool IsProfile)
    {
        if (IsProfile)
        {
            if (ProfileTextureMap.TryGetValue(User.Id, out Texture? t)) return t;
            Stream UserStream = await User.GetAvatar(CancellationToken.None);
            t = Globals.ms.TextureManager.AddTexture(UserStream);
            ProfileTextureMap.Add(User.Id, t);
            UserStream.Dispose();
            t.Unit = TextureUnit.Texture1;
            return t;
        }
        else
        {
            if (UserTextureMap.TryGetValue(User.Id, out Texture? t)) return t;
            Stream UserStream = await User.GetAvatar(CancellationToken.None);
            t = Globals.ms.TextureManager.AddTexture(UserStream);
            UserTextureMap.Add(User.Id, t);
            UserStream.Dispose();
            t.Unit = TextureUnit.Texture1;
            return t;
        }
    }

    public static Settings Settings { get; set; }

    public static FontFamily DefaultFontFamly { get; set; }
    public static FontInteraction DefaultFont { get; set; }
    public static FontInteraction TopTimeFont { get; set; }
    public static FontInteraction MessageFont { get; set; }
    public static FontInteraction SmallTimeFont { get; set; }

    public static PublicServer? GetCurentPublicServer()
    {
        if (Luski.LoadedServers.Count > 0)
            return Luski.LoadedServers[0];
        else
            return null;
    }
    
    public static UpdaterSettings UpdaterSettings { get; set; }

    public static Texture LuskiTexture;

    public static TResult GetSettings<TResult>(string path, JsonTypeInfo<TResult> TypeInfo) where TResult : new()
    {
        TResult? @out;
        if (!File.Exists(path))
        {
            @out = new();
            File.WriteAllText(path, JsonSerializer.Serialize(@out, TypeInfo));
        }

        try
        {
            @out = JsonSerializer.Deserialize(File.ReadAllText(path), TypeInfo);
            if (@out is null)
            {
                @out = new();
            }
        }
        catch
        {
            @out = new();
        }
        File.WriteAllText(path, JsonSerializer.Serialize(@out, TypeInfo));
        return @out;
    }
    
    public static TResult GetSettings<TResult>(Stream data, JsonTypeInfo<TResult> TypeInfo) where TResult : new()
    {
        TResult? @out;

        try
        {
            @out = JsonSerializer.Deserialize(data, TypeInfo);
            if (@out is null)
            {
                @out = new();
            }
        }
        catch
        {
            @out = new();
        }
        return @out;
    }
    
    public static void SaveSettings<TResult>(this TResult json, string path, JsonTypeInfo<TResult> TypeInfo) where TResult : new()
    {
        File.WriteAllText(path, JsonSerializer.Serialize(json, TypeInfo));
    }
    
    public static string JT
    {
        get
        {
            string tmp = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "JacobTech");
            if (OperatingSystem.IsLinux())
            {
                tmp = Path.Combine(Environment.GetEnvironmentVariable("HOME")!, ".config/");
                tmp += "JacobTech";
            }

            if (!Directory.Exists(tmp)) Directory.CreateDirectory(tmp);
            if (!Directory.Exists(Path.Combine(tmp, "Luski"))) Directory.CreateDirectory(Path.Combine(tmp, "Luski"));
            return tmp;
        }
    }
    public static WindowIcon Icon { get; set; }
    
    public static string LuskiPath
    {
        get
        {
            return Path.Combine(JT, "Luski");
        }
    }

    public static bool Empty(string? str)
    {
        return (string.IsNullOrEmpty(str) || string.IsNullOrWhiteSpace(str));
    }
}
#pragma warning restore CS8618