using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Xml.Linq;

namespace Barotrauma.Items.Components
{
    partial class Rope : ItemComponent, IDrawableComponent
    {
        private Sprite sprite, startSprite, endSprite;

        [Serialize(5, IsPropertySaveable.No)]
        public int SpriteWidth
        {
            get;
            set;
        }

        [Serialize("255,255,255,255", IsPropertySaveable.No)]
        public Color SpriteColor
        {
            get;
            set;
        }

        [Serialize(false, IsPropertySaveable.No)]
        public bool Tile
        {
            get;
            set;
        }

        [Serialize("0.5,0.5)", IsPropertySaveable.No)]
        public Vector2 Origin { get; set; } = new Vector2(0.5f, 0.5f);

        [Serialize(true, IsPropertySaveable.No, description: "")]
        public bool BreakFromMiddle
        {
            get;
            set;
        }

        public Vector2 DrawSize
        {
            get 
            {
                if (target == null || source == null) { return Vector2.Zero; }

                Vector2 sourcePos = GetSourcePos();

                return new Vector2(
                    Math.Abs(target.DrawPosition.X - sourcePos.X),
                    Math.Abs(target.DrawPosition.Y - sourcePos.Y)) * 1.5f;
            }
        }

        private Vector2 GetSourcePos()
        {
            Vector2 sourcePos = source.WorldPosition;
            if (source is Item sourceItem)
            {
                sourcePos = sourceItem.DrawPosition;
            }
            else if (source is Limb sourceLimb && sourceLimb.body != null)
            {
                sourcePos = sourceLimb.body.DrawPosition;
            }
            return sourcePos;
        }

        partial void InitProjSpecific(ContentXElement element)
        {
            foreach (var subElement in element.Elements())
            {
                switch (subElement.Name.ToString().ToLowerInvariant())
                {
                    case "sprite":
                        sprite = new Sprite(subElement);
                        break;
                    case "startsprite":
                        startSprite = new Sprite(subElement);
                        break;
                    case "endsprite":
                        endSprite = new Sprite(subElement);
                        break;
                }
            }
        }

        public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
        {
            if (target == null || target.Removed) { return; }
            if (target.ParentInventory != null) { return; }
            if (source is Limb limb && limb.Removed) { return; }
            if (source is Entity e && e.Removed) { return; }

            Vector2 startPos = GetSourcePos();
            startPos.Y = -startPos.Y;
            if (source is Item sourceItem && !sourceItem.Removed)
            {
                var turret = sourceItem.GetComponent<Turret>();
                var weapon = sourceItem.GetComponent<RangedWeapon>();
                if (turret != null)
                {
                    startPos = new Vector2(sourceItem.WorldRect.X + turret.TransformedBarrelPos.X, -(sourceItem.WorldRect.Y - turret.TransformedBarrelPos.Y));
                    if (turret.BarrelSprite != null)
                    {
                        startPos += new Vector2((float)Math.Cos(turret.Rotation), (float)Math.Sin(turret.Rotation)) * turret.BarrelSprite.size.Y * turret.BarrelSprite.RelativeOrigin.Y * item.Scale * 0.9f;
                    }
                    startPos -= turret.GetRecoilOffset();
                }
                else if (weapon != null)
                {
                    Vector2 barrelPos = FarseerPhysics.ConvertUnits.ToDisplayUnits(weapon.TransformedBarrelPos);
                    barrelPos.Y = -barrelPos.Y;
                    startPos += barrelPos;
                }
            }
            Vector2 endPos = new Vector2(target.DrawPosition.X, target.DrawPosition.Y);
            Vector2 flippedPos = target.Sprite.size * target.Scale * (Origin - new Vector2(0.5f));
            if (target.body.Dir < 0.0f)
            {
                flippedPos.X = -flippedPos.X;
            }
            endPos += Vector2.Transform(flippedPos, Matrix.CreateRotationZ(target.body.Rotation));
            endPos.Y = -endPos.Y;

            if (Snapped)
            {
                float snapState = 1.0f - snapTimer / SnapAnimDuration;
                Vector2 diff = target.DrawPosition - new Vector2(startPos.X, -startPos.Y);
                diff.Y = -diff.Y;

                int width = (int)(SpriteWidth * snapState);
                if (width > 0.0f) 
                {
                    float positionMultiplier = snapState;
                    if (BreakFromMiddle)
                    {
                        positionMultiplier /= 2;
                        DrawRope(spriteBatch, endPos - diff * positionMultiplier, endPos, width);
                    }
                    DrawRope(spriteBatch, startPos, startPos + diff * positionMultiplier, width);
                }
            }
            else
            {
                DrawRope(spriteBatch, startPos, endPos, SpriteWidth);
            }

            if (startSprite != null || endSprite != null)
            {
                Vector2 dir = endPos - startPos;
                float angle = (float)Math.Atan2(dir.Y, dir.X);
                if (startSprite != null)
                {
                    float depth = Math.Min(item.GetDrawDepth() + (startSprite.Depth - item.Sprite.Depth), 0.999f);
                    startSprite?.Draw(spriteBatch, startPos, SpriteColor, angle, depth: depth);
                }
                if (endSprite != null && (!Snapped || BreakFromMiddle))
                {
                    float depth = Math.Min(item.GetDrawDepth() + (endSprite.Depth - item.Sprite.Depth), 0.999f);
                    endSprite?.Draw(spriteBatch, endPos, SpriteColor, angle, depth: depth);
                }
            }
        }

        private void DrawRope(SpriteBatch spriteBatch, Vector2 startPos, Vector2 endPos, int width)
        {
            float depth = sprite == null ?
                item.Sprite.Depth + 0.001f :
                Math.Min(item.GetDrawDepth() + (sprite.Depth - item.Sprite.Depth), 0.999f);
            
            if (sprite?.Texture == null)
            {
                GUI.DrawLine(spriteBatch,
                    startPos,
                    endPos,
                    SpriteColor, depth: depth, width: width);
                return;
            }

            if (Tile)
            {
                float length = Vector2.Distance(startPos, endPos);
                Vector2 dir = (endPos - startPos) / length;
                float x;
                for (x = 0.0f; x <= length - sprite.size.X; x += sprite.size.X)
                {
                    GUI.DrawLine(spriteBatch, sprite,
                        startPos + dir * (x - 5.0f),
                        startPos + dir * (x + sprite.size.X),
                        SpriteColor, depth: depth, width: width);
                }
                float leftOver = length - x;
                if (leftOver > 0.0f)
                {
                    GUI.DrawLine(spriteBatch, sprite,
                        startPos + dir * (x - 5.0f),
                        endPos,
                        SpriteColor, depth: depth, width: width);
                }
            }
            else
            {
                GUI.DrawLine(spriteBatch, sprite,
                    startPos,
                    endPos,
                    SpriteColor, depth: depth, width: width);
            }
        }

        public void ClientEventRead(IReadMessage msg, float sendingTime)
        {
            snapped = msg.ReadBoolean();

            if (!snapped)
            {
                UInt16 targetId = msg.ReadUInt16();
                UInt16 sourceId = msg.ReadUInt16();
                byte limbIndex = msg.ReadByte();

                Item target = Entity.FindEntityByID(targetId) as Item;
                if (target == null) { return; }
                var source = Entity.FindEntityByID(sourceId);
                if (source is Character sourceCharacter && limbIndex >= 0 && limbIndex < sourceCharacter.AnimController.Limbs.Length)
                {
                    Limb sourceLimb = sourceCharacter.AnimController.Limbs[limbIndex];
                    Attach(sourceLimb, target);
                }
                else if (source is ISpatialEntity spatialEntity)
                {
                    Attach(spatialEntity, target);
                }
            }
        }

        protected override void RemoveComponentSpecific()
        {
            sprite?.Remove(); sprite = null;
            startSprite?.Remove(); startSprite = null;
            endSprite?.Remove(); endSprite = null;
        }
    }
}
