using System;
using Decal.Adapter.Support;
using Decal.Interop.Filters;

namespace Decal.Adapter.Wrappers;

public class WorldFilter : GenericDisposableWrapper<World>
{
	private DisposableObjectDictionary<int, WorldObject> worldObjectCache;

	private EventHandler<AcceptTradeEventArgs> mAcceptTrade;

	private EventHandler<AddTradeItemEventArgs> mAddTradeItem;

	private EventHandler<ApproachVendorEventArgs> mApproachVendor;

	private EventHandler<ChangeObjectEventArgs> mChangeObject;

	private EventHandler<CreateObjectEventArgs> mCreateObject;

	private EventHandler<DeclineTradeEventArgs> mDeclineTrade;

	private EventHandler<EndTradeEventArgs> mEndTrade;

	private EventHandler<EnterTradeEventArgs> mEnterTrade;

	private EventHandler<FailToAddTradeItemEventArgs> mFailToAddTradeItem;

	private EventHandler mFailToCompleteTrade;

	private EventHandler<MoveObjectEventArgs> mMoveObject;

	private EventHandler mReleaseDone;

	private EventHandler<ReleaseObjectEventArgs> mReleaseObject;

	private EventHandler<ResetTradeEventArgs> mResetTrade;

	public WorldObject this[int guid] => GetCachedWorldObject(guid);

	/// <summary>
	/// Returns the currently open vendor object.  You **MUST** dispose this object when finished with it.
	/// </summary>
	public Vendor OpenVendor
	{
		get
		{
			Decal.Interop.Filters.Vendor vendor = base.Wrapped.Vendor;
			if (vendor != null)
			{
				return new Vendor(this, vendor);
			}
			return null;
		}
	}

	[CLSCompliant(false)]
	public event EventHandler<AcceptTradeEventArgs> AcceptTrade
	{
		add
		{
			mAcceptTrade = (EventHandler<AcceptTradeEventArgs>)Delegate.Combine(mAcceptTrade, value);
		}
		remove
		{
			mAcceptTrade = (EventHandler<AcceptTradeEventArgs>)Delegate.Remove(mAcceptTrade, value);
		}
	}

	[CLSCompliant(false)]
	public event EventHandler<AddTradeItemEventArgs> AddTradeItem
	{
		add
		{
			mAddTradeItem = (EventHandler<AddTradeItemEventArgs>)Delegate.Combine(mAddTradeItem, value);
		}
		remove
		{
			mAddTradeItem = (EventHandler<AddTradeItemEventArgs>)Delegate.Remove(mAddTradeItem, value);
		}
	}

	[CLSCompliant(false)]
	public event EventHandler<ApproachVendorEventArgs> ApproachVendor
	{
		add
		{
			mApproachVendor = (EventHandler<ApproachVendorEventArgs>)Delegate.Combine(mApproachVendor, value);
		}
		remove
		{
			mApproachVendor = (EventHandler<ApproachVendorEventArgs>)Delegate.Remove(mApproachVendor, value);
		}
	}

	[CLSCompliant(false)]
	public event EventHandler<ChangeObjectEventArgs> ChangeObject
	{
		add
		{
			mChangeObject = (EventHandler<ChangeObjectEventArgs>)Delegate.Combine(mChangeObject, value);
		}
		remove
		{
			mChangeObject = (EventHandler<ChangeObjectEventArgs>)Delegate.Remove(mChangeObject, value);
		}
	}

	[CLSCompliant(false)]
	public event EventHandler<CreateObjectEventArgs> CreateObject
	{
		add
		{
			mCreateObject = (EventHandler<CreateObjectEventArgs>)Delegate.Combine(mCreateObject, value);
		}
		remove
		{
			mCreateObject = (EventHandler<CreateObjectEventArgs>)Delegate.Remove(mCreateObject, value);
		}
	}

	[CLSCompliant(false)]
	public event EventHandler<DeclineTradeEventArgs> DeclineTrade
	{
		add
		{
			mDeclineTrade = (EventHandler<DeclineTradeEventArgs>)Delegate.Combine(mDeclineTrade, value);
		}
		remove
		{
			mDeclineTrade = (EventHandler<DeclineTradeEventArgs>)Delegate.Remove(mDeclineTrade, value);
		}
	}

	[CLSCompliant(false)]
	public event EventHandler<EndTradeEventArgs> EndTrade
	{
		add
		{
			mEndTrade = (EventHandler<EndTradeEventArgs>)Delegate.Combine(mEndTrade, value);
		}
		remove
		{
			mEndTrade = (EventHandler<EndTradeEventArgs>)Delegate.Remove(mEndTrade, value);
		}
	}

	[CLSCompliant(false)]
	public event EventHandler<EnterTradeEventArgs> EnterTrade
	{
		add
		{
			mEnterTrade = (EventHandler<EnterTradeEventArgs>)Delegate.Combine(mEnterTrade, value);
		}
		remove
		{
			mEnterTrade = (EventHandler<EnterTradeEventArgs>)Delegate.Remove(mEnterTrade, value);
		}
	}

	[CLSCompliant(false)]
	public event EventHandler<FailToAddTradeItemEventArgs> FailToAddTradeItem
	{
		add
		{
			mFailToAddTradeItem = (EventHandler<FailToAddTradeItemEventArgs>)Delegate.Combine(mFailToAddTradeItem, value);
		}
		remove
		{
			mFailToAddTradeItem = (EventHandler<FailToAddTradeItemEventArgs>)Delegate.Remove(mFailToAddTradeItem, value);
		}
	}

	[CLSCompliant(false)]
	public event EventHandler FailToCompleteTrade
	{
		add
		{
			mFailToCompleteTrade = (EventHandler)Delegate.Combine(mFailToCompleteTrade, value);
		}
		remove
		{
			mFailToCompleteTrade = (EventHandler)Delegate.Remove(mFailToCompleteTrade, value);
		}
	}

	[CLSCompliant(false)]
	public event EventHandler<MoveObjectEventArgs> MoveObject
	{
		add
		{
			mMoveObject = (EventHandler<MoveObjectEventArgs>)Delegate.Combine(mMoveObject, value);
		}
		remove
		{
			mMoveObject = (EventHandler<MoveObjectEventArgs>)Delegate.Remove(mMoveObject, value);
		}
	}

	[CLSCompliant(false)]
	public event EventHandler ReleaseDone
	{
		add
		{
			mReleaseDone = (EventHandler)Delegate.Combine(mReleaseDone, value);
		}
		remove
		{
			mReleaseDone = (EventHandler)Delegate.Remove(mReleaseDone, value);
		}
	}

	[CLSCompliant(false)]
	public event EventHandler<ReleaseObjectEventArgs> ReleaseObject
	{
		add
		{
			mReleaseObject = (EventHandler<ReleaseObjectEventArgs>)Delegate.Combine(mReleaseObject, value);
		}
		remove
		{
			mReleaseObject = (EventHandler<ReleaseObjectEventArgs>)Delegate.Remove(mReleaseObject, value);
		}
	}

	[CLSCompliant(false)]
	public event EventHandler<ResetTradeEventArgs> ResetTrade
	{
		add
		{
			mResetTrade = (EventHandler<ResetTradeEventArgs>)Delegate.Combine(mResetTrade, value);
		}
		remove
		{
			mResetTrade = (EventHandler<ResetTradeEventArgs>)Delegate.Remove(mResetTrade, value);
		}
	}

	public WorldFilter(World obj)
		: base(obj)
	{
		worldObjectCache = new DisposableObjectDictionary<int, WorldObject>("Id");
		base.Wrapped.AcceptTrade += myWF_AcceptTrade;
		base.Wrapped.AddTradeItem += myWF_AddTradeItem;
		base.Wrapped.ApproachVendor += myWF_ApproachVendor;
		base.Wrapped.ChangeObject += myWF_ChangeObject;
		base.Wrapped.CreateObject += myWF_CreateObject;
		base.Wrapped.DeclineTrade += myWF_DeclineTrade;
		base.Wrapped.EndTrade += myWF_EndTrade;
		base.Wrapped.EnterTrade += myWF_EnterTrade;
		base.Wrapped.FailToAddTradeItem += myWF_FailToAddTradeItem;
		base.Wrapped.FailToCompleteTrade += myWF_FailToCompleteTrade;
		base.Wrapped.MoveObject += myWF_MoveObject;
		base.Wrapped.ReleaseDone += myWF_ReleaseDone;
		base.Wrapped.ReleaseObject += myWF_ReleaseObject;
		base.Wrapped.ResetTrade += myWF_ResetTrade;
		CoreManager.Current.PluginTermComplete += corePluginTermComplete;
	}

	protected override void Dispose(bool userCalled)
	{
		if (userCalled && base.Wrapped != null)
		{
			ClearCache();
			base.Wrapped.AcceptTrade -= myWF_AcceptTrade;
			base.Wrapped.AddTradeItem -= myWF_AddTradeItem;
			base.Wrapped.ApproachVendor -= myWF_ApproachVendor;
			base.Wrapped.ChangeObject -= myWF_ChangeObject;
			base.Wrapped.CreateObject -= myWF_CreateObject;
			base.Wrapped.DeclineTrade -= myWF_DeclineTrade;
			base.Wrapped.EndTrade -= myWF_EndTrade;
			base.Wrapped.EnterTrade -= myWF_EnterTrade;
			base.Wrapped.FailToAddTradeItem -= myWF_FailToAddTradeItem;
			base.Wrapped.FailToCompleteTrade -= myWF_FailToCompleteTrade;
			base.Wrapped.MoveObject -= myWF_MoveObject;
			base.Wrapped.ReleaseDone -= myWF_ReleaseDone;
			base.Wrapped.ReleaseObject -= myWF_ReleaseObject;
			base.Wrapped.ResetTrade -= myWF_ResetTrade;
			CoreManager.Current.PluginTermComplete -= corePluginTermComplete;
			worldObjectCache.Dispose();
		}
		base.Dispose(userCalled);
	}

	private void ClearCache()
	{
		worldObjectCache.Clear();
	}

	private void corePluginTermComplete(object sender, EventArgs e)
	{
		ClearCache();
	}

	private void myWF_AcceptTrade(int TargetID)
	{
		Util.SafeFireEvent(this, mAcceptTrade, new AcceptTradeEventArgs(TargetID));
	}

	private void myWF_AddTradeItem(int ItemId, int SideId)
	{
		Util.SafeFireEvent(this, mAddTradeItem, new AddTradeItemEventArgs(ItemId, SideId));
	}

	private void myWF_ApproachVendor(int MerchantId)
	{
		Vendor openVendor = OpenVendor;
		Util.SafeFireEvent(this, mApproachVendor, new ApproachVendorEventArgs(MerchantId, openVendor));
		openVendor.Dispose();
	}

	private void myWF_ChangeObject(Decal.Interop.Filters.WorldObject pObject, Decal.Interop.Filters.WorldChangeType Change)
	{
		WorldObject worldObject = null;
		if (Change != Decal.Interop.Filters.WorldChangeType.wevtVendorIdentReceived)
		{
			worldObject = GetCachedWorldObject(pObject);
			Util.SafeFireEvent(this, mChangeObject, new ChangeObjectEventArgs(worldObject, Change));
			return;
		}
		using Vendor vendor = OpenVendor;
		worldObject = vendor.GetCachedObject(pObject);
		Util.SafeFireEvent(this, mChangeObject, new ChangeObjectEventArgs(worldObject, Change));
	}

	private void myWF_CreateObject(Decal.Interop.Filters.WorldObject pObject)
	{
		WorldObject cachedWorldObject = GetCachedWorldObject(pObject);
		Util.SafeFireEvent(this, mCreateObject, new CreateObjectEventArgs(cachedWorldObject));
	}

	private void myWF_DeclineTrade(int TraderId)
	{
		Util.SafeFireEvent(this, mDeclineTrade, new DeclineTradeEventArgs(TraderId));
	}

	private void myWF_EndTrade(int ReasonId)
	{
		Util.SafeFireEvent(this, mEndTrade, new EndTradeEventArgs(ReasonId));
	}

	private void myWF_EnterTrade(int TraderId, int TradeeId)
	{
		Util.SafeFireEvent(this, mEnterTrade, new EnterTradeEventArgs(TraderId, TradeeId));
	}

	private void myWF_FailToAddTradeItem(int ItemId, int ReasonId)
	{
		Util.SafeFireEvent(this, mFailToAddTradeItem, new FailToAddTradeItemEventArgs(ItemId, ReasonId));
	}

	private void myWF_FailToCompleteTrade()
	{
		Util.SafeFireEvent(this, mFailToCompleteTrade, new EventArgs());
	}

	private void myWF_MoveObject(Decal.Interop.Filters.WorldObject pObject)
	{
		WorldObject cachedWorldObject = GetCachedWorldObject(pObject);
		Util.SafeFireEvent(this, mMoveObject, new MoveObjectEventArgs(cachedWorldObject));
	}

	private void myWF_ReleaseDone()
	{
		Util.SafeFireEvent(this, mReleaseDone, new EventArgs());
	}

	private void myWF_ReleaseObject(Decal.Interop.Filters.WorldObject pObject)
	{
		if (pObject != null)
		{
			WorldObject cachedWorldObject = GetCachedWorldObject(pObject);
			Util.SafeFireEvent(this, mReleaseObject, new ReleaseObjectEventArgs(cachedWorldObject));
			cachedWorldObject.Dispose();
		}
	}

	private void myWF_ResetTrade(int TraderId)
	{
		Util.SafeFireEvent(this, mResetTrade, new ResetTradeEventArgs(TraderId));
	}

	internal WorldObject GetCachedWorldObject(Decal.Interop.Filters.WorldObject iwo)
	{
		WorldObject value = null;
		if (!worldObjectCache.TryGetValue(iwo.GUID, out value))
		{
			value = new WorldObject(iwo);
			worldObjectCache.Add(value.Id, value);
		}
		return value;
	}

	internal WorldObject GetCachedWorldObject(int id)
	{
		WorldObject value = null;
		if (!worldObjectCache.TryGetValue(id, out value))
		{
			Decal.Interop.Filters.WorldObject worldObject = base.Wrapped[id];
			if (worldObject != null)
			{
				value = new WorldObject(worldObject);
				worldObjectCache.Add(value.Id, value);
			}
		}
		return value;
	}

	/// <summary>
	/// Returns the 2D distance between two items
	/// </summary>
	/// <param name="id1">Id of the first item</param>
	/// <param name="id2">Id of the second item</param>
	/// <returns>The distance between the items</returns>
	public double Distance(int id1, int id2)
	{
		return Distance(id1, id2, use3d: false);
	}

	/// <summary>
	/// Returns the distance between two items
	/// </summary>
	/// <param name="id1">Id of the first item</param>
	/// <param name="id2">Id of the second item</param>
	/// <param name="use3d">Whether or not height is taken into account</param>
	/// <returns>The distance between the items</returns>
	public double Distance(int id1, int id2, bool use3d)
	{
		if (use3d)
		{
			return base.Wrapped.Distance3D(id1, id2);
		}
		return base.Wrapped.Distance2D(id1, id2);
	}

	private WorldObjectCollection wrapIterator(WorldIterator wi)
	{
		if (wi != null)
		{
			return new WorldObjectCollection(this, wi);
		}
		return null;
	}

	/// <summary>
	/// Gets all of the items in the player's inventory.
	/// </summary>
	/// <returns>WorldObjectCollection containing the items in the character's inventory</returns>
	public WorldObjectCollection GetInventory()
	{
		return wrapIterator(base.Wrapped.Inventory);
	}

	/// <summary>
	/// Gets all of the items known to WorldFilter.
	/// </summary>
	/// <returns>WorldObjectCollection containing all of the items in the world</returns>
	public WorldObjectCollection GetAll()
	{
		return wrapIterator(base.Wrapped.All);
	}

	/// <summary>
	/// Gets all of the items known to WorldFilter that don't have a container.
	/// </summary>
	/// <returns>WorldObjectCollection containing all of the items on the landscape</returns>
	public WorldObjectCollection GetLandscape()
	{
		return wrapIterator(base.Wrapped.Landscape);
	}

	/// <summary>
	/// Gets all of the items in the given container.
	/// </summary>
	/// <param name="container">Id of the container</param>
	/// <returns>WorldObjectCollection containing the items in the container</returns>
	public WorldObjectCollection GetByContainer(int container)
	{
		return wrapIterator(((IWorld)base.Wrapped).get_ByContainer(container));
	}

	/// <summary>
	/// Gets all of the items with the specified category.
	/// </summary>
	/// <param name="category">Category</param>
	/// <returns>WorldObjectCollection containing the items in the category</returns>
	public WorldObjectCollection GetByCategory(int category)
	{
		return wrapIterator(((IWorld)base.Wrapped).get_ByCategory(category));
	}

	/// <summary>
	/// Gets all of the items that have the specified name.
	/// </summary>
	/// <param name="name">Name of the items</param>
	/// <returns>WorldObjectCollection containing the items with the name</returns>
	public WorldObjectCollection GetByName(string name)
	{
		return wrapIterator(((IWorld)base.Wrapped).get_ByName(name));
	}

	/// <summary>
	/// Gets all of the items with names that contain the specified string.
	/// </summary>
	/// <param name="name">Partial name of items</param>
	/// <returns>WorldObjectCollection containing the items with the name part</returns>
	public WorldObjectCollection GetByNameSubstring(string name)
	{
		return wrapIterator(((IWorld)base.Wrapped).get_ByNameSubstring(name));
	}

	/// <summary>
	/// Gets all of the items in the object class
	/// </summary>
	/// <param name="objClass">class of the objects</param>
	/// <returns>WorldObjectCollection containing the items in the class</returns>
	public WorldObjectCollection GetByObjectClass(ObjectClass objClass)
	{
		return wrapIterator(((IWorld)base.Wrapped).get_ByObjectClass((eObjectClass)objClass));
	}

	/// <summary>
	/// Gets the items owned by the specified character
	/// </summary>
	/// <param name="owner">Id of the owning character</param>
	/// <returns>WorldObjectCollection containing the items owned by the character</returns>
	public WorldObjectCollection GetByOwner(int owner)
	{
		return wrapIterator(((IWorld)base.Wrapped).get_ByOwner(owner));
	}
}
