using Newtonsoft.Json;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core;
using ProtoBuf;
using System.Collections.Generic;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("MapNote Teleport", "MON@H", "2.2.1")]
    [Description("Teleports player to marker on map when placed.")]
    public class MapNoteTeleport : CovalencePlugin
    {
        #region Initialization

        private const string PermissionUse = "mapnoteteleport.use";
        private readonly List<ulong> _playersOnCooldown = new List<ulong> ();
        private readonly List<ulong> _playersWithGodMode = new List<ulong> ();

        private void Init()
        {
            LoadData();
            permission.RegisterPermission(PermissionUse, this);
            foreach (var command in _configData.GlobalSettings.Commands)
            {
                AddCovalenceCommand(command, nameof(CmdMapNoteTeleport));
            }
            Unsubscribe(nameof(OnEntityTakeDamage));
        }

        private void OnServerInitialized()
        {
            if (_configData.GlobalSettings.Commands.Length == 0)
            {
                _configData.GlobalSettings.Commands = new[] { "mnt" };
                SaveConfig();
            } 
        }

        private void OnServerSave() => timer.Once(UnityEngine.Random.Range(0f, 60f), SaveData);

        private void Unload()
        {
            SaveData();
        }

        #endregion Initialization

        #region Configuration

        private ConfigData _configData;

        private class ConfigData
        {
            [JsonProperty(PropertyName = "Global settings")]
            public GlobalConfiguration GlobalSettings = new GlobalConfiguration();

            [JsonProperty(PropertyName = "Chat settings")]
            public ChatConfiguration ChatSettings = new ChatConfiguration();

            public class GlobalConfiguration
            {
                [JsonProperty(PropertyName = "Use permissions")]
                public bool UsePermission = true;

                [JsonProperty(PropertyName = "Allow admins to use without permission")]
                public bool AdminsAllowed = true;

                [JsonProperty(PropertyName = "Default Enabled")]
                public bool DefaultEnabled = true;

                [JsonProperty(PropertyName = "Default Cooldown")]
                public float DefaultCooldown = 10f;

                [JsonProperty(PropertyName = "Maximum Cooldown")]
                public float MaximumCooldown = 15f;

                [JsonProperty(PropertyName = "Minimum Cooldown")]
                public float MinimumCooldown = 5f;

                [JsonProperty(PropertyName = "GodMode Cooldown")]
                public float GodModeCooldown = 5f;

                [JsonProperty(PropertyName = "Commands list")]
                public string[] Commands = new[] { "mnt", "mapnoteteleport" };
            }

            public class ChatConfiguration
            {
                [JsonProperty(PropertyName = "Chat steamID icon")]
                public ulong SteamIDIcon = 0;

                [JsonProperty(PropertyName = "Notifications Enabled by default")]
                public bool DefaultNotification = true;
            }
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _configData = Config.ReadObject<ConfigData>();
                if (_configData == null)
                {
                    LoadDefaultConfig();
                    SaveConfig();
                }
            }
            catch
            {
                PrintError("The configuration file is corrupted");
                LoadDefaultConfig();
                SaveConfig();
            }
        }

        protected override void LoadDefaultConfig()
        {
            PrintWarning("Creating a new configuration file");
            _configData = new ConfigData();
        }

        protected override void SaveConfig() => Config.WriteObject(_configData);

        #endregion Configuration

        #region DataFile

        private StoredData _storedData;

        private class StoredData
        {
            public readonly Dictionary<ulong, PlayerData> PlayerData = new Dictionary<ulong, PlayerData>();
        }

        public class PlayerData
        {
            public bool Enabled;
            public float Cooldown;
            public bool Notification;
        }

        private PlayerData GetPlayerData(ulong playerId, bool addToStored = false)
        {
            PlayerData playerData;
            if (!_storedData.PlayerData.TryGetValue(playerId, out playerData))
            {
                playerData = new PlayerData
                {
                    Enabled = _configData.GlobalSettings.DefaultEnabled,
                    Cooldown = _configData.GlobalSettings.DefaultCooldown,
                    Notification = _configData.ChatSettings.DefaultNotification,
                };

                if (addToStored)
                {
                    _storedData.PlayerData.Add(playerId, playerData);
                }
            }

            return playerData;
        }

        private void LoadData()
        {
            try
            {
                _storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);
            }
            catch
            {
                _storedData = null;
            }
            finally
            {
                if (_storedData == null)
                {
                    ClearData();
                }
            }
        }

        private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(Name, _storedData);

        private void ClearData()
        {
            _storedData = new StoredData();
            SaveData();
        }

        #endregion DataFile

        #region Localization

        private string Lang(string key, string id = null, params object[] args) => string.Format(lang.GetMessage(key, this, id), args);

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["CooldownEnded"] = "Cooldown is now ended. You can teleport again.",
                ["GodModeEnded"] = "Godmode is now <color=#B22222>Disabled</color>.",
                ["Disabled"] = "<color=#B22222>Disabled</color>",
                ["Enabled"] = "<color=#228B22>Enabled</color>",
                ["MapNoteTeleport"] = "Teleporting to map marker is now {0}",
                ["MapNoteTeleportCooldown"] = "Teleporting to map marker Cooldown set to <color=#FFA500>{0}</color>s.",
                ["MapNoteTeleportCooldownLimit"] = "Teleporting to map marker Cooldown allowed is between <color=#FFA500>{0}</color>s and <color=#FFA500>{1}</color>s",
                ["MapNoteTeleportDead"] = "You can't teleport while being dead!",
                ["MapNoteTeleportMounted"] = "You can't teleport while seated!",
                ["MapNoteTeleportNotification"] = "Notification to chat is now {0}",
                ["NotAllowed"] = "You do not have permission to use this command!",
                ["Prefix"] = "<color=#00FFFF>[MapNote Teleport]</color>: ",
                ["Teleported"] = "Teleported to <color=#FFA500>{0}</color>. Godmode is now <color=#228B22>Enabled</color> for <color=#FFA500>{1}</color>s. Teleport is on Cooldown for <color=#FFA500>{2}</color>s.",

                ["SyntaxError"] = "Syntax error occured!\n"
                + "<color=#FFFF00>/{0} <time (seconds)></color> - Set teleport Cooldown time, the allowed time is between <color=#FFA500>{1}</color>s and <color=#FFA500>{2}</color>s.\n"
                + "<color=#FFFF00>/{0} <t | toggle></color> - Enable/Disable teleporting to map marker\n"
                + "<color=#FFFF00>/{0} <n | notify></color> - Enable/Disable notification to chat",
            }, this);
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["CooldownEnded"] = "Перезарядка завершена. Можете телепортироваться снова.",
                ["GodModeEnded"] = "Неуязвимость <color=#B22222>Откл.</color>.",
                ["Disabled"] = "<color=#B22222>Откл.</color>",
                ["Enabled"] = "<color=#228B22>Вкл.</color>",
                ["MapNoteTeleport"] = "Телепортация к маркеру на карте сейчас {0}",
                ["MapNoteTeleportCooldown"] = "Время перезарядки телепорта к маркеру на карте установлено на <color=#FFA500>{0}</color> секунд.",
                ["MapNoteTeleportCooldownLimit"] = "Значение времени перезарядки телепорта к маркеру на карте должно быть между <color=#FFA500>{0}</color> сек и <color=#FFA500>{1}</color> сек",
                ["MapNoteTeleportDead"] = "Вы не можете телепортироваться, пока мертвы!",
                ["MapNoteTeleportMounted"] = "Вы не можете телепортироваться, когда сидите!",
                ["MapNoteTeleportNotification"] = "Уведомления в чат сейчас {0}",
                ["NotAllowed"] = "У вас нет разрешения на использование этой команды!",
                ["Prefix"] = "<color=#00FFFF>[MapNote Teleport]</color>: ",
                ["Teleported"] = "Вы телепортированы на <color=#FFA500>{0}</color>. Неуязвимость <color=#228B22>Вкл.</color> на <color=#FFA500>{1}</color> сек. Перезарядка телепорта <color=#FFA500>{2}</color> сек.",

                ["SyntaxError"] = "Синтаксическая ошибка!\n"
                + "<color=#FFFF00>/{0} <время (сек.)></color> - Установить время перезарядки телепорта к маркеру на карте. Значение должно быть между <color=#FFA500>{1}</color> сек. и <color=#FFA500>{2}</color> сек.\n"
                + "<color=#FFFF00>/{0} <t | toggle></color> - Вкл./Откл. телепортацию к маркеру на карте\n"
                + "<color=#FFFF00>/{0} <n | notify></color> - Вкл./Откл. уведомления в чат",
            }, this, "ru");
        }

        #endregion Localization

        #region Commands

        private void CmdMapNoteTeleport(IPlayer player, string command, string[] args)
        {

            if (_configData.GlobalSettings.UsePermission && !permission.UserHasPermission(player.Id, PermissionUse))
            {
                if (!_configData.GlobalSettings.AdminsAllowed || !player.IsAdmin)
                {
                    Print(player, Lang("NotAllowed", player.Id));
                    return;
                }
            }

            if (args == null || args.Length == 0)
            {
                Print(player, Lang("SyntaxError", player.Id, _configData.GlobalSettings.Commands[0], _configData.GlobalSettings.MinimumCooldown, _configData.GlobalSettings.MaximumCooldown));
                return;
            }

            var playerData = GetPlayerData(ulong.Parse(player.Id), true);

            float Cooldown;
            if (float.TryParse(args[0], out Cooldown))
            {
                if (Cooldown <= _configData.GlobalSettings.MaximumCooldown && Cooldown >= _configData.GlobalSettings.MinimumCooldown)
                {
                    playerData.Cooldown = Cooldown;
                    if (!playerData.Enabled)
                    {
                        playerData.Enabled = true;
                    }

                    Print(player, Lang("MapNoteTeleportCooldown", player.Id, Cooldown));
                    return;
                }

                Print(player, Lang("MapNoteTeleportCooldownLimit", player.Id, _configData.GlobalSettings.MinimumCooldown, _configData.GlobalSettings.MaximumCooldown));
                return;
            }

            switch (args[0].ToLower())
            {
                case "t":
                case "toggle":
                    playerData.Enabled = !playerData.Enabled;
                    Print(player, Lang("MapNoteTeleport", player.Id, playerData.Enabled ? Lang("Enabled", player.Id) : Lang("Disabled", player.Id)));
                    return;
                case "n":
                case "notify":
                    playerData.Notification = !playerData.Notification;
                    Print(player, Lang("MapNoteTeleportNotification", player.Id, playerData.Notification ? Lang("Enabled", player.Id) : Lang("Disabled", player.Id)));
                    return;
            }

            Print(player, Lang("SyntaxError", player.Id, _configData.GlobalSettings.Commands[0], _configData.GlobalSettings.MinimumCooldown, _configData.GlobalSettings.MaximumCooldown));
        }

        #endregion Commands

        #region Helpers

        private void TeleportToNote(BasePlayer player, MapNote note)
        {
            var err = CheckPlayer(player);
            if (err != null)
            {
                Print(player.IPlayer, Lang(err, player.UserIDString));
                return;
            }

            ulong userID = player.userID;
            _playersOnCooldown.Add(userID);
            _playersWithGodMode.Add(userID);
            Subscribe(nameof(OnEntityTakeDamage));
            player.flyhackPauseTime = _configData.GlobalSettings.MaximumCooldown;
            var pos = note.worldPosition;
            pos.y = GetGroundPosition(pos);

            player.Teleport(pos);
            player.RemoveFromTriggers();
            player.ForceUpdateTriggers();
            var playerData = GetPlayerData(userID);
            if (playerData.Notification)
            {
                Print(player.IPlayer, Lang("Teleported", player.UserIDString, pos, _configData.GlobalSettings.GodModeCooldown, playerData.Cooldown));
            }

            timer.Once(playerData.Cooldown, () =>
            {
                _playersOnCooldown.Remove(userID);

                if (playerData.Notification && player != null)
                {
                    Print(player.IPlayer, Lang("CooldownEnded", player.UserIDString));
                }
            });

            timer.Once(_configData.GlobalSettings.GodModeCooldown, () =>
            {
                _playersWithGodMode.Remove(userID);
                if (_playersWithGodMode.Count == 0)
                {
                    Unsubscribe(nameof(OnEntityTakeDamage));
                }

                if (playerData.Notification && player != null)
                {
                    Print(player.IPlayer, Lang("GodModeEnded", player.UserIDString));
                }
            });
        }

        private string CheckPlayer(BasePlayer player)
        {
            if (player.isMounted)
            {
                return "MapNoteTeleportMounted";
            }

            if (!player.IsAlive())
            {
                return "MapNoteTeleportDead";
            }

            return null;
        }

        private void OnMapMarkerAdded(BasePlayer player, MapNote note)
        {
            if (player == null || note == null || _playersOnCooldown.Contains(player.userID))
            {
                return;
            }

            if (_configData.GlobalSettings.UsePermission && !permission.UserHasPermission(player.UserIDString, PermissionUse))
            {
                if (!_configData.GlobalSettings.AdminsAllowed || !player.IsAdmin)
                {
                    return;
                }
            }

            var playerData = GetPlayerData(player.userID);
            if (!playerData.Enabled)
            {
                return;
            }

            TeleportToNote(player, note);
        }

        static float GetGroundPosition(Vector3 pos)
        {
            float y = TerrainMeta.HeightMap.GetHeight(pos);
            RaycastHit hitInfo;

            if (Physics.Raycast(
                new Vector3(pos.x, pos.y + 200f, pos.z),
                Vector3.down,
                out hitInfo,
                float.MaxValue,
                (Rust.Layers.Mask.Vehicle_Large | Rust.Layers.Solid | Rust.Layers.Mask.Water)))
            {
                var cargoShip = hitInfo.GetEntity() as CargoShip;
                if (cargoShip != null)
                {
                    return hitInfo.point.y;
                }

                return Mathf.Max(hitInfo.point.y, y);
            }

            return y;
        }

        private object OnEntityTakeDamage(BasePlayer player, HitInfo info)
        {
            if (player == null || !player.userID.IsSteamId())
            {
                return null;
            }

            if (_playersWithGodMode.Contains(player.userID))
            {
                return true;
            }

            return null;
        }

        private void Print(IPlayer player, string message)
        {
            string text;
            if (string.IsNullOrEmpty(Lang("Prefix", player.Id)))
            {
                text = message;
            }
            else
            {
                text = Lang("Prefix", player.Id) + message;
            }
#if RUST
            (player.Object as BasePlayer).SendConsoleCommand ("chat.add", 2, _configData.ChatSettings.SteamIDIcon, text);
            return;
#endif
            player.Message(text);
        }

        #endregion Helpers
    }
}