#region References
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;

using Server.ContextMenus;
using Server.Network;
#endregion

namespace Server.Items
{
    public delegate void OnItemConsumed(Item item, int amount);
    public delegate int CheckItemGroup(Item a, Item b);
    public delegate bool ResValidator(Item item);
    public delegate void ContainerSnoopHandler(Container cont, Mobile from);

    public class Container : Item
    {
        #region Enhanced Client Support
        public virtual void ValidateGridLocation(Item item)
        {
			var pos = item.GridLocation;

            if (!IsFreePosition(pos))
            {
                item.GridLocation = GetNewPosition(pos);
            }
        }

        public virtual bool IsFreePosition(byte pos)
        {
            if (pos < 0 || pos > 0x7C)
            {
                return false;
            }

			return Items.All(i => i.GridLocation != pos);
        }

        public virtual byte GetNewPosition(byte current)
        {
            int index = 0;
            byte next = (byte)(current + 1);

            while (++index < 0x7D)
            {
                if (!IsFreePosition(next))
                {
                    if (next == 0x7C)
                    {
                        next = 0;

                        if (IsFreePosition(next))
                        {
                            return next;
                        }
                    }
                }
                else
                {
                    return next;
                }

                next++;
            }

            return 0;
        }

        public virtual void ValidatePositions()
        {
            foreach (var item in Items)
            {
                if (IsFreePosition(item.GridLocation))
                {
                    item.GridLocation = GetNewPosition(item.GridLocation);
                }
            }
        }
        #endregion

        private static ContainerSnoopHandler m_SnoopHandler;

        public static ContainerSnoopHandler SnoopHandler { get { return m_SnoopHandler; } set { m_SnoopHandler = value; } }

        private ContainerData m_ContainerData;

        private int m_DropSound;
        private int m_GumpID;
        private int m_MaxItems;

        private int m_TotalItems;
        private int m_TotalWeight;
        private int m_TotalGold;

        private bool m_LiftOverride;

        internal List<Item> m_Items;

        public ContainerData ContainerData
        {
            get
            {
                if (m_ContainerData == null)
                {
                    UpdateContainerData();
                }

                return m_ContainerData;
            }
            set { m_ContainerData = value; }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public override int ItemID
        {
            get { return base.ItemID; }
            set
            {
                int oldID = ItemID;

                base.ItemID = value;

                if (ItemID != oldID)
                {
                    UpdateContainerData();
                }
            }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public int GumpID { get { return (m_GumpID == -1 ? DefaultGumpID : m_GumpID); } set { m_GumpID = value; } }

        [CommandProperty(AccessLevel.GameMaster)]
        public int DropSound { get { return (m_DropSound == -1 ? DefaultDropSound : m_DropSound); } set { m_DropSound = value; } }

        [CommandProperty(AccessLevel.GameMaster)]
        public int MaxItems
        {
            get { return (m_MaxItems == -1 ? DefaultMaxItems : m_MaxItems); }
            set
            {
                m_MaxItems = value;
                InvalidateProperties();
            }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public virtual int MaxWeight
        {
            get
            {
                if (Parent is Container && ((Container)Parent).MaxWeight == 0)
                {
                    return 0;
                }
                else
                {
                    return DefaultMaxWeight;
                }
            }
        }

        [CommandProperty(AccessLevel.GameMaster)]
        public bool LiftOverride { get { return m_LiftOverride; } set { m_LiftOverride = value; } }

        public virtual void UpdateContainerData()
        {
            ContainerData = ContainerData.GetData(ItemID);
        }

        public virtual Rectangle2D Bounds { get { return ContainerData.Bounds; } }
        public virtual int DefaultGumpID { get { return ContainerData.GumpID; } }
        public virtual int DefaultDropSound { get { return ContainerData.DropSound; } }

        public virtual int DefaultMaxItems { get { return m_GlobalMaxItems; } }
        public virtual int DefaultMaxWeight { get { return m_GlobalMaxWeight; } }

        public virtual bool IsDecoContainer { get { return !Movable && !IsLockedDown && !IsSecure && Parent == null && !m_LiftOverride; } }

        public virtual int GetDroppedSound(Item item)
        {
            int dropSound = item.GetDropSound();

            return dropSound != -1 ? dropSound : DropSound;
        }

        public override void OnSnoop(Mobile from)
        {
            if (m_SnoopHandler != null)
            {
                m_SnoopHandler(this, from);
            }
        }

        public override bool CheckLift(Mobile from, Item item, ref LRReason reject)
        {
            if (!from.IsStaff() && IsDecoContainer)
            {
                reject = LRReason.CannotLift;
                return false;
            }

            return base.CheckLift(from, item, ref reject);
        }

        public override bool CheckItemUse(Mobile from, Item item)
        {
            if (item != this && from.AccessLevel < AccessLevel.GameMaster && IsDecoContainer)
            {
                from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that.
                return false;
            }

            return base.CheckItemUse(from, item);
        }

        public bool CheckHold(Mobile m, Item item, bool message)
        {
            return CheckHold(m, item, message, true, 0, 0);
        }

        public bool CheckHold(Mobile m, Item item, bool message, bool checkItems)
        {
            return CheckHold(m, item, message, checkItems, 0, 0);
        }

        public virtual bool CheckHold(Mobile m, Item item, bool message, bool checkItems, int plusItems, int plusWeight)
        {
            if (!m.IsStaff())
            {
                if (IsDecoContainer)
                {
                    if (message)
                    {
                        SendCantStoreMessage(m, item);
                    }

                    return false;
                }

                int maxItems = MaxItems;

                if (checkItems && maxItems != 0 &&
                    (TotalItems + plusItems + item.TotalItems + (item.IsVirtualItem ? 0 : 1)) > maxItems)
                {
                    if (message)
                    {
                        SendFullItemsMessage(m, item);
                    }

                    return false;
                }
                else
                {
                    int maxWeight = MaxWeight;

                    if (maxWeight != 0 && (TotalWeight + plusWeight + item.TotalWeight + item.PileWeight) > maxWeight)
                    {
                        if (message)
                        {
                            SendFullWeightMessage(m, item);
                        }

                        return false;
                    }
                }
            }

			var parent = Parent;

            while (parent != null)
            {
                if (parent is Container)
                {
                    return ((Container)parent).CheckHold(m, item, message, checkItems, plusItems, plusWeight);
                }
                else if (parent is Item)
                {
                    parent = ((Item)parent).Parent;
                }
                else
                {
                    break;
                }
            }

            return true;
        }

        public virtual bool CheckStack(Mobile from, Item item)
        {
            foreach (var i in Items)
            {
                if (i.WillStack(from, item))
                {
                    return true;
                }
            }

            return false;
        }

        public virtual void SendFullItemsMessage(Mobile to, Item item)
        {
            to.SendMessage("That container cannot hold more items.");
        }

        public virtual void SendFullWeightMessage(Mobile to, Item item)
        {
            to.SendMessage("That container cannot hold more weight.");
        }

        public virtual void SendCantStoreMessage(Mobile to, Item item)
        {
            to.SendLocalizedMessage(500176); // That is not your container, you can't store things here.
        }

        public virtual bool OnDragDropInto(Mobile from, Item item, Point3D p)
        {
            if (!CheckHold(from, item, true, true))
            {
                return false;
            }

            item.Location = new Point3D(p.m_X, p.m_Y, 0);
            AddItem(item);

            from.SendSound(GetDroppedSound(item), GetWorldLocation());

            return true;
        }

        private class GroupComparer : IComparer
        {
            private readonly CheckItemGroup m_Grouper;

            public GroupComparer(CheckItemGroup grouper)
            {
                m_Grouper = grouper;
            }

            public int Compare(object x, object y)
            {
                Item a = (Item)x;
                Item b = (Item)y;

                return m_Grouper(a, b);
            }
        }

        #region Consume[...]
        public bool ConsumeTotalGrouped(Type type, int amount, bool recurse, OnItemConsumed callback, CheckItemGroup grouper)
        {
            return ConsumeTotalGrouped(type, amount, recurse, null, callback, grouper);
        }

        public bool ConsumeTotalGrouped(Type type, int amount, bool recurse, ResValidator validator, OnItemConsumed callback, CheckItemGroup grouper)
        {
            if (grouper == null)
            {
                throw new ArgumentNullException();
            }

            var typedItems = FindItemsByType(type, recurse);

            var groups = new List<List<Item>>();
            int idx = 0;

            while (idx < typedItems.Length)
            {
                Item a = typedItems[idx++];

                if (validator != null && !validator(a))
                    continue;

                var group = new List<Item>();

                group.Add(a);

                while (idx < typedItems.Length)
                {
                    Item b = typedItems[idx];

                    if (validator != null && !validator(b))
                        continue;

                    int v = grouper(a, b);

                    if (v == 0)
                    {
                        group.Add(b);
                    }
                    else
                    {
                        break;
                    }

                    ++idx;
                }

                groups.Add(group);
            }

            var items = new Item[groups.Count][];
            var totals = new int[groups.Count];

            bool hasEnough = false;

            for (int i = 0; i < groups.Count; ++i)
            {
                items[i] = groups[i].ToArray();
                //items[i] = (Item[])(((ArrayList)groups[i]).ToArray( typeof( Item ) ));

                for (int j = 0; j < items[i].Length; ++j)
                {
                    totals[i] += items[i][j].Amount;
                }

                if (totals[i] >= amount)
                {
                    hasEnough = true;
                }
            }

            if (!hasEnough)
            {
                return false;
            }

            for (int i = 0; i < items.Length; ++i)
            {
                if (totals[i] >= amount)
                {
                    int need = amount;

                    for (int j = 0; j < items[i].Length; ++j)
                    {
                        Item item = items[i][j];

                        int theirAmount = item.Amount;

                        if (theirAmount < need)
                        {
                            if (callback != null)
                            {
                                callback(item, theirAmount);
                            }

                            item.Delete();
                            need -= theirAmount;
                        }
                        else
                        {
                            if (callback != null)
                            {
                                callback(item, need);
                            }

                            item.Consume(need);
                            break;
                        }
                    }

                    break;
                }
            }

            return true;
        }

        public int ConsumeTotalGrouped(
            Type[] types, int[] amounts, bool recurse, OnItemConsumed callback, CheckItemGroup grouper)
        {
            return ConsumeTotalGrouped(types, amounts, recurse, null, callback, grouper);
        }

        public int ConsumeTotalGrouped(
            Type[] types, int[] amounts, bool recurse, ResValidator validator, OnItemConsumed callback, CheckItemGroup grouper)
        {
            if (types.Length != amounts.Length)
            {
                throw new ArgumentException();
            }
            else if (grouper == null)
            {
                throw new ArgumentNullException();
            }

            var items = new Item[types.Length][][];
            var totals = new int[types.Length][];

            for (int i = 0; i < types.Length; ++i)
            {
                var typedItems = FindItemsByType(types[i], recurse);

                var groups = new List<List<Item>>();
                int idx = 0;

                while (idx < typedItems.Length)
                {
                    Item a = typedItems[idx++];

                    if (validator != null && !validator(a))
                        continue;

                    var group = new List<Item>();

                    group.Add(a);

                    while (idx < typedItems.Length)
                    {
                        Item b = typedItems[idx];

                        if (validator != null && !validator(b))
                            continue;

                        int v = grouper(a, b);

                        if (v == 0)
                        {
                            group.Add(b);
                        }
                        else
                        {
                            break;
                        }

                        ++idx;
                    }
                    
                    groups.Add(group);
                }

                items[i] = new Item[groups.Count][];
                totals[i] = new int[groups.Count];

                bool hasEnough = false;

                for (int j = 0; j < groups.Count; ++j)
                {
                    items[i][j] = groups[j].ToArray();
                    //items[i][j] = (Item[])(((ArrayList)groups[j]).ToArray( typeof( Item ) ));

                    for (int k = 0; k < items[i][j].Length; ++k)
                    {
                        totals[i][j] += items[i][j][k].Amount;
                    }

                    if (totals[i][j] >= amounts[i])
                    {
                        hasEnough = true;
                    }
                }

                if (!hasEnough)
                {
                    return i;
                }
            }

            for (int i = 0; i < items.Length; ++i)
            {
                for (int j = 0; j < items[i].Length; ++j)
                {
                    if (totals[i][j] >= amounts[i])
                    {
                        int need = amounts[i];

                        for (int k = 0; k < items[i][j].Length; ++k)
                        {
                            Item item = items[i][j][k];

                            int theirAmount = item.Amount;

                            if (theirAmount < need)
                            {
                                if (callback != null)
                                {
                                    callback(item, theirAmount);
                                }

                                item.Delete();
                                need -= theirAmount;
                            }
                            else
                            {
                                if (callback != null)
                                {
                                    callback(item, need);
                                }

                                item.Consume(need);
                                break;
                            }
                        }

                        break;
                    }
                }
            }

            return -1;
        }

        public int ConsumeTotalGrouped(
            Type[][] types, int[] amounts, bool recurse, OnItemConsumed callback, CheckItemGroup grouper)
        {
            return ConsumeTotalGrouped(types, amounts, recurse, null, callback, grouper);
        }

        public int ConsumeTotalGrouped(
            Type[][] types, int[] amounts, bool recurse, ResValidator validator, OnItemConsumed callback, CheckItemGroup grouper)
        {
            if (types.Length != amounts.Length)
            {
                throw new ArgumentException();
            }
            else if (grouper == null)
            {
                throw new ArgumentNullException();
            }

            var items = new Item[types.Length][][];
            var totals = new int[types.Length][];

            for (int i = 0; i < types.Length; ++i)
            {
                var typedItems = FindItemsByType(types[i], recurse);

                var groups = new List<List<Item>>();
                int idx = 0;

                while (idx < typedItems.Length)
                {
                    Item a = typedItems[idx++];

                    if (validator != null && !validator(a))
                        continue;

                    var group = new List<Item>();

                    group.Add(a);

                    while (idx < typedItems.Length)
                    {
                        Item b = typedItems[idx];

                        if (validator != null && !validator(b))
                            continue;

                        int v = grouper(a, b);

                        if (v == 0)
                        {
                            group.Add(b);
                        }
                        else
                        {
                            break;
                        }

                        ++idx;
                    }

                    groups.Add(group);
                }

                items[i] = new Item[groups.Count][];
                totals[i] = new int[groups.Count];

                bool hasEnough = false;

                for (int j = 0; j < groups.Count; ++j)
                {
                    items[i][j] = groups[j].ToArray();

                    for (int k = 0; k < items[i][j].Length; ++k)
                    {
                        totals[i][j] += items[i][j][k].Amount;
                    }

                    if (totals[i][j] >= amounts[i])
                    {
                        hasEnough = true;
                    }
                }

                if (!hasEnough)
                {
                    return i;
                }
            }

            for (int i = 0; i < items.Length; ++i)
            {
                for (int j = 0; j < items[i].Length; ++j)
                {
                    if (totals[i][j] >= amounts[i])
                    {
                        int need = amounts[i];

                        for (int k = 0; k < items[i][j].Length; ++k)
                        {
                            Item item = items[i][j][k];

                            int theirAmount = item.Amount;

                            if (theirAmount < need)
                            {
                                if (callback != null)
                                {
                                    callback(item, theirAmount);
                                }

                                item.Delete();
                                need -= theirAmount;
                            }
                            else
                            {
                                if (callback != null)
                                {
                                    callback(item, need);
                                }

                                item.Consume(need);
                                break;
                            }
                        }

                        break;
                    }
                }
            }

            return -1;
        }

        public int ConsumeTotal(Type[][] types, int[] amounts)
        {
            return ConsumeTotal(types, amounts, true, null);
        }

        public int ConsumeTotal(Type[][] types, int[] amounts, bool recurse)
        {
            return ConsumeTotal(types, amounts, recurse, null);
        }

        public int ConsumeTotal(Type[][] types, int[] amounts, bool recurse, OnItemConsumed callback)
        {
            if (types.Length != amounts.Length)
            {
                throw new ArgumentException();
            }

            var items = new Item[types.Length][];
            var totals = new int[types.Length];

            for (int i = 0; i < types.Length; ++i)
            {
                items[i] = FindItemsByType(types[i], recurse);

                for (int j = 0; j < items[i].Length; ++j)
                {
                    totals[i] += items[i][j].Amount;
                }

                if (totals[i] < amounts[i])
                {
                    return i;
                }
            }

            for (int i = 0; i < types.Length; ++i)
            {
                int need = amounts[i];

                for (int j = 0; j < items[i].Length; ++j)
                {
                    Item item = items[i][j];

                    int theirAmount = item.Amount;

                    if (theirAmount < need)
                    {
                        if (callback != null)
                        {
                            callback(item, theirAmount);
                        }

                        item.Delete();
                        need -= theirAmount;
                    }
                    else
                    {
                        if (callback != null)
                        {
                            callback(item, need);
                        }

                        item.Consume(need);
                        break;
                    }
                }
            }

            return -1;
        }

        public int ConsumeTotal(Type[] types, int[] amounts)
        {
            return ConsumeTotal(types, amounts, true, null);
        }

        public int ConsumeTotal(Type[] types, int[] amounts, bool recurse)
        {
            return ConsumeTotal(types, amounts, recurse, null);
        }

        public int ConsumeTotal(Type[] types, int[] amounts, bool recurse, OnItemConsumed callback)
        {
            if (types.Length != amounts.Length)
            {
                throw new ArgumentException();
            }

            var items = new Item[types.Length][];
            var totals = new int[types.Length];

            for (int i = 0; i < types.Length; ++i)
            {
                items[i] = FindItemsByType(types[i], recurse);

                for (int j = 0; j < items[i].Length; ++j)
                {
                    totals[i] += items[i][j].Amount;
                }

                if (totals[i] < amounts[i])
                {
                    return i;
                }
            }

            for (int i = 0; i < types.Length; ++i)
            {
                int need = amounts[i];

                for (int j = 0; j < items[i].Length; ++j)
                {
                    Item item = items[i][j];

                    int theirAmount = item.Amount;

                    if (theirAmount < need)
                    {
                        if (callback != null)
                        {
                            callback(item, theirAmount);
                        }

                        item.Delete();
                        need -= theirAmount;
                    }
                    else
                    {
                        if (callback != null)
                        {
                            callback(item, need);
                        }

                        item.Consume(need);
                        break;
                    }
                }
            }

            return -1;
        }

        public bool ConsumeTotal(Type type, int amount)
        {
            return ConsumeTotal(type, amount, true, null);
        }

        public bool ConsumeTotal(Type type, int amount, bool recurse)
        {
            return ConsumeTotal(type, amount, recurse, null);
        }

        public bool ConsumeTotal(Type type, int amount, bool recurse, OnItemConsumed callback)
        {
            var items = FindItemsByType(type, recurse);

            // First pass, compute total
            int total = 0;

            for (int i = 0; i < items.Length; ++i)
            {
                total += items[i].Amount;
            }

            if (total >= amount)
            {
                // We've enough, so consume it

                int need = amount;

                for (int i = 0; i < items.Length; ++i)
                {
                    Item item = items[i];

                    int theirAmount = item.Amount;

                    if (theirAmount < need)
                    {
                        if (callback != null)
                        {
                            callback(item, theirAmount);
                        }

                        item.Delete();
                        need -= theirAmount;
                    }
                    else
                    {
                        if (callback != null)
                        {
                            callback(item, need);
                        }

                        item.Consume(need);

                        return true;
                    }
                }
            }

            return false;
        }

        public int ConsumeUpTo(Type type, int amount)
        {
            return ConsumeUpTo(type, amount, true);
        }

        public int ConsumeUpTo(Type type, int amount, bool recurse)
        {
            int consumed = 0;

            var toDelete = new Queue<Item>();

            RecurseConsumeUpTo(this, type, amount, recurse, ref consumed, toDelete);

            while (toDelete.Count > 0)
            {
                toDelete.Dequeue().Delete();
            }

            return consumed;
        }

        private static void RecurseConsumeUpTo(
            Item current, Type type, int amount, bool recurse, ref int consumed, Queue<Item> toDelete)
        {
            if (current != null && current.Items.Count > 0)
            {
                var list = current.Items;

                for (int i = 0; i < list.Count; ++i)
                {
                    Item item = list[i];

                    if (type.IsAssignableFrom(item.GetType()))
                    {
                        int need = amount - consumed;
                        int theirAmount = item.Amount;

                        if (theirAmount <= need)
                        {
                            toDelete.Enqueue(item);
                            consumed += theirAmount;
                        }
                        else
                        {
                            item.Amount -= need;
                            consumed += need;

                            return;
                        }
                    }
                    else if (recurse && item is Container)
                    {
                        RecurseConsumeUpTo(item, type, amount, recurse, ref consumed, toDelete);
                    }
                }
            }
        }
        #endregion

        #region Get[BestGroup]Amount
        public int GetBestGroupAmount(Type type, bool recurse, CheckItemGroup grouper)
        {
            if (grouper == null)
            {
                throw new ArgumentNullException();
            }

            int best = 0;

            var typedItems = FindItemsByType(type, recurse);

            var groups = new List<List<Item>>();
            int idx = 0;

            while (idx < typedItems.Length)
            {
                Item a = typedItems[idx++];
                var group = new List<Item>();

                group.Add(a);

                while (idx < typedItems.Length)
                {
                    Item b = typedItems[idx];
                    int v = grouper(a, b);

                    if (v == 0)
                    {
                        group.Add(b);
                    }
                    else
                    {
                        break;
                    }

                    ++idx;
                }

                groups.Add(group);
            }

            for (int i = 0; i < groups.Count; ++i)
            {
                var items = groups[i].ToArray();

                //Item[] items = (Item[])(((ArrayList)groups[i]).ToArray( typeof( Item ) ));
                int total = 0;

                for (int j = 0; j < items.Length; ++j)
                {
                    total += items[j].Amount;
                }

                if (total >= best)
                {
                    best = total;
                }
            }

            return best;
        }

        public int GetBestGroupAmount(Type[] types, bool recurse, CheckItemGroup grouper)
        {
            if (grouper == null)
            {
                throw new ArgumentNullException();
            }

            int best = 0;

            var typedItems = FindItemsByType(types, recurse);

            var groups = new List<List<Item>>();
            int idx = 0;

            while (idx < typedItems.Length)
            {
                Item a = typedItems[idx++];
                var group = new List<Item>();

                group.Add(a);

                while (idx < typedItems.Length)
                {
                    Item b = typedItems[idx];
                    int v = grouper(a, b);

                    if (v == 0)
                    {
                        group.Add(b);
                    }
                    else
                    {
                        break;
                    }

                    ++idx;
                }

                groups.Add(group);
            }

            for (int j = 0; j < groups.Count; ++j)
            {
                var items = groups[j].ToArray();
                //Item[] items = (Item[])(((ArrayList)groups[j]).ToArray( typeof( Item ) ));
                int total = 0;

                for (int k = 0; k < items.Length; ++k)
                {
                    total += items[k].Amount;
                }

                if (total >= best)
                {
                    best = total;
                }
            }

            return best;
        }

        public int GetBestGroupAmount(Type[][] types, bool recurse, CheckItemGroup grouper)
        {
            if (grouper == null)
            {
                throw new ArgumentNullException();
            }

            int best = 0;

            for (int i = 0; i < types.Length; ++i)
            {
                var typedItems = FindItemsByType(types[i], recurse);

                var groups = new List<List<Item>>();
                int idx = 0;

                while (idx < typedItems.Length)
                {
                    Item a = typedItems[idx++];
                    var group = new List<Item>();

                    group.Add(a);

                    while (idx < typedItems.Length)
                    {
                        Item b = typedItems[idx];
                        int v = grouper(a, b);

                        if (v == 0)
                        {
                            group.Add(b);
                        }
                        else
                        {
                            break;
                        }

                        ++idx;
                    }

                    groups.Add(group);
                }

                for (int j = 0; j < groups.Count; ++j)
                {
                    var items = groups[j].ToArray();
                    //Item[] items = (Item[])(((ArrayList)groups[j]).ToArray( typeof( Item ) ));
                    int total = 0;

                    for (int k = 0; k < items.Length; ++k)
                    {
                        total += items[k].Amount;
                    }

                    if (total >= best)
                    {
                        best = total;
                    }
                }
            }

            return best;
        }

        public int GetAmount(Type type)
        {
            return GetAmount(type, true);
        }

        public int GetAmount(Type type, bool recurse)
        {
            var items = FindItemsByType(type, recurse);

            int amount = 0;

            for (int i = 0; i < items.Length; ++i)
            {
                amount += items[i].Amount;
            }

            return amount;
        }

        public int GetAmount(Type[] types)
        {
            return GetAmount(types, true);
        }

        public int GetAmount(Type[] types, bool recurse)
        {
            var items = FindItemsByType(types, recurse);

            int amount = 0;

            for (int i = 0; i < items.Length; ++i)
            {
                amount += items[i].Amount;
            }

            return amount;
        }
        #endregion

        private static readonly List<Item> m_FindItemsList = new List<Item>();

        #region Non-Generic FindItem[s] by Type
        public Item[] FindItemsByType(Type type)
        {
            return FindItemsByType(type, true);
        }

        public Item[] FindItemsByType(Type type, bool recurse)
        {
            if (m_FindItemsList.Count > 0)
            {
                m_FindItemsList.Clear();
            }

            RecurseFindItemsByType(this, type, recurse, m_FindItemsList);

            return m_FindItemsList.ToArray();
        }

        private static void RecurseFindItemsByType(Item current, Type type, bool recurse, List<Item> list)
        {
            if (current != null && current.Items.Count > 0)
            {
                var items = current.Items;

                for (int i = 0; i < items.Count; ++i)
                {
                    Item item = items[i];

                    if (type.IsAssignableFrom(item.GetType())) // item.GetType().IsAssignableFrom( type ) )
                    {
                        list.Add(item);
                    }

                    if (recurse && item is Container)
                    {
                        RecurseFindItemsByType(item, type, recurse, list);
                    }
                }
            }
        }

        public Item[] FindItemsByType(Type[] types)
        {
            return FindItemsByType(types, true);
        }

        public Item[] FindItemsByType(Type[] types, bool recurse)
        {
            if (m_FindItemsList.Count > 0)
            {
                m_FindItemsList.Clear();
            }

            RecurseFindItemsByType(this, types, recurse, m_FindItemsList);

            return m_FindItemsList.ToArray();
        }

        private static void RecurseFindItemsByType(Item current, Type[] types, bool recurse, List<Item> list)
        {
            if (current != null && current.Items.Count > 0)
            {
                var items = current.Items;

                for (int i = 0; i < items.Count; ++i)
                {
                    Item item = items[i];

                    if (InTypeList(item, types))
                    {
                        list.Add(item);
                    }

                    if (recurse && item is Container)
                    {
                        RecurseFindItemsByType(item, types, recurse, list);
                    }
                }
            }
        }

        public Item FindItemByType(Type type)
        {
            return FindItemByType(type, true);
        }

        public Item FindItemByType(Type type, bool recurse)
        {
            return RecurseFindItemByType(this, type, recurse);
        }

        private static Item RecurseFindItemByType(Item current, Type type, bool recurse)
        {
            if (current != null && current.Items.Count > 0)
            {
                var list = current.Items;

                for (int i = 0; i < list.Count; ++i)
                {
                    Item item = list[i];

                    if (type.IsAssignableFrom(item.GetType()))
                    {
                        return item;
                    }
                    else if (recurse && item is Container)
                    {
                        Item check = RecurseFindItemByType(item, type, recurse);

                        if (check != null)
                        {
                            return check;
                        }
                    }
                }
            }

            return null;
        }

        public Item FindItemByType(Type[] types)
        {
            return FindItemByType(types, true);
        }

        public Item FindItemByType(Type[] types, bool recurse)
        {
            return RecurseFindItemByType(this, types, recurse);
        }

        private static Item RecurseFindItemByType(Item current, Type[] types, bool recurse)
        {
            if (current != null && current.Items.Count > 0)
            {
                var list = current.Items;

                for (int i = 0; i < list.Count; ++i)
                {
                    Item item = list[i];

                    if (InTypeList(item, types))
                    {
                        return item;
                    }
                    else if (recurse && item is Container)
                    {
                        Item check = RecurseFindItemByType(item, types, recurse);

                        if (check != null)
                        {
                            return check;
                        }
                    }
                }
            }

            return null;
        }
        #endregion

        #region Generic FindItem[s] by Type
        public List<T> FindItemsByType<T>() where T : Item
        {
            return FindItemsByType<T>(true, null);
        }

        public List<T> FindItemsByType<T>(bool recurse) where T : Item
        {
            return FindItemsByType<T>(recurse, null);
        }

        public List<T> FindItemsByType<T>(Predicate<T> predicate) where T : Item
        {
            return FindItemsByType(true, predicate);
        }

        public List<T> FindItemsByType<T>(bool recurse, Predicate<T> predicate) where T : Item
        {
            if (m_FindItemsList.Count > 0)
            {
                m_FindItemsList.Clear();
            }

            var list = new List<T>();

            RecurseFindItemsByType(this, recurse, list, predicate);

            return list;
        }

        private static void RecurseFindItemsByType<T>(Item current, bool recurse, List<T> list, Predicate<T> predicate)
            where T : Item
        {
            if (current != null && current.Items.Count > 0)
            {
                var items = current.Items;

                for (int i = 0; i < items.Count; ++i)
                {
                    Item item = items[i];

                    if (typeof(T).IsAssignableFrom(item.GetType()))
                    {
                        T typedItem = (T)item;

                        if (predicate == null || predicate(typedItem))
                        {
                            list.Add(typedItem);
                        }
                    }

                    if (recurse && item is Container)
                    {
                        RecurseFindItemsByType(item, recurse, list, predicate);
                    }
                }
            }
        }

        public T FindItemByType<T>() where T : Item
        {
            return FindItemByType<T>(true);
        }

        public T FindItemByType<T>(Predicate<T> predicate) where T : Item
        {
            return FindItemByType(true, predicate);
        }

        public T FindItemByType<T>(bool recurse) where T : Item
        {
            return FindItemByType<T>(recurse, null);
        }

        public T FindItemByType<T>(bool recurse, Predicate<T> predicate) where T : Item
        {
            return RecurseFindItemByType(this, recurse, predicate);
        }

        private static T RecurseFindItemByType<T>(Item current, bool recurse, Predicate<T> predicate) where T : Item
        {
            if (current != null && current.Items.Count > 0)
            {
                var list = current.Items;

                for (int i = 0; i < list.Count; ++i)
                {
                    Item item = list[i];

                    if (typeof(T).IsAssignableFrom(item.GetType()))
                    {
                        T typedItem = (T)item;

                        if (predicate == null || predicate(typedItem))
                        {
                            return typedItem;
                        }
                    }
                    else if (recurse && item is Container)
                    {
                        T check = RecurseFindItemByType(item, recurse, predicate);

                        if (check != null)
                        {
                            return check;
                        }
                    }
                }
            }

            return null;
        }
        #endregion

        private static bool InTypeList(Item item, Type[] types)
        {
            Type t = item.GetType();

            for (int i = 0; i < types.Length; ++i)
            {
                if (types[i].IsAssignableFrom(t))
                {
                    return true;
                }
            }

            return false;
        }

        private static void SetSaveFlag(ref SaveFlag flags, SaveFlag toSet, bool setIf)
        {
            if (setIf)
            {
                flags |= toSet;
            }
        }

        private static bool GetSaveFlag(SaveFlag flags, SaveFlag toGet)
        {
            return ((flags & toGet) != 0);
        }

        [Flags]
        private enum SaveFlag : byte
        {
            None = 0x00000000,
            MaxItems = 0x00000001,
            GumpID = 0x00000002,
            DropSound = 0x00000004,
            LiftOverride = 0x00000008,
            GridPositions = 0x00000010
        }

        public override void Serialize(GenericWriter writer)
        {
            base.Serialize(writer);

            writer.Write(3); // version

            SaveFlag flags = SaveFlag.None;

            SetSaveFlag(ref flags, SaveFlag.MaxItems, m_MaxItems != -1);
            SetSaveFlag(ref flags, SaveFlag.GumpID, m_GumpID != -1);
            SetSaveFlag(ref flags, SaveFlag.DropSound, m_DropSound != -1);
            SetSaveFlag(ref flags, SaveFlag.LiftOverride, m_LiftOverride);

            writer.Write((byte)flags);

            /*if (GetSaveFlag(flags, SaveFlag.GridPositions))
            {
                writer.Write(_Positions.Count);

                foreach (var kvp in _Positions)
                {
                    writer.Write(kvp.Key);
                    writer.Write(kvp.Value);
                }
            }*/

            if (GetSaveFlag(flags, SaveFlag.MaxItems))
            {
                writer.WriteEncodedInt(m_MaxItems);
            }

            if (GetSaveFlag(flags, SaveFlag.GumpID))
            {
                writer.WriteEncodedInt(m_GumpID);
            }

            if (GetSaveFlag(flags, SaveFlag.DropSound))
            {
                writer.WriteEncodedInt(m_DropSound);
            }
        }

        public override void Deserialize(GenericReader reader)
        {
            base.Deserialize(reader);

            int version = reader.ReadInt();

            switch (version)
            {
                case 3:
                case 2:
                    {
                        SaveFlag flags = (SaveFlag)reader.ReadByte();

                        if (GetSaveFlag(flags, SaveFlag.MaxItems))
                        {
                            m_MaxItems = reader.ReadEncodedInt();
                        }
                        else
                        {
                            m_MaxItems = -1;
                        }

                        if (GetSaveFlag(flags, SaveFlag.GumpID))
                        {
                            m_GumpID = reader.ReadEncodedInt();
                        }
                        else
                        {
                            m_GumpID = -1;
                        }

                        if (GetSaveFlag(flags, SaveFlag.DropSound))
                        {
                            m_DropSound = reader.ReadEncodedInt();
                        }
                        else
                        {
                            m_DropSound = -1;
                        }

                        m_LiftOverride = GetSaveFlag(flags, SaveFlag.LiftOverride);

                        break;
                    }
                case 1:
                    {
                        m_MaxItems = reader.ReadInt();
                        goto case 0;
                    }
                case 0:
                    {
                        if (version < 1)
                        {
                            m_MaxItems = m_GlobalMaxItems;
                        }

                        m_GumpID = reader.ReadInt();
                        m_DropSound = reader.ReadInt();

                        if (m_GumpID == DefaultGumpID)
                        {
                            m_GumpID = -1;
                        }

                        if (m_DropSound == DefaultDropSound)
                        {
                            m_DropSound = -1;
                        }

                        if (m_MaxItems == DefaultMaxItems)
                        {
                            m_MaxItems = -1;
                        }

                        //m_Bounds = new Rectangle2D( reader.ReadPoint2D(), reader.ReadPoint2D() );
                        reader.ReadPoint2D();
                        reader.ReadPoint2D();

                        break;
                    }
            }

            UpdateContainerData();
        }

        private static int m_GlobalMaxItems = 125;
        private static int m_GlobalMaxWeight = 400;

        public static int GlobalMaxItems { get { return m_GlobalMaxItems; } set { m_GlobalMaxItems = value; } }
        public static int GlobalMaxWeight { get { return m_GlobalMaxWeight; } set { m_GlobalMaxWeight = value; } }

        public Container(int itemID)
            : base(itemID)
        {
            m_GumpID = -1;
            m_DropSound = -1;
            m_MaxItems = -1;

            UpdateContainerData();
        }

        public override int GetTotal(TotalType type)
        {
            switch (type)
            {
                case TotalType.Gold:
                    return m_TotalGold;

                case TotalType.Items:
                    return m_TotalItems;

                case TotalType.Weight:
                    return m_TotalWeight;
            }

            return base.GetTotal(type);
        }

        public override void UpdateTotal(Item sender, TotalType type, int delta)
        {
            if (sender != this && delta != 0 && !sender.IsVirtualItem)
            {
                switch (type)
                {
                    case TotalType.Gold:
                        m_TotalGold += delta;
                        break;

                    case TotalType.Items:
                        m_TotalItems += delta;
                        InvalidateProperties();
                        break;

                    case TotalType.Weight:
                        m_TotalWeight += delta;
                        InvalidateProperties();
                        break;
                }
            }

            base.UpdateTotal(sender, type, delta);
        }

        public override void UpdateTotals()
        {
            m_TotalGold = 0;
            m_TotalItems = 0;
            m_TotalWeight = 0;

            var items = m_Items;

            if (items == null)
            {
                return;
            }

            for (int i = 0; i < items.Count; ++i)
            {
                Item item = items[i];

                item.UpdateTotals();

                if (item.IsVirtualItem)
                {
                    continue;
                }

                m_TotalGold += item.TotalGold;
                m_TotalItems += item.TotalItems + 1;
                m_TotalWeight += item.TotalWeight + item.PileWeight;
            }
        }

        public Container(Serial serial)
            : base(serial)
        { }

        public virtual bool OnStackAttempt(Mobile from, Item stack, Item dropped)
        {
            if (!CheckHold(from, dropped, true, false))
            {
                return false;
            }

            return stack.StackWith(from, dropped);
        }

        public override bool OnDragDrop(Mobile from, Item dropped)
        {
            if (TryDropItem(from, dropped, true))
            {
                from.SendSound(GetDroppedSound(dropped), GetWorldLocation());

                return true;
            }
            else
            {
                return false;
            }
        }

        public virtual bool TryDropItem(Mobile from, Item dropped, bool sendFullMessage)
        {
			if (!CheckStack(from, dropped) && !CheckHold(from, dropped, sendFullMessage, true))
            {
                return false;
            }

            var list = Items;

            for (int i = 0; i < list.Count; ++i)
            {
                Item item = list[i];

                if (!(item is Container) && item.StackWith(from, dropped, false))
                {
                    return true;
                }
            }

            DropItem(dropped);

            return true;
        }

        public override void AddItem(Item item)
        {
            ValidateGridLocation(item);

            base.AddItem(item);
        }

        public virtual void Destroy()
        {
            Point3D loc = GetWorldLocation();
            Map map = Map;

            for (int i = Items.Count - 1; i >= 0; --i)
            {
                if (i < Items.Count)
                {
                    Items[i].SetLastMoved();
                    Items[i].MoveToWorld(loc, map);
                }
            }

            Delete();
        }

        public virtual void DropItem(Item dropped)
        {
            if (dropped == null)
            {
                return;
            }

            AddItem(dropped);

            Rectangle2D bounds = dropped.GetGraphicBounds();
            Rectangle2D ourBounds = Bounds;

            int x, y;

            if (bounds.Width >= ourBounds.Width)
            {
                x = (ourBounds.Width - bounds.Width) / 2;
            }
            else
            {
                x = Utility.Random(ourBounds.Width - bounds.Width);
            }

            if (bounds.Height >= ourBounds.Height)
            {
                y = (ourBounds.Height - bounds.Height) / 2;
            }
            else
            {
                y = Utility.Random(ourBounds.Height - bounds.Height);
            }

            x += ourBounds.X;
            x -= bounds.X;

            y += ourBounds.Y;
            y -= bounds.Y;

            dropped.Location = new Point3D(x, y, 0);
        }

        public override void OnDoubleClickSecureTrade(Mobile from)
        {
            if (from.InRange(GetWorldLocation(), 2))
            {
                DisplayTo(from);

                SecureTradeContainer cont = GetSecureTradeCont();

                if (cont != null)
                {
                    SecureTrade trade = cont.Trade;

                    if (trade != null && trade.From.Mobile == from)
                    {
                        DisplayTo(trade.To.Mobile);
                    }
                    else if (trade != null && trade.To.Mobile == from)
                    {
                        DisplayTo(trade.From.Mobile);
                    }
                }
            }
            else
            {
                from.SendLocalizedMessage(500446); // That is too far away.
            }
        }

        public virtual bool DisplaysContent { get { return true; } }

        public virtual bool CheckContentDisplay(Mobile from)
        {
            if (!DisplaysContent)
            {
                return false;
            }

			var root = RootParent;

            if (root == null || root is Item || root == from || from.IsStaff())
            {
                return true;
            }

            return false;
        }

        public override void OnSingleClick(Mobile from)
        {
            base.OnSingleClick(from);

            if (CheckContentDisplay(from))
            {
                LabelTo(from, "({0} items, {1} stones)", TotalItems, TotalWeight);
            }
        }

		public List<Mobile> Openers { get; set; }

        public virtual bool IsPublicContainer { get { return false; } }

        public override void OnDelete()
        {
            base.OnDelete();

			Openers = null;
        }

        public virtual void DisplayTo(Mobile to)
        {
			ProcessOpeners(to);

            NetState ns = to.NetState;

            if (ns == null)
            {
                return;
            }

            ValidatePositions();

            if (ns.HighSeas)
            {
                to.Send(new ContainerDisplayHS(this));
            }
            else
            {
                to.Send(new ContainerDisplay(this));
            }

            if (ns.ContainerGridLines)
            {
                to.Send(new ContainerContent6017(to, this));
            }
            else
            {
                to.Send(new ContainerContent(to, this));
            }

			if (to.ViewOPL)
            {
                var items = Items;

				foreach (var o in items)
                {
					to.Send(o.OPLPacket);
                }
            }
        }

        public void ProcessOpeners(Mobile opener)
        {
            if (!IsPublicContainer)
            {
                bool contains = false;

				if (Openers != null)
                {
                    Point3D worldLoc = GetWorldLocation();
                    Map map = Map;

					for (int i = 0; i < Openers.Count; ++i)
                    {
						Mobile mob = Openers[i];

                        if (mob == opener)
                        {
                            contains = true;
                        }
                        else
                        {
                            int range = GetUpdateRange(mob);

                            if (mob.Map != map || !mob.InRange(worldLoc, range))
                            {
								Openers.RemoveAt(i--);
                            }
                        }
                    }
                }

                if (!contains)
                {
					if (Openers == null)
                    {
						Openers = new List<Mobile>();
                    }

					Openers.Add(opener);
                }
				else if (Openers != null && Openers.Count == 0)
                {
					Openers = null;
                }
            }
        }

        public override void GetProperties(ObjectPropertyList list)
        {
            base.GetProperties(list);

            if (DisplaysContent) //CheckContentDisplay( from ) )
            {
                if (Core.ML)
                {
                    if (ParentsContain<Item>() || IsLockedDown || IsSecure) //Root Parent is the Mobile.  Parent could be another containter.
                    {
                        list.Add(1073841, "{0}\t{1}\t{2}", TotalItems, MaxItems, TotalWeight);
                        // Contents: ~1_COUNT~/~2_MAXCOUNT~ items, ~3_WEIGHT~ stones
                    }
                    else
                    {
                        list.Add(1072241, "{0}\t{1}\t{2}\t{3}", TotalItems, MaxItems, TotalWeight, MaxWeight);
                        // Contents: ~1_COUNT~/~2_MAXCOUNT~ items, ~3_WEIGHT~/~4_MAXWEIGHT~ stones
                    }

                    //TODO: Where do the other clilocs come into play? 1073839 & 1073840?
                }
                else
                {
                    list.Add(1050044, "{0}\t{1}", TotalItems, TotalWeight); // ~1_COUNT~ items, ~2_WEIGHT~ stones
                }
            }
        }

        public override void OnDoubleClick(Mobile from)
        {
            if (from.IsStaff() || from.InRange(GetWorldLocation(), 2))
            {
                DisplayTo(from);
            }
            else
            {
                from.SendLocalizedMessage(500446); // That is too far away.
            }
        }
    }

    public class ContainerData
    {
        static ContainerData()
        {
            m_Table = new Dictionary<int, ContainerData>();

            string path = Path.Combine(Core.BaseDirectory, "Data/containers.cfg");

            if (!File.Exists(path))
            {
                m_Default = new ContainerData(0x3C, new Rectangle2D(44, 65, 142, 94), 0x48);
                return;
            }

            using (StreamReader reader = new StreamReader(path))
            {
                string line;

                while ((line = reader.ReadLine()) != null)
                {
                    line = line.Trim();

                    if (line.Length == 0 || line.StartsWith("#"))
                    {
                        continue;
                    }

                    try
                    {
                        var split = line.Split('\t');

                        if (split.Length >= 3)
                        {
                            int gumpID = Utility.ToInt32(split[0]);

                            var aRect = split[1].Split(' ');
                            if (aRect.Length < 4)
                            {
                                continue;
                            }

                            int x = Utility.ToInt32(aRect[0]);
                            int y = Utility.ToInt32(aRect[1]);
                            int width = Utility.ToInt32(aRect[2]);
                            int height = Utility.ToInt32(aRect[3]);

                            Rectangle2D bounds = new Rectangle2D(x, y, width, height);

                            int dropSound = Utility.ToInt32(split[2]);

                            ContainerData data = new ContainerData(gumpID, bounds, dropSound);

                            if (m_Default == null)
                            {
                                m_Default = data;
                            }

                            if (split.Length >= 4)
                            {
                                var aIDs = split[3].Split(',');

                                for (int i = 0; i < aIDs.Length; i++)
                                {
                                    int id = Utility.ToInt32(aIDs[i]);

                                    if (m_Table.ContainsKey(id))
                                    {
                                        Console.WriteLine(@"Warning: double ItemID entry in Data\containers.cfg");
                                    }
                                    else
                                    {
                                        m_Table[id] = data;
                                    }
                                }
                            }
                        }
                    }
                    catch
                    { }
                }
            }

            if (m_Default == null)
            {
                m_Default = new ContainerData(0x3C, new Rectangle2D(44, 65, 142, 94), 0x48);
            }
        }

        private static ContainerData m_Default;
        private static readonly Dictionary<int, ContainerData> m_Table;

        public static ContainerData Default { get { return m_Default; } set { m_Default = value; } }

        public static ContainerData GetData(int itemID)
        {
            ContainerData data = null;
            m_Table.TryGetValue(itemID, out data);

            if (data != null)
            {
                return data;
            }
            else
            {
                return m_Default;
            }
        }

        private readonly int m_GumpID;
        private readonly Rectangle2D m_Bounds;
        private readonly int m_DropSound;

        public int GumpID { get { return m_GumpID; } }
        public Rectangle2D Bounds { get { return m_Bounds; } }
        public int DropSound { get { return m_DropSound; } }

        public ContainerData(int gumpID, Rectangle2D bounds, int dropSound)
        {
            m_GumpID = gumpID;
            m_Bounds = bounds;
            m_DropSound = dropSound;
        }
    }
}
