﻿using Newtonsoft.Json.Converters;
using Newtonsoft.Json;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using System;
using System.Collections.Generic;
using UnityEngine;

namespace Oxide.Plugins;

[Info("Magic Clock Panel", "MJSU", "1.0.4")]
[Description("Displays the in game time in magic panel")]
public class MagicClockPanel : RustPlugin
{
    #region Class Fields

    [PluginReference] private readonly Plugin MagicPanel;

    private PluginConfig _pluginConfig;

    private TOD_Sky _sky;
    private TOD_Time _time;

    private enum UpdateEnum
    {
        All = 1,
        Panel = 2,
        Image = 3,
        Text = 4
    }

    private string _textFormat;

    #endregion

    #region Setup & Loading

    private void Init()
    {
        _textFormat = _pluginConfig.Panel.Text.Text;
    }

    protected override void LoadDefaultConfig()
    {
        PrintWarning("Loading Default Config");
    }

    protected override void LoadConfig()
    {
        string path = $"{Manager.ConfigPath}/MagicPanel/{Name}.json";
        DynamicConfigFile newConfig = new(path);
        if (!newConfig.Exists())
        {
            LoadDefaultConfig();
            newConfig.Save();
        }

        try
        {
            newConfig.Load();
        }
        catch (Exception ex)
        {
            RaiseError($"Failed to load config file {path}: {ex.Message}");
            return;
        }

        newConfig.Settings.DefaultValueHandling = DefaultValueHandling.Populate;
        _pluginConfig = AdditionalConfig(newConfig.ReadObject<PluginConfig>());
        newConfig.WriteObject(_pluginConfig);
    }

    private PluginConfig AdditionalConfig(PluginConfig config)
    {
        config.Panel = new Panel
        {
            Image = new PanelImage
            {
                Enabled = config.Panel?.Image?.Enabled ?? true,
                Color = config.Panel?.Image?.Color ?? "#FFFFFFFF",
                Order = config.Panel?.Image?.Order ?? 0,
                Width = config.Panel?.Image?.Width ?? 0.28f,
                Url = config.Panel?.Image?.Url ?? "https://i.postimg.cc/vZFLtS8V/cSykHxd.png",
                Padding = config.Panel?.Image?.Padding ?? new TypePadding(0.05f, 0.0f, 0.1f, 0.1f)
            },
            Text = new PanelText
            {
                Enabled = config.Panel?.Text?.Enabled ?? true,
                Color = config.Panel?.Text?.Color ?? "#FFFFFFFF",
                Order = config.Panel?.Text?.Order ?? 1,
                Width = config.Panel?.Text?.Width ?? 0.72f,
                FontSize = config.Panel?.Text?.FontSize ?? 14,
                Padding = config.Panel?.Text?.Padding ?? new TypePadding(0.05f, 0.05f, 0.05f, 0.05f),
                TextAnchor = config.Panel?.Text?.TextAnchor ?? TextAnchor.MiddleCenter,
                Text = config.Panel?.Text?.Text ?? "{0:hh:mm tt}"
            }
        };
        config.PanelSettings = new PanelRegistration
        {
            BackgroundColor = config.PanelSettings?.BackgroundColor ?? "#FFF2DF08",
            Dock = config.PanelSettings?.Dock ?? "leftbottom",
            Order = config.PanelSettings?.Order ?? 1,
            Width = config.PanelSettings?.Width ?? 0.075f
        };
        return config;
    }

    private void OnServerInitialized()
    {
        MagicPanelRegisterPanels();
    }

    private void Unload()
    {
        if (_time)
        {
            _time.OnMinute -= UpdateTime;
        }
    }

    #endregion

    #region MagicPanel Hooks

    private void MagicPanelRegisterPanels()
    {
        if (MagicPanel?.IsLoaded is not true)
        {
            PrintError("Missing plugin dependency MagicPanel: https://umod.org/plugins/magic-panel");
            return;
        }

        if (!_pluginConfig.Panel.Image.Enabled && !_pluginConfig.Panel.Text.Enabled)
        {
            PrintWarning("Not registering panel because all items are disabled in config");
            return;
        }

        if (!_time)
        {
            _sky = TOD_Sky.Instance;
            _time = _sky.Components.Time;
            _time.OnMinute += UpdateTime;
        }

        MagicPanel?.Call("RegisterGlobalPanel", this, Name, JsonConvert.SerializeObject(_pluginConfig.PanelSettings), nameof(GetPanel));
    }

    private Hash<string, object> GetPanel()
    {
        Panel panel = _pluginConfig.Panel;
        PanelText text = panel.Text;
        if (text?.Enabled is true)
        {
            text.Text = string.Format(_textFormat, _sky.Cycle.DateTime);
        }

        return panel.ToHash();
    }

    #endregion

    #region Helper Methods

    private void UpdateTime()
    {
        MagicPanel?.Call("UpdatePanel", Name, (int)UpdateEnum.Text);
    }

    #endregion

    #region Classes

    private sealed class PluginConfig
    {
        [JsonProperty(PropertyName = "Panel Settings")]
        public PanelRegistration PanelSettings { get; set; }

        [JsonProperty(PropertyName = "Panel Layout")]
        public Panel Panel { get; set; }
    }

    private sealed class PanelRegistration
    {
        public string Dock { get; set; }
        public float Width { get; set; }
        public int Order { get; set; }
        public string BackgroundColor { get; set; }
    }

    private sealed class Panel
    {
        public PanelImage Image { get; set; }
        public PanelText Text { get; set; }

        [JsonIgnore] private Hash<string, object> _hash;

        public Hash<string, object> ToHash()
        {
            // only create new hash if one doesn't yet exist
            // populate it with non-dynamic data only this once
            _hash ??= new Hash<string, object>
            {
                [nameof(Image)] = Image.ToHash()
            };
            // update dynamic data every time
            _hash[nameof(Text)] = Text.ToHash();
            return _hash;
        }
    }

    private abstract class PanelType
    {
        public bool Enabled { get; set; }
        public string Color { get; set; }
        public int Order { get; set; }
        public float Width { get; set; }
        public TypePadding Padding { get; set; }

        [JsonIgnore] private Hash<string, object> _hash;

        public virtual Hash<string, object> ToHash()
        {
            // only create new hash if one doesn't yet exist
            // populate it with non-dynamic data only this once
            _hash ??= new Hash<string, object>
            {
                [nameof(Enabled)] = Enabled,
                [nameof(Color)] = Color,
                [nameof(Order)] = Order,
                [nameof(Width)] = Width,
                [nameof(Padding)] = Padding.ToHash()
            };
            return _hash;
        }
    }

    private sealed class PanelImage : PanelType
    {
        public string Url { get; set; }

        public override Hash<string, object> ToHash()
        {
            Hash<string, object> hash = base.ToHash();
            // populate with non-dynamic data only when missing
            hash.TryAdd(nameof(Url), Url);
            return hash;
        }
    }

    private sealed class PanelText : PanelType
    {
        public string Text { get; set; }
        public int FontSize { get; set; }

        [JsonConverter(typeof(StringEnumConverter))]
        public TextAnchor TextAnchor { get; set; }

        public override Hash<string, object> ToHash()
        {
            Hash<string, object> hash = base.ToHash();
            // update dynamic data every time
            hash[nameof(Text)] = Text;
            // populate with non-dynamic data only when missing
            hash.TryAdd(nameof(FontSize), FontSize);
            hash.TryAdd(nameof(TextAnchor), TextAnchor);
            return hash;
        }
    }

    private sealed class TypePadding
    {
        public float Left { get; set; }
        public float Right { get; set; }
        public float Top { get; set; }
        public float Bottom { get; set; }

        [JsonIgnore] private Hash<string, object> _hash;


        public TypePadding(float left, float right, float top, float bottom)
        {
            Left = left;
            Right = right;
            Top = top;
            Bottom = bottom;
        }

        public Hash<string, object> ToHash()
        {
            // only create new hash if one doesn't yet exist
            // populate it with non-changing data only on creation
            _hash ??= new Hash<string, object>
            {
                [nameof(Left)] = Left,
                [nameof(Right)] = Right,
                [nameof(Top)] = Top,
                [nameof(Bottom)] = Bottom
            };
            return _hash;
        }
    }
    #endregion
}