using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Decal.Interop.Core;
using Decal.Interop.Filters;
using Decal.Interop.Net;

namespace Decal.DecalFilters
{
    [ComVisible(true)]
    [Guid("53092D1B-F0B0-46FF-BF11-8F031EC9B137")]
    [ClassInterface(ClassInterfaceType.None)]
    [ComSourceInterfaces("Decal.Interop.Filters.IWorldEvents\0\0")]
    [ProgId("DecalFilters.World")]
    public class WorldImpl : IWorld, INetworkFilter2
    {
        private readonly Dictionary<int, WorldObjectImpl> _objects = new Dictionary<int, WorldObjectImpl>();
        private readonly VendorImpl _vendor = new VendorImpl();

        // COM events
        public event IWorldEvents_CreateObjectEventHandler CreateObject;
        public event IWorldEvents_ReleaseObjectEventHandler ReleaseObject;
        public event IWorldEvents_ChangeObjectEventHandler ChangeObject;
        public event IWorldEvents_MoveObjectEventHandler MoveObject;
        public event IWorldEvents_ReleaseDoneEventHandler ReleaseDone;
        public event IWorldEvents_EnterTradeEventHandler EnterTrade;
        public event IWorldEvents_EndTradeEventHandler EndTrade;
        public event IWorldEvents_AddTradeItemEventHandler AddTradeItem;
        public event IWorldEvents_AcceptTradeEventHandler AcceptTrade;
        public event IWorldEvents_DeclineTradeEventHandler DeclineTrade;
        public event IWorldEvents_ResetTradeEventHandler ResetTrade;
        public event IWorldEvents_FailToAddTradeItemEventHandler FailToAddTradeItem;
        public event IWorldEvents_FailToCompleteTradeEventHandler FailToCompleteTrade;
        public event IWorldEvents_ApproachVendorEventHandler ApproachVendor;

        // IWorld indexer - default property [DispId(0)]
        public WorldObject this[int GUID]
        {
            get
            {
                if (_objects.TryGetValue(GUID, out var obj))
                    return (WorldObject)(object)obj;
                return null;
            }
        }

        // Parameterized properties - returned as iterators
        public WorldIterator ByName => (WorldIterator)(object)new WorldIteratorImpl(this);
        public WorldIterator All
        {
            get
            {
                var iter = new WorldIteratorImpl(this);
                iter.ByAll();
                return (WorldIterator)(object)iter;
            }
        }
        public WorldIterator ByObjectClass => (WorldIterator)(object)new WorldIteratorImpl(this);
        public WorldIterator ByNameSubstring => (WorldIterator)(object)new WorldIteratorImpl(this);
        public WorldIterator ByContainer => (WorldIterator)(object)new WorldIteratorImpl(this);
        public WorldIterator Inventory
        {
            get
            {
                var iter = new WorldIteratorImpl(this);
                iter.ByInventory();
                return (WorldIterator)(object)iter;
            }
        }
        public WorldIterator ByOwner => (WorldIterator)(object)new WorldIteratorImpl(this);
        public WorldIterator Landscape
        {
            get
            {
                var iter = new WorldIteratorImpl(this);
                iter.ByLandscape();
                return (WorldIterator)(object)iter;
            }
        }
        public int NumObjectClasses => 40; // eObjectClass has ~40 values
        public WorldIterator ByCategory => (WorldIterator)(object)new WorldIteratorImpl(this);

        public double Distance2D(int GUID1, int GUID2) => 0;
        public double Distance3D(int GUID1, int GUID2) => 0;

        public Vendor Vendor => (Vendor)(object)_vendor;

        // INetworkFilter2
        public void Initialize(NetService pService) { }
        public void Terminate() { _objects.Clear(); }

        public void DispatchServer(IMessage2 Message)
        {
            int type = Message.Type;
            switch (type)
            {
                case unchecked((int)0xF745): OnCreateObject(Message); break;
                case unchecked((int)0xF747): OnDeleteObject(Message); break;
                case unchecked((int)0xF748): OnUpdateObject(Message); break;
                case unchecked((int)0xF749): OnMoveObject(Message); break;
            }
        }

        public void DispatchClient(IMessage2 Message) { }

        private void OnCreateObject(IMessage2 msg)
        {
            byte[] raw = msg.RawData;
            if (raw == null || raw.Length < 8) return;
            int objectId = BitConverter.ToInt32(raw, 4);
            var obj = new WorldObjectImpl { GUID = objectId };
            _objects[objectId] = obj;
            CreateObject?.Invoke((WorldObject)(object)obj);
        }

        private void OnDeleteObject(IMessage2 msg)
        {
            byte[] raw = msg.RawData;
            if (raw == null || raw.Length < 8) return;
            int objectId = BitConverter.ToInt32(raw, 4);
            if (_objects.TryGetValue(objectId, out var obj))
            {
                ReleaseObject?.Invoke((WorldObject)(object)obj);
                _objects.Remove(objectId);
            }
        }

        private void OnUpdateObject(IMessage2 msg)
        {
            byte[] raw = msg.RawData;
            if (raw == null || raw.Length < 8) return;
            int objectId = BitConverter.ToInt32(raw, 4);
            if (_objects.TryGetValue(objectId, out var obj))
                ChangeObject?.Invoke((WorldObject)(object)obj, WorldChangeType.wevtSizeChange);
        }

        private void OnMoveObject(IMessage2 msg)
        {
            byte[] raw = msg.RawData;
            if (raw == null || raw.Length < 8) return;
            int objectId = BitConverter.ToInt32(raw, 4);
            if (_objects.TryGetValue(objectId, out var obj))
                MoveObject?.Invoke((WorldObject)(object)obj);
        }

        internal List<WorldObjectImpl> GetAllObjects() => new List<WorldObjectImpl>(_objects.Values);
        internal WorldObjectImpl GetObject(int id) { _objects.TryGetValue(id, out var o); return o; }
    }
}
