context: using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Globalization;
  using Server;
  using Server.Items;
  using Server.Mobiles;
  using Server.Gumps;
  using Server.Prompts;
  using Server.Targeting;
  using Server.Misc;
  using Server.Multis;
  using Server.ContextMenus;
  
  namespace Server.Mobiles
  {
  	[AttributeUsage( AttributeTargets.Class )]
  	public class PlayerVendorTargetAttribute : Attribute
  	{
  		public PlayerVendorTargetAttribute()
  		{
  		}
  	}
  
  	public class VendorItem
  	{
  		private Item m_Item;
  		private int m_Price;
  		private string m_Description;
  		private DateTime m_Created;
  
  		private bool m_Valid;
  
  		public Item Item{ get{ return m_Item; } }
  		public int Price{ get{ return m_Price; } }
  
  		public string FormattedPrice
  		{
  			get
  			{
  				if ( Core.ML )
  					return m_Price.ToString( "N0", CultureInfo.GetCultureInfo( "en-US" ) );
  
  				return m_Price.ToString();
  			}
  		}
  
  		public string Description
  		{
  			get{ return m_Description; }
  			set
  			{
  				if ( value != null )
  					m_Description = value;
  				else
  					m_Description = "";
  
  				if ( Valid )
  					Item.InvalidateProperties();
  			}
  		}
  
  		public DateTime Created{ get{ return m_Created; } }
  
  		public bool IsForSale{ get{ return Price >= 0; } }
  		public bool IsForFree{ get{ return Price == 0; } }
  
  		public bool Valid{ get{ return m_Valid; } }
  
  		public VendorItem( Item item, int price, string description, DateTime created )
  		{
  			m_Item = item;
  			m_Price = price;
  
  			if ( description != null )
  				m_Description = description;
  			else
  				m_Description = "";
  
  			m_Created = created;
  
  			m_Valid = true;
  		}
  
  		public void Invalidate()
  		{
  			m_Valid = false;
  		}
  	}
  
  	public class VendorBackpack : Backpack
  	{
  		public VendorBackpack()
  		{
  			Layer = Layer.Backpack;
  			Weight = 1.0;
  		}
  
  		public override int DefaultMaxWeight{ get{ return 0; } }
  
  		public override bool CheckHold( Mobile m, Item item, bool message, bool checkItems, int plusItems, int plusWeight )
  		{
  			if ( !base.CheckHold( m, item, message, checkItems, plusItems, plusWeight ) )
  				return false;
  
  			if ( Ethics.Ethic.IsImbued( item, true ) )
  			{
  				if ( message )
  					m.SendMessage( "Imbued items may not be sold here." );
  
  				return false;
  			}
  
  			if ( !BaseHouse.NewVendorSystem && Parent is PlayerVendor )
  			{
  				BaseHouse house = ((PlayerVendor)Parent).House;
  
  				if ( house != null && house.IsAosRules && !house.CheckAosStorage( 1 + item.TotalItems + plusItems ) )
  				{
  					if ( message )
  						m.SendLocalizedMessage( 1061839 ); // This action would exceed the secure storage limit of the house.
  
  					return false;
  				}
  			}
  
  			return true;
  		}
  
  		public override bool IsAccessibleTo( Mobile m )
  		{
  			return true;
  		}
  
  		public override bool CheckItemUse( Mobile from, Item item )
  		{
  			if ( !base.CheckItemUse( from, item ) )
  				return false;
  
  			if ( item is Container || item is Engines.BulkOrders.BulkOrderBook )
  				return true;
  
  			from.SendLocalizedMessage( 500447 ); // That is not accessible.
  			return false;
  		}
  
  		public override bool CheckTarget( Mobile from, Target targ, object targeted )
  		{
  			if ( !base.CheckTarget( from, targ, targeted ) )
  				return false;
  
  			if ( from.AccessLevel >= AccessLevel.GameMaster )
  				return true;
  
  			return targ.GetType().IsDefined( typeof( PlayerVendorTargetAttribute ), false );
  		}
  
  		public override void GetChildContextMenuEntries( Mobile from, List<ContextMenuEntry> list, Item item )
  		{
  			base.GetChildContextMenuEntries( from, list, item );
  
  			PlayerVendor pv = RootParent as PlayerVendor;
  
  			if ( pv == null || pv.IsOwner( from ) )
  				return;
  
  			VendorItem vi = pv.GetVendorItem( item );
  
  			if ( vi != null )
  				list.Add( new BuyEntry( item ) );
  		}
  
  		private class BuyEntry : ContextMenuEntry
  		{
  			private Item m_Item;
  
  			public BuyEntry( Item item ) : base( 6103 )
  			{
  				m_Item = item;
  			}
  
  			public override bool NonLocalUse{ get{ return true; } }
  
  			public override void OnClick()
  			{
  				if ( m_Item.Deleted )
  					return;
  
  				PlayerVendor.TryToBuy( m_Item, Owner.From );
  			}
  		}
  
  		public override void GetChildNameProperties( ObjectPropertyList list, Item item )
  		{
  			base.GetChildNameProperties( list, item );
  
  			PlayerVendor pv = RootParent as PlayerVendor;
  
  			if ( pv == null )
  				return;
  
  			VendorItem vi = pv.GetVendorItem( item );
  
  			if ( vi == null )
  				return;
  
  			if ( !vi.IsForSale )
  				list.Add( 1043307 ); // Price: Not for sale.
  			else if ( vi.IsForFree )
  				list.Add( 1043306 ); // Price: FREE!
  			else
  				list.Add( 1043304, vi.FormattedPrice ); // Price: ~1_COST~
  		}
  
  		public override void GetChildProperties( ObjectPropertyList list, Item item )
  		{
  			base.GetChildProperties( list, item );
  
  			PlayerVendor pv = RootParent as PlayerVendor;
  
  			if ( pv == null )
  				return;
  
  			VendorItem vi = pv.GetVendorItem( item );
  
  			if ( vi != null && vi.Description != null && vi.Description.Length > 0 )
  				list.Add( 1043305, vi.Description ); // <br>Seller's Description:<br>"~1_DESC~"
  		}
  
  		public override void OnSingleClickContained( Mobile from, Item item )
  		{
  			if ( RootParent is PlayerVendor )
  			{
  				PlayerVendor vendor = (PlayerVendor)RootParent;
  
  				VendorItem vi = vendor.GetVendorItem( item );
  
  				if ( vi != null )
  				{
  					if ( !vi.IsForSale )
  						item.LabelTo( from, 1043307 ); // Price: Not for sale.
  					else if ( vi.IsForFree )
  						item.LabelTo( from, 1043306 ); // Price: FREE!
  					else
  						item.LabelTo( from, 1043304, vi.FormattedPrice ); // Price: ~1_COST~
  
  					if ( !String.IsNullOrEmpty( vi.Description ) )
  					{
  						// The localized message (1043305) is no longer valid - <br>Seller's Description:<br>"~1_DESC~"
  						item.LabelTo( from, "Description: {0}", vi.Description );
  					}
  				}
  			}
  
  			base.OnSingleClickContained( from, item );
  		}
  
  		public VendorBackpack( Serial serial ) : base( serial )
  		{
  		}
  
  		public override void Serialize( GenericWriter writer )
  		{
  			base.Serialize( writer );
  
  			writer.Write( (int) 0 ); // version
  		}
  
  		public override void Deserialize( GenericReader reader )
  		{
  			base.Deserialize( reader );
  
  			int version = reader.ReadInt();
  		}
  	}
  
  	public class PlayerVendor : Mobile
  	{
  		private Hashtable m_SellItems;
  
  		private Mobile m_Owner;
  		private BaseHouse m_House;
  
  		private int m_BankAccount;
  		private int m_HoldGold;
  
  		private string m_ShopName;
  
  		private Timer m_PayTimer;
  		private DateTime m_NextPayTime;
  
  		private PlayerVendorPlaceholder m_Placeholder;
  
  		public PlayerVendor( Mobile owner, BaseHouse house )
  		{
  			Owner = owner;
  			House = house;
  
  			if ( BaseHouse.NewVendorSystem )
  			{
  				m_BankAccount = 0;
  				m_HoldGold = 4;
  			}
  			else
  			{
  				m_BankAccount = 1000;
  				m_HoldGold = 0;
  			}
  
  			ShopName = "Shop Not Yet Named";
  
  			m_SellItems = new Hashtable();
  
  			CantWalk = true;
  
  			if ( !Core.AOS )
  				NameHue = 0x35;
  
  			InitStats( 100, 100, 25 );
  			InitBody();
  			InitOutfit();
  
  			TimeSpan delay = PayTimer.GetInterval();
  
  			m_PayTimer = new PayTimer( this, delay );
  			m_PayTimer.Start();
  
  			m_NextPayTime = DateTime.Now + delay;
  		}
  
  		public PlayerVendor( Serial serial ) : base( serial )
  		{
  		}
  
  		public override void Serialize( GenericWriter writer )
  		{
  			base.Serialize( writer );
  
  			writer.Write( (int) 2 ); // version
  
  			writer.Write( (bool) BaseHouse.NewVendorSystem );
  			writer.Write( (string) m_ShopName );
  			writer.WriteDeltaTime( (DateTime) m_NextPayTime );
  			writer.Write( (Item) House );
  
  			writer.Write( (Mobile) m_Owner );
  			writer.Write( (int) m_BankAccount );
  			writer.Write( (int) m_HoldGold );
  
  			writer.Write( (int) m_SellItems.Count );
  			foreach ( VendorItem vi in m_SellItems.Values )
  			{
  				writer.Write( (Item) vi.Item );
  				writer.Write( (int) vi.Price );
  				writer.Write( (string) vi.Description );
  
  				writer.Write( (DateTime) vi.Created );
  			}
  		}
  
  		public override void Deserialize( GenericReader reader )
  		{
  			base.Deserialize( reader );
  
  			int version = reader.ReadInt();
  
  			bool newVendorSystem = false;
  
  			switch ( version )
  			{
  				case 2:
  				case 1:
  				{
  					newVendorSystem = reader.ReadBool();
  					m_ShopName = reader.ReadString();
  					m_NextPayTime = reader.ReadDeltaTime();
  					House = (BaseHouse) reader.ReadItem();
  
  					goto case 0;
  				}
  				case 0:
  				{
  					m_Owner = reader.ReadMobile();
  					m_BankAccount = reader.ReadInt();
  					m_HoldGold = reader.ReadInt();
  
  					m_SellItems = new Hashtable();
  
  					int count = reader.ReadInt();
  					for ( int i = 0; i < count; i++ )
  					{
  						Item item = reader.ReadItem();
  
  						int price = reader.ReadInt();
  						if ( price > 100000000 )
  							price = 100000000;
  
  						string description = reader.ReadString();
  
  						DateTime created = version < 1 ? DateTime.Now : reader.ReadDateTime();
  
  						if ( item != null )
  						{
  							SetVendorItem( item, version < 1 && price <= 0 ? -1 : price, description, created );
  						}
  					}
  
  					break;	
  				}
  			}
  
  			bool newVendorSystemActivated = BaseHouse.NewVendorSystem && !newVendorSystem;
  
  			if ( version < 1 || newVendorSystemActivated )
  			{
  				if ( version < 1 )
  				{
  					m_ShopName = "Shop Not Yet Named";
  					Timer.DelayCall( TimeSpan.Zero, new TimerStateCallback( UpgradeFromVersion0 ), newVendorSystemActivated );
  				}
  				else
  				{
  					Timer.DelayCall( TimeSpan.Zero, new TimerCallback( FixDresswear ) );
  				}
  
  				m_NextPayTime = DateTime.Now + PayTimer.GetInterval();
  
  				if ( newVendorSystemActivated )
  				{
  					m_HoldGold += m_BankAccount;
  					m_BankAccount = 0;
  				}
  			}
  
  			if ( version < 2 && RawStr == 75 && RawDex == 75 && RawInt == 75 )
  				InitStats( 100, 100, 25 );
  
  			TimeSpan delay = m_NextPayTime - DateTime.Now;
  
  			m_PayTimer = new PayTimer( this, delay > TimeSpan.Zero ? delay : TimeSpan.Zero );
  			m_PayTimer.Start();
  
  			Blessed = false;
  
  			if ( Core.AOS && NameHue == 0x35 )
  				NameHue = -1;
  		}
  
  		private void UpgradeFromVersion0( object newVendorSystem )
  		{
  			List<Item> toRemove = new List<Item>();
  
  			foreach ( VendorItem vi in m_SellItems.Values )
  				if ( !CanBeVendorItem( vi.Item ) )
  					toRemove.Add( vi.Item );
  				else
  					vi.Description = Utility.FixHtml( vi.Description );
  
  			foreach ( Item item in toRemove )
  				RemoveVendorItem( item );
  
  			House = BaseHouse.FindHouseAt( this );
  
  			if ( (bool) newVendorSystem )
  				ActivateNewVendorSystem();
  		}
  
  		private void ActivateNewVendorSystem()
  		{
  			FixDresswear();
  
  			if ( House != null && !House.IsOwner( Owner ) )
  				Destroy( false );
  		}
  
  		public void InitBody()
  		{
  			Hue = Utility.RandomSkinHue();
  			SpeechHue = 0x3B2;
  
  			if ( !Core.AOS )
  				NameHue = 0x35;
  
  			if ( this.Female = Utility.RandomBool() )
  			{
  				this.Body = 0x191;
  				this.Name = NameList.RandomName( "female" );
  			}
  			else
  			{
  				this.Body = 0x190;
  				this.Name = NameList.RandomName( "male" );
  			}
  		}
  
  		public virtual void InitOutfit()
  		{
  			Item item = new FancyShirt( Utility.RandomNeutralHue() );
  			item.Layer = Layer.InnerTorso;
  			AddItem( item );
  			AddItem( new LongPants( Utility.RandomNeutralHue() ) );
  			AddItem( new BodySash( Utility.RandomNeutralHue() ) );
  			AddItem( new Boots( Utility.RandomNeutralHue() ) );
  			AddItem( new Cloak( Utility.RandomNeutralHue() ) );
  
  			Utility.AssignRandomHair( this );
  
  			Container pack = new VendorBackpack();
  			pack.Movable = false;
  			AddItem( pack );
  		}
  
  		[CommandProperty( AccessLevel.GameMaster )]
  		public Mobile Owner
  		{
  			get{ return m_Owner; }
  			set{ m_Owner = value; }
  		}
  
  		[CommandProperty( AccessLevel.GameMaster )]
  		public int BankAccount
  		{
  			get{ return m_BankAccount; }
  			set{ m_BankAccount = value; }
  		}
  
  		[CommandProperty( AccessLevel.GameMaster )]
  		public int HoldGold
  		{
  			get{ return m_HoldGold; }
  			set{ m_HoldGold = value; }
  		}
  
  		[CommandProperty( AccessLevel.GameMaster )]
  		public string ShopName
  		{
  			get{ return m_ShopName; }
  			set
  			{
  				if ( value == null )
  					m_ShopName = "";
  				else
  					m_ShopName = value;
  
  				InvalidateProperties();
  			}
  		}
  
  		[CommandProperty( AccessLevel.GameMaster )]
  		public DateTime NextPayTime
  		{
  			get{ return m_NextPayTime; }
  		}
  
  		public PlayerVendorPlaceholder Placeholder
  		{
  			get{ return m_Placeholder; } 
  			set{ m_Placeholder = value; }
  		}
  
  		public BaseHouse House
  		{
  			get{ return m_House; }
  			set
  			{
  				if ( m_House != null )
  					m_House.PlayerVendors.Remove( this );
  
  				if ( value != null )
  					value.PlayerVendors.Add( this );
  
  				m_House = value;
  			}
  		}
  
  		public int ChargePerDay
  		{
  			get
  			{ 
  				if ( BaseHouse.NewVendorSystem )
  				{
  					return ChargePerRealWorldDay / 12;
  				}
  				else
  				{
  					long total = 0;
  					foreach ( VendorItem vi in m_SellItems.Values )
  					{
  						total += vi.Price;
  					}
  
  					total -= 500;
  
  					if ( total < 0 )
  						total = 0;
  
  					return (int)( 20 + (total / 500) );
  				}
  			}
  		}
  
  		public int ChargePerRealWorldDay
  		{
  			get
  			{
  				if ( BaseHouse.NewVendorSystem )
  				{
  					long total = 0;
  					foreach ( VendorItem vi in m_SellItems.Values )
  					{
  						total += vi.Price;
  					}
  
  					return (int)( 60 + (total / 500) * 3 );
  				}
  				else
  				{
  					return ChargePerDay * 12;
  				}
  			}
  		}
  
  		public virtual bool IsOwner( Mobile m )
  		{
  			if ( m.AccessLevel >= AccessLevel.GameMaster )
  				return true;
  
  			if ( BaseHouse.NewVendorSystem && House != null )
  			{
  				return House.IsOwner( m );
  			}
  			else
  			{
  				return m == Owner;
  			}
  		}
  
  		protected List<Item> GetItems()
  		{
  			List<Item> list = new List<Item>();
  
  			foreach ( Item item in this.Items )
  				if ( item.Movable && item != this.Backpack && item.Layer != Layer.Hair && item.Layer != Layer.FacialHair )
  					list.Add( item );
  
  			if ( this.Backpack != null )
  				list.AddRange( this.Backpack.Items );
  
  			return list;
  		}
  
  		public virtual void Destroy( bool toBackpack )
  		{
  			Return();
  
  			if ( !BaseHouse.NewVendorSystem )
  				FixDresswear();
  
  			/* Possible cases regarding item return:
  			 * 
  			 * 1. No item must be returned
  			 *       -> do nothing.
  			 * 2. ( toBackpack is false OR the vendor is in the internal map ) AND the vendor is associated with a AOS house
  			 *       -> put the items into the moving crate or a vendor inventory,
  			 *          depending on whether the vendor owner is also the house owner.
  			 * 3. ( toBackpack is true OR the vendor isn't associated with any AOS house ) AND the vendor isn't in the internal map
  			 *       -> put the items into a backpack.
  			 * 4. The vendor isn't associated with any house AND it's in the internal map
  			 *       -> do nothing (we can't do anything).
  			 */
  
  			List<Item> list = GetItems();
  
  			if ( list.Count > 0 || HoldGold > 0 ) // No case 1
  			{
  				if ( ( !toBackpack || this.Map == Map.Internal ) && House != null && House.IsAosRules ) // Case 2
  				{
  					if ( House.IsOwner( Owner ) ) // Move to moving crate
  					{
  						if ( House.MovingCrate == null )
  							House.MovingCrate = new MovingCrate( House );
  
  						if ( HoldGold > 0 )
  							Banker.Deposit( House.MovingCrate, HoldGold );
  
  						foreach ( Item item in list )
  						{
  							House.MovingCrate.DropItem( item );
  						}
  					}
  					else // Move to vendor inventory
  					{
  						VendorInventory inventory = new VendorInventory( House, Owner, Name, ShopName );
  						inventory.Gold = HoldGold;
  
  						foreach ( Item item in list )
  						{
  							inventory.AddItem( item );
  						}
  
  						House.VendorInventories.Add( inventory );
  					}
  				}
  				else if ( ( toBackpack || House == null || !House.IsAosRules ) && this.Map != Map.Internal ) // Case 3 - Move to backpack
  				{
  					Container backpack = new Backpack();
  
  					if ( HoldGold > 0 )
  						Banker.Deposit( backpack, HoldGold );
  
  					foreach ( Item item in list )
  					{
  						backpack.DropItem( item );
  					}
  
  					backpack.MoveToWorld( this.Location, this.Map );
  				}
  			}
  
  			Delete();
  		}
  
  		private void FixDresswear()
  		{
  			for ( int i = 0; i < Items.Count; ++i )
  			{
  				Item item = Items[i] as Item;
  
  				if ( item is BaseHat )
  					item.Layer = Layer.Helm;
  				else if ( item is BaseMiddleTorso )
  					item.Layer = Layer.MiddleTorso;
  				else if ( item is BaseOuterLegs )
  					item.Layer = Layer.OuterLegs;
  				else if ( item is BaseOuterTorso )
  					item.Layer = Layer.OuterTorso;
  				else if ( item is BasePants )
  					item.Layer = Layer.Pants;
  				else if ( item is BaseShirt )
  					item.Layer = Layer.Shirt;
  				else if ( item is BaseWaist )
  					item.Layer = Layer.Waist;
  				else if ( item is BaseShoes )
  				{
  					if ( item is Sandals )
  						item.Hue = 0;
  
  					item.Layer = Layer.Shoes;
  				}
  			}
  		}
  
  		public override void OnAfterDelete()
  		{
  			base.OnAfterDelete();
  
  			m_PayTimer.Stop();
  
  			House = null;
  
  			if ( Placeholder != null )
  				Placeholder.Delete();
  		}
  
  		public override bool IsSnoop( Mobile from )
  		{
  			return false;
  		}
  
  		public override void GetProperties( ObjectPropertyList list )
  		{
  			base.GetProperties( list );
  
  			if ( BaseHouse.NewVendorSystem )
  			{
  				list.Add( 1062449, ShopName ); // Shop Name: ~1_NAME~
  			}
  		}
  
  		public VendorItem GetVendorItem( Item item )
  		{
  			return (VendorItem) m_SellItems[item];
  		}
  
  		private VendorItem SetVendorItem( Item item, int price, string description )
  		{
  			return SetVendorItem( item, price, description, DateTime.Now );
  		}
  
  		private VendorItem SetVendorItem( Item item, int price, string description, DateTime created )
  		{
  			RemoveVendorItem( item );
  
  			VendorItem vi = new VendorItem( item, price, description, created );
  			m_SellItems[item] = vi;
  
  			item.InvalidateProperties();
  
  			return vi;
  		}
  
  		private void RemoveVendorItem( Item item )
  		{
  			VendorItem vi = GetVendorItem( item );
  
  			if ( vi != null )
  			{
  				vi.Invalidate();
  				m_SellItems.Remove( item );
  
  				foreach ( Item subItem in item.Items )
  				{
  					RemoveVendorItem( subItem );
  				}
  
  				item.InvalidateProperties();
  			}
  		}
  
  		private bool CanBeVendorItem( Item item )
  		{
  			Item parent = item.Parent as Item;
  
  			if ( parent == this.Backpack )
  				return true;
  
  			if ( parent is Container )
  			{
  				VendorItem parentVI = GetVendorItem( parent );
  
  				if ( parentVI != null )
  					return !parentVI.IsForSale;
  			}
  
  			return false;
  		}
  
  		public override void OnSubItemAdded( Item item )
  		{
  			base.OnSubItemAdded( item );
  
  			if ( GetVendorItem( item ) == null && CanBeVendorItem( item ) )
  			{
  				// TODO: default price should be dependent to the type of object
  				SetVendorItem( item, 999, "" );
  			}
  		}
  
  		public override void OnSubItemRemoved( Item item )
  		{
  			base.OnSubItemRemoved( item );
  
  			if ( item.GetBounce() == null )
  				RemoveVendorItem( item );
  		}
  
  		public override void OnSubItemBounceCleared( Item item )
  		{
  			base.OnSubItemBounceCleared( item );
  
  			if ( !CanBeVendorItem( item ) )
  				RemoveVendorItem( item );
  		}
  
  		public override void OnItemRemoved( Item item )
  		{
  			base.OnItemRemoved( item );
  
  			if ( item == this.Backpack )
  			{
  				foreach ( Item subItem in item.Items )
  				{
  					RemoveVendorItem( subItem );
  				}
  			}
  		}
  
  		public override bool OnDragDrop( Mobile from, Item item )
  		{
  			if ( !IsOwner( from ) )
  			{
  				SayTo( from, 503209 ); // I can only take item from the shop owner.
  				return false;
  			}
  
  			if ( item is Gold )
  			{
  				if ( BaseHouse.NewVendorSystem )
  				{
  					if ( this.HoldGold < 1000000 )
  					{
  						SayTo( from, 503210 ); // I'll take that to fund my services.
  
  						this.HoldGold += item.Amount;
  						item.Delete();
  
  						return true;
  					}
  					else
  					{
  						from.SendLocalizedMessage( 1062493 ); // Your vendor has sufficient funds for operation and cannot accept this gold.
  
  						return false;
  					}
  				}
  				else
  				{
  					if ( this.BankAccount < 1000000 )
  					{
  						SayTo( from, 503210 ); // I'll take that to fund my services.
  
  						this.BankAccount += item.Amount;
  						item.Delete();
  
  						return true;
  					}
  					else
  					{
  						from.SendLocalizedMessage( 1062493 ); // Your vendor has sufficient funds for operation and cannot accept this gold.
  
  						return false;
  					}
  				}
  			}
  			else
  			{
  
gt: 				bool newItem = ( GetVendorItem( item ) == null );