using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Decal.Interop.Filters;

namespace Decal.DecalFilters
{
    [ComVisible(true)]
    [Guid("2681B113-294E-4ABF-B543-624194846BE1")]
    [ClassInterface(ClassInterfaceType.None)]
    [ProgId("DecalFilters.WorldIterator")]
    public class WorldIteratorImpl : IWorldIterator
    {
        private readonly WorldImpl _world;
        private List<WorldObjectImpl> _filtered;
        private int _index = -1;
        private readonly List<Func<WorldObjectImpl, bool>> _filters = new List<Func<WorldObjectImpl, bool>>();

        internal WorldIteratorImpl(WorldImpl world)
        {
            _world = world;
            ApplyFilters();
        }

        public WorldObject Next_Old
        {
            get
            {
                _index++;
                return (_index < _filtered.Count)
                    ? (WorldObject)(object)_filtered[_index]
                    : null;
            }
        }

        public void Reset() => _index = -1;

        public int Count => _filtered?.Count ?? 0;

        public bool Next(ref WorldObject ppObject)
        {
            _index++;
            if (_index < _filtered.Count)
            {
                ppObject = (WorldObject)(object)_filtered[_index];
                return true;
            }
            ppObject = null;
            return false;
        }

        public void Pop()
        {
            if (_filters.Count > 0)
            {
                _filters.RemoveAt(_filters.Count - 1);
                ApplyFilters();
            }
        }

        public void ByName(string strName)
        {
            _filters.Add(o => string.Equals(o.Name, strName, StringComparison.OrdinalIgnoreCase));
            ApplyFilters();
        }

        public void ByNameSubstring(string strSubstring)
        {
            _filters.Add(o => o.Name != null && o.Name.IndexOf(strSubstring, StringComparison.OrdinalIgnoreCase) >= 0);
            ApplyFilters();
        }

        public void ByObjectClass(eObjectClass Class)
        {
            _filters.Add(o => o.ObjectClass == Class);
            ApplyFilters();
        }

        public void ByAll()
        {
            _filters.Clear();
            ApplyFilters();
        }

        public void ByInventory()
        {
            _filters.Add(o => o.Container != 0);
            ApplyFilters();
        }

        public void ByContainer(int nContainer)
        {
            _filters.Add(o => o.Container == nContainer);
            ApplyFilters();
        }

        public int Quantity
        {
            get
            {
                int total = 0;
                foreach (var obj in _filtered)
                    total += Math.Max(1, obj.Longs(LongValueKey.keyStackCount, 1));
                return total;
            }
        }

        public void ByOwner(int nOwner)
        {
            _filters.Add(o => o.Longs(LongValueKey.keyContainer) == nOwner);
            ApplyFilters();
        }

        public void ByLandscape()
        {
            _filters.Add(o => o.Container == 0);
            ApplyFilters();
        }

        public void ByCategory(int nCategory)
        {
            _filters.Add(o => o.Category == nCategory);
            ApplyFilters();
        }

        public IEnumerator GetEnumerator()
        {
            Reset();
            return new WorldIteratorEnumerator(this);
        }

        private void ApplyFilters()
        {
            var all = _world.GetAllObjects();
            _filtered = all.Where(o =>
            {
                foreach (var f in _filters)
                    if (!f(o)) return false;
                return true;
            }).ToList();
            _index = -1;
        }

        private class WorldIteratorEnumerator : IEnumerator
        {
            private readonly WorldIteratorImpl _iter;
            public WorldIteratorEnumerator(WorldIteratorImpl iter) { _iter = iter; }
            public object Current => _iter._index >= 0 && _iter._index < _iter._filtered.Count
                ? _iter._filtered[_iter._index] : null;
            public bool MoveNext()
            {
                _iter._index++;
                return _iter._index < _iter._filtered.Count;
            }
            public void Reset() => _iter._index = -1;
        }
    }
}
