﻿using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;


#region Patchnotes
/******************************************************************************
* 3.0.0 - Rewrite for more functionality.
*       - Removed Linq usage.
*       - Removed BattlePass due to several differet plugins with that name (for now).
*       - Added Profiles based on permissions (can add more).
*       - Added priority settings to permissions.
*       - Added support for custom items.
*       - Added support for itemlist.
*       - Added support for the QueuedPopups plugin.
*       - Multi rewards get displayed in 1 message
*       - Simplified the language file.
*       - Fix for duplicate Crate rewards.
*       - Hack Crates now count as a lootcrate count.
*       - Fixed and added abuse of shooting barrels from a distance.
*       - Optimised checkups and progress reset on death.
*       - Added some API options for other plugins.
* 
*******************************************************************************/

#endregion

namespace Oxide.Plugins
{
    [Info("Barrel Points" , "Krungh Crow" , "3.0.0")]
    [Description("Rewards players for destroying barrels or looting crates")]
    public class BarrelPoints : RustPlugin
    {
        #region References
        [PluginReference] Plugin Economics, ServerRewards, QueuedPopups;
        #endregion

        #region Variables
        ConfigData configData;
        bool debug;
        readonly Dictionary<ulong , int> barrelCount = new Dictionary<ulong , int>();
        readonly HashSet<NetworkableId> crateCache = new HashSet<NetworkableId>();
        #endregion

        #region Configuration
        private class ConfigData
        {
            [JsonProperty(PropertyName = "Debug")]
            public bool Debug = false;
            [JsonProperty(PropertyName = "Reset Barrel Count On Death")]
            public bool ResetOnDeath = true;
            [JsonProperty(PropertyName = "Send Notification Message")]
            public bool Notify = true;
            [JsonProperty(PropertyName = "Give Reward Every X triggers")]
            public int GiveEvery = 1;
            [JsonProperty(PropertyName = "Give Rewards For Barrels")]
            public bool UseBarrels = true;
            [JsonProperty(PropertyName = "Give Rewards For Crates")]
            public bool UseCrates = false;
            [JsonProperty(PropertyName = "Max Reward Distance")]
            public float MaxRewardDistance = 3f;
            [JsonProperty(PropertyName = "Reward Integrations")]
            public RewardSystems Systems = new RewardSystems();
            [JsonProperty(PropertyName = "Notification Settings")]
            public NotificationSettings Notifications = new NotificationSettings();
            [JsonProperty(PropertyName = "Permission Rewards")]
            public Dictionary<string , RewardProfile> Permissions = new Dictionary<string , RewardProfile>();
        }

        private class RewardSystems
        {
            [JsonProperty(PropertyName = "Use Economics")]
            public bool Economics = true;
            [JsonProperty(PropertyName = "Use ServerRewards")]
            public bool ServerRewards = false;
        }

        private class NotificationSettings
        {
            [JsonProperty(PropertyName = "Use QueuedPopups")]
            public bool UseQueuedPopups = false;
            [JsonProperty(PropertyName = "QueuedPopups Icon Type")]
            public int IconType = 1;
            [JsonProperty(PropertyName = "QueuedPopups Icon Data")]
            public string IconData = "assets/icons/loot.png";
            [JsonProperty(PropertyName = "QueuedPopups Background Color")]
            public string BackgroundColor = "0.2 0.2 0.2 0.85";
            [JsonProperty(PropertyName = "QueuedPopups Text Color")]
            public string TextColor = "1 1 1 1";
            [JsonProperty(PropertyName = "QueuedPopups Icon Color")]
            public string IconColor = "1 1 1 1";
            [JsonProperty(PropertyName = "QueuedPopups Height")]
            public float Height = 40f;
            [JsonProperty(PropertyName = "QueuedPopups Font Size")]
            public int FontSize = 14;
            [JsonProperty(PropertyName = "QueuedPopups Duration")]
            public float Duration = 6f;
        }

        private class RewardProfile
        {
            [JsonProperty(PropertyName = "Priority")]
            public int Priority = 0;
            [JsonProperty(PropertyName = "Economics Money")]
            public double Economics = 0.0;
            [JsonProperty(PropertyName = "ServerRewards Points")]
            public int ServerRewards = 0;
            [JsonProperty(PropertyName = "Items")]
            public List<ItemReward> Items = new List<ItemReward>();
        }

        private class ItemReward
        {
            [JsonProperty(PropertyName = "Item Shortname")]
            public string Item = "";
            [JsonProperty(PropertyName = "Item Name")]
            public string Name = "";
            [JsonProperty(PropertyName = "Skin ID" , DefaultValueHandling = DefaultValueHandling.Include)]
            public ulong Skin = 0;
            [JsonProperty(PropertyName = "Amount")]
            public int Amount = 1;
        }

        private class RewardAccumulator
        {
            public double Economics = 0;
            public int ServerRewards = 0;
            public Dictionary<string , MergedItemReward> Items = new Dictionary<string , MergedItemReward>();
        }

        private class MergedItemReward
        {
            public string DisplayName = "";
            public string ShortName = "";
            public ulong Skin = 0;
            public int Amount = 0;
        }

        Dictionary<string , RewardProfile> DefaultPermissions()
        {
            return new Dictionary<string , RewardProfile>
            {
                ["barrelpoints.default"] = new RewardProfile
                {
                    Priority = 0 ,
                    Economics = 2 ,
                    ServerRewards = 2 ,
                    Items = new List<ItemReward>
                    {
                        new ItemReward
                        {
                            Item="scrap",
                            Name="",
                            Skin=0,
                            Amount=1
                        }
                    }
                } ,
                ["barrelpoints.vip"] = new RewardProfile
                {
                    Priority = 1 ,
                    Economics = 5 ,
                    ServerRewards = 5 ,
                    Items = new List<ItemReward>
                    {
                        new ItemReward
                        {
                            Item="scrap",
                            Name="",
                            Skin=0,
                            Amount=3
                        },
                        new ItemReward
                        {
                            Item="wood",
                            Name="",
                            Skin=0,
                            Amount=25
                        }
                    }
                }
            };
        }

        bool LoadConfigVariables()
        {
            try { configData = Config.ReadObject<ConfigData>(); }
            catch { return false; }

            if (configData == null) configData = new ConfigData();
            if (configData.Systems == null) configData.Systems = new RewardSystems();
            if (configData.Notifications == null) configData.Notifications = new NotificationSettings();
            if (configData.Permissions == null || configData.Permissions.Count == 0)
                configData.Permissions = DefaultPermissions();
            if (configData.GiveEvery < 1)
                configData.GiveEvery = 1;
            if (configData.MaxRewardDistance < 0f)
                configData.MaxRewardDistance = 0f;

            foreach (var entry in configData.Permissions)
            {
                if (entry.Value == null)
                {
                    configData.Permissions[entry.Key] = new RewardProfile();
                    continue;
                }

                if (entry.Value.Items == null)
                    entry.Value.Items = new List<ItemReward>();
            }

            SaveConf();
            return true;
        }

        protected override void LoadDefaultConfig()
        {
            Puts("Creating new BarrelPoints config.");
            configData = new ConfigData
            {
                Permissions = DefaultPermissions()
            };
            SaveConf();
        }

        void SaveConf() => Config.WriteObject(configData , false);
        #endregion

        #region Language
        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string , string>
            {
                ["Reward Header"] = "Rewards received:" ,
                ["Reward Economy"] = "${0}" ,
                ["Reward RP"] = "{0} RP" ,
                ["Reward Item"] = "{0}x {1}"
            } , this);
        }

        string Msg(string key , string id = null) => lang.GetMessage(key , this , id);
        #endregion

        #region Hooks
        void Init()
        {
            if (!LoadConfigVariables())
            {
                Puts("Config issue detected.");
                return;
            }

            debug = configData.Debug;
            RegisterPermissions();
        }

        void OnServerInitialized() => ValidatePlugins();

        void Unload()
        {
            barrelCount.Clear();
            crateCache.Clear();
        }

        void OnEntityDeath(BaseCombatEntity entity , HitInfo info)
        {
            if (!configData.UseBarrels) return;
            if (entity == null || info == null) return;

            BasePlayer player = info.InitiatorPlayer;
            if (player == null || !player.IsValid()) return;
            if (!IsBarrel(entity.ShortPrefabName)) return;
            if (!IsWithinRewardDistance(player , entity)) return;

            RewardProfile reward = GetPlayerRewardProfile(player);
            if (reward == null) return;

            int count;
            if (!barrelCount.TryGetValue(player.userID , out count))
                count = 0;

            count++;

            if (count >= configData.GiveEvery)
            {
                ProcessRewards(player , reward);
                barrelCount[player.userID] = 0;
                return;
            }

            barrelCount[player.userID] = count;
        }

        void OnLootEntity(BasePlayer player , BaseEntity entity)
        {
            if (!configData.UseCrates) return;
            if (player == null || entity == null) return;
            if (!IsCrate(entity.ShortPrefabName)) return;
            if (!IsWithinRewardDistance(player , entity)) return;
            if (!crateCache.Add(entity.net.ID)) return;

            RewardProfile reward = GetPlayerRewardProfile(player);
            if (reward == null) return;

            ProcessRewards(player , reward);
        }
        void OnEntityKill(BaseNetworkable entity)
        {
            if (!configData.UseCrates) return;
            if (entity == null) return;
            if (!IsCrate(entity.ShortPrefabName)) return;

            crateCache.Remove(entity.net.ID);
        }

        void OnPlayerDeath(BasePlayer player , HitInfo info)
        {
            if (!configData.ResetOnDeath) return;
            if (player == null) return;

            if (barrelCount.ContainsKey(player.userID))
                barrelCount[player.userID] = 0;
        }
        #endregion

        #region Core
        void RegisterPermissions()
        {
            foreach (var entry in configData.Permissions)
                if (!permission.PermissionExists(entry.Key , this))
                    permission.RegisterPermission(entry.Key , this);
        }

        void ValidatePlugins()
        {
            if (configData.Systems.Economics && Economics == null)
                Puts("Economics enabled but plugin missing");

            if (configData.Systems.ServerRewards && ServerRewards == null)
                Puts("ServerRewards enabled but plugin missing");

            if (configData.Notifications.UseQueuedPopups && QueuedPopups == null)
                Puts("QueuedPopups enabled but plugin missing");
        }

        bool IsBarrel(string name)
        {
            if (string.IsNullOrEmpty(name)) return false;
            if (name.StartsWith("loot-barrel")) return true;
            if (name.StartsWith("loot_barrel")) return true;
            if (name == "oil_barrel") return true;
            return false;
        }

        bool IsCrate(string name)
        {
            if (string.IsNullOrEmpty(name)) return false;
            if (name.Contains("crate_")) return true;
            if (name == "heli_crate") return true;
            if (name == "bradley_crate") return true;
            if (name == "codelockedhackablecrate") return true;
            if (name == "vehicle_parts") return true;
            if (name == "foodbox") return true;

            return false;
        }

        RewardProfile GetPlayerRewardProfile(BasePlayer player)
        {
            RewardProfile best = null;
            int bestPriority = int.MinValue;

            foreach (var entry in configData.Permissions)
            {
                if (entry.Value == null) continue;
                if (!permission.UserHasPermission(player.UserIDString , entry.Key)) continue;

                if (best == null || entry.Value.Priority > bestPriority)
                {
                    best = entry.Value;
                    bestPriority = entry.Value.Priority;
                }
            }

            return best;
        }

        void ProcessRewards(BasePlayer player , RewardProfile reward)
        {
            if (player == null || reward == null) return;

            RewardAccumulator acc = new RewardAccumulator();

            TryGiveEconomics(player , reward , acc);
            TryGiveServerRewards(player , reward , acc);
            TryGiveItems(player , reward , acc);

            Interface.CallHook("OnBarrelPointsHandout" , player); // API

            SendRewardSummary(player , acc);
        }

        void TryGiveEconomics(BasePlayer player , RewardProfile reward , RewardAccumulator acc)
        {
            if (!configData.Systems.Economics || Economics == null) return;
            if (reward.Economics <= 0) return;

            Economics.Call("Deposit" , player.userID , reward.Economics);
            acc.Economics += reward.Economics;
        }

        void TryGiveServerRewards(BasePlayer player , RewardProfile reward , RewardAccumulator acc)
        {
            if (!configData.Systems.ServerRewards || ServerRewards == null) return;
            if (reward.ServerRewards <= 0) return;

            ServerRewards.Call("AddPoints" , player.userID , reward.ServerRewards);
            acc.ServerRewards += reward.ServerRewards;
        }

        void TryGiveItems(BasePlayer player , RewardProfile reward , RewardAccumulator acc)
        {
            if (reward.Items == null || reward.Items.Count == 0) return;

            foreach (var entry in reward.Items)
            {
                if (entry == null) continue;
                if (string.IsNullOrEmpty(entry.Item) || entry.Amount <= 0) continue;

                Item item = ItemManager.CreateByName(entry.Item , entry.Amount , entry.Skin);
                if (item == null) continue;

                if (!string.IsNullOrEmpty(entry.Name))
                    item.name = entry.Name;

                player.GiveItem(item);
                MergeItemReward(entry , acc);
            }
        }

        void MergeItemReward(ItemReward reward , RewardAccumulator acc)
        {
            string displayName = !string.IsNullOrEmpty(reward.Name) ? reward.Name : reward.Item;
            string key = $"{reward.Item}|{displayName}|{reward.Skin}";

            MergedItemReward existing;
            if (acc.Items.TryGetValue(key , out existing))
            {
                existing.Amount += reward.Amount;
                return;
            }

            acc.Items[key] = new MergedItemReward
            {
                ShortName = reward.Item ,
                DisplayName = displayName ,
                Skin = reward.Skin ,
                Amount = reward.Amount
            };
        }

        void SendRewardSummary(BasePlayer player , RewardAccumulator acc)
        {
            if (!configData.Notify) return;
            if (player == null) return;

            StringBuilder sb = new StringBuilder();

            if (acc.Economics > 0)
                AppendRewardPart(sb , string.Format(Msg("Reward Economy" , player.UserIDString) , acc.Economics));

            if (acc.ServerRewards > 0)
                AppendRewardPart(sb , string.Format(Msg("Reward RP" , player.UserIDString) , acc.ServerRewards));

            foreach (var entry in acc.Items)
                AppendRewardPart(sb , string.Format(Msg("Reward Item" , player.UserIDString) , entry.Value.Amount , entry.Value.DisplayName));

            if (sb.Length <= 0) return;

            string message = $"{Msg("Reward Header" , player.UserIDString)} {sb}";

            if (TrySendQueuedPopup(player , message))
                return;

            Player.Message(player , message);
        }

        void AppendRewardPart(StringBuilder sb , string text)
        {
            if (sb.Length > 0)
                sb.Append(", ");
            sb.Append(text);
        }

        bool TrySendQueuedPopup(BasePlayer player , string message)
        {
            if (!configData.Notifications.UseQueuedPopups) return false;
            if (QueuedPopups == null) return false;

            QueuedPopups.Call(
                "AddPopup" ,
                player ,
                message ,
                configData.Notifications.IconType ,
                configData.Notifications.IconData ,
                configData.Notifications.BackgroundColor ,
                configData.Notifications.TextColor ,
                configData.Notifications.IconColor ,
                configData.Notifications.Height ,
                configData.Notifications.FontSize ,
                configData.Notifications.Duration
            );

            return true;
        }
        #endregion

        #region Helpers
        void DebugLog(string msg)
        {
            if (debug)
                Puts("[Debug] " + msg);
        }

        bool IsWithinRewardDistance(BasePlayer player , BaseEntity entity)
        {
            if (player == null || entity == null)
                return false;

            return Vector3.Distance(player.transform.position , entity.transform.position) <= configData.MaxRewardDistance;
        }

        #endregion

        #region API

        int BarrelPointsCount(BasePlayer player)
        {
            if (player == null)
                return 0;

            int count;
            if (barrelCount.TryGetValue(player.userID , out count))
                return count;

            return 0;
        }

        #endregion
    }
}