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

namespace Barotrauma.Items.Components
{
    partial class ItemContainer : ItemComponent, IDrawableComponent
    {
        private List<RelatedItem> containableItems;
        public ItemInventory Inventory;

        private List<Pair<Item, StatusEffect>> itemsWithStatusEffects;
        
        private ushort[] itemIds;

        //how many items can be contained
        private int capacity;
        [Serialize(5, false)]
        public int Capacity
        {
            get { return capacity; }
            set { capacity = Math.Max(value, 1); }
        }

        private bool hideItems;
        [Serialize(true, false)]
        public bool HideItems
        {
            get { return hideItems; }
            set 
            { 
                hideItems = value;
                Drawable = !hideItems;
            }
        }
        
        [Serialize(true, false)]
        public bool DrawInventory
        {
            get;
            set;
        }


        [Serialize(false, false)]
        public bool AutoInteractWithContained
        {
            get;
            set;
        }

        [Serialize("0.5,0.5", false)]
        public Vector2 HudPos { get; set; }
        [Serialize(5, false)]
        public int SlotsPerRow { get; set; }

        public List<RelatedItem> ContainableItems
        {
            get { return containableItems; }
        }

        public ItemContainer(Item item, XElement element)
            : base (item, element)
        {
            Inventory = new ItemInventory(item, this, capacity, HudPos, SlotsPerRow);            
            containableItems = new List<RelatedItem>();
            
            foreach (XElement subElement in element.Elements())
            {
                switch (subElement.Name.ToString().ToLowerInvariant())
                {
                    case "containable":
                        RelatedItem containable = RelatedItem.Load(subElement, item.Name);
                        if (containable == null)
                        {
                            DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - containable with no identifiers.");
                            continue;
                        }
                        containableItems.Add(containable);
                        break;
                }
            }

            InitProjSpecific(element);

            itemsWithStatusEffects = new List<Pair<Item, StatusEffect>>();
        }

        partial void InitProjSpecific(XElement element);

        public void OnItemContained(Item containedItem)
        {
            item.SetContainedItemPositions();
            
            RelatedItem ri = containableItems.Find(x => x.MatchesItem(containedItem));
            if (ri != null)
            {
                foreach (StatusEffect effect in ri.statusEffects)
                {
                    itemsWithStatusEffects.Add(new Pair<Item, StatusEffect>(containedItem, effect));
                }
            }

            //no need to Update() if this item has no statuseffects and no physics body
            IsActive = itemsWithStatusEffects.Count > 0 || containedItem.body != null;
        }

        public void OnItemRemoved(Item item)
        {
            itemsWithStatusEffects.RemoveAll(i => i.First == item);

            //deactivate if the inventory is empty
            IsActive = itemsWithStatusEffects.Count > 0 || item.body != null;
        }

        public bool CanBeContained(Item item)
        {
            if (containableItems.Count == 0) return true;
            return (containableItems.Find(x => x.MatchesItem(item)) != null);
        }

        public override void Update(float deltaTime, Camera cam)
        {
            if (item.body != null && 
                item.body.Enabled &&
                item.body.FarseerBody.Awake)
            {
                item.SetContainedItemPositions();
            }

            foreach (Pair<Item, StatusEffect> itemAndEffect in itemsWithStatusEffects)
            {
                Item contained = itemAndEffect.First;
                if (contained.Condition <= 0.0f) continue;

                StatusEffect effect = itemAndEffect.Second;

                if (effect.HasTargetType(StatusEffect.TargetType.This))                 
                    effect.Apply(ActionType.OnContaining, deltaTime, item, item.AllPropertyObjects);
                if (effect.HasTargetType(StatusEffect.TargetType.Contained)) 
                    effect.Apply(ActionType.OnContaining, deltaTime, item, contained.AllPropertyObjects);
                if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
                    effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
                {
                    var targets = new List<ISerializableEntity>();
                    effect.GetNearbyTargets(item.WorldPosition, targets);
                    effect.Apply(ActionType.OnActive, deltaTime, item, targets);
                }
            }
        }

        public override bool Select(Character character)
        {
            if (item.Container != null) { return false; }

            if (AutoInteractWithContained && character.SelectedConstruction == null)
            {
                foreach (Item contained in Inventory.Items)
                {
                    if (contained == null) continue;
                    if (contained.TryInteract(character))
                    {
                        character.FocusedItem = contained;
                        return false;
                    }
                }
            }
            return base.Select(character);
        }

        public override bool Pick(Character picker)
        {
            if (AutoInteractWithContained)
            {
                foreach (Item contained in Inventory.Items)
                {
                    if (contained == null) continue;
                    if (contained.TryInteract(picker))
                    {
                        picker.FocusedItem = contained;
                        return true;
                    }
                }
            }

            return (picker != null);
        }

        public override bool Combine(Item item)
        {
            if (!containableItems.Any(x => x.MatchesItem(item))) return false;
            
            if (Inventory.TryPutItem(item, null))
            {            
                IsActive = true;
                if (hideItems && item.body != null) item.body.Enabled = false;
                            
                return true;
            }

            return false;
        }

        public void SetContainedItemPositions()
        {
            Vector2 simPos = item.SimPosition;
            Vector2 displayPos = item.Position;

            foreach (Item contained in Inventory.Items)
            {
                if (contained == null) continue;
                if (contained.body != null)
                {
                    try
                    {
                        contained.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, 0.0f);
                    }
                    catch (Exception e)
                    {
                        DebugConsole.Log("SetTransformIgnoreContacts threw an exception in SetContainedItemPositions ("+e.Message+")\n"+e.StackTrace);
                        GameAnalyticsManager.AddErrorEventOnce("ItemContainer.SetContainedItemPositions.InvalidPosition:"+contained.Name,
                            GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
                            "SetTransformIgnoreContacts threw an exception in SetContainedItemPositions (" + e.Message + ")\n" + e.StackTrace);
                    }
                }

                contained.Rect =
                    new Rectangle(
                        (int)(displayPos.X - contained.Rect.Width / 2.0f),
                        (int)(displayPos.Y + contained.Rect.Height / 2.0f),
                        contained.Rect.Width, contained.Rect.Height);

                contained.Submarine = item.Submarine;
                contained.CurrentHull = item.CurrentHull;

                contained.SetContainedItemPositions();
            }
        }

        public override void OnMapLoaded()
        {
            if (itemIds == null) return;

            for (ushort i = 0; i < itemIds.Length; i++)
            {
                Item item = Entity.FindEntityByID(itemIds[i]) as Item;
                if (item == null) continue;

                if (i >= Inventory.Capacity)
                {
                    continue;
                }

                Inventory.TryPutItem(item, i, false, false, null, false);
            }

            itemIds = null;
        }

        protected override void ShallowRemoveComponentSpecific()
        {
        }

        protected override void RemoveComponentSpecific()
        {
#if CLIENT
            inventoryTopSprite?.Remove();
            inventoryBackSprite?.Remove();
            inventoryBottomSprite?.Remove();
            ContainedStateIndicator?.Remove();

            if (Screen.Selected == GameMain.SubEditorScreen && !Submarine.Unloading)
            {
                GameMain.SubEditorScreen.HandleContainerContentsDeletion(Item, Inventory);
                return;
            }
#endif

            foreach (Item item in Inventory.Items)
            {
                if (item == null) continue;
                item.Drop(null);
            }               
        }        

        public override void Load(XElement componentElement)
        {
            base.Load(componentElement);

            string containedString = componentElement.GetAttributeString("contained", "");

            string[] itemIdStrings = containedString.Split(',');

            itemIds = new ushort[itemIdStrings.Length];
            for (int i = 0; i < itemIdStrings.Length; i++)
            {
                ushort id = 0;
                if (!ushort.TryParse(itemIdStrings[i], out id)) continue;

                itemIds[i] = id;
            }
        }

        public override XElement Save(XElement parentElement)
        {
            XElement componentElement = base.Save(parentElement);

            string[] itemIdStrings = new string[Inventory.Items.Length];
            for (int i = 0; i < Inventory.Items.Length; i++)
            {
                itemIdStrings[i] = (Inventory.Items[i] == null) ? "0" : Inventory.Items[i].ID.ToString();
            }

            componentElement.Add(new XAttribute("contained", string.Join(",", itemIdStrings)));

            return componentElement;
        }
    }
}
