// Copyright (c) Strange Loop Games. All rights reserved.
// See LICENSE file in the project root for full license information.

namespace Eco.Mods.TechTree
{
    using System;
    using Eco.Core.Items;
    using Eco.Gameplay.Animals;
    using Eco.Gameplay.DynamicValues;
    using Eco.Gameplay.GameActions;
    using Eco.Gameplay.Interactions;
    using Eco.Gameplay.Items;
    using Eco.Gameplay.Players;
    using Eco.Gameplay.Systems.TextLinks;
    using Eco.Shared.Localization;
    using Eco.Shared.Math;
    using Eco.Shared.Networking;
    using Eco.Shared.Serialization;
    using Eco.Shared.Utils;
    using Eco.Simulation.Agents;

    [Tag("Harvester")]
    public partial class BowItem
    {
        private static readonly SkillModifiedValue CaloriesBurnValue;
        private static readonly SkillModifiedValue DamageValue;

        static BowItem()
        {
            string bowUiLink = new BowItem().UILink();
            CaloriesBurnValue = CreateCalorieValue(20, typeof(HuntingSkill), typeof(BowItem), new LocString(bowUiLink));
            DamageValue = CreateDamageValue(1, typeof(HuntingSkill), typeof(BowItem), new LocString(bowUiLink));
        }
        
        private static readonly IDynamicValue SkilledRepairCostValue = new ConstantValue(5);
        public override IDynamicValue SkilledRepairCost => SkilledRepairCostValue;

        public override IDynamicValue CaloriesBurn      => CaloriesBurnValue;
        public override IDynamicValue Damage => DamageValue;
        public override Type ExperienceSkill            => typeof(HuntingSkill);
        private static readonly IDynamicValue Exp       = new ConstantValue(1f);
        public override IDynamicValue ExperienceRate    => Exp;
        public override Tag RepairTag                   => TagManager.Tag("Log");
        public override int FullRepairAmount            => 5;
        protected virtual Type ToolType                 => typeof(BowItem);

        [RPC]
        public int FireArrow(Player player, Vector3 position, Vector3 velocity)
        {
            // If the player has an arrow to fire then use the bow, but do not gain any experience from using the tool. Experience is ONLY gained if the Arrow hits an animal.
            if (player.User.Inventory.TryRemoveItem<ArrowItem>(player.User) && AtomicActions.UseToolNow(this.CreateMultiblockContext(player, false)))
            {
                var arrow = new ArrowEntity
                {
                    Damage = this.Damage.GetCurrentValue(player.User),                  // Set the Damage so that Experience Points are gained if the arrow hits an animal.
                    Controller = player,
                    Position = position,
                    Velocity = velocity,
                    BowItem = this
                };
                arrow.SetActiveAndCreate();
                return arrow.ID;
            }
            return -1;
        }

        public override bool ShouldHighlight(Type block)
        {
            return false;
        }
    }

    public class ArrowEntity : NetEntity, IDetectHarvest
    {
        public BowItem BowItem { get; set; }
        public float Damage { get; set; }
        public INetObjectViewer Controller { get; set; }
        public Vector3 Velocity { get; set; }
        public NetObjAttachInfo Attached;
        private readonly double destroyTime;
        private const double LifeTime    = 120f;
        private const float HeadDistance = 2f;
        
        public ArrowEntity()
            : base("Arrow")
        {
            this.destroyTime = TimeUtil.Seconds + LifeTime;
        }

        

        [RPC]
        public override void Destroy()
        {
            // let clients help decide when to destroy
            base.Destroy();
        }

        [RPC]
        public void Hit(NetObjAttachInfo hitAttachInfo, Vector3 position, string location)
        {
            if (!(this.Controller is Player player)) return;

            var target = NetObjectManager.Default.GetObject<INetObject>(hitAttachInfo.ParentID);

            // Prevent attaching to targets that are not within accessible distance
            if (target is INetObjectPosition targetPosition && targetPosition.Position.WrappedDistance(position) > HeadDistance)
            {
                this.Destroy();
                return;
            }
            
            switch (target)
            {
                // bow only damages animals // Auth will be checked inside TryApplyDamage via HarvestOrHunt. Other cases are harmless.
                case AnimalEntity targetAnimal:
                {
                    targetAnimal.AttachedEntities.Add(this);

                    var hitHead = location.Contains("Head");
                    // player will get x2.5 exp when hit head
                    var experienceMultiplier = hitHead ? 2.5f : 1f;
                    // player will do x2 damage when hit head and x2 again if they have HuntingDeadeyeTalent
                    var locationMultiplier = hitHead ? player.User.Talentset.HasTalent(typeof(HuntingDeadeyeTalent)) ? 4 : 2 : 1;
                    var interactionContext = new InteractionContext() { SelectedStack = new ItemStack(this.BowItem, 1) };
                    if (targetAnimal.Dead || targetAnimal.TryApplyDamageNow(player, this.Damage * locationMultiplier, interactionContext, this.BowItem, typeof(ArrowItem), experienceMultiplier))
                        this.Attached = hitAttachInfo;
                    else
                        this.Destroy();
                    break;
                }
                case Player targetPlayer:
                    // arrows look silly sticking in player capsule colliders
                    player.ErrorLoc($"You must obtain authorization to shoot {targetPlayer.User.MarkedUpName}.");
                    this.Destroy();
                    break;
                default:
                    this.Attached = hitAttachInfo;
                    break;
            }

            Animal.AlertNearbyAnimals(this.Position, 15f);

            if (this.Attached != null)
                this.RPC("Attach", this.Attached);

            this.Position = position;
            this.Rotation = Quaternion.LookRotation(this.Velocity.Normalized);
            this.Velocity = Vector3.Zero;
        }

        [RPC]
        public void HitStatic(Vector3 position, Quaternion rotation)
        {
            this.Position = position;
            this.Rotation = rotation;
            this.Velocity = Vector3.Zero;
            this.RPC("Attach", position, rotation);
            Animal.AlertNearbyAnimals(this.Position, 15f);
        }

        public override bool IsRelevant(INetObjectViewer viewer)
        {
            if (this.Attached != null)
            {
                var obj = NetObjectManager.Default.GetObject<INetObject>(this.Attached.ParentID);
                if (obj != null)
                    return obj.IsRelevant(viewer);

                this.Destroy();
                return false;
            }

            return base.IsRelevant(viewer);
        }

        public override bool IsNotRelevant(INetObjectViewer viewer)
        {
            if (TimeUtil.Seconds > this.destroyTime)
            {
                this.Destroy();
                return true;
            }

            if (this.Attached != null)
            {
                var obj = NetObjectManager.Default.GetObject<INetObject>(this.Attached.ParentID);
                if (obj != null)
                    return obj.IsNotRelevant(viewer);

                this.Destroy();
                return true;
            }

            return base.IsNotRelevant(viewer);
        }

        public override void SendUpdate(BSONObject bsonObj, INetObjectViewer viewer)
        {
            if (this.Attached == null)
                base.SendUpdate(bsonObj, viewer);
        }

        public override void SendInitialState(BSONObject bsonObj, INetObjectViewer viewer)
        {
            base.SendInitialState(bsonObj, viewer);
            if (this.Attached != null)
                bsonObj["attached"] = this.Attached.ToBson();
            bsonObj["v"] = this.Velocity;
            if (this.Controller != null && this.Controller is INetObject)
                bsonObj["controller"] = ((INetObject) this.Controller).ID;
        }

        public void OnHarvest(Player player)
        {
            if (player != null && player.User.Talentset.HasTalent(typeof(HuntingArrowRecoveryTalent)))
                player.User.Inventory.TryAddItems(typeof(ArrowItem), 1);
        }
    }
}
