using System;
using System.Collections.Generic;
using UnityEngine;
using Oxide.Core;
using Oxide.Core.Plugins;

namespace Oxide.Plugins
{
    [Info("MagicArea", "Norn", "0.2.1", ResourceId = 1551)]
    [Description("Areas to practice building/pvp.")]
    public class MagicArea : RustPlugin
    {
        [PluginReference] Plugin Kits, MagicTeleportation;

        // -------------- [ SAVING VARIABLES ] --------------

        class MA
        {
            public Dictionary<int, AreaInfo> Areas = new Dictionary<int, AreaInfo>();
            public Dictionary<ulong, PlayerData> PlayerData = new Dictionary<ulong, PlayerData>();
            public Dictionary<uint, AreaEntities> Entities = new Dictionary<uint, AreaEntities>();
            public MA() { }
        }
        public class AreaEntities
        {
            public uint iID;
            public int iAreaID;
            public ulong uCreatorID;
            public int iCreated;
            public int iExpire;
            public AreaEntities() { }
        }
        class PlayerData
        {
            public ulong uUserID;
            public int iInArea;
            public Int32 iInitStamp;
            public PlayerData() { }
        }
        class SpawnInfo
        {
            public float fX;
            public float fY;
            public float fZ;
            public int iEID;
            public SpawnInfo() { }
        }
        class AreaInfo
        {
            public int iID;
            public string tTitle;
            public string tDescription;
            public float fMinX;
            public float fMinY;
            public float fMinZ;
            public float fRadius;
            public bool uEnabled;
            public bool bGod;
            public bool bResetInv;
            public int iCount;
            public string tKit;
            public bool bCanResearch;
            public bool bRemoveEntities;
            public int iEntityExpire;
            public Dictionary<int, SpawnInfo> Spawns = new Dictionary<int, SpawnInfo>();
            public AreaInfo() { }
        }
        MA MAData;

        #region Localization

        private void LoadDefaultMessages()
        {
            var messages = new Dictionary<string, string>
            {
                {"KitReceived", "[<color=green>INFO:</color>] You have been given kit: <color=yellow>{kit_name}</color> to use inside of <color=yellow>{area_title}</color>."},
                {"InventoryReset", "[<color=green>INFO:</color>] Your inventory has been <color=red>reset</color> because you left an area."},
                {"ResearchBlocked", "[<color=yellow>ERROR</color>] Researching is <color=red>blocked</color> in this area."},
                {"AreaCreated", "[<color=green>INFO:</color>] You have successfully created area id: {area_id}."},
                {"TeleportedBack", "[<color=green>INFO</color>] You have been teleported back to <color=yellow>{area_title}</color>." },
                {"EntityExpiry", "[<color=yellow>INFO</color>] This object will expire at: <color=yellow>{expire_time}</color>!\n(Current Time: <color=yellow>{current_time}</color>)" }
            };
            lang.RegisterMessages(messages, this);
        }
        string GetMessage(string key, string steamId = null) => lang.GetMessage(key, this, steamId);

        #endregion
        // -----------------------------------------------

        Timer AreaSync;
        private void OnServerInitialized()
        {
            Puts("Loaded " + MAData.Areas.Count.ToString() + " area(s).");

            // --- [ TIMERS ] ---
            int seconds = Convert.ToInt32(Config["Settings", "TimerInterval"]);
            AreaSync = timer.Repeat(seconds, 0, () => AreaTimer());
            LoadDefaultMessages();
        }

        private void AreaTimer()
        {
            // ==================================== [ ENTITY PORTION ] ==========================================
            new List<uint>(MAData.Entities.Keys).ForEach(u =>
            {
                BaseNetworkable ent = BaseNetworkable.serverEntities.Find(u);
                if (ent != null && UnixTimeStampUTC() >= MAData.Entities[u].iExpire)
                {
                    int area_id = MAData.Entities[u].iAreaID;
                    if (area_id != -1 && MagicAreaExists(area_id))
                    {
                        if (MAData.Areas[area_id].bRemoveEntities && MAData.Areas[area_id].iEntityExpire != 0)
                        {
                            MAData.Entities.Remove(u);
                            ent.Kill();
                            if (Convert.ToBoolean(Config["Settings", "Debug"])) { Puts("Removing entity ID: " + u.ToString() + " [EXPIRED]"); }
                        }
                    }
                }
            });
            // ================================== [ PLAYER ] ====================================================
            foreach (BasePlayer connected_player in BasePlayer.activePlayerList)
            {
                bool found = false;
                if (connected_player != null && connected_player.IsConnected)
                {
                    if (!PlayerExists(connected_player)) { InitPlayer(connected_player); }
                    foreach(var area in MAData.Areas.Values)
                    {
                        if (PlayerToPoint(connected_player, area.fRadius, area.fMinX, area.fMinY, area.fMinZ))
                        {
                            if (MAData.PlayerData[connected_player.userID].iInArea != area.iID)
                            {
                                MAData.PlayerData[connected_player.userID].iInArea = area.iID;
                                Interface.Oxide.CallHook("OnPlayerEnterMagicArea", connected_player, MAData.PlayerData[connected_player.userID].iInArea);
                                if (area.tKit.Length >= 1 && area.tKit != null)
                                {
                                    object iskit = Kits?.Call("isKit", area.tKit);
                                    if (iskit is bool && (bool)iskit)
                                    {
                                        connected_player.inventory.Strip();
                                        object successkit = Kits.Call("GiveKit", connected_player, area.tKit);
                                        if (successkit is bool && (bool)successkit)
                                        {
                                            string parsed_config = GetMessage("KitReceived", connected_player.UserIDString);
                                            parsed_config = parsed_config.Replace("{kit_name}", area.tKit);
                                            parsed_config = parsed_config.Replace("{area_title}", area.tTitle);
                                            PrintToChat(connected_player, parsed_config);
                                        }
                                    }
                                }
                            }
                            found = true;
                        }
                    }
                    if (!found && MAData.PlayerData[connected_player.userID].iInArea != -1)
                    {
                        if (MAData.Areas[MAData.PlayerData[connected_player.userID].iInArea].bResetInv)
                        {
                            PrintToChat(connected_player, GetMessage("InventoryReset", connected_player.UserIDString));
                            connected_player.inventory.Strip();
                        }
                        Interface.Oxide.CallHook("OnPlayerExitMagicArea", connected_player, MAData.PlayerData[connected_player.userID].iInArea);
                        MAData.PlayerData[connected_player.userID].iInArea = -1;
                    }
                }
            }
        }

        private void OnPlayerExitMagicArea(BasePlayer player, int area)
        {
            if (MagicAreaExists(area)) {
                if (Convert.ToBoolean(Config["Settings", "Debug"])) { Puts(player.displayName + " has left: " + MAData.Areas[area].tTitle + " [ " + area.ToString() + " ]"); PrintToChat(player, player.displayName + " has left: " + MAData.Areas[area].tTitle + " [ " + area.ToString() + " ]"); } }
        }

        private void OnPlayerEnterMagicArea(BasePlayer player, int area)
        {
            if (MagicAreaExists(area)) { if (Convert.ToBoolean(Config["Settings", "Debug"])) { Puts(player.displayName + " has entered: " + MAData.Areas[area].tTitle + " [ " + area.ToString() + " ]"); PrintToChat(player, player.displayName + " has entered: " + MAData.Areas[area].tTitle + " [ " + area.ToString() + " ]"); } }
        }

        private int CreateMagicArea(Vector3 position, float radius, string title = "-1", string description = "-1", bool enabled = true)
        {
            int id = -1;
            if (position != Vector3.zero)
            {
                AreaInfo Area = new AreaInfo();
                Area.iID = GetRandomNumber(0, 25);
                if (title == "-1") { title = "Area" + Area.iID.ToString(); }
                Area.tTitle = title;
                Area.tDescription = description;
                Area.uEnabled = enabled;
                Area.iCount = 0;
                Area.fMinX = position.x;
                Area.fMinY = position.y;
                Area.fMinZ = position.z;
                Area.fRadius = radius;
                Area.bGod = false;
                Area.tKit = "";
                Area.bCanResearch = false;
                Area.bResetInv = true;
                Area.bRemoveEntities = true;
                Area.iEntityExpire = Convert.ToInt32(Config["Settings", "DefaultExpire"]);
                id = Area.iID;
                MAData.Areas.Add(Area.iID, Area);
                SaveData();
            }
            return id;
        }

        private bool MagicAreaExists(int id)
        {
            AreaInfo item = null;
            if (MAData.Areas.TryGetValue(id, out item)) { return true; }
            return false;
        }

        public static DateTime UnixTimeStampToDateTime(double unixTimeStamp)
        {
            System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
            dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime();
            return dtDateTime;
        }

        private void OnEntityBuilt(Planner planner, GameObject gameObject)
        {
            BaseEntity e = gameObject.ToBaseEntity();
            BasePlayer player = planner.GetOwnerPlayer();
            if (!(e is BaseEntity) || player == null)
            {
                return;
            }
            int id = MAData.PlayerData[player.userID].iInArea;
            if (PlayerExists(player) && id != -1 && MagicAreaExists(id))
            {
                if (MAData.Areas[id].bRemoveEntities && MAData.Areas[id].iEntityExpire != 0)
                {
                    AreaEntities Area = new AreaEntities();
                    Area.iAreaID = id;
                    Area.iCreated = UnixTimeStampUTC();
                    Area.iExpire = Area.iCreated + MAData.Areas[id].iEntityExpire;
                    Area.iID = (uint)e.net.ID;
                    Area.uCreatorID = player.userID;
                    MAData.Entities.Add(Area.iID, Area);
                    if (Convert.ToBoolean(Config["Settings", "Debug"]))
                    {
                        string parsed_config = GetMessage("EntityExpiry", player.UserIDString);
                        parsed_config = parsed_config.Replace("{expire_time}", UnixTimeStampToDateTime(Area.iExpire).ToLongTimeString());
                        parsed_config = parsed_config.Replace("{current_time}", UnixTimeStampToDateTime(UnixTimeStampUTC()).ToLongTimeString());
                        PrintToChat(player, parsed_config);
                    }
                }
            }
        }

        private void OnEntityDeath(BaseCombatEntity entity, HitInfo info)
        {
            if (entity is BasePlayer)
            {
                var player = entity as BasePlayer;
                if (PlayerExists(player))
                {
                    int id = MAData.PlayerData[player.userID].iInArea;
                    if (id != -1 && MagicAreaExists(id)) { MAData.PlayerData[player.userID].iInArea = -1; }
                }
            }
            else
            {
                uint id = entity.net.ID;
                if (MAData.Entities.ContainsKey(id))
                {
                    if (Convert.ToBoolean(Config["Settings", "Debug"])) { Puts("Destroying entity: " + id.ToString() + ". [DEATH]"); }
                    MAData.Entities.Remove(id);
                }
            }
        }

        private void OnEntitySpawned(BaseNetworkable entity)
        {
            uint id = entity.net.ID;
            if (MAData.Entities.ContainsKey(id))
            {
                if (UnixTimeStampUTC() >= MAData.Entities[id].iExpire)
                {
                    if (Convert.ToBoolean(Config["Settings", "Debug"])) { Puts("Destroying entity: " + id.ToString() + ". [EXPIRED]"); }
                    entity.Kill();
                    MAData.Entities.Remove(id);
                }
            }
        }

        private object OnItemResearch(Item item, BasePlayer player)
        {
            if (PlayerExists(player))
            {
                int id = MAData.PlayerData[player.userID].iInArea;
                if (id != -1 && MagicAreaExists(id)) { if (!MAData.Areas[id].bCanResearch) { PrintToChat(player, GetMessage("ResearchBlocked", player.UserIDString)); return false; } }
            }
            return null;
        }

        // --- [ CONSOLE ] ---

        [ConsoleCommand("area.create")]
        private void ccmdCreateArea(ConsoleSystem.Arg arg)
        {
            if (arg.Connection.authLevel >= Convert.ToInt32(Config["Admin", "MaxLevel"]))
            {
                var player = arg.Connection?.player as BasePlayer;
                if (player == null) return;
                int id = CreateMagicArea(player.transform.position, 50);
                Puts(GetMessage("AreaCreated", player.UserIDString).Replace("{area_id}", id.ToString()));
                PrintToChat(player, GetMessage("AreaCreated", player.UserIDString).Replace("{area_id}", id.ToString()));
            }
        }

        // ------------------

        private object OnPlayerRespawned(BasePlayer player)
        {
            int id = MAData.PlayerData[player.userID].iInArea; bool success = false;
            if (PlayerExists(player) && id != -1 && MagicAreaExists(id))
            {
                if (MAData.Areas[id].Spawns.Count == 0)
                {
                    success = Convert.ToBoolean(MagicTeleportation.CallHook("InitTeleport", player, MAData.Areas[id].fMinX, MAData.Areas[id].fMinY, MAData.Areas[id].fMinZ, false, true, MAData.Areas[id].tTitle, null, 1, 3));
                }
                if (success)
                {
                    string parsed_config = GetMessage("TeleportedBack", player.UserIDString);
                    parsed_config = parsed_config.Replace("{area_title}", MAData.Areas[id].tTitle);
                    PrintToChat(player, parsed_config);
                }
                MAData.PlayerData[player.userID].iInArea = -1;
                return false;
            }
            return null;
        }

        private bool PlayerToPoint(BasePlayer player, float radi, float x, float y, float z)
        {
            float oldposx = 0.0f, oldposy = 0.0f, oldposz = 0.0f, tempposx = 0.0f, tempposy = 0.0f, tempposz = 0.0f;
            oldposx = player.transform.position.x;
            oldposy = player.transform.position.y;
            oldposz = player.transform.position.z;
            tempposx = (oldposx - x);
            tempposy = (oldposy - y);
            tempposz = (oldposz - z);
            if (((tempposx < radi) && (tempposx > -radi)) && ((tempposy < radi) && (tempposy > -radi)) && ((tempposz < radi) && (tempposz > -radi)))
            {
                return true;
            }
            return false;
        }

        private void Loaded()
        {
            MAData = Interface.Oxide.DataFileSystem.ReadObject<MA>(this.Title);
        }

        private void Unload()
        {
            SaveData();
        }

        private void SaveData()
        {
            Interface.Oxide.DataFileSystem.WriteObject(this.Title, MAData);
        }

        // ======================== [ PLAYER ] ===========================

        private bool PlayerExists(BasePlayer player)
        {
            PlayerData item = null;
            if (MAData.PlayerData.TryGetValue(player.userID, out item))
            {
                return true;
            }
            return false;
        }

        private bool InitPlayer(BasePlayer player)
        {
            if (!PlayerExists(player))
            {
                PlayerData z = new PlayerData();
                z.uUserID = player.userID;
                z.iInArea = -1;
                z.iInitStamp = UnixTimeStampUTC();
                MAData.PlayerData.Add(z.uUserID, z);
                if (Convert.ToBoolean(Config["Settings", "Debug"])) { Puts("Registering " + player.displayName + " [ " + player.userID.ToString() + " ]."); }
                return true;
            }
            return false;
        }

        // ===============================================================

        private bool IsPlayerInArea(BasePlayer player, float MinX, float MinY, float MaxX, float MaxY)
        {
            if (player != null && player.IsConnected)
            { float X = player.transform.position.x; float Y = player.transform.position.y; float Z = player.transform.position.z; if (X >= MinX && X <= MaxX && Y >= MinY && Y <= MaxY) { return true; } }
            return false;
        }

        protected override void LoadDefaultConfig()
        {
            Puts("No configuration file found, generating..."); Config.Clear();
            Config["Settings", "TimerInterval"] = 1;
            Config["Settings", "Debug"] = false;
            Config["Settings", "DefaultExpire"] = 10800; // 3 hours
            Config["Admin", "MaxLevel"] = 2;
        }

        private HitInfo OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo)
        {
            if (entity is BasePlayer)
            {
                var player = entity as BasePlayer;
                if (PlayerExists(player))
                {
                    int id = MAData.PlayerData[player.userID].iInArea;
                    if (id != -1 && MagicAreaExists(id))
                    {
                        if (MAData.Areas[id].bGod)
                        {
                            hitInfo.damageTypes.ScaleAll(0f);
                            return hitInfo;
                        }
                    }
                }
            }
            return null;
        }

        object OnStructureUpgrade(BuildingBlock block, BasePlayer player, BuildingGrade.Enum grade)
        {
            return null;
        }

        public static Int32 UnixTimeStampUTC()
        {
            Int32 unixTimeStamp;
            DateTime currentTime = DateTime.Now;
            DateTime zuluTime = currentTime.ToUniversalTime();
            DateTime unixEpoch = new DateTime(1970, 1, 1);
            unixTimeStamp = (Int32)(zuluTime.Subtract(unixEpoch)).TotalSeconds;
            return unixTimeStamp;
        }

        public static int GetRandomNumber(int min, int max)
        {
            System.Random r = new System.Random();
            int n = r.Next();
            return n;
        }
    }
}