context: /***************************************************************************
   *                               Container.cs
   *                            -------------------
   *   begin                : May 1, 2002
   *   copyright            : (C) The RunUO Software Team
   *   email                : info@runuo.com
   *
   *   $Id: Container.cs 564 2010-10-18 04:56:28Z asayre $
   *
   ***************************************************************************/
  
  /***************************************************************************
   *
   *   This program is free software; you can redistribute it and/or modify
   *   it under the terms of the GNU General Public License as published by
   *   the Free Software Foundation; either version 2 of the License, or
   *   (at your option) any later version.
   *
   ***************************************************************************/
  
  using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.IO;
  using Server.Network;
  
  namespace Server.Items
  {
  	public delegate void OnItemConsumed( Item item, int amount );
  	public delegate int CheckItemGroup( Item a, Item b );
  
  	public delegate void ContainerSnoopHandler( Container cont, Mobile from );
  
  	public class Container : Item
  	{
  		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 = this.ItemID;
  
  				base.ItemID = value;
  
  				if ( this.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()
  		{
  			this.ContainerData = ContainerData.GetData( this.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.AccessLevel < AccessLevel.GameMaster && 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.AccessLevel < AccessLevel.GameMaster )
  			{
  				if ( IsDecoContainer )
  				{
  					if ( message )
  						SendCantStoreMessage( m, item );
  
  					return false;
  				}
  
  				int maxItems = this.MaxItems;
  
  				if ( checkItems && maxItems != 0 && (this.TotalItems + plusItems + item.TotalItems + (item.IsVirtualItem ? 0 : 1)) > maxItems )
  				{
  					if ( message )
  						SendFullItemsMessage( m, item );
  
  					return false;
  				}
  				else
  				{
  					int maxWeight = this.MaxWeight;
  
  					if ( maxWeight != 0 && (this.TotalWeight + plusWeight + item.TotalWeight + item.PileWeight) > maxWeight )
  					{
  						if ( message )
  							SendFullWeightMessage( m, item );
  
  						return false;
  					}
  				}
  			}
  
  			object parent = this.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 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 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 )
  		{
  			if ( grouper == null )
  				throw new ArgumentNullException();
  
  			Item[] typedItems = FindItemsByType( type, recurse );
  
  			List<List<Item>> groups = new List<List<Item>>();
  			int idx = 0;
  
  			while ( idx < typedItems.Length )
  			{
  				Item a = typedItems[idx++];
  				List<Item> 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 );
  			}
  
  			Item[][] items = new Item[groups.Count][];
  			int[] 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 )
  		{
  			if ( types.Length != amounts.Length )
  				throw new ArgumentException();
  			else if ( grouper == null )
  				throw new ArgumentNullException();
  
  			Item[][][] items = new Item[types.Length][][];
  			int[][] totals = new int[types.Length][];
  
  			for ( int i = 0; i < types.Length; ++i )
  			{
  				Item[] typedItems = FindItemsByType( types[i], recurse );
  
  				List<List<Item>> groups = new List<List<Item>>();
  				int idx = 0;
  
  				while ( idx < typedItems.Length )
  				{
  					Item a = typedItems[idx++];
  					List<Item> 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 );
  				}
  
  				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 )
  		{
  			if ( types.Length != amounts.Length )
  				throw new ArgumentException();
  			else if ( grouper == null )
  				throw new ArgumentNullException();
  
  			Item[][][] items = new Item[types.Length][][];
  			int[][] totals = new int[types.Length][];
  
  			for ( int i = 0; i < types.Length; ++i )
  			{
  				Item[] typedItems = FindItemsByType( types[i], recurse );
  
  				List<List<Item>> groups = new List<List<Item>>();
  				int idx = 0;
  
  				while ( idx < typedItems.Length )
  				{
  					Item a = typedItems[idx++];
  					List<Item> 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 );
  				}
  
  				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();
  
  			Item[][] items = new Item[types.Length][];
  			int[] 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();
  
  			Item[][] items = new Item[types.Length][];
  			int[] 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 )
  		{
  			Item[] 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;
  
  			Queue<Item> 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 )
  			{
  				List<Item> 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;
  
  			Item[] typedItems = FindItemsByType( type, recurse );
  
  			List<List<Item>> groups = new List<List<Item>>();
  			int idx = 0;
  
  			while( idx < typedItems.Length )
  			{
  				Item a = typedItems[idx++];
  				List<Item> 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 )
  			{
  				Item[] 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;
  
  			Item[] typedItems = FindItemsByType( types, recurse );
  
  			List<List<Item>> groups = new List<List<Item>>();
  			int idx = 0;
  
  			while( idx < typedItems.Length )
  			{
  				Item a = typedItems[idx++];
  
gt: 				List<Item> group = new List<Item>();