context: using System;
  using System.Collections.Generic;
  using System.Linq;
  
  using Server;
  using Server.Mobiles;
  using Server.ContextMenus;
  using Server.Engines.Points;
  using Server.Engines;
  using Server.Items;
  using Server.Multis;
  using Server.Regions;
  using Server.Engines.SeasonalEvents;
  
  namespace Server.Engines.CityLoyalty
  {
      public enum TradeTitle
      {
          Trader      = 1151739,
          Exporter    = 1151741,
          Broker      = 1151743,
          Tycoon      = 1151745,
          Smuggler    = 1151747,
          Magnate     = 1155481
      }
  
  	public class CityTradeSystem : PointsSystem
  	{
          public static readonly int TurnInGold = Config.Get("CityTrading.TurnInGold", 10000);
          public static readonly int CrateDuration = Config.Get("CityTrading.CrateDuration", 24);
          public static readonly int AmbushWaitDuration = Config.Get("CityTrading.AmbushWaitDuration", 5);
          public static readonly int AmbusherDelete = Config.Get("CityTrading.AmbusherDelete", 10);
  
          public override TextDefinition Name { get { return new TextDefinition("City Trading"); } }
          public override PointsType Loyalty { get { return PointsType.CityTrading; } }
          public override bool AutoAdd { get { return false; } }
          public override double MaxPoints { get { return double.MaxValue; } }
          public override bool ShowOnLoyaltyGump { get { return false; } }
  
          public static bool KrampusEncounterActive { get { return KrampusEncounter.Enabled && KrampusEncounter.Encounter != null; } }
  
          public static Dictionary<Mobile, TradeOrderCrate> ActiveTrades { get; private set; }
          public static Dictionary<BaseCreature, DateTime> Ambushers { get; private set; }
  		
  		public CityTradeSystem() 
  		{
  			ActiveTrades = new Dictionary<Mobile, TradeOrderCrate>();
  
              _NameBuffer = new Dictionary<Type, string>();
  		}
  
          public override PointsEntry GetSystemEntry(PlayerMobile pm)
          {
              return new CityTradeEntry(pm);
          }
  
  		public int GetMaxTrades(Mobile m)
  		{
              CityTradeEntry entry = GetPlayerEntry<CityTradeEntry>(m as PlayerMobile);
  
  			if(entry == null)
  				return 1;
  
              return Math.Min(8, Math.Max(1, (entry.Completed / 25) + 1));
  		}
  		
  		public static bool HasTrade(Mobile from)
  		{
              return ActiveTrades.ContainsKey(from);
  		}
  		
  		public bool HasTurnIn(Mobile from, TradeMinister minister)
  		{
  			if(from == null || minister == null || !ActiveTrades.ContainsKey(from) || ActiveTrades[from] == null)
  				return false;
  
              TradeOrderCrate crate = ActiveTrades[from];
  
              return crate.Entry != null && crate.Entry.Origin != minister.City;
  		}
  		
  		public bool TryOfferTrade(Mobile from, TradeMinister minister)
  		{
  			if(from == null || from.Backpack == null)
  				return true;
  
              if (ActiveTrades.ContainsKey(from))
              {
                  minister.SayTo(from, 1151722); // It appears you are already delivering a trade order. Deliver your current order before requesting another.
              }
              else if (KrampusEncounterActive && (KrampusEncounter.Encounter.Krampus != null || KrampusEncounter.Encounter.KrampusSpawning))
              {
                  var p = KrampusEncounter.Encounter.SpawnLocation;
                  var map = KrampusEncounter.Encounter.SpawnMap;
  
                  minister.SayTo(
                      from,
                      1158790,
                      String.Format("{0}\t{1}",
                      WorldLocationInfo.GetLocationString(p, map),
                      Sextant.GetCoords(p, map)), 1150);
                      // Take notice! The vile Krampus has been spotted near ~2_where~ at ~1_coords~!  New Trade Orders are suspended until Krampus has been defeated!
              }
              else
              {
                  City origin = minister.City;
                  City destination;
  
                  do
                  {
                      destination = CityLoyaltySystem.GetRandomCity();
                  }
                  while (destination == origin);
  
                  int distance = GetDistance(minister, destination);
                  int trades = Utility.RandomMinMax(1, GetMaxTrades(from));
                  TradeEntry entry = new TradeEntry(destination, origin, distance);
                  TradeOrderCrate crate = new TradeOrderCrate(from, entry);
  
                  GetPlayerEntry<CityTradeEntry>(from as PlayerMobile, true);
  
                  for (int i = 0; i < trades; i++)
                  {
                      int worth = 1;
                      string name = null;
  
                      Type t = GetRandomTrade(origin, destination, ref worth, ref name);
  
                      if (t != null)
                      {
                          int amount = Utility.RandomList(5, 10, 15, 20);
                          entry.Details.Add(new TradeEntry.TradeDetails(t, worth, amount, name));
                      }
                      else
                      {
                          minister.SayTo(from, "There are no trades available at this time.");
                          return false;
                      }
                  }
  
                  if (from.Backpack == null || !from.Backpack.TryDropItem(from, crate, false))
                  {
                      crate.Delete();
                      from.SendLocalizedMessage(114456); // Your backpack cannot hold the Trade Order.  Free up space and speak to the Trade Minister again.
                  }
  
                  ActiveTrades[from] = crate;
  
                  return true;
              }
  			
  			return false;
  		}
  		
  		public bool TryTurnIn(Mobile from, TradeOrderCrate order, Mobile turninMobile)
  		{
              if (order == null || from == null || turninMobile == null || order.Entry == null)
  				return false;
  				
  			TradeEntry entry = order.Entry;
              TradeMinister minister = turninMobile as TradeMinister;
  
  			if(from.AccessLevel == AccessLevel.Player && minister != null && minister.City != entry.Destination)
                  turninMobile.SayTo(from, 1151738, String.Format("#{0}", CityLoyaltySystem.GetCityLocalization(entry.Destination))); // Begging thy pardon, but those goods are destined for the City of ~1_city~
  			else if(!order.Fulfilled)
                  turninMobile.SayTo(from, 1151732); // This trade order has not been fulfilled.  Fill the trade order with all necessary items and try again.
  			else
  			{
  				CityLoyaltySystem.OnTradeComplete(from, order.Entry);
                  CityTradeEntry pentry = GetPlayerEntry<CityTradeEntry>(from as PlayerMobile);
  
                  if (pentry != null)
                  {
                      pentry.Points++;
                      pentry.DistanceTraveled += entry.Distance;
                      pentry.Completed++;
                      CheckTitle(pentry);
                  }
  				
  				order.Delete();
                  return true;
  			}
  
              return false;
  		}
  
          public bool TryTurnInToSlim(Mobile from, TradeOrderCrate order, SlimTheFence slim)
          {
              if (order == null || from == null || slim == null || order.Entry == null)
                  return false;
  
              TradeEntry entry = order.Entry;
  
              if (!order.Fulfilled)
                  slim.SayTo(from, 1151732); // This trade order has not been fulfilled.  Fill the trade order with all necessary items and try again.
              else
              {
                  CityLoyaltySystem.OnSlimTradeComplete(from, order.Entry);
                  CityTradeEntry pentry = GetPlayerEntry<CityTradeEntry>(from as PlayerMobile, true);
  
                  if (pentry != null)
                  {
                      pentry.Points++;
                      pentry.DistanceTraveled += entry.Distance;
                      pentry.CompletedSlim++;
                      CheckTitle(pentry);
                  }
  
                  slim.SayTo(from, 1151736); // Haha! These goods will fetch me quite a bit o' coin!  Thanks fer yer help!  Here's a little something I was able to get me hands on...
  
                  order.Delete();
                  return true;
              }
  
              return false;
          }
  
          public void CheckTitle(CityTradeEntry entry)
          {
              switch (entry.Completed)
              {
                  case 1: entry.Player.AddRewardTitle((int)TradeTitle.Trader); break;
                  case 25: entry.Player.AddRewardTitle((int)TradeTitle.Exporter); break;
                  case 50: entry.Player.AddRewardTitle((int)TradeTitle.Broker); break;
                  case 100: entry.Player.AddRewardTitle((int)TradeTitle.Tycoon); break;
                  case 150: entry.Player.AddRewardTitle((int)TradeTitle.Magnate); break;
              }
  
              if(entry.CompletedSlim == 50)
                  entry.Player.AddRewardTitle((int)TradeTitle.Smuggler);
          }
  
          public override void OnPlayerAdded(PlayerMobile m)
          {
              m.Backpack.DropItem(new MysteriousNote());
              m.PrivateOverheadMessage(Server.Network.MessageType.Regular, 1150, 1151734, m.NetState); // *A passerby slips a rolled bit of parchment into your hand...*
          }
  		
  		public static void CancelTradeOrder(Mobile from, TradeOrderCrate crate)
  		{
              if (from == null)
                  from = crate.Owner;
  
              if (from != null)
              {
                  crate.Items.ForEach(i =>
                  {
                      from.Backpack.DropItem(i);
                  });
  
                  CityTradeEntry entry = CityLoyaltySystem.CityTrading.GetPlayerEntry<CityTradeEntry>(from as PlayerMobile, true);
  
                  if (entry != null)
                      entry.Canceled++;
              }
  			
  			crate.Delete();
  		}
  
          private Dictionary<Type, string> _NameBuffer;
  
          public string GetNameFor(Type t, string fallbackname)
          {
              if (_NameBuffer.ContainsKey(t))
                  return _NameBuffer[t];
  
              Item item = Loot.Construct(t);
  
              if (item != null)
              {
                  string name;
  
                  if (item.Name != null)
                  {
                      name = item.Name;
                  }
                  else
                  {
                      name = item.LabelNumber.ToString();
                  }
  
                  _NameBuffer[t] = name;
                  item.Delete();
  
                  return name;
              }
  
              Console.WriteLine("WARNING: Using Fallback name for: {0}", t.Name);
              return fallbackname;
          }
  		
  		public void RemoveCrate(Mobile from, TradeOrderCrate crate)
  		{
  			if(ActiveTrades.ContainsKey(from))
  			{
                  ActiveTrades.Remove(from);
  			}
  		}
  
          public static void OnPublicMoongateUsed(Mobile from)
          {
              if (ActiveTrades.ContainsKey(from))
              {
                  ActiveTrades[from].Entry.Distance = 0;
              }
          }
  		
  		public static int GetDistance(TradeMinister origin, City destination)
  		{
              TradeMinister destMinister = TradeMinister.Ministers.FirstOrDefault(m => m.City == destination);
  			
  			if(destMinister != null)
  			{
  				return (int)origin.GetDistanceToSqrt(destMinister.Location);
  			}
  			
  			return 0;
  		}
  
          public static Type GetRandomTrade(City originCity, City dest, ref int worth, ref string name)
          {
              Region region = CityLoyaltySystem.GetCityInstance(originCity).Definition.Region;
              List<BaseVendor> list = new List<BaseVendor>(region.GetEnumeratedMobiles().OfType<BaseVendor>().Where(bv => bv.GetBuyInfo() != null && bv.GetBuyInfo().Length > 0));
  
              if (list.Count == 0)
                  return null;
  
              do
              {
                  BaseVendor vendor = list[Utility.Random(list.Count)];
                  IBuyItemInfo[] buyInfo = vendor.GetBuyInfo();
  
                  GenericBuyInfo info = buyInfo[Utility.Random(buyInfo.Length)] as GenericBuyInfo;
  
                  if (!(info is BeverageBuyInfo) && !(info is AnimalBuyInfo) && info != null && info.Type != null && info.Args == null && info.Price < 5000)
                  {
                      list.Clear();
                      list.TrimExcess();
  
                      worth = info.Price;
                      name = info.Name;
  
                      return info.Type;
                  }
                  else
                      list.Remove(vendor);
              }
              while (list.Count > 0);
  
              list.Clear();
              list.TrimExcess();
              return null;
          }
  
          public static void OnTick()
          {
              List<TradeOrderCrate> crates = new List<TradeOrderCrate>(ActiveTrades.Values);
  
gt:             List<BaseCreature> toDelete = new List<BaseCreature>();