﻿/***********************************************************************************************************************/
/*** 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

/*
 * TODO: Add optional GUI icons to indicate what is enabled/disabled
 * TODO: Add option for PvP during airdrop or heli (or both) event only
 * TODO: Add option to only allow damage for offline players
 * TODO: Add option to only allow purge on weekends/weekdays, select days
 * TODO: Add option to protect player (not building or loot) in own cupboard radius
 * TODO: Add support for Clans, Friends, and other sharing plugins
 * TOOD: Add option to show how many deaths occured during purge
 */

using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Core.Libraries.Covalence;
using Rust;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("The Purge", "Wulf", "2.0.0")]
    [Description("Allows damage and killing only during specific times")]
    public class Purge : CovalencePlugin
    {
        #region Configuration

        private Configuration config;

        public class PurgeRule
        {
            public bool AnimalDamage;
            public bool BarricadeDamage;
            public bool HeliDamage;
            public bool LootDamage;
            public bool SelfDamage;
            public bool StructureDamage;
            public bool TrapDamage;
            public bool TurretDamage;
            public bool WorldDamage;
        }

        public class Configuration
        {
            [JsonProperty("Purge start time")]
            public TimeSpan PurgeStartTime = new TimeSpan(18, 00, 00);

            [JsonProperty("Purge end time")]
            public TimeSpan PurgeEndTime = new TimeSpan(06, 00, 00);

            [JsonProperty("Use real time")]
            public bool UseRealTime = false;

            [JsonProperty("Warning period")]
            public int WarningPeriod = 60;

            [JsonProperty("Purge rules")]
            public PurgeRule PurgeRules = new PurgeRule
            {
                AnimalDamage = true,
                BarricadeDamage = true,
                HeliDamage = true,
                LootDamage = true,
                SelfDamage = true,
                StructureDamage = true,
                TrapDamage = true,
                TurretDamage = true,
                WorldDamage = true
            };

            [JsonProperty("Safe rules")]
            public PurgeRule SafeRules = new PurgeRule
            {
                AnimalDamage = false,
                BarricadeDamage = false,
                HeliDamage = false,
                LootDamage = false,
                SelfDamage = false,
                StructureDamage = false,
                TrapDamage = false,
                TurretDamage = false,
                WorldDamage = false
            };

            [JsonProperty("Log to console/file")]
            public bool LogOutput = false;

            public 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
            {
                config = Config.ReadObject<Configuration>();
                if (config == null)
                {
                    throw new JsonException();
                }

                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
            {
                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

        #region Localization

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["PurgeWarning"] = "Purge commencing in {0}...",
                ["PurgeStarted"] = "The purge is commencing...",
                ["PurgeEnded"] = "The purge has ended..."
            }, this);
        }

        private string GetLang(string langKey, string playerId = null, params object[] args)
        {
            return string.Format(lang.GetMessage(langKey, this, playerId), args);
        }

        #endregion

        #region Initialization

        private const string permAllow = "purge.allow";
        private const string permProtect = "purge.protect";

        private void Init()
        {
            permission.RegisterPermission(permAllow, this);
            permission.RegisterPermission(permProtect, this);
        }

        #endregion

        #region Purging

        private bool purgeActive;
        private bool warningStarted;

        private bool IsPurgeActive() => purgeActive;

        private bool PurgeTime
        {
            get
            {
                TimeSpan time = config.UseRealTime ? DateTime.Now.TimeOfDay : server.Time.TimeOfDay;
                return config.PurgeStartTime < config.PurgeEndTime ? time >= config.PurgeStartTime
                    && time < config.PurgeEndTime : time >= config.PurgeStartTime || time < config.PurgeEndTime;
            }
        }

        private bool PurgeWarning
        {
            get
            {
                double totalSeconds = config.PurgeStartTime.Subtract(server.Time.TimeOfDay).TotalSeconds;
                return totalSeconds > 0 && totalSeconds <= (config.WarningPeriod * ConVar.Server.tickrate);
            }
        }

        private void OnTick()
        {
            if (!purgeActive && PurgeWarning && !warningStarted)
            {
                warningStarted = true;
                int countdown = config.WarningPeriod - 1;

                timer.Repeat(config.WarningPeriod * ConVar.Server.tickrate / 60, config.WarningPeriod, () =>
                {
                    if (countdown == 0)
                    {
                        warningStarted = false;
                    }
                    Broadcast("PurgeWarning", countdown);
                    countdown--;
                });

                return;
            }

            if (PurgeTime && !purgeActive)
            {
                Broadcast("PurgeStarted");
                purgeActive = true;
            }
            else if (!PurgeTime && purgeActive)
            {
                Broadcast("PurgeEnded");
                purgeActive = false;
            }
        }

        private void OnEntityTakeDamage(BaseEntity entity, HitInfo info)
        {
            BaseEntity attacker = info.Initiator;
            string target = entity.PrefabName;

            if (purgeActive)
            {
                BasePlayer player = entity.ToPlayer();

                if (player != null && !permission.UserHasPermission(player.UserIDString, permProtect))
                {
                    return;
                }

                if (config.PurgeRules.AnimalDamage && (target.Contains("animals") || target.Contains("corpse") || (attacker != null && attacker.name.Contains("animals")))) return;
                if (config.PurgeRules.HeliDamage && (entity is BaseHelicopter || attacker is BaseHelicopter)) return;
                if (config.PurgeRules.LootDamage && (target.Contains("loot") || target.Contains("box") || target.Contains("barrel"))) return;
                if (config.PurgeRules.BarricadeDamage && target.Contains("door barricades")) return;
                if (config.PurgeRules.StructureDamage && attacker != null && (entity is Barricade || entity is BuildingBlock || entity is Door || entity is SimpleBuildingBlock)) return;
                if (config.PurgeRules.TrapDamage && entity is BasePlayer && (attacker is Barricade || attacker is BaseTrap || attacker.PrefabName.Contains("wall.external.high"))) return;
                if (config.PurgeRules.TurretDamage && (entity is AutoTurret || attacker is AutoTurret || entity is FlameTurret || attacker is FlameTurret)) return;
                if (config.PurgeRules.WorldDamage && (entity is BasePlayer && attacker == null)) return;
            }
            else
            {
                BasePlayer owner = attacker?.ToPlayer();

                if (owner != null && entity.OwnerID == owner.userID)
                {
                    return;
                }

                if (config.SafeRules.AnimalDamage && (target.Contains("animals") || target.Contains("corpse") || (attacker != null && attacker.name.Contains("animals")))) return;
                if (config.SafeRules.HeliDamage && (entity is BaseHelicopter || attacker is BaseHelicopter)) return;
                if (config.SafeRules.LootDamage && (target.Contains("loot") || target.Contains("box") || target.Contains("barrel"))) return;
                if (config.SafeRules.BarricadeDamage && target.Contains("door barricades")) return;
                if (config.SafeRules.SelfDamage && entity == attacker) return;
                if (config.SafeRules.StructureDamage && attacker != null && (entity is Barricade || entity is BuildingBlock || entity is Door || entity is SimpleBuildingBlock)) return;
                if (config.SafeRules.TrapDamage && entity is BasePlayer && (attacker is Barricade || attacker is BaseTrap || attacker.PrefabName.Contains("wall.external.high"))) return;
                if (config.SafeRules.TurretDamage && (entity is AutoTurret || attacker is AutoTurret || entity is FlameTurret || attacker is FlameTurret)) return;
                if (config.SafeRules.WorldDamage && (entity is BasePlayer && attacker == null)) return;
            }

            // Nullify damage to entity
            info.damageTypes = new DamageTypeList();
            info.PointStart = Vector3.zero;
            info.HitMaterial = 0;
        }

        #endregion

        #region Helpers

        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);
            }
        }

        private void Broadcast(string textOrLang, params object[] args)
        {
            foreach (IPlayer player in players.Connected)
            {
                Message(player, textOrLang);
            }

            if (config.LogOutput)
            {
                string message = GetLang(textOrLang, null, args);
                Log(message != textOrLang ? message : textOrLang);
            }
        }

        #endregion
    }
}
