using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml.Linq;

namespace Barotrauma.Items.Components
{
    struct LimbPos
    {
        public LimbType limbType;
        public Vector2 position;

        public LimbPos(LimbType limbType, Vector2 position)
        {
            this.limbType = limbType;
            this.position = position;
        }
    }

    partial class Controller : ItemComponent
    {
        //where the limbs of the user should be positioned when using the controller
        private List<LimbPos> limbPositions;

        private Direction dir;

        //the position where the user walks to when using the controller 
        //(relative to the position of the item)
        private Vector2 userPos;

        private Camera cam;

        private Character character;

        private Item focusTarget;
        private float targetRotation;

        public Vector2 UserPos
        {
            get { return userPos; }
            set { userPos = value; }
        }

        public Controller(Item item, XElement element)
            : base(item, element)
        {
            limbPositions = new List<LimbPos>();

            userPos = element.GetAttributeVector2("UserPos", Vector2.Zero);

            Enum.TryParse<Direction>(element.GetAttributeString("direction", "None"), out dir);
                
            foreach (XElement el in element.Elements())
            {
                if (el.Name != "limbposition") continue;

                LimbPos lp = new LimbPos();

                try
                {
                    lp.limbType = (LimbType)Enum.Parse(typeof(LimbType), el.Attribute("limb").Value, true);
                }
                catch (Exception e)
                {
                    DebugConsole.ThrowError("Error in " + element + ": " + e.Message, e);
                }

                lp.position = el.GetAttributeVector2("position", Vector2.Zero);

                limbPositions.Add(lp);
            }

            IsActive = true;
        }

        public override void Update(float deltaTime, Camera cam) 
        {
            this.cam = cam;
            
            if (character == null 
                || character.Removed
                || character.SelectedConstruction != item
                || !character.CanInteractWith(item))
            {
                if (character != null)
                {
                    CancelUsing(character);
                    character = null;
                }
                IsActive = false;
                return;
            }

            character.AnimController.Anim = AnimController.Animation.UsingConstruction;

            if (userPos != Vector2.Zero)
            {
                Vector2 diff = (item.WorldPosition + userPos) - character.WorldPosition;

                if (character.AnimController.InWater)
                {
                    if (diff.Length() > 30.0f)
                    {
                        character.AnimController.TargetMovement = Vector2.Clamp(diff*0.01f, -Vector2.One, Vector2.One);
                        character.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
                    }
                    else
                    {
                        character.AnimController.TargetMovement = Vector2.Zero;
                    }
                }
                else
                {
                    diff.Y = 0.0f;
                    if (diff != Vector2.Zero && diff.LengthSquared() > 10.0f * 10.0f)
                    {
                        character.AnimController.TargetMovement = Vector2.Normalize(diff);
                        character.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
                        return;
                    }
                    character.AnimController.TargetMovement = Vector2.Zero;                    
                }
            }

            ApplyStatusEffects(ActionType.OnActive, deltaTime, character);

            if (limbPositions.Count == 0) return;

            character.AnimController.Anim = AnimController.Animation.UsingConstruction;

            character.AnimController.ResetPullJoints();

            if (dir != 0) character.AnimController.TargetDir = dir;

            foreach (LimbPos lb in limbPositions)
            {
                Limb limb = character.AnimController.GetLimb(lb.limbType);
                if (limb == null || !limb.body.Enabled) continue;

                limb.Disabled = true;
                
                Vector2 worldPosition = new Vector2(item.WorldRect.X, item.WorldRect.Y) + lb.position * item.Scale;
                Vector2 diff = worldPosition - limb.WorldPosition;

                limb.PullJointEnabled = true;
                limb.PullJointWorldAnchorB = limb.SimPosition + ConvertUnits.ToSimUnits(diff);
            }
        }

        public override bool Use(float deltaTime, Character activator = null)
        {
            if (activator != character)
            {
                return false;
            }

            if (character == null || character.Removed ||
                character.SelectedConstruction != item || !character.CanInteractWith(item))
            {
                character = null;
                return false;
            }

            item.SendSignal(0, "1", "trigger_out", character);
            
            ApplyStatusEffects(ActionType.OnUse, 1.0f, activator);
            
            return true;
        }
        
        public override bool SecondaryUse(float deltaTime, Character character = null)
        {
            if (this.character != character)
            {
                return false;
            }

            if (this.character == null || character.Removed ||
                this.character.SelectedConstruction != item || !character.CanInteractWith(item))
            {
                this.character = null;
                return false;
            }
            if (character == null) return false;

            focusTarget = GetFocusTarget();
            if (focusTarget == null)
            {
                Vector2 centerPos = new Vector2(item.WorldRect.Center.X, item.WorldRect.Center.Y);

                Vector2 offset = character.CursorWorldPosition - centerPos;
                offset.Y = -offset.Y;

                targetRotation = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(offset));
                return false;
            }

            character.ViewTarget = focusTarget;
#if CLIENT
            if (character == Character.Controlled && cam != null)
            {
                Lights.LightManager.ViewTarget = focusTarget;
                cam.TargetPos = focusTarget.WorldPosition;

                cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, (focusTarget as Item).Prefab.OffsetOnSelected, deltaTime * 10.0f);
            }
#endif

            if (!character.IsRemotePlayer || character.ViewTarget == focusTarget)
            {
                Vector2 centerPos = new Vector2(item.WorldRect.Center.X, item.WorldRect.Center.Y);

                Item targetItem = focusTarget as Item;
                if (targetItem != null)
                {
                    Turret turret = targetItem.GetComponent<Turret>();
                    if (turret != null)
                    {
                        centerPos = new Vector2(targetItem.WorldRect.X + turret.TransformedBarrelPos.X, targetItem.WorldRect.Y - turret.TransformedBarrelPos.Y);
                    }
                }

                Vector2 offset = character.CursorWorldPosition - centerPos;
                offset.Y = -offset.Y;

                targetRotation = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(offset));
            }
            return true;
        }

        private Item GetFocusTarget()
        {
            item.SendSignal(0, MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), "position_out", character);

            for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--)
            {
                if (item.LastSentSignalRecipients[i].Condition <= 0.0f) continue;
                if (item.LastSentSignalRecipients[i].Prefab.FocusOnSelected)
                {
                    return item.LastSentSignalRecipients[i];
                }
            }
            
            return null;
        }

        public override bool Pick(Character picker)
        {
            item.SendSignal(0, "1", "signal_out", picker);

#if CLIENT
            PlaySound(ActionType.OnUse, item.WorldPosition, picker);
#endif

            return true;
        }

        private void CancelUsing(Character character)
        {
            if (character == null || character.Removed) return;

            foreach (LimbPos lb in limbPositions)
            {
                Limb limb = character.AnimController.GetLimb(lb.limbType);
                if (limb == null) continue;

                limb.Disabled = false;
                limb.PullJointEnabled = false;
            }

            if (character.SelectedConstruction == this.item) character.SelectedConstruction = null;

            character.AnimController.Anim = AnimController.Animation.None;
        }

        public override bool Select(Character activator)
        {
            if (activator == null || activator.Removed) return false;

            //someone already using the item
            if (character != null && !character.Removed)
            {
                if (character == activator)
                {
                    IsActive = false;
                    CancelUsing(character);
                    character = null;
                    return false;
                }
            }
            else
            {
                character = activator;                    
                IsActive = true;
            }

            item.SendSignal(0, "1", "signal_out", character);
            return true;
        }

        public override void FlipX(bool relativeToSub)
        {
            if (dir != Direction.None)
            {
                dir = dir == Direction.Left ? Direction.Right : Direction.Left;
            }

            userPos.X = -UserPos.X;            

            for (int i = 0; i < limbPositions.Count; i++)
            {
                float diff = (item.Rect.X + limbPositions[i].position.X * item.Scale) - item.Rect.Center.X;

                Vector2 flippedPos =
                    new Vector2(
                        (item.Rect.Center.X - diff - item.Rect.X) / item.Scale,
                        limbPositions[i].position.Y);

                limbPositions[i] = new LimbPos(limbPositions[i].limbType, flippedPos);
            }
        }

        public override void FlipY(bool relativeToSub)
        {
            userPos.Y = -UserPos.Y;

            for (int i = 0; i < limbPositions.Count; i++)
            {
                float diff = (item.Rect.Y + limbPositions[i].position.Y) - item.Rect.Center.Y;

                Vector2 flippedPos =
                    new Vector2(
                        limbPositions[i].position.X,
                        item.Rect.Center.Y - diff - item.Rect.Y);

                limbPositions[i] = new LimbPos(limbPositions[i].limbType, flippedPos);
            }
        }
    }
}
