﻿using Facepunch;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;

namespace Oxide.Plugins
{
    [Info("AntiItems", "Author redBDGR, Maintainer nivex", "1.0.16")]
    [Description("Remove the need for certain items in crafting and repairing")]
    class AntiItems : RustPlugin
    {
        private Dictionary<string, int> componentList = new();

        private const string permissionName = "antiitems.use";

        private Configuration config;

        private void OnPlayerConnected(BasePlayer player) => timer.Once(5f, () => DoItems(player));

        private void OnPlayerDisconnected(BasePlayer player, string reason)
        {
            if (!IsInvalid(player) && permission.UserHasPermission(player.UserIDString, permissionName))
            {
                RemoveItems(player);
            }
        }

        private void OnPlayerRespawned(BasePlayer player)
        {
            if (!IsInvalid(player))
                player.Invoke(() => DoItems(player), 0.075f);
        }

        private void Init()
        {
            permission.RegisterPermission(permissionName, this);
            if (config.settings.useActiveRefreshing && config.settings.refreshTime > 0)
            {
                timer.Repeat(config.settings.refreshTime, 0, () =>
                {
                    foreach (var player in BasePlayer.activePlayerList)
                    {
                        RefreshItems(player);
                    }
                });
            }
            VerifyShortnames();
        }

        private void OnGroupPermissionRevoked(string group, string perm)
        {
            if (perm != permissionName) return;
            var users = permission.GetUsersInGroup(group);
            foreach (var user in users)
            {
                var userid = user.Split('(')[0].Trim();
                var player = BasePlayer.FindAwakeOrSleeping(userid);
                if (IsInvalid(player)) continue;
                RemoveItems(player);
            }
        }

        private void OnUserPermissionRevoked(string userid, string perm)
        {
            if (perm != permissionName) return;
            var player = BasePlayer.FindAwakeOrSleeping(userid);
            if (IsInvalid(player)) return;
            RemoveItems(player);
        }

        private void OnEntityDeath(BasePlayer player, HitInfo info)
        {
            if (!IsInvalid(player) && permission.UserHasPermission(player.UserIDString, permissionName))
            {
                RemoveItems(player);
            }
        }

        private void OnItemCraftCancelled(ItemCraftTask task)
        {
            foreach (var entry in task.takenItems)
            {
                if (componentList.ContainsKey(entry.info.shortname))
                {
                    timer.Once(0.01f, () =>
                    {
                        if (entry != null)
                        {
                            entry.RemoveFromContainer();
                            entry.Remove();
                        }
                    });
                }
            }
        }

        private void RefreshItems(BasePlayer player)
        {
            if (IsInvalid(player) || player.IsDead() || !permission.UserHasPermission(player.UserIDString, permissionName)) return;
            for (var i = 0; i < componentList.Count; i++)
            {
                Item item = player.inventory.containerMain.GetSlot(24 + i);
                if (item == null) continue;
                item.RemoveFromContainer();
                item.Remove();
            }
            DoItems(player);
        }

        private void DoItems(BasePlayer player, int baseSlots = 24)
        {
            if (IsInvalid(player) || player.IsDead() || !permission.UserHasPermission(player.UserIDString, permissionName)) return;
            player.inventory.containerMain.capacity = baseSlots + componentList.Count;
            int slot = baseSlots;
            foreach (var entry in componentList)
            {
                var item = ItemManager.CreateByName(entry.Key, entry.Value);
                if (item == null)
                {
                    Puts($"{entry.Key} was not able to be created properly. Perhaps the name is wrong");
                    continue;
                }

                if (!item.MoveToContainer(player.inventory.containerMain, slot, true, config.settings.ignoreStackLimit))
                    item.Remove();

                slot++;
            }
        }

        private void RemoveItems(BasePlayer player)
        {
            using var foundComponents = Pool.Get<PooledList<Item>>();
            foreach (var item in player.inventory.containerMain.itemList)
            {
                if (item != null && componentList.ContainsKey(item.info.shortname))
                {
                    foundComponents.Add(item);
                }
            }
            foreach (var key in foundComponents)
            {
                key.RemoveFromContainer();
                key.Remove(0.1f);
            }
            ItemManager.DoRemoves();
        }

        private void VerifyShortnames()
        {
            foreach (var component in config.settings.componentList)
            {
                if (ItemManager.FindItemDefinition(component.Key) == null) Puts($"Error: '{component.Key}' is not a valid shortname");
                else componentList[component.Key] = component.Value;
            }
        }

        private bool IsInvalid(BasePlayer player) => player == null || !player.userID.IsSteamId() || player.IsDestroyed || player.inventory == null || player.inventory.containerMain == null;

        #region Configuration

        public class Settings
        {
            [JsonProperty("Components", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public Dictionary<string, int> componentList = new()
            {
                { "propanetank", 1000 }, 
                { "gears", 1000 }, 
                { "metalpipe", 1000 },
                { "metalspring", 1000 }, 
                { "riflebody", 1000 }, 
                { "roadsigns", 1000 },
                { "rope", 1000 }, 
                { "semibody", 1000 }, 
                { "sewingkit", 1000 },
                { "smgbody", 1000 },
                { "tarp", 1000 }, 
                { "techparts", 1000 }, 
                { "sheetmetal", 1000 }
            };
            
            [JsonProperty("Use Active Item Refreshing")]
            public bool useActiveRefreshing = true;

            [JsonProperty("Refresh Time")]
            public float refreshTime = 600f;

            [JsonProperty("Ignore Stack Limit")]
            public bool ignoreStackLimit;
        }

        private class Configuration
        {
            [JsonProperty("Settings")]
            public Settings settings = new();

            private string ToJson() => JsonConvert.SerializeObject(this);

            public Dictionary<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(ToJson());
        }

        protected override void LoadDefaultConfig() => config = new();

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                config = Config.ReadObject<Configuration>();
                if (!config.ToDictionary().Keys.SequenceEqual(Config.ToDictionary(x => x.Key, x => x.Value).Keys))
                {
                    Puts("Configuration appears to be outdated; updating and saving.");
                    SaveConfig();
                }
            }
            catch
            {
                Puts($"Configuration file {Name}.json is invalid; using defaults");
                LoadDefaultConfig();
            }

        }

        protected override void SaveConfig()
        {
            Puts($"Configuration changes saved to {Name}.json");
            Config.WriteObject(config, true);
        }

        #endregion Configuration

    }
}