using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MonoGame.FourEngine.Components;
using MonoGame.FourEngine.Extensions;
using MonoGame.FourEngine.Services;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace MonoGame.FourEngine
{
    public abstract class Scene : IDisposable
    {
        public IReadOnlyCollection<Entity> Entities { get; private set; }
        public DrawingSurfaceBase DrawingSurface
        {
            get => _drawingSurface;
            internal set
            {
                if (_drawingSurface != null)
                    OnDetach(_drawingSurface);
                OnAttach(_drawingSurface = value);
            }
        }

        protected virtual ISpriteBatchFactory SpriteBatchFactory => ServiceLocator.SpriteBatchFactory;

        private List<Entity> EntitiesToAdd { get; } = new List<Entity>();
        private List<Entity> EntitiesToRemove { get; } = new List<Entity>();
        
        private DrawingSurfaceBase _drawingSurface;

        private List<Entity> _entities = new List<Entity>();

        private SpriteBatch _batch;

        public Scene()
        {
            _batch = SpriteBatchFactory.Create(FourEngineGame.Current.GraphicsDevice);
            Entities = new List<Entity>();
        }

        public void Update(GameTime time)
        {
            Update(new UpdateEventArgs(
                this, 
                ServiceLocator.PointerControl, 
                ServiceLocator.KeyboardControl, 
                ServiceLocator.GamePadControl,
                time.ElapsedGameTime));
        }

        public void Update(UpdateEventArgs args)
        {
            OnUpdate(args);
            // Add entites
            foreach (var entity in EntitiesToAdd)
            {
                _entities.Add(entity);
            }
            EntitiesToAdd.Clear();
            RefreshEntities();

            // Remove entites
            foreach (var entity in EntitiesToRemove)
            {
                _entities.Remove(entity);
            }
            EntitiesToRemove.Clear();
            RefreshEntities();
        }

        public void Draw(GameTime time)
        {
            Clear();
            SpriteBatchFactory.Begin(_batch);
            var drawArgs = new DrawEventArgs(this, _batch, time.ElapsedGameTime);
            OnDraw(drawArgs);
            SpriteBatchFactory.End(_batch);
        }

        private void Clear()
        {
            var clearArgs = new ClearEventArgs(this);
            OnClear(clearArgs);
            if (!clearArgs.IsCanceled)
                FourEngineGame.Current.GraphicsDevice.Clear(clearArgs.Options, clearArgs.Color, clearArgs.Depth, clearArgs.Stencil);
        }

        public Entity AddEntity(Entity entity)
        {
            if (_disposed) return null;

            entity.OnAttach(this);
            EntitiesToAdd.Add(entity);
            return entity;
        }

        public void RemoveEntity(Entity entity)
        {
            if (_disposed) return;

            entity.OnDetach(this);
            EntitiesToRemove.Add(entity);
        }

        public void RemoveAllEntities(Func<Entity, bool> predicate = null)
        {
            if (_disposed) return;

            foreach (var entity in Entities)
            {
                if (predicate?.Invoke(entity) ?? true)
                    RemoveEntity(entity);
            }
        }

        public virtual void OnUpdate(UpdateEventArgs args)
        {
            if (_disposed) return;

            // Update entites
            var components = Entities.SelectMany(x => x.Components).ToList();
            var argsCopy = args.Clone();
            var pointerCopy = argsCopy.Pointer.Clone();
            foreach (var component in components)
            {
                if (component is IDrawableComponent drawable && drawable.ManipulationEnabled)
                {
                    argsCopy.Pointer = pointerCopy.Clone().Apply(drawable.DrawArgs);
                }
                else
                {
                    argsCopy.Pointer = pointerCopy;
                };
                component.OnUpdate(argsCopy);
            }

            // Update controls
            var rect = new Rectangle(0, 0, DrawingSurface.Width, DrawingSurface.Height);
            if (!rect.Contains(args.Pointer.Position))
                return;
            if (args.Pointer.HasPrimaryClicked)
            {
                var clickedArgs = new ClickedEventArgs(this, args.Pointer);
                OnPrimaryClick(clickedArgs);
            }
            if (args.Pointer.HasSecondaryClicked)
            {
                var clickedArgs = new ClickedEventArgs(this, args.Pointer);
                OnSecondaryClick(clickedArgs);
            }
        }

        public virtual void OnDraw(DrawEventArgs args)
        {
            if (_disposed) return;

            var components = Entities.SelectMany(x => x.Components).OrderBy(x => x.Layer).ToList();
            foreach (var component in components)
                component.OnDraw(args);
        }

        public virtual void OnAttach(DrawingSurfaceBase surface)
        {
        }

        public virtual void OnDetach(DrawingSurfaceBase surface)
        {
        }

        public virtual void OnClear(ClearEventArgs args)
        {
        }

        private bool _disposed;
        public void Dispose()
        {
            if (!_disposed)
            {
                foreach (var entity in _entities.ToList())
                {
                    entity.OnDetach(this);
                    entity.Dispose();
                }
                _entities.Clear();

                foreach (var entity in EntitiesToAdd.ToList())
                {
                    entity.OnDetach(this);
                    entity.Dispose();
                }
                EntitiesToAdd.Clear();
                
                foreach (var entity in EntitiesToRemove.ToList())
                {
                    entity.OnDetach(this);
                    entity.Dispose();
                }
                EntitiesToRemove.Clear();

                RefreshEntities();
                ServiceLocator.ResourceManager.Clear();

                _disposed = true;
            }
        }

        public void EnableManipulation()
            => DrawingSurface.Component.ManipulationEnabled = true;

        public virtual void OnPrimaryClick(ClickedEventArgs point) { }

        public virtual void OnSecondaryClick(ClickedEventArgs args) { }

        private void RefreshEntities()
        {
            Entities = new ReadOnlyCollection<Entity>(_entities);
        }
    }
}