/***********************************************************************************************************************/
/*** DO NOT edit this file! Edit the files under `oxide/config` and/or `oxide/lang`, created once plugin has loaded. ***/
/*** Please note, support cannot be provided if the plugin has been modified. Please use a fresh copy if modified.   ***/
/***********************************************************************************************************************/

//#define DEBUG

using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;

namespace Oxide.Plugins
{
    [Info("Rename", "Wulf", "1.1.2")]
    [Description("Allows players with permission to instantly rename other players or themselves")]
    internal class Rename : CovalencePlugin
    {
        #region Configuration

        private Configuration _config;

        public class Configuration
        {
            [JsonProperty("Notify player of rename")]
            public bool NotifyPlayer = true;

            [JsonProperty("Persistence for renames")]
            public bool Persistence = true;

            [JsonProperty("Prevent admin renames")]
            public bool PreventAdmin = true;

            private string ToJson() => JsonConvert.SerializeObject(this);

            public Dictionary<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(ToJson());
        }

        protected override void LoadDefaultConfig() => _config = new Configuration();

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                // Try loading existing configuration
                _config = Config.ReadObject<Configuration>();
                if (_config == null)
                {
                    throw new JsonException();
                }

                // Check if configuration needs to be updated
                if (!_config.ToDictionary().Keys.SequenceEqual(Config.ToDictionary(x => x.Key, x => x.Value).Keys))
                {
                    LogWarning("Configuration appears to be outdated; updating and saving");
                    SaveConfig();
                }
            }
            catch
            {
                // Load default configuration if existing is invalid
                LogWarning($"Configuration file {Name}.json is invalid; using defaults");
                LoadDefaultConfig();
            }
        }

        protected override void SaveConfig()
        {
            LogWarning($"Configuration changes saved to {Name}.json");
            Config.WriteObject(_config, true);
        }

        #endregion Configuration

        #region Stored Data

        private class StoredData
        {
            public readonly HashSet<RenameInfo> Renames = new HashSet<RenameInfo>();
        }

        private class RenameInfo : IEquatable<RenameInfo>
        {
            public string Id;
            public string Old;
            public string New;

            public RenameInfo()
            {
            }

            public RenameInfo(string playerId, string oldName, string newName)
            {
                Id = playerId;
                Old = oldName;
                New = newName;
            }

            public override int GetHashCode()
            {
                return Id?.GetHashCode() ?? 0;
            }

            public bool Equals(RenameInfo other)
            {
                return other != null && (Id?.Equals(other.Id) ?? false);
            }
        }

        private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(Name, _storedData);

        private void OnServerSave() => SaveData();

        private void Unload() => SaveData();

        #endregion Stored Data

        #region Localization

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["CommandRename"] = "rename",
                ["CommandResetName"] = "resetname",
                ["CommandUsageRename"] = "Usage: {0} <name or id> [new name] (new name only if renaming self)",
                ["CommandUsageReset"] = "Usage: {0} [name or id] (optional if resetting self)",
                ["NotAllowed"] = "You are not allowed to use the '{0}' command",
                ["PlayerAlreadyNamed"] = "{0} is already named {1}",
                ["PlayerIsAdmin"] = "{0} is admin and cannot be renamed",
                ["PlayerNameReset"] = "Reset the player name for {0} to {1}",
                ["PlayerNotFound"] = "{0} was not found",
                ["PlayerNotRenamed"] = "{0} is not renamed",
                ["PlayerRenamed"] = "{0} was renamed to {1}",
                ["YouAreNotRenamed"] = "You are not renamed",
                ["YouWereRenamed"] = "You were renamed to {0}",
                ["YourNameIsAlready"] = "Your name is already {0}",
                ["YourNameReset"] = "Your name was reset to {0}",
                ["NoPlayersFound"] = "No players found with name or ID {0}",
                ["PlayersFound"] = "Multiple players were found, please specify: {0}"
            }, this);
        }

        #endregion Localization

        #region Initialization

        private const string PermissionOthers = "rename.others";
        private const string PermissionSelf = "rename.self";

        private StoredData _storedData;

        private void Init()
        {
            // Load stored data from file to memory
            _storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();

            // Clean up any old data with null values
            foreach (RenameInfo renameInfo in _storedData.Renames)
            {
                if (renameInfo.Id == null || renameInfo.Old == null || renameInfo.New == null)
                {
                    _storedData.Renames.Remove(renameInfo);
                }
            }

            // Register permissions and add commands
            permission.RegisterPermission(PermissionOthers, this);
            permission.RegisterPermission(PermissionSelf, this);
            AddLocalizedCommand(nameof(CommandRename));
            AddLocalizedCommand(nameof(CommandResetName));

            // Disable select hooks if persistence is disabled
            if (!_config.Persistence)
            {
                Unsubscribe(nameof(OnServerSave));
                Unsubscribe(nameof(Unload));
            }
        }

        #endregion Initialization

        #region Name Handling

        private void OnUserConnected(IPlayer player)
        {
            // Check if player should be renamed on connection
            RenameInfo renameInfo = _storedData.Renames.FirstOrDefault(r => r.Id == player.Id);
            if (renameInfo != null && _config.Persistence)
            {
                player.Rename(renameInfo.New);
                if (_config.NotifyPlayer)
                {
                    Message(player, "YouWereRenamed", renameInfo.New);
                }
                Log(GetLang("PlayerRenamed", null, player.Name, renameInfo.New));
            }
        }

        #endregion Name Handling

        #region Commands

        private void CommandRename(IPlayer player, string command, string[] args)
        {
            if ((args.Length >= 2 && !player.HasPermission(PermissionOthers)) || !player.HasPermission(PermissionSelf))
            {
                Message(player, "NotAllowed", command);
                return;
            }

            if (args.Length < 1 || args.Length == 1 && player.IsServer)
            {
                Message(player, "CommandUsageRename", command);
                return;
            }

            IPlayer target = args.Length >= 2 ? FindPlayer(args[0], player) : player;
            if (target == null)
            {
                return;
            }

            if (args.Length >= 2 && _config.PreventAdmin && target.IsAdmin && !Equals(target, player))
            {
                Message(player, "PlayerIsAdmin", target.Name);
                return;
            }

            string newName = args.Length >= 2 ? args[1] : args[0];

            if (target.Name.Equals(newName))
            {
                if (Equals(target, player))
                {
                    Message(target, "YourNameIsAlready", newName);
                }
                else
                {
                    Message(target, "PlayerAlreadyNamed", target.Name, newName);
                }
                return;
            }

            RenameInfo renameInfo = _storedData.Renames.FirstOrDefault(r => r.Id == target.Id);
            if (renameInfo == null)
            {
                _storedData.Renames.Add(new RenameInfo(target.Id, target.Name, newName));
            }
            else
            {
                renameInfo.New = newName;
            }

            if (!Equals(target, player))
            {
                Message(player, "PlayerRenamed", target.Name, newName);
            }

            if (target.IsConnected)
            {
                if (_config.NotifyPlayer)
                {
                    Message(target, "YouWereRenamed", newName);
                }
                target.Rename(newName);
            }
        }

        private void CommandResetName(IPlayer player, string command, string[] args)
        {
            if ((args.Length >= 1 && !player.HasPermission(PermissionOthers)) || !player.HasPermission(PermissionSelf))
            {
                Message(player, "NotAllowed", command);
                return;
            }

            if (args.Length < 1 && player.IsServer)
            {
                Message(player, "CommandUsageReset", command);
                return;
            }

            IPlayer target = args.Length >= 1 ? FindPlayer(args[0], player) : player;
            if (target == null)
            {
                return;
            }

            RenameInfo renameInfo = _storedData.Renames.FirstOrDefault(r => r.Id == target.Id);
            if (renameInfo == null)
            {
                if (args.Length > 0)
                {
                    Message(player, "PlayerNotRenamed", args[0]);
                }
                else
                {
                    Message(player, "YouAreNotRenamed");
                }

                return;
            }

            _storedData.Renames.Remove(renameInfo);

            if (!Equals(target, player))
            {
                Message(player, "PlayerNameReset", target.Name, renameInfo.Old);
            }

            if (target.IsConnected)
            {
                if (_config.NotifyPlayer)
                {
                    Message(target, "YourNameReset", renameInfo.Old);
                }
                target.Rename(renameInfo.Old);
            }
        }

        #endregion Commands

        #region Helpers

        private void AddLocalizedCommand(string command)
        {
            foreach (string language in lang.GetLanguages(this))
            {
                foreach (KeyValuePair<string, string> message in lang.GetMessages(language, this))
                {
                    if (message.Key.Equals(command) && !string.IsNullOrEmpty(message.Value))
                    {
                        AddCovalenceCommand(message.Value, command);
                    }
                }
            }
        }

        private IPlayer FindPlayer(string playerNameOrId, IPlayer player)
        {
            IPlayer[] foundPlayers = players.FindPlayers(playerNameOrId).ToArray();
            if (foundPlayers.Length > 1)
            {
                Message(player, "PlayersFound", string.Join(", ", foundPlayers.Select(p => p.Name).Take(10).ToArray()).Truncate(60));
                return null;
            }

            IPlayer target = foundPlayers.Length == 1 ? foundPlayers[0] : null;
            if (target == null)
            {
                Message(player, "NoPlayersFound", playerNameOrId);
                return null;
            }

            return target;
        }

        private string GetLang(string langKey, string playerId = null, params object[] args)
        {
            return string.Format(lang.GetMessage(langKey, this, playerId), args);
        }

        private void Message(IPlayer player, string textOrLang, params object[] args)
        {
            if (player.IsConnected)
            {
                string message = GetLang(textOrLang, player.Id, args);
                player.Reply(message != textOrLang ? message : textOrLang);
            }
        }

        #endregion Helpers
    }
}