﻿using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core.Plugins;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("CustomSpawnPoints", "Reneb / k1lly0u", "1.1.4")]
    [Description("Allows you to set a spawnfile created via SpawnsDatabase to override Rusts default spawn points")]
    class CustomSpawnPoints : RustPlugin
    {
        #region Fields
        [PluginReference] Plugin Spawns;

        private List<Vector3> spawnPoints = new List<Vector3>();

        private List<Vector3> remainingPoints = new List<Vector3>();

        private bool initialized;

        #endregion

        #region Oxide Hooks        
        private void OnServerInitialized()
        {
            LoadVariables();

            if (Spawns)
                LoadSpawnpoints();
        }

        private void OnPluginLoaded(Plugin plugin)
        {
            if (!initialized && plugin?.Title == "Spawns")            
                LoadSpawnpoints();            
        }

        private object OnPlayerRespawn(BasePlayer player)
        {
            if (!initialized)
                return null;

            object position = GetSpawnPoint();
            if (position is Vector3)            
                return new BasePlayer.SpawnPoint() { pos = (Vector3)position, rot = new Quaternion(0, 0, 0, 1) };
            return null;
        }
        #endregion

        #region Functions
        private void LoadSpawnpoints()
        {
            initialized = false;
            if (string.IsNullOrEmpty(configData.Spawnfile))
            {
                PrintError("No spawnfile set in the config. Unable to continue");
                return;
            }

            object success = Spawns?.Call("LoadSpawnFile", configData.Spawnfile);
            if (success is List<Vector3>)
            {
                spawnPoints = success as List<Vector3>;
                if (spawnPoints.Count == 0)
                {
                    PrintError("Loaded spawnfile contains no spawn points. Unable to continue");
                    return;
                }
                PrintWarning($"Successfully loaded {spawnPoints.Count} spawn points");
            }
            else
            {
                PrintError($"Unable to load the specified spawnfile: {configData.Spawnfile}");
                return;
            }
            remainingPoints = new List<Vector3>(spawnPoints);
            initialized = true;
        }

        private object GetSpawnPoint(int attempt = 0)
        {
            if (attempt >= 10)
                return null;

            Vector3 position = remainingPoints.GetRandom();

            List<BaseEntity> entities = Facepunch.Pool.Get<List<BaseEntity>>();
            Vis.Entities(position, configData.Detect, entities, LayerMask.GetMask("Construction", "Deployable"));
            int count = entities.Count;
            Facepunch.Pool.FreeUnmanaged(ref entities);

            remainingPoints.Remove(position);
            if (remainingPoints.Count == 0)            
                remainingPoints = new List<Vector3>(spawnPoints);            

            if (count > 0) 
                return GetSpawnPoint(++attempt);

            return position;
        }
        #endregion

        #region Commands
        [ConsoleCommand("spawns.config")]
        private void ccmdSpawnFile(ConsoleSystem.Arg arg)
        {
            if (arg == null)
                return;
          
            if (arg.Connection == null || (arg.Connection != null && arg.Connection.authLevel == 2))
            {
                if (arg.Args == null || arg.Args.Length == 0)
                {
                    SendReply(arg, "spawns.config \"spawnfile name\" - Set a new spawnfile");
                    return;
                }

                if (!Spawns)
                {
                    SendReply(arg, "Unable to find SpawnsDatabase. Unable to continue...");
                    return;
                }

                object success = Spawns.Call("GetSpawnsCount", new object[] { arg.Args[0] });
                if (success is string)
                {
                    SendReply(arg, $"Unable to load the specified spawnfile: {arg.Args[0]}");
                    return;
                }

                if (success is int)
                {
                    if ((int)success == 0)
                    {
                        PrintError("Loaded spawnfile contains no spawn points. Unable to continue");
                        return;
                    }
                    configData.Spawnfile = arg.GetString(0);
                    SaveConfig(configData);
                    LoadSpawnpoints();
                }                
            }
            else SendReply(arg, "You do not have permission to use this command");           
        }
        #endregion

        #region Config        
        private ConfigData configData;

        private class ConfigData
        {
            [JsonProperty(PropertyName = "Spawnfile name")]
            public string Spawnfile { get; set; }
            [JsonProperty(PropertyName = "Entity detection radius")]
            public float Detect { get; set; }            
        }

        private void LoadVariables()
        {
            LoadConfigVariables();
            SaveConfig();
        }

        protected override void LoadDefaultConfig()
        {
            ConfigData config = new ConfigData
            {
                Spawnfile = string.Empty,
                Detect = 10
            };
            SaveConfig(config);
        }

        private void LoadConfigVariables() => configData = Config.ReadObject<ConfigData>();

        private void SaveConfig(ConfigData config) => Config.WriteObject(config, true);
        #endregion       
    }
}
