﻿using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using UnityEngine;
using System.Linq;
using System.Reflection;

namespace Oxide.Plugins
{
    [Info("Airstrike", "k1lly0u", "0.3.7", ResourceId = 1489)]
    class Airstrike : RustPlugin
    {
        #region Fields
        [PluginReference] Plugin Economics, ServerRewards;

        StoredData storedData;
        private DynamicConfigFile data;

        private Dictionary<ulong, StrikeType> toggleList = new Dictionary<ulong, StrikeType>();

        private Dictionary<string, int> shortnameToId = new Dictionary<string, int>();
        private Dictionary<string, string> shortnameToDn = new Dictionary<string, string>();

        private static Airstrike ins;
        
        const string cargoPlanePrefab = "assets/prefabs/npc/cargo plane/cargo_plane.prefab";
        const string basicRocket = "assets/prefabs/ammo/rocket/rocket_basic.prefab";
        const string fireRocket = "assets/prefabs/ammo/rocket/rocket_fire.prefab";

        enum StrikeType { Strike, Squad }
        #endregion

        #region Oxide Hooks
        private void Loaded()
        {
            data = Interface.Oxide.DataFileSystem.GetFile("airstrike_data");

            lang.RegisterMessages(Messages, this);
            permission.RegisterPermission("airstrike.signal.strike", this);
            permission.RegisterPermission("airstrike.signal.squad", this);
            permission.RegisterPermission("airstrike.purchase.strike", this);
            permission.RegisterPermission("airstrike.purchase.squad", this);
            permission.RegisterPermission("airstrike.chat.strike", this);
            permission.RegisterPermission("airstrike.chat.squad", this);
            permission.RegisterPermission("airstrike.ignorecooldown", this);
        }

        private void OnServerInitialized()
        {
            ins = this;
            LoadVariables();
            LoadData();

            shortnameToId = ItemManager.itemList.ToDictionary(x => x.shortname, y => y.itemid);
            shortnameToDn = ItemManager.itemList.ToDictionary(x => x.shortname, y => y.displayName.translated);

            CallRandomStrike();
        }

        private void Unload()
        {
            ins = null;
            SaveData();

            StrikePlane[] objects = UnityEngine.Object.FindObjectsOfType<StrikePlane>();
            if (objects != null)
            {
                foreach (StrikePlane obj in objects)
                    UnityEngine.Object.Destroy(obj);
            }
        }

        private void OnServerSave() => SaveData();

        private void OnExplosiveThrown(BasePlayer player, BaseEntity entity)
        {            
            if (toggleList.ContainsKey(player.userID) && entity is SupplySignal)
            {
                StrikeType type = toggleList[player.userID];
                toggleList.Remove(player.userID);
                AddCooldownData(player, type);

                entity.CancelInvoke((entity as SupplySignal).Explode);
                entity.Invoke(entity.KillMessage, 30f);
                timer.Once(3, () =>
                {
                    Effect.server.Run("assets/bundled/prefabs/fx/smoke_signal_full.prefab", entity, 0, new Vector3(), new Vector3());
                    Vector3 pos = entity.transform.position;
                    switch (type)
                    {
                        case StrikeType.Strike:
                            SendReply(player, string.Format(msg("strikeConfirmed", player.UserIDString), pos));
                            CallStrike(pos);
                            return;
                        case StrikeType.Squad:
                            SendReply(player, string.Format(msg("strikeConfirmed", player.UserIDString), pos));
                            CallSquad(pos);
                            return;
                    }                    
                });
            }
        }
        #endregion

        #region Plane Control
        private class StrikePlane : MonoBehaviour
        {
            private CargoPlane entity;
            private Vector3 targetPos;

            private RocketOptions rocketOptions;

            private Vector3 startPos;
            private Vector3 endPos;
            private float secondsToTake;

            private int rocketsFired;
            private float fireDistance;
            private bool isFiring;
            
            private void Awake()
            {
                entity = GetComponent<CargoPlane>();
                rocketOptions = ins.configData.Rocket;
                fireDistance = ins.configData.Plane.Distance;

                entity.dropped = true;
                enabled = false;
            }

            private void Update()
            {
                if (!isFiring && Vector3.Distance(transform.position, targetPos) <= fireDistance)
                {
                    isFiring = true;
                    FireRocketLoop();
                }
            }

            private void OnDestroy()
            {
                entity.CancelInvoke(LaunchRocket);
            }

            public void InitializeFlightPath(Vector3 targetPos)
            {                
                this.targetPos = targetPos;

                float size = TerrainMeta.Size.x;
                float highestPoint = 170f + ins.configData.Plane.Height;
                
                startPos = Vector3Ex.Range(-1f, 1f);
                startPos.y = 0f;
                startPos.Normalize();
                startPos = startPos * (size * 2f);
                startPos.y = highestPoint;
                
                endPos = startPos * -1f;
                endPos.y = startPos.y;
                startPos = startPos + targetPos;
                endPos = endPos + targetPos;
                
                secondsToTake = (Vector3.Distance(startPos, endPos) / ins.configData.Plane.Speed) * UnityEngine.Random.Range(0.95f, 1.05f);
                
                entity.transform.position = startPos;
                entity.transform.rotation = Quaternion.LookRotation(endPos - startPos);

                entity.startPos = startPos;
                entity.endPos = endPos;
                entity.dropPosition = targetPos;
                entity.secondsToTake = secondsToTake;
                
                enabled = true;
            }

            public void GetFlightData(out Vector3 startPos, out Vector3 endPos, out float secondsToTake)
            {
                startPos = this.startPos;
                endPos = this.endPos;
                secondsToTake = this.secondsToTake;
            }

            public void SetFlightData(Vector3 startPos, Vector3 endPos, Vector3 targetPos, float secondsToTake)
            {
                this.startPos = startPos;
                this.endPos = endPos;
                this.targetPos = targetPos;
                this.secondsToTake = secondsToTake;

                entity.transform.position = startPos;
                entity.transform.rotation = Quaternion.LookRotation(endPos - startPos);

                entity.startPos = startPos;
                entity.endPos = endPos;
                entity.dropPosition = targetPos;
                entity.secondsToTake = secondsToTake;

                enabled = true;
            }

            private void FireRocketLoop()
            {
                entity.InvokeRepeating(LaunchRocket, 0, rocketOptions.Interval);
            }
            
            private void LaunchRocket()
            {
                if (rocketsFired >= rocketOptions.Amount)
                {
                    entity.CancelInvoke(LaunchRocket);
                    return;
                }
                string rocketType = rocketOptions.Type == "Normal" ? basicRocket : fireRocket;
                if (rocketOptions.Mixed && UnityEngine.Random.Range(1, rocketOptions.FireChance) == 1)
                    rocketType = fireRocket;

                Vector3 launchPos = entity.transform.position;
                Vector3 newTarget = Quaternion.Euler(GetRandom(), GetRandom(), GetRandom()) * targetPos;
                                
                BaseEntity rocket = GameManager.server.CreateEntity(rocketType, launchPos, new Quaternion(), true);

                TimedExplosive rocketExplosion = rocket.GetComponent<TimedExplosive>();
                ServerProjectile rocketProjectile = rocket.GetComponent<ServerProjectile>();

                rocketProjectile.speed = rocketOptions.Speed;
                rocketProjectile.gravityModifier = 0;
                rocketExplosion.timerAmountMin = 60;
                rocketExplosion.timerAmountMax = 60;
                for (int i = 0; i < rocketExplosion.damageTypes.Count; i++)
                    rocketExplosion.damageTypes[i].amount *= rocketOptions.Damage;

                Vector3 newDirection = (newTarget - launchPos);

                rocket.SendMessage("InitializeVelocity", (newDirection));
                rocket.Spawn();
                ++rocketsFired;
            }

            private float GetRandom() => UnityEngine.Random.Range(-rocketOptions.Accuracy * 0.2f, rocketOptions.Accuracy * 0.2f);
        }
        #endregion

        #region Functions
        private void CallRandomStrike()
        {
            if (!configData.Other.RandomStrikes && !configData.Other.RandomSquads) return;

            timer.In(UnityEngine.Random.Range(configData.Other.RandomTimer[0], configData.Other.RandomTimer[1]), () =>
            {
                StrikeType type;
                if (configData.Other.RandomStrikes && configData.Other.RandomSquads)
                    type = UnityEngine.Random.Range(1, 2) == 1 ? type = StrikeType.Strike : type = StrikeType.Squad;
                else if (configData.Other.RandomStrikes)
                    type = StrikeType.Strike;
                else type = StrikeType.Squad;

                if (type == StrikeType.Strike)
                    CallStrike(GetRandomPosition());
                else CallSquad(GetRandomPosition());

                CallRandomStrike();
            });
        }
        private void CallStrike(Vector3 position)
        {
            CargoPlane entity = CreatePlane();
            entity.Spawn();

            StrikePlane plane = entity.gameObject.AddComponent<StrikePlane>();
            plane.InitializeFlightPath(position);

            if (configData.Other.Broadcast)
                PrintToChat(msg("strikeInbound"));
        }
        private void CallSquad(Vector3 position)
        {
            CargoPlane leaderEnt = CreatePlane();
            leaderEnt.Spawn();

            StrikePlane leaderPlane = leaderEnt.gameObject.AddComponent<StrikePlane>();
            leaderPlane.InitializeFlightPath(position);

            leaderPlane.GetFlightData(out Vector3 startPos, out Vector3 endPos, out float secondsToTake);

            CargoPlane leftEnt = CreatePlane();
            leftEnt.Spawn();
            StrikePlane leftPlane = leftEnt.gameObject.AddComponent<StrikePlane>();
            Vector3 leftOffset = (leaderEnt.transform.right * 70) + (-leaderEnt.transform.forward * 80);
            leftPlane.SetFlightData(startPos + leftOffset, endPos + leftOffset, position + (leftOffset / 4), secondsToTake);

            CargoPlane rightEnt = CreatePlane();
            rightEnt.Spawn();
            StrikePlane rightPlane = rightEnt.gameObject.AddComponent<StrikePlane>();
            Vector3 rightOffset = (-leaderEnt.transform.right * 70) + (-leaderEnt.transform.forward * 80);
            rightPlane.SetFlightData(startPos + rightOffset, endPos + rightOffset, position + (rightOffset / 4), secondsToTake);

            if (configData.Other.Broadcast)
                PrintToChat(msg("squadInbound"));
        }

        private bool CanBuyStrike(BasePlayer player, StrikeType type)
        {
            Dictionary<string, int> costToBuy = type == StrikeType.Strike ? configData.Buy.StrikeCost : configData.Buy.SquadCost;
                        
            foreach(KeyValuePair<string, int> item in costToBuy)
            {
                if (item.Key == "RP")
                {
                    if (ServerRewards)
                    {
                        if ((int)ServerRewards.Call("CheckPoints", player.userID) < item.Value)
                        {
                            SendReply(player, string.Format(msg("buyItem", player.UserIDString), item.Value, item.Key));
                            Effect.server.Run("assets/prefabs/locks/keypad/effects/lock.code.denied.prefab", player.transform.position);
                            return false;
                        }
                    }
                }
                if (item.Key == "Economics")
                {
                    if (Economics)
                    {
                        if ((double)Economics.Call("GetPlayerMoney", player.userID) < item.Value)
                        {
                            SendReply(player, string.Format(msg("buyItem", player.UserIDString), item.Value, item.Key));
                            Effect.server.Run("assets/prefabs/locks/keypad/effects/lock.code.denied.prefab", player.transform.position);
                            return false;
                        }
                    }
                }
                if (shortnameToId.ContainsKey(item.Key))
                {
                    if (player.inventory.GetAmount(shortnameToId[item.Key]) < item.Value)
                    {
                        SendReply(player, string.Format(msg("buyItem", player.UserIDString), item.Value, shortnameToDn[item.Key]));
                        Effect.server.Run("assets/prefabs/locks/keypad/effects/lock.code.denied.prefab", player.transform.position);
                        return false;
                    }
                }
            }
            Effect.server.Run("assets/prefabs/locks/keypad/effects/lock.code.updated.prefab", player.transform.position);
            return true;
        }

        private void BuyStrike(BasePlayer player, StrikeType type)
        {
            Dictionary<string, int> costToBuy = type == StrikeType.Strike ? configData.Buy.StrikeCost : configData.Buy.SquadCost;

            foreach (KeyValuePair<string, int> item in costToBuy)
            {
                if (item.Key == "RP")
                {
                    if (ServerRewards)
                        ServerRewards.Call("TakePoints", player.userID, item.Value);
                }
                if (item.Key == "Economics")
                {
                    if (Economics)                    
                        Economics.Call("Withdraw", player.userID, (double)item.Value); 
                }
                if (shortnameToId.ContainsKey(item.Key))                
                    player.inventory.Take(null, shortnameToId[item.Key], item.Value);
            }
            if (type == StrikeType.Strike)
            {
                CallStrike(player.transform.position);
                SendReply(player, string.Format(msg("strikeConfirmed", player.UserIDString), player.transform.position));
            }
            else
            {
                CallSquad(player.transform.position);
                SendReply(player, string.Format(msg("squadConfirmed", player.UserIDString), player.transform.position));
            }
        }
        #endregion

        #region Helpers
        private double GrabCurrentTime() => DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;
        private CargoPlane CreatePlane() => (CargoPlane)GameManager.server.CreateEntity(cargoPlanePrefab, new Vector3(), new Quaternion(), true);
        private bool isStrikePlane(CargoPlane plane) => plane.GetComponent<StrikePlane>();
        private bool HasPermission(BasePlayer player, string perm) => permission.UserHasPermission(player.UserIDString, perm);

        private Vector3 GetRandomPosition()
        {
            float mapSize = (TerrainMeta.Size.x / 2) - 600f;

            float randomX = UnityEngine.Random.Range(-mapSize, mapSize);
            float randomY = UnityEngine.Random.Range(-mapSize, mapSize);

            return new Vector3(randomX, 0f, randomY);
        }

        private string FormatTime(double time)
        {
            TimeSpan dateDifference = TimeSpan.FromSeconds((float)time);
            int days = dateDifference.Days;
            int hours = dateDifference.Hours;
            hours += (days * 24);
            int mins = dateDifference.Minutes;
            int secs = dateDifference.Seconds;
            return string.Format("{0:00}:{1:00}:{2:00}", hours, mins, secs);
        }

        private void AddCooldownData(BasePlayer player, StrikeType type)
        {
            if (!configData.Cooldown.Enabled) return;

            if (!storedData.cooldowns.ContainsKey(player.userID))
                storedData.cooldowns.Add(player.userID, new CooldownData());

            if (type == StrikeType.Strike)
                storedData.cooldowns[player.userID].strikeCd = GrabCurrentTime() + configData.Cooldown.Strike;
            else storedData.cooldowns[player.userID].squadCd = GrabCurrentTime() + configData.Cooldown.Squad;
        }

        private List<BasePlayer> FindPlayer(string arg)
        {
            List<BasePlayer> foundPlayers = new List<BasePlayer>();

            ulong.TryParse(arg, out ulong steamid);
            string lowerarg = arg.ToLower();

            foreach (BasePlayer player in BasePlayer.activePlayerList)
            {
                if (steamid != 0L)
                {
                    if (player.userID == steamid)
                    {
                        foundPlayers.Clear();
                        foundPlayers.Add(player);
                        return foundPlayers;
                    }
                }
                string lowername = player.displayName.ToLower();
                if (lowername.Contains(lowerarg))
                {
                    foundPlayers.Add(player);
                }
            }
            return foundPlayers;
        }
        #endregion

        #region Commands
        [ChatCommand("airstrike")]
        private void cmdAirstrike(BasePlayer player, string command, string[] args)
        {
            if (args.Length == 0)
            {
                SendReply(player, string.Format("Airstrike  v.{0}", Version));
                if (HasPermission(player, "airstrike.signal.strike"))
                    SendReply(player, msg("help1", player.UserIDString));
                if (HasPermission(player, "airstrike.signal.squad"))
                    SendReply(player, msg("help2", player.UserIDString));
                if (HasPermission(player, "airstrike.purchase.strike"))
                    SendReply(player, msg("help3", player.UserIDString));
                if (HasPermission(player, "airstrike.purchase.squad"))
                    SendReply(player, msg("help4", player.UserIDString));
                if (HasPermission(player, "airstrike.chat.strike"))
                {
                    SendReply(player, msg("help5", player.UserIDString));
                    SendReply(player, msg("help6", player.UserIDString));
                    SendReply(player, msg("help7", player.UserIDString));
                }
                if (HasPermission(player, "airstrike.chat.squad"))
                {
                    SendReply(player, msg("help8", player.UserIDString));
                    SendReply(player, msg("help9", player.UserIDString));
                    SendReply(player, msg("help10", player.UserIDString));
                }
                return;
            }
            if (args.Length >= 2)
            {
                double time = GrabCurrentTime();
                StrikeType type = args[1].ToLower() == "squad" ? StrikeType.Squad : StrikeType.Strike;
                
                if (!HasPermission(player, "airstrike.ignorecooldown"))
                {
                    if (configData.Cooldown.Enabled)
                    {
                        if (storedData.cooldowns.TryGetValue(player.userID, out CooldownData data))
                        {
                            double nextUse = type == StrikeType.Strike ? data.strikeCd : data.squadCd;
                            if (nextUse > time)
                            {
                                double remaining = nextUse - time;
                                SendReply(player, string.Format(msg("onCooldown", player.UserIDString), FormatTime(remaining)));
                                return;
                            }
                        }
                    }
                }
                switch (args[0].ToLower())
                {
                    case "signal":
                        if ((type == StrikeType.Strike && configData.Other.SignalStrike) || (type == StrikeType.Squad && configData.Other.SignalSquad))
                        {
                            if (!HasPermission(player, $"airstrike.signal.{type.ToString().ToLower()}"))
                            {
                                SendReply(player, msg("noPerms", player.UserIDString));
                                return;
                            }
                        }
                        if (toggleList.ContainsKey(player.userID))
                            toggleList[player.userID] = type;
                        else toggleList.Add(player.userID, type);
                        SendReply(player, msg("signalReady", player.UserIDString));                
                        return;
                    case "buy":
                        if ((type == StrikeType.Strike && configData.Buy.PermissionStrike) || (type == StrikeType.Squad && configData.Buy.PermissionSquad))
                        {
                            if (!HasPermission(player, $"airstrike.purchase.{type.ToString().ToLower()}"))
                            {
                                SendReply(player, msg("noPerms", player.UserIDString));
                                return;
                            }
                        }
                        if (CanBuyStrike(player, type))
                        {
                            BuyStrike(player, type);
                            AddCooldownData(player, type);
                        }
                        return;
                    case "call":
                        if (HasPermission(player, $"airstrike.chat.{type.ToString().ToLower()}"))
                        {
                            Vector3 position;
                            if (args.Length == 4)
                            {
                                if (!float.TryParse(args[2], out float x) || !float.TryParse(args[3], out float z))
                                {
                                    SendReply(player, msg("invCoords", player.UserIDString));
                                    return;                                                                     
                                }
                                else position = new Vector3(x, 0, z);
                            }
                            else if (args.Length == 3)
                            {
                                List<BasePlayer> players = FindPlayer(args[2]);
                                if (players.Count > 1)
                                {
                                    SendReply(player, msg("multiplePlayers", player.UserIDString));
                                    return;
                                }
                                else if (players.Count == 0)
                                {
                                    SendReply(player, msg("noPlayers", player.UserIDString));
                                    return;
                                }
                                else position = players[0].transform.position;
                            }
                            else position = player.transform.position;
                            
                            if (type == StrikeType.Strike)
                            {
                                CallStrike(position);
                                SendReply(player, string.Format(msg("strikeConfirmed", player.UserIDString), position));
                            }
                            else
                            {
                                CallSquad(position);
                                SendReply(player, string.Format(msg("squadConfirmed", player.UserIDString), position));
                            }
                            AddCooldownData(player, type);
                        }
                        else SendReply(player, msg("noPerms", player.UserIDString));
                        return;
                    default:
                        break;
                }
            }
        }

        [ConsoleCommand("airstrike")]
        private void ccmdAirstrike(ConsoleSystem.Arg arg)
        {
            if (arg.Connection != null)
                return;

            if (arg.Args == null || arg.Args.Length == 0)
            {
                SendReply(arg, "airstrike strike <x> <z> - Call a airstrike to the target position");
                SendReply(arg, "airstrike squad <x> <z> - Call a squadstrike to the target position");
                SendReply(arg, "airstrike strike <playername> - Call a airstrike to the target player");
                SendReply(arg, "airstrike squad <playername> - Call a squadstrike to the target player");
                SendReply(arg, "airstrike strike random - Call a random airstrike");
                SendReply(arg, "airstrike squad random - Call a random squadstrike");
                return;
            }

            StrikeType type = arg.GetString(0).ToLower() == "squad" ? StrikeType.Squad : StrikeType.Strike;

            Vector3 position = Vector3.zero;
            
            if (arg.Args.Length == 3)
            {
                if (!float.TryParse(arg.GetString(1), out float x) || !float.TryParse(arg.GetString(2), out float z))
                {
                    SendReply(arg, "Invalid co-ordinates set. You must enter number values for X and Z");
                    return;
                }
                else position = new Vector3(x, 0, z);
            }
            else if (arg.Args.Length == 2)
            {
                if (arg.GetString(1).ToLower() == "random")
                    position = GetRandomPosition();
                else
                {
                    List<BasePlayer> players = FindPlayer(arg.GetString(1));
                    if (players.Count > 1)
                    {
                        SendReply(arg, "Multiple players found");
                        return;
                    }
                    else if (players.Count == 0)
                    {
                        SendReply(arg, "No players found");
                        return;
                    }
                    else position = players[0].transform.position;
                }
            }

            if (type == StrikeType.Strike)
            {
                CallStrike(position);
                SendReply(arg, string.Format("Airstrike confirmed at co-ordinates: {0}!", position));
            }
            else
            {
                CallSquad(position);
                SendReply(arg, string.Format("Squadstrike confirmed at co-ordinates: {0}!", position));
            }
        }
        #endregion

        #region Config 
        private ConfigData configData;
        class RocketOptions
        {
            [JsonProperty(PropertyName = "Speed of the rocket")]
            public float Speed { get; set; }
            [JsonProperty(PropertyName = "Damage modifier")]
            public float Damage { get; set; }
            [JsonProperty(PropertyName = "Accuracy of rocket (a lower number is more accurate)")]
            public float Accuracy { get; set; }
            [JsonProperty(PropertyName = "Interval between rockets (seconds)")]
            public float Interval { get; set; }
            [JsonProperty(PropertyName = "Type of rocket (Normal, Napalm)")]
            public string Type { get; set; }
            [JsonProperty(PropertyName = "Use both rocket types")]
            public bool Mixed { get; set; }
            [JsonProperty(PropertyName = "Chance of a fire rocket (when using both types)")]
            public int FireChance { get; set; }
            [JsonProperty(PropertyName = "Amount of rockets to fire")]
            public int Amount { get; set; }
        }

        class CooldownOptions
        {
            [JsonProperty(PropertyName = "Use cooldown timers")]
            public bool Enabled { get; set; }            
            [JsonProperty(PropertyName = "Strike cooldown time (seconds)")]
            public int Strike { get; set; }
            [JsonProperty(PropertyName = "Squad cooldown time (seconds)")]
            public int Squad { get; set; }
        }

        class PlaneOptions
        {
            [JsonProperty(PropertyName = "Flight speed (meters per second)")]
            public float Speed { get; set; }
            [JsonProperty(PropertyName = "Distance from target to engage")]
            public float Distance { get; set; }
            [JsonProperty(PropertyName = "Height modifier")]
            public float Height { get; set; }
        }

        class BuyOptions
        {
            [JsonProperty(PropertyName = "Can purchase standard strike")]
            public bool StrikeEnabled { get; set; }
            [JsonProperty(PropertyName = "Can purchase squad strike")]
            public bool SquadEnabled { get; set; }
            [JsonProperty(PropertyName = "Require permission to purchase strike")]
            public bool PermissionStrike { get; set; }
            [JsonProperty(PropertyName = "Require permission to purchase squad strike")]
            public bool PermissionSquad { get; set; }
            [JsonProperty(PropertyName = "Cost to purchase a standard strike (shortname, amount)")]
            public Dictionary<string, int> StrikeCost { get; set; }
            [JsonProperty(PropertyName = "Cost to purchase a squad strike (shortname, amount)")]
            public Dictionary<string, int> SquadCost { get; set; }
        }

        class OtherOptions
        {
            [JsonProperty(PropertyName = "Broadcast strikes to chat")]
            public bool Broadcast { get; set; }
            [JsonProperty(PropertyName = "Can call standard strikes using a supply signal")]
            public bool SignalStrike { get; set; }
            [JsonProperty(PropertyName = "Can call squad strikes using a supply signal")]
            public bool SignalSquad { get; set; }
            [JsonProperty(PropertyName = "Use random airstrikes")]
            public bool RandomStrikes { get; set; }
            [JsonProperty(PropertyName = "Use random squad strikes")]
            public bool RandomSquads { get; set; }
            [JsonProperty(PropertyName = "Random timer (minimum, maximum. In seconds)")]
            public int[] RandomTimer { get; set; }
        }

        class ConfigData
        {
            [JsonProperty(PropertyName = "Rocket Options")]
            public RocketOptions Rocket { get; set; }
            [JsonProperty(PropertyName = "Cooldown Options")]
            public CooldownOptions Cooldown { get; set; }
            [JsonProperty(PropertyName = "Plane Options")]
            public PlaneOptions Plane { get; set; }
            [JsonProperty(PropertyName = "Purchase Options")]
            public BuyOptions Buy { get; set; }
            [JsonProperty(PropertyName = "Other Options")]
            public OtherOptions Other { get; set; }
        }

        private void LoadVariables()
        {
            LoadConfigVariables();
            SaveConfig();
        }

        protected override void LoadDefaultConfig()
        {
            ConfigData config = new ConfigData
            {
                Buy = new BuyOptions
                {
                    SquadCost = new Dictionary<string, int>
                    {
                        ["metal.refined"] = 100,
                        ["techparts"] = 50,
                        ["targeting.computer"] = 1
                    },
                    SquadEnabled = true,
                    PermissionSquad = true,
                    StrikeCost = new Dictionary<string, int>
                    {
                        ["metal.refined"] = 50,
                        ["targeting.computer"] = 1
                    },
                    StrikeEnabled = true,
                    PermissionStrike = true
                },
                Cooldown = new CooldownOptions
                {
                    Enabled = true,
                    Squad = 3600,
                    Strike = 3600
                },
                Other = new OtherOptions
                {
                    Broadcast = true,
                    SignalSquad = true,
                    SignalStrike = true,
                    RandomSquads = true,
                    RandomStrikes = true,
                    RandomTimer = new int[] { 1800, 3600 }
                },
                Plane = new PlaneOptions
                {
                    Distance = 900,
                    Speed = 105,
                    Height = 0
                },
                Rocket = new RocketOptions
                {
                    Accuracy = 1.5f,
                    Amount = 15,
                    Damage = 1.0f,
                    FireChance = 4,
                    Interval = 0.6f,
                    Mixed = true,
                    Speed = 110f,
                    Type = "Normal"
                }
            };
            SaveConfig(config);
        }

        private void LoadConfigVariables() => configData = Config.ReadObject<ConfigData>();

        private void SaveConfig(ConfigData config) => Config.WriteObject(config, true);
        #endregion

        #region Data Management
        private void SaveData() => data.WriteObject(storedData);

        private void LoadData()
        {
            try
            {
                storedData = data.ReadObject<StoredData>();
            }
            catch
            {
                storedData = new StoredData();
            }

            if (storedData.cooldowns == null)
                storedData.cooldowns = new Dictionary<ulong, CooldownData>();
        }

        private void ClearData()
        {
            storedData = new StoredData();
            SaveData();
        }

        class StoredData
        {
            public Dictionary<ulong, CooldownData> cooldowns = new Dictionary<ulong, CooldownData>();
        }

        class CooldownData
        {
            public double strikeCd, squadCd;
        }
        #endregion

        #region Localization
        string msg(string key, string playerId = null) => lang.GetMessage(key, this, playerId);

        Dictionary<string, string> Messages = new Dictionary<string, string>
        {
            ["strikeConfirmed"] = "Airstrike confirmed at co-ordinates: {0}",
            ["squadConfirmed"] = "Squadstrike confirmed at co-ordinates: {0}",
            ["strikeInbound"] = "Airstrike inbound!",
            ["squadInbound"] = "Squadstrike inbound!",
            ["buyItem"] = "You need another {0} {1} to buy this strike",
            ["help1"] = "/airstrike signal strike - Use a supply signal to mark a airstrike position",
            ["help2"] = "/airstrike signal squad - Use a supply signal to mark a squadstrike position",
            ["help3"] = "/airstrike buy strike - Purchase a airstrike on your position",
            ["help4"] = "/airstrike buy squad - Purchase a squadstrike on your position",
            ["help5"] = "/airstrike call strike - Call a airstrike on your position",
            ["help6"] = "/airstrike call strike <x> <z> - Call a airstrike to the target position",
            ["help7"] = "/airstrike call strike <player name> - Call a airstrike to the target player",
            ["help8"] = "/airstrike call squad - Call a squadstrike on your position",
            ["help9"] = "/airstrike call squad <x> <z> - Call a squadstrike to the target position",
            ["help10"] = "/airstrike call squad <player name> - Call a squadstrike to the target player",
            ["onCooldown"] = "You must wait another {0} before calling this type again",
            ["noPerms"] = "You do not have permission to use that strike type",
            ["signalReady"] = "Throw a supply signal to call a strike",
            ["invCoords"] = "Invalid co-ordinates set. You must enter number values for X and Z",
            ["multiplePlayers"] = "Multiple players found",
            ["noPlayers"] = "No players found"
        };
        #endregion
    }
}
