context: #region Header
  //   Vorspire    _,-'/-'/  AdvancedVendor.cs
  //   .      __,-; ,'( '/
  //    \.    `-.__`-._`:_,-._       _ , . ``
  //     `:-._,------' ` _,`--` -: `_ , ` ,' :
  //        `---..__,,--'  (C) 2018  ` -'. -'
  //        #  Vita-Nex [http://core.vita-nex.com]  #
  //  {o)xxx|===============-   #   -===============|xxx(o}
  //        #        The MIT License (MIT)          #
  #endregion
  
  #region References
  using System;
  using System.Collections.Generic;
  using System.Drawing;
  using System.Linq;
  
  using Server;
  using Server.Accounting;
  using Server.ContextMenus;
  using Server.Items;
  using Server.Mobiles;
  using Server.Multis;
  using Server.Network;
  
  using VitaNex.Collections;
  using VitaNex.Network;
  using VitaNex.SuperGumps.UI;
  #endregion
  
  namespace VitaNex.Mobiles
  {
  	public class DynamicVendor : AdvancedVendor
  	{
  		[Constructable]
  		public DynamicVendor()
  			: base("the Vendor", typeof(Gold), "Gold", "GP")
  		{ }
  
  		public DynamicVendor(Serial serial)
  			: base(serial)
  		{ }
  
  		protected override void InitBuyInfo()
  		{ }
  
  		public override void Serialize(GenericWriter writer)
  		{
  			base.Serialize(writer);
  
  			writer.SetVersion(0);
  		}
  
  		public override void Deserialize(GenericReader reader)
  		{
  			base.Deserialize(reader);
  
  			reader.GetVersion();
  		}
  	}
  
  	public abstract class AdvancedVendor : BaseVendor, IAdvancedVendor
  	{
  		public static event Action<AdvancedVendor> OnCreated;
  		public static event Action<AdvancedSBInfo> OnInit;
  
  		public static bool ConsumeCash(Type type, Container cont, double amount)
  		{
  			return ConsumeCash(type, cont, amount, true);
  		}
  
  		public static bool ConsumeCash(Type type, Container cont, double amount, bool recurse)
  		{
  			if (type == null || cont == null)
  			{
  				return false;
  			}
  
  			if (amount <= 0)
  			{
  				return true;
  			}
  
  			var items = new Queue<Item>(FindCash(type, cont, recurse));
  			var total = items.Aggregate(0.0, (c, o) => c + o.Amount);
  
  			if (total < amount)
  			{
  				items.Free(true);
  
  				return false;
  			}
  
  			var consume = amount;
  
  			while (consume > 0)
  			{
  				var o = items.Dequeue();
  
  				if (o.Amount > consume)
  				{
  					o.Consume((int)consume);
  
  					consume = 0;
  				}
  				else
  				{
  					consume -= o.Amount;
  
  					o.Delete();
  				}
  			}
  
  			items.Free(true);
  
  			return true;
  		}
  
  		private static IEnumerable<Item> FindCash(Type type, Container cont, bool recurse)
  		{
  			if (type == null || cont == null || cont.Items.Count == 0)
  			{
  				yield break;
  			}
  
  			if (cont is ILockable && ((ILockable)cont).Locked)
  			{
  				yield break;
  			}
  
  			if (cont is TrapableContainer && ((TrapableContainer)cont).TrapType != TrapType.None)
  			{
  				yield break;
  			}
  
  			var count = cont.Items.Count;
  
  			while (--count >= 0)
  			{
  				if (count >= cont.Items.Count)
  				{
  					continue;
  				}
  
  				var item = cont.Items[count];
  
  				if (item is Container)
  				{
  					if (!recurse)
  					{
  						continue;
  					}
  
  					foreach (var o in FindCash(type, (Container)item, true))
  					{
  						yield return o;
  					}
  				}
  				else if (item.TypeEquals(type))
  				{
  					yield return item;
  				}
  			}
  		}
  
  		public static List<AdvancedVendor> Instances { get; private set; }
  
  		static AdvancedVendor()
  		{
  			Instances = new List<AdvancedVendor>();
  		}
  
  		public static bool IsDynamicStock(Item item)
  		{
  			var root = item.RootParent as AdvancedVendor;
  
  			return root != null && item.IsChildOf(root.Backpack) && root._DynamicStock.GetValue(item) != null;
  		}
  
  		private readonly Dictionary<Item, DynamicBuyInfo> _DynamicStock = new Dictionary<Item, DynamicBuyInfo>();
  
  		private readonly List<SBInfo> _SBInfos = new List<SBInfo>();
  
  		protected sealed override List<SBInfo> SBInfos { get { return _SBInfos; } }
  
  		public AdvancedSBInfo AdvancedStock { get; private set; }
  
  		private DateTime _NextYell = DateTime.UtcNow.AddSeconds(Utility.RandomMinMax(30, 120));
  
  		[CommandProperty(AccessLevel.Administrator)]
  		public int Discount { get; set; }
  
  		[CommandProperty(AccessLevel.Administrator)]
  		public bool DiscountEnabled { get; set; }
  
  		[CommandProperty(AccessLevel.Administrator)]
  		public bool DiscountYell { get; set; }
  
  		[CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)]
  		public ObjectProperty CashProperty { get; set; }
  
  		[CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)]
  		public TypeSelectProperty<object> CashType { get; set; }
  
  		private TextDefinition _CashName;
  
  		[CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)]
  		public TextDefinition CashName
  		{
  			get { return _CashName; }
  			set
  			{
  				_CashName = value;
  				InvalidateProperties();
  			}
  		}
  
  		private TextDefinition _CashAbbr;
  
  		[CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)]
  		public TextDefinition CashAbbr
  		{
  			get { return _CashAbbr; }
  			set
  			{
  				_CashAbbr = value;
  				InvalidateProperties();
  			}
  		}
  
  		private bool _ShowCashName;
  
  		[CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)]
  		public bool ShowCashName
  		{
  			get { return _ShowCashName; }
  			set
  			{
  				_ShowCashName = value;
  				InvalidateProperties();
  			}
  		}
  
  		private bool _Trading;
  
  		[CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)]
  		public bool Trading
  		{
  			get { return _Trading; }
  			set
  			{
  				_Trading = value;
  				InvalidateProperties();
  			}
  		}
  
  		[CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)]
  		public bool CanRestock { get; set; }
  
  		public override VendorShoeType ShoeType
  		{
  			get { return Utility.RandomBool() ? VendorShoeType.ThighBoots : VendorShoeType.Boots; }
  		}
  
  		public virtual Race DefaultRace { get { return Race.Human; } }
  
  		public virtual Race[] RequiredRaces { get { return null; } }
  
  		public override bool CanTeach { get { return false; } }
  
  		public AdvancedVendor(
  			string title,
  			Type cashType,
  			TextDefinition cashName,
  			TextDefinition cashAbbr = null,
  			bool showCashName = true)
  			: this(title)
  		{
  			CashProperty = new ObjectProperty();
  			CashType = cashType ?? typeof(Gold);
  			CashName = cashName ?? "Gold";
  			CashAbbr = cashAbbr ?? String.Empty;
  			ShowCashName = showCashName;
  		}
  
  		public AdvancedVendor(
  			string title,
  			string cashProp,
  			TextDefinition cashName,
  			TextDefinition cashAbbr = null,
  			bool showCashName = true)
  			: this(title)
  		{
  			CashProperty = new ObjectProperty(cashProp);
  			CashType = typeof(ObjectProperty);
  			CashName = cashName ?? "Credits";
  			CashAbbr = cashAbbr ?? String.Empty;
  			ShowCashName = showCashName;
  		}
  
  		private AdvancedVendor(string title)
  			: base(title)
  		{
  			Instances.Add(this);
  
  			Trading = true;
  			CanRestock = true;
  
  			Female = Utility.RandomBool();
  			Name = NameList.RandomName(Female ? "female" : "male");
  
  			Race = DefaultRace;
  
  			if (Backpack == null)
  			{
  				AddItem(
  					new Backpack
  					{
  						Movable = false
  					});
  			}
  
  			if (OnCreated != null)
  			{
  				Timer.DelayCall(v => OnCreated(v), this);
  			}
  		}
  
  		public AdvancedVendor(Serial serial)
  			: base(serial)
  		{
  			Instances.Add(this);
  		}
  
  		public override void OnSubItemAdded(Item item)
  		{
  			base.OnSubItemAdded(item);
  
  			if (!World.Loading && item.IsChildOf(Backpack))
  			{
  				var info = _DynamicStock.GetValue(item);
  
  				if (info == null)
  				{
  					_DynamicStock[item] = new DynamicBuyInfo(item);
  
  					item.Movable = false;
  				}
  			}
  		}
  
  		public override void OnSubItemRemoved(Item item)
  		{
  			base.OnSubItemRemoved(item);
  
  			if (!World.Loading)
  			{
  				var info = _DynamicStock.GetValue(item);
  
  				if (_DynamicStock.Remove(item) && info != null)
  				{
  					info.Free();
  
  					item.Movable = true;
  				}
  			}
  		}
  
  		public override void GetChildProperties(ObjectPropertyList list, Item item)
  		{
  			base.GetChildProperties(list, item);
  
  			var info = _DynamicStock.GetValue(item);
  
  			if (info != null)
  			{
  				var price = info.Price < 0 ? "Not For Sale" : info.Price == 0 ? "Free" : info.Price.ToString("#,0");
  
  				// Price: ~1_COST~
  				list.Add(1043304, price);
  			}
  		}
  
  		public override void GetChildContextMenuEntries(Mobile m, List<ContextMenuEntry> list, Item item)
  		{
  			base.GetChildContextMenuEntries(m, list, item);
  
  			if (m == null || m.AccessLevel < AccessLevel.GameMaster)
  			{
  				return;
  			}
  
  			var info = _DynamicStock.GetValue(item);
  
  			if (info != null)
  			{
  				list.Add(new CustomContextEntry(1150627, u => OnSetPrice(u, info)));
  			}
  		}
  
  		public override void GetContextMenuEntries(Mobile m, List<ContextMenuEntry> list)
  		{
  			base.GetContextMenuEntries(m, list);
  
  			if (m != null && m.AccessLevel >= AccessLevel.GameMaster)
  			{
  				// Refresh
  				list.Add(new CustomContextEntry(1015002, OnRestock));
  			}
  		}
  
  		protected virtual void OnRestock(Mobile m)
  		{
  			Restock();
  
  			if (m != null)
  			{
  				m.SendMessage("They have been restocked!");
  			}
  		}
  
  		protected virtual void OnSetPrice(Mobile m, DynamicBuyInfo info)
  		{
  			new InputDialogGump(m)
  			{
  				InputText = info.Price.ToString(),
  				Icon = info.ItemID,
  				IconHue = info.Hue,
  				IconItem = true,
  				Title = "Set Price",
  				Html = "Enter a price for " + info.Name + "\n\nEnter -1 to mark not for sale.",
  				Callback = (b, t) =>
  				{
  					int value;
  
  					if (Int32.TryParse(t, out value))
  					{
  						info.Price = Math.Max(-1, value);
  
  						if (info.Item != null)
  						{
  							info.Item.InvalidateProperties();
  						}
  					}
  				}
  			}.Send();
  		}
  
  		public override int GetPriceScalar()
  		{
  			var scalar = CashType.TypeEquals<Gold>() ? base.GetPriceScalar() : 100;
  
  			if (DiscountEnabled)
  			{
  				scalar -= Math.Max(0, Math.Min(100, Discount));
  			}
  
  			return Math.Max(0, scalar);
  		}
  
  		protected override void OnRaceChange(Race oldRace)
  		{
  			base.OnRaceChange(oldRace);
  
  			Items.ForEachReverse(
  				item =>
  				{
  					if (item != null && !item.Deleted && !(item is Container) && !(item is IMount) && !(item is IMountItem) &&
  						item.IsEquipped())
  					{
  						item.Delete();
  					}
  				});
  
  			Hue = /*FaceHue =*/ Race.RandomSkinHue();
  
  			//FaceItemID = Race.RandomFace(this);
  			//FaceHue = Hue;
  
  			HairItemID = Race.RandomHair(this);
  			HairHue = Race.RandomHairHue();
  
  			FacialHairItemID = Race.RandomFacialHair(this);
  			FacialHairHue = HairHue;
  
  			InitOutfit();
  		}
  
  		public virtual void ResolveCurrency(out Type type, out TextDefinition name)
  		{
  			type = CashType;
  			name = CashName;
  		}
  
  		public virtual object GetCashObject(Mobile m)
  		{
  			return m;
  		}
  
  #if ServUO
  		/*public override void InitOutfit()
  		{
  			if (Race == Race.Gargoyle)
  			{
  				InitGargOutfit();
  				return;
  			}
  
  			base.InitOutfit();
  		}*/
  #endif
  
  		private bool _WasStocked, _Restocking;
  
  		public override void Restock()
  		{
  			if (_Restocking || !CanRestock)
  			{
  				return;
  			}
  
  			_Restocking = true;
  
  			LastRestock = DateTime.UtcNow;
  
  			var buyInfo = GetBuyInfo();
  
  			foreach (var bii in buyInfo)
  			{
  				if (bii is DynamicBuyInfo)
  				{
  					((DynamicBuyInfo)bii).Update();
  				}
  				else
  				{
  					bii.OnRestock();
  				}
  			}
  
  			if (!_WasStocked)
  			{
  				OnStocked();
  			}
  			else
  			{
  				OnRestocked();
  			}
  
  			_WasStocked = true;
  			_Restocking = false;
  		}
  
  		public sealed override void InitSBInfo()
  		{
  			ClearBuyInfo();
  
  			_SBInfos.Add(AdvancedStock = new AdvancedSBInfo(this));
  
  			InitBuyInfo();
  
  			if (OnInit != null)
  			{
  				OnInit(AdvancedStock);
  			}
  		}
  
  		public void ClearBuyInfo()
  		{
  			_SBInfos.ForEach(sb => sb.BuyInfo.ForEach(b => b.DeleteDisplayEntity()));
  			_SBInfos.Clear();
  		}
  
  		protected virtual void OnStocked()
  		{ }
  
  		protected virtual void OnRestocked()
  		{ }
  
  		protected abstract void InitBuyInfo();
  
  		public void AddStock<TObj>(int price, string name = null, int amount = 100, object[] args = null)
  		{
  			AddStock(typeof(TObj), price, name, amount, args);
  		}
  
  		public virtual void AddStock(Type type, int price, string name = null, int amount = 100, object[] args = null)
  		{
  			AdvancedStock.AddStock(type, price, name, amount, args);
  		}
  
  		public void RemoveStock<TObj>()
  		{
  			RemoveStock(typeof(TObj));
  		}
  
  		public virtual void RemoveStock(Type type)
  		{
  			AdvancedStock.RemoveStock(type);
  		}
  
  		public void AddOrder<TObj>(int price)
  		{
  			AddOrder(typeof(TObj), price);
  		}
  
  		public virtual void AddOrder(Type type, int price)
  		{
  			AdvancedStock.AddOrder(type, price);
  		}
  
  		public void RemoveOrder<TObj>()
  		{
  			RemoveOrder(typeof(TObj));
  		}
  
  		public virtual void RemoveOrder(Type type)
  		{
  			AdvancedStock.RemoveOrder(type);
  		}
  
  		public override bool CheckVendorAccess(Mobile m)
  		{
  			if (m == null || m.Deleted)
  			{
  				return false;
  			}
  
  			if (m.AccessLevel >= AccessLevel.GameMaster)
  			{
  				return true;
  			}
  
  			if (!Trading || !DesignContext.Check(m))
  			{
  				return false;
  			}
  
  			var races = RequiredRaces;
  
  			if (races != null && races.Length > 0 && (m.Race == null || !races.Contains(m.Race)))
  			{
  				return false;
  			}
  
  			return base.CheckVendorAccess(m);
  		}
  
  		public override void OnThink()
  		{
  			base.OnThink();
  
  			if (Deleted || Map == null || Map == Map.Internal || !DiscountEnabled || !DiscountYell || Discount <= 0 ||
  				DateTime.UtcNow <= _NextYell)
  			{
  				return;
  			}
  
  			Yell("Sale! {0}% Off!", Discount.ToString("#,0"));
  			_NextYell = DateTime.UtcNow.AddSeconds(Utility.RandomMinMax(20, 120));
  		}
  
  		public override void OnDelete()
  		{
  			base.OnDelete();
  
  			Instances.Remove(this);
  		}
  
  		public override void OnAfterDelete()
  		{
  			base.OnAfterDelete();
  
  			Instances.Remove(this);
  		}
  
  		private GenericBuyInfo LookupDisplayObject(object obj)
  		{
  			return GetBuyInfo().OfType<GenericBuyInfo>().FirstOrDefault(gbi => gbi.GetDisplayEntity() == obj);
  		}
  
  		private void ProcessSinglePurchase(
  			BuyItemResponse buy,
  			IBuyItemInfo bii,
  			ICollection<BuyItemResponse> validBuy,
  			ref int controlSlots,
  			ref bool fullPurchase,
  			ref double totalCost)
  		{
  			if (!Trading || CashType == null || !CashType.IsNotNull)
  			{
  				return;
  			}
  
  			var amount = buy.Amount;
  
  			if (amount > bii.Amount)
  			{
  				amount = bii.Amount;
  			}
  
  			if (amount <= 0)
  			{
  				return;
  			}
  
  			var slots = bii.ControlSlots * amount;
  
  			if (controlSlots >= slots)
  			{
  				controlSlots -= slots;
  			}
  			else
  			{
  				fullPurchase = false;
  				return;
  			}
  
  			totalCost += (double)bii.Price * amount;
  			validBuy.Add(buy);
  		}
  
  		private void ProcessValidPurchase(int amount, IBuyItemInfo bii, Mobile buyer, Container cont)
  		{
  			if (!Trading || CashType == null || !CashType.IsNotNull)
  			{
  				return;
  			}
  
  			if (amount > bii.Amount)
  			{
  				amount = bii.Amount;
  			}
  
  			if (amount < 1)
  			{
  				return;
  			}
  
  			bii.Amount -= amount;
  
  			var o = bii.GetEntity();
  
  			if (o is Item)
  			{
  				var item = (Item)o;
  
  				if (item.Stackable)
  				{
  					item.Amount = amount;
  
  					if (cont != null)
  					{
  						cont.DropItem(item);
  					}
  					else
  					{
  						item.MoveToWorld(buyer.Location, buyer.Map);
  					}
  
  					OnItemReceived(buyer, item, bii);
  
  					if (cont != null && !item.Deleted)
  					{
  						cont.MergeStacks(item.GetType(), buyer);
  					}
  				}
  				else
  				{
  					item.Stackable = true;
  					item.Amount = 1;
  					item.Stackable = false;
  
  					if (cont != null)
  					{
  						cont.DropItem(item);
  					}
  					else
  					{
  						item.MoveToWorld(buyer.Location, buyer.Map);
  					}
  
  					OnItemReceived(buyer, item, bii);
  
  					for (var i = 1; i < amount; i++)
  					{
  						item = (Item)bii.GetEntity();
  
  						if (item == null)
  						{
  							continue;
  						}
  
  						item.Stackable = true;
  						item.Amount = 1;
  						item.Stackable = false;
  
  						if (cont != null)
  						{
  							cont.DropItem(item);
  						}
  						else
  						{
  							item.MoveToWorld(buyer.Location, buyer.Map);
  						}
  
  						OnItemReceived(buyer, item, bii);
  					}
  				}
  			}
  			else if (o is Mobile)
  			{
  				var m = (Mobile)o;
  
  				m.Direction = (Direction)Utility.Random(8);
  				m.MoveToWorld(buyer.Location, buyer.Map);
  				m.PlaySound(m.GetIdleSound());
  
  				if (m is BaseCreature)
  				{
  					((BaseCreature)m).SetControlMaster(buyer);
  				}
  
  				OnMobileReceived(buyer, m, bii);
  
  				for (var i = 1; i < amount; ++i)
  				{
  					m = (Mobile)bii.GetEntity();
  
  					if (m == null)
  					{
  						continue;
  					}
  
  					m.Direction = (Direction)Utility.Random(8);
  					m.MoveToWorld(buyer.Location, buyer.Map);
  
  					if (m is BaseCreature)
  					{
  						((BaseCreature)m).SetControlMaster(buyer);
  					}
  
  					OnMobileReceived(buyer, m, bii);
  				}
  			}
  		}
  
  		protected virtual void OnItemReceived(Mobile buyer, Item item, IBuyItemInfo buy)
  		{
  			if (buy is DynamicBuyInfo)
  			{
  				item.Movable = true;
  
  				if (item is Container)
  				{
  					foreach (var o in ((Container)item).FindItemsByType<Item>(true, o => !o.Movable))
  					{
  						o.Movable = true;
  					}
  				}
  			}
  		}
  
  		protected virtual void OnMobileReceived(Mobile buyer, Mobile mob, IBuyItemInfo buy)
  		{ }
  
  		public override void VendorSell(Mobile m)
  		{
  			if (!IsActiveBuyer)
  			{
  				return;
  			}
  
  			if (!m.CheckAlive())
  			{
  				return;
  			}
  
  			if (!CheckVendorAccess(m))
  			{
  				Say("I can't serve you! Company policy.");
  				//Say(501522); // I shall not treat with scum like thee!
  				return;
  			}
  
  			var pack = m.Backpack;
  
  			if (pack == null)
  			{
  				return;
  			}
  
  			var info = GetSellInfo();
  
  			var table = new List<SellItemState>();
  
  			foreach (var ssi in info.Where(ssi => ssi != null && ssi.Types != null))
  			{
  				var range = pack.FindItemsByType(ssi.Types).Where(i => CanSellItem(i, ssi));
  
  				table.AddRange(range.Select(item => new SellItemState(item, ssi.GetSellPriceFor(item), ssi.GetNameFor(item))));
  			}
  
  			if (table.Count > 0)
  			{
  				SendPacksTo(m);
  
  				m.Send(new VendorSellList(this, table));
  			}
  			else
  			{
  				Say(true, "You have nothing I would be interested in.");
  			}
  		}
  
  		public virtual bool CanSellItem(Item item, IShopSellInfo ssi)
  		{
  			if (item == null || item.Deleted || !item.Movable || !item.IsStandardLoot() || !ssi.IsSellable(item))
  			{
  				return false;
  			}
  
  			if (_DynamicStock.GetValue(item) != null)
  			{
  				return false;
  			}
  
  
gt: 			if (item is Container && item.Items.Count > 0)