using Dalamud.Hooking;
using Dalamud.IoC;
using Dalamud.Plugin;
using Dalamud.Utility.Signatures;
using Ktisis.Structs.Env;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;

namespace VoidBox;

public sealed unsafe class Plugin : IDalamudPlugin
{
    private readonly List<Cmd> _cmds;

    public Plugin(IDalamudPluginInterface pluginInterface)
    {
        pluginInterface.Create<Service>();

        Service.Plugin = this;

        try
        {
            Service.Config = Service.PluginInterface.GetPluginConfig() as Configuration ?? new();
        }
        catch (Exception e)
        {
            Service.PluginLog.Error($"Failed to load config: {e}");
            Service.Config = new();
        }

        Service.PluginUI = new();

        Service.Envs = new();

        Service.Config.Initialize(Service.PluginInterface);

        _cmds = typeof(Plugin).Assembly.GetTypes()
            .Where(t => !t.IsAbstract && typeof(Cmd).IsAssignableFrom(t))
            .Select(t => (Cmd) Activator.CreateInstance(t)!)
            .ToList();

        foreach (Cmd cmd in _cmds)
        {
            cmd.Register(Service.CommandManager);
        }

        Service.GameInteropProvider.InitializeFromAttributes(this);

        _envStateCopyHook?.Enable();
    }

    public string Name => "VoidBox";

    public EnvState Current { get; private set; }
    public SavedEnv? Preview { get; set; }

    public void Dispose()
    {
        _envStateCopyHook?.Dispose();

        foreach (Cmd cmd in _cmds)
        {
            cmd.Dispose();
        }

        Service.PluginUI.Dispose();

        Service.Plugin = null!;
    }

    private void Replace(EnvState* env)
    {
        var config = Service.Config;
        if (!config.IsEnabled)
        {
            return;
        }

        var savedEnv = (Preview ?? (Service.ClientState.IsLoggedIn ? config.HousingEnv : config.LoginEnv))?.Env;
        if (savedEnv == null)
        {
            return;
        }

        var savedEnvVal = savedEnv.Value;
        Replace(env, &savedEnvVal);
    }

    private void Replace(EnvState* to, EnvState* from)
    {
        to->SkyId = from->SkyId;

        to->Lighting.SunLightColor = from->Lighting.SunLightColor;
        to->Lighting.MoonLightColor = from->Lighting.MoonLightColor;
        to->Lighting.Ambient = from->Lighting.Ambient;
        to->Lighting._unk1 = from->Lighting._unk1;
        to->Lighting.AmbientSaturation = from->Lighting.AmbientSaturation;
        to->Lighting.Temperature = from->Lighting.Temperature;
        to->Lighting._unk2 = from->Lighting._unk2;
        to->Lighting._unk3 = from->Lighting._unk3;
        to->Lighting._unk4 = from->Lighting._unk4;

        to->Stars.ConstellationIntensity = from->Stars.ConstellationIntensity;
        to->Stars.Constellations = from->Stars.Constellations;
        to->Stars.Stars = from->Stars.Stars;
        to->Stars.GalaxyIntensity = from->Stars.GalaxyIntensity;
        to->Stars.StarIntensity = from->Stars.StarIntensity;
        to->Stars.MoonColor = from->Stars.MoonColor;
        to->Stars.MoonBrightness = from->Stars.MoonBrightness;

        to->Fog.Color = from->Fog.Color;
        to->Fog.Distance = from->Fog.Distance;
        to->Fog.Thickness = from->Fog.Thickness;
        to->Fog._unk1 = from->Fog._unk1;
        to->Fog._unk2 = from->Fog._unk2;
        to->Fog.Opacity = from->Fog.Opacity;
        to->Fog.SkyVisibility = from->Fog.SkyVisibility;

        to->Clouds.CloudColor = from->Clouds.CloudColor;
        to->Clouds.Color2 = from->Clouds.Color2;
        to->Clouds.Gradient = from->Clouds.Gradient;
        to->Clouds.SideHeight = from->Clouds.SideHeight;
        to->Clouds.CloudTexture = from->Clouds.CloudTexture;
        to->Clouds.CloudSideTexture = from->Clouds.CloudSideTexture;

        to->Rain.Raindrops = from->Rain.Raindrops;
        to->Rain.Intensity = from->Rain.Intensity;
        to->Rain.Weight = from->Rain.Weight;
        to->Rain.Scatter = from->Rain.Scatter;
        to->Rain._unk1 = from->Rain._unk1;
        to->Rain.Size = from->Rain.Size;
        to->Rain.Color = from->Rain.Color;
        to->Rain._unk2 = from->Rain._unk2;
        to->Rain._unk3 = from->Rain._unk3;
        to->Rain._unk4 = from->Rain._unk4;

        to->Dust._unk1 = from->Dust._unk1;
        to->Dust.Intensity = from->Dust.Intensity;
        to->Dust.Weight = from->Dust.Weight;
        to->Dust.Spread = from->Dust.Spread;
        to->Dust.Speed = from->Dust.Speed;
        to->Dust.Size = from->Dust.Size;
        to->Dust.Color = from->Dust.Color;
        to->Dust.Glow = from->Dust.Glow;
        to->Dust.Spin = from->Dust.Spin;
        to->Dust.TextureId = from->Dust.TextureId;

        to->Wind.Direction = from->Wind.Direction;
        to->Wind.Angle = from->Wind.Angle;
        to->Wind.Speed = from->Wind.Speed;
    }

    private unsafe delegate nint EnvStateCopyDelegate(EnvState* dest, EnvState* src);

    [Signature("E8 ?? ?? ?? ?? 49 3B F5 75 0D", DetourName = nameof(EnvStateCopyDetour))]
    private Hook<EnvStateCopyDelegate> _envStateCopyHook = null!;
    private unsafe nint EnvStateCopyDetour(EnvState* dest, EnvState* src)
    {
        Current = *dest;

        var exec = _envStateCopyHook.Original(dest, src);

        var replace = Preview != null;

        if (!Service.ClientState.IsLoggedIn)
        {
            replace |= dest->SkyId == 0;
        }
        else
        {
            replace |=
                dest->SkyId == 0 &&
                dest->Clouds.Gradient == 0.5f &&
                dest->Lighting._unk1 == 0.5f &&
                Math.Abs(dest->Lighting.Temperature - 0.6f) < 0.01f &&
                dest->Lighting._unk2 == 0.5f &&
                dest->Fog.Color == new Vector4(0, 0, 0, 0) &&
                dest->Fog.Distance == 0.0f &&
                dest->Fog.Thickness == 0.0f &&
                dest->Fog._unk1 == 1000.0f &&
                dest->Fog._unk2 == 1.0f &&
                dest->Fog.Opacity == 0.0f &&
                dest->Fog.SkyVisibility == 1.0f;
        }

        if (replace)
        {
            Replace(dest);
        }

        return exec;
    }
}