using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Furnace Splitter", "FastBurst", "2.4.0")]
    [Description("Splits up resources in furnaces automatically and shows useful furnace information")]
    public class FurnaceSplitter : RustPlugin
    {
        [PluginReference]
        private Plugin UIScaleManager;

        private class OvenSlot
        {
            /// <summary>The item in this slot. May be null.</summary>
            public Item Item;

            /// <summary>The slot position</summary>
            public int? Position;

            /// <summary>The slot's index in the itemList list.</summary>
            public int Index;

            /// <summary>How much should be added/removed from stack</summary>
            public int DeltaAmount;
        }

        public class OvenInfo
        {
            public float ETA;
            public float FuelNeeded;
        }

        private class StoredData
        {
            public Dictionary<ulong, PlayerOptions> AllPlayerOptions { get; private set; } = new Dictionary<ulong, PlayerOptions>();
        }

        private class PluginConfig
        {
            //[JsonRequired]
            public Vector2 UiPosition { get; set; } = new Vector2(0.6505f, 0.022f);
        }

        private class PlayerOptions
        {
            public bool Enabled;
            public Dictionary<string, int> TotalStacks = new Dictionary<string, int>();
        }

        public enum MoveResult
        {
            Ok,
            SlotsFilled,
            NotEnoughSlots
        }

        private class Vector2Converter : JsonConverter
        {
            public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
            {
                Vector2 vec = (Vector2)value;
                serializer.Serialize(writer, new { vec.x, vec.y });
            }

            public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
            {
                Vector2 result = new Vector2();
                JObject jVec = JObject.Load(reader);

                result.x = jVec["x"].ToObject<float>();
                result.y = jVec["y"].ToObject<float>();

                return result;
            }

            public override bool CanConvert(Type objectType)
            {
                return objectType == typeof(Vector2);
            }
        }

        private Dictionary<ulong, PlayerOptions> allPlayerOptions => storedData.AllPlayerOptions;
        private PluginConfig config;
        private StoredData storedData;

        private const string permUse = "furnacesplitter.use";

        private readonly Dictionary<ulong, string> openUis = new Dictionary<ulong, string>();
        private readonly Dictionary<BaseOven, List<BasePlayer>> looters = new Dictionary<BaseOven, List<BasePlayer>>();
        private readonly Stack<BaseOven> queuedUiUpdates = new Stack<BaseOven>();

        private readonly string[] compatibleOvens =
        {
            "bbq.deployed",
            "bbq.static",
            "bbq.campermodule",
            "campfire",
            "campfire.static",
            "cursedcauldron.deployed",
            "fireplace.deployed",
            "furnace",
            "furnace.large",
            "furnace.static",
            "furnace_static",
            "hobobarrel_static",
            "hobobarrel.deployed",
            "refinery_small_deployed",
            "small_refinery_static",
            "skull_fire_pit"
        };

        private void OnPlayerDisconnected(BasePlayer player, string reason)
        {
            DestroyUI(player);
        }

        private void Init()
        {
            // Only add if not already been added in LoadDefaultConfig. That would be the case the first time the plugin is initialized.
            if (Config.Settings.Converters.All(conv => conv.GetType() != typeof(Vector2Converter)))
                Config.Settings.Converters.Add(new Vector2Converter());

            storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();

            permission.RegisterPermission(permUse, this);

            if (config == null)
            {
                config = Config.ReadObject<PluginConfig>();
            }
            else
            {
                Config.WriteObject(config);
            }
        }

        #region Configuration

        protected override void LoadDefaultConfig()
        {
            Config.Settings.Converters.Add(new Vector2Converter());
            PrintWarning("Creating default config for FurnaceSplitter.");
            config = new PluginConfig();
        }

        #endregion Configuration

        private void OnServerSave()
        {
            SaveData();
        }

        private void SaveData()
        {
            Interface.Oxide.DataFileSystem.WriteObject(Name, storedData);
        }

        private void InitPlayer(BasePlayer player)
        {
            if (!allPlayerOptions.ContainsKey(player.userID))
            {
                allPlayerOptions[player.userID] = new PlayerOptions
                {
                    Enabled = true,
                    TotalStacks = new Dictionary<string, int>()
                };
            }

            var initialStackOptions = new Dictionary<string, int>
            {
                {"furnace", 3},
                {"furnace.static", 3},
                {"furnace_static", 3},
                {"bbq.deployed", 9},
                {"bbq.campermodule", 9},
                {"bbq.static", 9},
                {"campfire", 2},
                {"campfire.static", 2},
                {"cursedcauldron.deployed", 2},
                {"fireplace.deployed", 2},
                {"furnace.large", 15},
                {"hobobarrel_static", 2},
                {"hobobarrel.deployed", 2},
                {"refinery_small_deployed", 3},
                {"small_refinery_static", 3},
                {"skull_fire_pit", 2}
            };

            PlayerOptions options = allPlayerOptions[player.userID];

            foreach (var kv in initialStackOptions)
            {
                if (!options.TotalStacks.ContainsKey(kv.Key))
                    options.TotalStacks.Add(kv.Key, kv.Value);
            }
        }

        private void OnTick()
        {
            while (queuedUiUpdates.Count > 0)
            {
                BaseOven oven = queuedUiUpdates.Pop();

                if (!oven || oven.IsDestroyed)
                    continue;

                OvenInfo ovenInfo = GetOvenInfo(oven);

                GetLooters(oven)?.ForEach(plr =>
                {
                    if (plr && !plr.IsDestroyed)
                    {
                        CreateUi(plr, oven, ovenInfo);
                    }
                });
            }
        }

        public OvenInfo GetOvenInfo(BaseOven oven)
        {
            OvenInfo result = new OvenInfo();
            var smeltTimes = GetSmeltTimes(oven);

            if (smeltTimes.Count > 0)
            {
                var longestStack = smeltTimes.OrderByDescending(kv => kv.Value).First();
                float fuelUnits = oven.fuelType.GetComponent<ItemModBurnable>().fuelAmount;
                float neededFuel = (float)Math.Ceiling(longestStack.Value * (oven.cookingTemperature / 200.0f) / fuelUnits);

                result.FuelNeeded = neededFuel;
                result.ETA = longestStack.Value;
            }

            return result;
        }

        private void Unload()
        {
            SaveData();

            foreach (var kv in openUis.ToDictionary(kv => kv.Key, kv => kv.Value))
            {
                BasePlayer player = BasePlayer.FindByID(kv.Key);
                DestroyUI(player);
            }
        }

        private bool GetEnabled(BasePlayer player)
        {
            if (!allPlayerOptions.ContainsKey(player.userID))
                InitPlayer(player);
            
            return allPlayerOptions[player.userID].Enabled;
        }

        private void SetEnabled(BasePlayer player, bool enabled)
        {
            if (allPlayerOptions.ContainsKey(player.userID))
                allPlayerOptions[player.userID].Enabled = enabled;
            CreateUiIfFurnaceOpen(player);
        }

        private bool IsSlotCompatible(Item item, BaseOven oven, ItemDefinition itemDefinition)
        {
            ItemModCookable cookable = item.info.GetComponent<ItemModCookable>();

            if (item.amount < item.info.stackable && item.info == itemDefinition)
                return true;

            if (oven.allowByproductCreation && oven.fuelType.GetComponent<ItemModBurnable>().byproductItem == item.info)
                return true;

            if (cookable == null || cookable.becomeOnCooked == itemDefinition)
                return true;

            if (CanCook(cookable, oven))
                return true;

            return false;
        }

        private void OnFuelConsume(BaseOven oven, Item fuel, ItemModBurnable burnable)
        {
            if (compatibleOvens.Contains(oven.ShortPrefabName))
                queuedUiUpdates.Push(oven);
        }

        private List<BasePlayer> GetLooters(BaseOven oven)
        {
            if (looters.ContainsKey(oven))
                return looters[oven];

            return null;
        }

        private void AddLooter(BaseOven oven, BasePlayer player)
        {
            if (!looters.ContainsKey(oven))
                looters[oven] = new List<BasePlayer>();

            var list = looters[oven];
            list.Add(player);
        }

        private void RemoveLooter(BaseOven oven, BasePlayer player)
        {
            if (!looters.ContainsKey(oven))
                return;

            looters[oven].Remove(player);
        }

        private object CanMoveItem(Item item, PlayerInventory inventory, uint targetContainer, int targetSlot)
        {
            if (item == null || inventory == null)
                return null;

            BasePlayer player = inventory.GetComponent<BasePlayer>();
            if (player == null)
                return null;

            ItemContainer container = inventory.FindContainer(targetContainer);
            ItemContainer originalContainer = item.GetRootContainer();

            if (container == null || originalContainer == null)
                return null;

            Func<object> splitFunc = () =>
            {
                if (player == null || !HasPermission(player) || !GetEnabled(player))
                    return null;

                PlayerOptions playerOptions = allPlayerOptions[player.userID];

                if (container == null || originalContainer == null || container == item.GetRootContainer())
                    return null;

                BaseOven oven = container.entityOwner as BaseOven;
                ItemModCookable cookable = item.info.GetComponent<ItemModCookable>();

                if (oven == null || cookable == null)
                    return null;

                int totalSlots = 2 + (oven.allowByproductCreation ? 1 : 0);

                if (playerOptions.TotalStacks.ContainsKey(oven.ShortPrefabName))
                {
                    totalSlots = playerOptions.TotalStacks[oven.ShortPrefabName];
                }

                if (cookable.lowTemp > oven.cookingTemperature || cookable.highTemp < oven.cookingTemperature)
                    return null;

                MoveSplitItem(item, oven, totalSlots);
                return true;
            };

            object returnValue = splitFunc();

            if (HasPermission(player) && GetEnabled(player))
            {
                BaseOven oven = container?.entityOwner as BaseOven ?? item.GetRootContainer().entityOwner as BaseOven;
                if (oven != null && compatibleOvens.Contains(oven.ShortPrefabName))
                {
                    if (returnValue is bool && (bool)returnValue)
                        AutoAddFuel(inventory, oven);

                    queuedUiUpdates.Push(oven);
                }
            }

            return returnValue;
        }

        private MoveResult MoveSplitItem(Item item, BaseOven oven, int totalSlots)
        {
            ItemContainer container = oven.inventory;
            int invalidItemsCount = container.itemList.Count(slotItem => !IsSlotCompatible(slotItem, oven, item.info));
            int numOreSlots = Math.Min(container.capacity - invalidItemsCount, totalSlots);
            int totalMoved = 0;
            int totalAmount = Math.Min(item.amount + container.itemList.Where(slotItem => slotItem.info == item.info).Take(numOreSlots).Sum(slotItem => slotItem.amount), Math.Abs(item.info.stackable * numOreSlots));

            if (numOreSlots <= 0)
            {
                return MoveResult.NotEnoughSlots;
            }

            //Puts("---------------------------");

            int totalStackSize = Math.Min(totalAmount / numOreSlots, item.info.stackable);
            int remaining = totalAmount - totalAmount / numOreSlots * numOreSlots;

            List<int> addedSlots = new List<int>();

            //Puts("total: {0}, remaining: {1}, totalStackSize: {2}", totalAmount, remaining, totalStackSize);

            List<OvenSlot> ovenSlots = new List<OvenSlot>();

            for (int i = 0; i < numOreSlots; ++i)
            {
                Item existingItem;
                int slot = FindMatchingSlotIndex(container, out existingItem, item.info, addedSlots);

                if (slot == -1) // full
                {
                    return MoveResult.NotEnoughSlots;
                }

                addedSlots.Add(slot);

                OvenSlot ovenSlot = new OvenSlot
                {
                    Position = existingItem?.position,
                    Index = slot,
                    Item = existingItem
                };

                int currentAmount = existingItem?.amount ?? 0;
                int missingAmount = totalStackSize - currentAmount + (i < remaining ? 1 : 0);
                ovenSlot.DeltaAmount = missingAmount;

                //Puts("[{0}] current: {1}, delta: {2}, total: {3}", slot, currentAmount, ovenSlot.DeltaAmount, currentAmount + missingAmount);

                if (currentAmount + missingAmount <= 0)
                    continue;

                ovenSlots.Add(ovenSlot);
            }

            foreach (OvenSlot slot in ovenSlots)
            {
                if (slot.Item == null)
                {
                    Item newItem = ItemManager.Create(item.info, slot.DeltaAmount, item.skin);
                    slot.Item = newItem;
                    newItem.MoveToContainer(container, slot.Position ?? slot.Index);
                }
                else
                {
                    slot.Item.amount += slot.DeltaAmount;
                }

                totalMoved += slot.DeltaAmount;
            }

            container.MarkDirty();

            if (totalMoved >= item.amount)
            {
                item.Remove();
                item.GetRootContainer()?.MarkDirty();
                return MoveResult.Ok;
            }
            else
            {
                item.amount -= totalMoved;
                item.GetRootContainer()?.MarkDirty();
                return MoveResult.SlotsFilled;
            }
        }

        private void AutoAddFuel(PlayerInventory playerInventory, BaseOven oven)
        {
            int neededFuel = (int)Math.Ceiling(GetOvenInfo(oven).FuelNeeded);
            neededFuel -= oven.inventory.GetAmount(oven.fuelType.itemid, false);
            var playerFuel = playerInventory.FindItemIDs(oven.fuelType.itemid);

            if (neededFuel <= 0 || playerFuel.Count <= 0)
                return;

            foreach (Item fuelItem in playerFuel)
            {
                if (oven.inventory.CanAcceptItem(fuelItem, -1) != ItemContainer.CanAcceptResult.CanAccept)
                    break;

                Item largestFuelStack = oven.inventory.itemList.Where(item => item.info == oven.fuelType).OrderByDescending(item => item.amount).FirstOrDefault();
                int toTake = Math.Min(neededFuel, oven.fuelType.stackable - (largestFuelStack?.amount ?? 0));

                if (toTake > fuelItem.amount)
                    toTake = fuelItem.amount;

                if (toTake <= 0)
                    break;

                neededFuel -= toTake;

                if (toTake >= fuelItem.amount)
                {
                    fuelItem.MoveToContainer(oven.inventory);
                }
                else
                {
                    Item splitItem = fuelItem.SplitItem(toTake);
                    if (!splitItem.MoveToContainer(oven.inventory)) // Break if oven is full
                        break;
                }

                if (neededFuel <= 0)
                    break;
            }
        }

        private int FindMatchingSlotIndex(ItemContainer container, out Item existingItem, ItemDefinition itemType, List<int> indexBlacklist)
        {
            existingItem = null;
            int firstIndex = -1;
            Dictionary<int, Item> existingItems = new Dictionary<int, Item>();

            for (int i = 0; i < container.capacity; ++i)
            {
                if (indexBlacklist.Contains(i))
                    continue;

                Item itemSlot = container.GetSlot(i);
                if (itemSlot == null || itemType != null && itemSlot.info == itemType)
                {
                    if (itemSlot != null)
                        existingItems.Add(i, itemSlot);

                    if (firstIndex == -1)
                    {
                        existingItem = itemSlot;
                        firstIndex = i;
                    }
                }
            }

            if (existingItems.Count <= 0 && firstIndex != -1)
            {
                return firstIndex;
            }
            else if (existingItems.Count > 0)
            {
                var largestStackItem = existingItems.OrderByDescending(kv => kv.Value.amount).First();
                existingItem = largestStackItem.Value;
                return largestStackItem.Key;
            }

            existingItem = null;
            return -1;
        }

        private void OnLootEntity(BasePlayer player, BaseEntity entity)
        {
            BaseOven oven = entity as BaseOven;

            if (oven == null || !HasPermission(player) || !compatibleOvens.Contains(oven.ShortPrefabName))
                return;

            AddLooter(oven, player);
            queuedUiUpdates.Push(oven);
        }

        private void OnLootEntityEnd(BasePlayer player, BaseCombatEntity entity)
        {
            BaseOven oven = entity as BaseOven;

            if (oven == null || !compatibleOvens.Contains(oven.ShortPrefabName))
                return;

            DestroyUI(player);
            RemoveLooter(oven, player);
        }

        private void OnEntityKill(BaseNetworkable networkable)
        {
            BaseOven oven = networkable as BaseOven;

            if (oven != null)
            {
                DestroyOvenUI(oven);
            }
        }

        private void OnOvenToggle(BaseOven oven, BasePlayer player)
        {
            if (compatibleOvens.Contains(oven.ShortPrefabName))
                queuedUiUpdates.Push(oven);
        }

        private void CreateUiIfFurnaceOpen(BasePlayer player)
        {
            BaseOven oven = player.inventory.loot?.entitySource as BaseOven;

            if (oven != null && compatibleOvens.Contains(oven.ShortPrefabName))
                queuedUiUpdates.Push(oven);
        }

        private CuiElementContainer CreateUi(BasePlayer player, BaseOven oven, OvenInfo ovenInfo)
        {
            PlayerOptions options = allPlayerOptions[player.userID];
            int totalSlots = GetTotalStacksOption(player, oven) ?? oven.inventory.capacity - (oven.allowByproductCreation ? 1 : 2);
            string remainingTimeStr;
            string neededFuelStr;

            if (ovenInfo.ETA <= 0)
            {
                remainingTimeStr = "0s";
                neededFuelStr = "0";
            }
            else
            {
                remainingTimeStr = FormatTime(ovenInfo.ETA);
                neededFuelStr = ovenInfo.FuelNeeded.ToString("##,###");
            }

            float uiScale = 1.0f;
            float[] playerUiInfo = UIScaleManager?.Call<float[]>("API_CheckPlayerUIInfo", player.UserIDString);
            if (playerUiInfo?.Length > 0)
            {
                uiScale = playerUiInfo[2];
            }
            string contentColor = "0.7 0.7 0.7 1.0";
            int contentSize = Convert.ToInt32(10 * uiScale);
            string toggleStateStr = (!options.Enabled).ToString();
            string toggleButtonColor = !options.Enabled
                    ? "0.415 0.5 0.258 0.4"
                    : "0.8 0.254 0.254 0.4";
            string toggleButtonTextColor = !options.Enabled
                    ? "0.607 0.705 0.431"
                    : "0.705 0.607 0.431";
            string buttonColor = "0.75 0.75 0.75 0.1";
            string buttonTextColor = "0.77 0.68 0.68 1";

            int nextDecrementSlot = totalSlots - 1;
            int nextIncrementSlot = totalSlots + 1;

            DestroyUI(player);

            Vector2 uiPosition = new Vector2(
                ((((config.UiPosition.x) - 0.5f) * uiScale) + 0.5f),
                (config.UiPosition.y - 0.02f) + 0.02f * uiScale);
            Vector2 uiSize = new Vector2(0.1785f * uiScale, 0.111f * uiScale);

            CuiElementContainer result = new CuiElementContainer();
            string rootPanelName = result.Add(new CuiPanel
            {
                Image = new CuiImageComponent
                {
                    Color = "0 0 0 0"
                },
                RectTransform =
                {
                    AnchorMin = uiPosition.x + " " + uiPosition.y,
                    AnchorMax = uiPosition.x + uiSize.x + " " + (uiPosition.y + uiSize.y)
                    //AnchorMin = "0.6505 0.022",
                    //AnchorMax = "0.829 0.133"
                }
            }, "Hud.Menu");

            string headerPanel = result.Add(new CuiPanel
            {
                Image = new CuiImageComponent
                {
                    Color = "0.75 0.75 0.75 0.1"
                },
                RectTransform =
                {
                    AnchorMin = "0 0.775",
                    AnchorMax = "1 1"
                }
            }, rootPanelName);

            // Header label
            result.Add(new CuiLabel
            {
                RectTransform =
                {
                    AnchorMin = "0.051 0",
                    AnchorMax = "1 0.95"
                },
                Text =
                {
                    Text = lang.GetMessage("title", this, player.UserIDString),
                    Align = TextAnchor.MiddleLeft,
                    Color = "0.77 0.7 0.7 1",
                    FontSize = Convert.ToInt32(13 * uiScale)
                }
            }, headerPanel);

            string contentPanel = result.Add(new CuiPanel
            {
                Image = new CuiImageComponent
                {
                    Color = "0.65 0.65 0.65 0.06"
                },
                RectTransform =
                {
                    AnchorMin = "0 0",
                    AnchorMax = "1 0.74"
                }
            }, rootPanelName);

            // ETA label
            result.Add(new CuiLabel
            {
                RectTransform =
                {
                    AnchorMin = "0.02 0.7",
                    AnchorMax = "0.98 1"
                },
                Text =
                {
                    Text = string.Format("{0}: " + (ovenInfo.ETA > 0 ? "~" : "") + remainingTimeStr + " (" + neededFuelStr +  " " + oven.fuelType.displayName.english.ToLower() + ")", lang.GetMessage("eta", this, player.UserIDString)),
                    Align = TextAnchor.MiddleLeft,
                    Color = contentColor,
                    FontSize = contentSize
                }
            }, contentPanel);

            // Toggle button
            result.Add(new CuiButton
            {
                RectTransform =
                {
                    AnchorMin = "0.02 0.4",
                    AnchorMax = "0.25 0.7"
                },
                Button =
                {
                    Command = "furnacesplitter.enabled " + toggleStateStr,
                    Color = toggleButtonColor
                },
                Text =
                {
                    Align = TextAnchor.MiddleCenter,
                    Text = options.Enabled ? lang.GetMessage("turnoff", this, player.UserIDString) : lang.GetMessage("turnon", this, player.UserIDString),
                    Color = toggleButtonTextColor,
                    FontSize = Convert.ToInt32(11 * uiScale)
                }
            }, contentPanel);

            // Trim button
            result.Add(new CuiButton
            {
                RectTransform =
                {
                    AnchorMin = "0.27 0.4",
                    AnchorMax = "0.52 0.7"
                },
                Button =
                {
                    Command = "furnacesplitter.trim",
                    Color = buttonColor
                },
                Text =
                {
                    Align = TextAnchor.MiddleCenter,
                    Text = lang.GetMessage("trim", this, player.UserIDString),
                    Color = contentColor,
                    FontSize = Convert.ToInt32(11 * uiScale)
                }
            }, contentPanel);

            // Decrease stack button
            result.Add(new CuiButton
            {
                RectTransform =
                {
                    AnchorMin = "0.02 0.05",
                    AnchorMax = "0.07 0.35"
                },
                Button =
                {
                    Command = "furnacesplitter.totalstacks " + nextDecrementSlot,
                    Color = buttonColor
                },
                Text =
                {
                    Align = TextAnchor.MiddleCenter,
                    Text = "<",
                    Color = buttonTextColor,
                    FontSize = contentSize
                }
            }, contentPanel);

            // Empty slots label
            result.Add(new CuiLabel
            {
                RectTransform =
                {
                    AnchorMin = "0.08 0.05",
                    AnchorMax = "0.19 0.35"
                },
                Text =
                {
                    Align = TextAnchor.MiddleCenter,
                    Text = totalSlots.ToString(),
                    Color = contentColor,
                    FontSize = contentSize
                }
            }, contentPanel);

            // Increase stack button
            result.Add(new CuiButton
            {
                RectTransform =
                {
                    AnchorMin = "0.19 0.05",
                    AnchorMax = "0.25 0.35"
                },
                Button =
                {
                    Command = "furnacesplitter.totalstacks " + nextIncrementSlot,
                    Color = buttonColor
                },
                Text =
                {
                    Align = TextAnchor.MiddleCenter,
                    Text = ">",
                    Color = buttonTextColor,
                    FontSize = contentSize
                }
            }, contentPanel);

            // Stack itemType label
            result.Add(new CuiLabel
            {
                RectTransform =
                {
                    AnchorMin = "0.27 0.05",
                    AnchorMax = "1 0.35"
                },
                Text =
                {
                    Align = TextAnchor.MiddleLeft,
                    Text = string.Format("({0})", lang.GetMessage("totalstacks", this, player.UserIDString)),
                    Color = contentColor,
                    FontSize = contentSize
                }
            }, contentPanel);

            openUis.Add(player.userID, rootPanelName);
            CuiHelper.AddUi(player, result);
            return result;
        }

        private string FormatTime(float totalSeconds)
        {
            double hours = Math.Floor(totalSeconds / 3600);
            double minutes = Math.Floor(totalSeconds / 60 % 60);
            float seconds = totalSeconds % 60;

            if (hours <= 0 && minutes <= 0)
                return seconds + "s";
            if (hours <= 0)
                return minutes + "m" + seconds + "s";
            return hours + "h" + minutes + "m" + seconds + "s";
        }

        private Dictionary<ItemDefinition, float> GetSmeltTimes(BaseOven oven)
        {
            ItemContainer container = oven.inventory;
            var cookables = container.itemList.Where(item =>
            {
                ItemModCookable cookable = item.info.GetComponent<ItemModCookable>();
                return cookable != null && CanCook(cookable, oven);
            }).ToList();

            if (cookables.Count == 0)
                return new Dictionary<ItemDefinition, float>();

            var distinctCookables = cookables.GroupBy(item => item.info, item => item).ToList();
            Dictionary<ItemDefinition, int> amounts = new Dictionary<ItemDefinition, int>();

            foreach (var group in distinctCookables)
            {
                int biggestAmount = group.Max(item => item.amount);
                amounts.Add(group.Key, biggestAmount);
            }

            var smeltTimes = amounts.ToDictionary(kv => kv.Key, kv => GetSmeltTime(kv.Key.GetComponent<ItemModCookable>(), kv.Value));
            return smeltTimes;
        }

        private bool CanCook(ItemModCookable cookable, BaseOven oven)
        {
            return oven.cookingTemperature >= cookable.lowTemp && oven.cookingTemperature <= cookable.highTemp;
        }

        private float GetSmeltTime(ItemModCookable cookable, int amount)
        {
            float smeltTime = cookable.cookTime * amount;
            return smeltTime;
        }

        private int? GetTotalStacksOption(BasePlayer player, BaseOven oven)
        {
            PlayerOptions options = allPlayerOptions[player.userID];

            if (options.TotalStacks.ContainsKey(oven.ShortPrefabName))
                return options.TotalStacks[oven.ShortPrefabName];

            return null;
        }

        private void DestroyUI(BasePlayer player)
        {
            if (!openUis.ContainsKey(player.userID))
                return;

            string uiName = openUis[player.userID];

            if (openUis.Remove(player.userID))
                CuiHelper.DestroyUi(player, uiName);
        }

        private void DestroyOvenUI(BaseOven oven)
        {
            if (oven == null) throw new ArgumentNullException(nameof(oven));

            foreach (KeyValuePair<ulong, string> kv in openUis.ToDictionary(kv => kv.Key, kv => kv.Value))
            {
                BasePlayer player = BasePlayer.FindByID(kv.Key);

                BaseOven playerLootOven = player.inventory.loot?.entitySource as BaseOven;

                if (playerLootOven != null && oven == playerLootOven)
                {
                    DestroyUI(player);
                    RemoveLooter(oven, player);
                }
            }
        }

        [ChatCommand("fs")]
        void cmdToggle(BasePlayer player, string cmd, string[] args)
        {
            if (!HasPermission(player))
            {
                player.ConsoleMessage(lang.GetMessage("nopermission", this, player.UserIDString));
                return;
            }

            var status = string.Empty;
            var statuson = lang.GetMessage("StatusONColor", this, player.UserIDString);
            var statusoff = lang.GetMessage("StatusOFFColor", this, player.UserIDString);
            if (args.Length == 0)
            {
                if (GetEnabled(player)) status = statuson;
                else status = statusoff;

                var helpmsg = new StringBuilder();
                helpmsg.Append("<size=22><color=green>Furnace Splitter</color></size> by: FastBurst\n");
                helpmsg.Append(lang.GetMessage("StatusMessage", this, player.UserIDString) + status + "\n");
                helpmsg.Append("<color=orange>/fs on</color> - Toggles Furnace Splitter to ON\n");
                helpmsg.Append("<color=orange>/fs off</color> - Toggles Furnace Splitter to OFF\n");
                player.ChatMessage(helpmsg.ToString().TrimEnd());
                return;
            }

            switch (args[0].ToLower())
            {
                case "on":
                    SetEnabled(player, true);
                    CreateUiIfFurnaceOpen(player);

                    if (GetEnabled(player)) status = statuson;
                    else status = statusoff;

                    player.ChatMessage(lang.GetMessage("StatusMessage", this, player.UserIDString) + status);
                    break;
                case "off":
                    SetEnabled(player, false);
                    DestroyUI(player);

                    if (GetEnabled(player)) status = statuson;
                    else status = statusoff;

                    player.ChatMessage(lang.GetMessage("StatusMessage", this, player.UserIDString) + status);
                    break;
                default:
                    player.ChatMessage("Invalid syntax!");
                    break;
            }
        }

        [ConsoleCommand("furnacesplitter.enabled")]
        private void ConsoleCommand_Toggle(ConsoleSystem.Arg arg)
        {
            BasePlayer player = arg.Player();
            if (player == null)
                return;

            if (!HasPermission(player))
            {
                player.ConsoleMessage(lang.GetMessage("nopermission", this, player.UserIDString));
                return;
            }

            if (!arg.HasArgs())
            {
                player.ConsoleMessage(GetEnabled(player).ToString());
                return;
            }

            bool enabled = arg.GetBool(0);
            SetEnabled(player, enabled);
            CreateUiIfFurnaceOpen(player);
        }

        [ConsoleCommand("furnacesplitter.totalstacks")]
        private void ConsoleCommand_TotalStacks(ConsoleSystem.Arg arg)
        {
            BasePlayer player = arg.Player();
            if (player == null)
                return;

            BaseOven lootSource = player.inventory.loot?.entitySource as BaseOven;

            if (!HasPermission(player))
            {
                player.ConsoleMessage(lang.GetMessage("nopermission", this, player.UserIDString));
                return;
            }

            if (lootSource == null || !compatibleOvens.Contains(lootSource.ShortPrefabName))
            {
                player.ConsoleMessage(lang.GetMessage("lootsource_invalid", this, player.UserIDString));
                return;
            }

            string ovenName = lootSource.ShortPrefabName;
            PlayerOptions playerOption = allPlayerOptions[player.userID];

            if (playerOption.TotalStacks.ContainsKey(ovenName))
            {
                if (!arg.HasArgs())
                {
                    player.ConsoleMessage(playerOption.TotalStacks[ovenName].ToString());
                }
                else
                {
                    int newValue = (int)Mathf.Clamp(arg.GetInt(0), 0, lootSource.inventory.capacity);
                    playerOption.TotalStacks[ovenName] = newValue;
                }
            }
            else
            {
                PrintWarning($"Unsupported furnace '{ovenName}'");
                player.ConsoleMessage(lang.GetMessage("unsupported_furnace", this, player.UserIDString));
            }

            CreateUiIfFurnaceOpen(player);
        }

        [ConsoleCommand("furnacesplitter.trim")]
        private void ConsoleCommand_Trim(ConsoleSystem.Arg arg)
        {
            BasePlayer player = arg.Player();
            if (player == null)
                return;

            BaseOven lootSource = player.inventory.loot?.entitySource as BaseOven;

            if (!HasPermission(player))
            {
                player.ConsoleMessage(lang.GetMessage("nopermission", this, player.UserIDString));
                return;
            }

            if (lootSource == null || !compatibleOvens.Contains(lootSource.ShortPrefabName))
            {
                player.ConsoleMessage(lang.GetMessage("lootsource_invalid", this, player.UserIDString));
                return;
            }

            OvenInfo ovenInfo = GetOvenInfo(lootSource);
            var fuelSlots = lootSource.inventory.itemList.Where(item => item.info == lootSource.fuelType).ToList();
            int totalFuel = fuelSlots.Sum(item => item.amount);
            int toRemove = (int)Math.Floor(totalFuel - ovenInfo.FuelNeeded);

            if (toRemove <= 0)
                return;

            foreach (Item fuelItem in fuelSlots)
            {
                int toTake = Math.Min(fuelItem.amount, toRemove);
                toRemove -= toTake;

                Vector3 dropPosition = player.GetDropPosition();
                Vector3 dropVelocity = player.GetDropVelocity();

                if (toTake >= fuelItem.amount)
                {
                    if (!player.inventory.GiveItem(fuelItem))
                        fuelItem.Drop(dropPosition, dropVelocity, Quaternion.identity);
                }
                else
                {
                    Item splitItem = fuelItem.SplitItem(toTake);
                    if (!player.inventory.GiveItem(splitItem))
                        splitItem.Drop(dropPosition, dropVelocity, Quaternion.identity);
                }

                if (toRemove <= 0)
                    break;
            }
        }

        private bool HasPermission(BasePlayer player)
        {
            return permission.UserHasPermission(player.UserIDString, permUse);
        }

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
                        {
                // English
                { "turnon", "Turn On" },
                { "turnoff", "Turn Off" },
                { "title", "Furnace Splitter" },
                { "eta", "ETA" },
                { "totalstacks", "Total stacks" },
                { "trim", "Trim fuel" },
                { "lootsource_invalid", "Current loot source invalid" },
                { "unsupported_furnace", "Unsupported furnace." },
                { "nopermission", "You don't have permission to use this." },
                { "StatusONColor", "<color=green>ON</color>"},
                { "StatusOFFColor", "<color=red>OFF</color>"},
                { "StatusMessage", "Furnace Splitter status set to: "}
            }, this);
        }

        #region Exposed plugin methods

        [HookMethod("MoveSplitItem")]
        public string Hook_MoveSplitItem(Item item, BaseOven oven, int totalSlots)
        {
            MoveResult result = MoveSplitItem(item, oven, totalSlots);
            return result.ToString();
        }

        [HookMethod("GetOvenInfo")]
        public JObject Hook_GetOvenInfo(BaseOven oven)
        {
            OvenInfo ovenInfo = GetOvenInfo(oven);
            return JObject.FromObject(ovenInfo);
        }

        #endregion Exposed plugin methods
    }
}