using Microsoft.Xna.Framework.Content;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace RecrownedAthenaeum.ContentSystem
{
    /// <summary>
    /// Wrapper for the content manager that helps with controlling it by adding automated multithreaded content loading.
    /// </summary>
    public class ContentManagerController
    {
        Thread thread;
        readonly ContentManager contentManager;
        readonly Queue<ContentData> queue;
        Dictionary<string, Object> assets;
        /// <summary>
        /// Path modifiers to change the path in which the content manager looks to load a file. Used for better organizing things while not needing to type entire path.
        /// </summary>
        public readonly Dictionary<Type, IContentPathResolver> contentPathModifier;
        /// <summary>
        /// Used when no path modifier is defined for that specific type.
        /// </summary>
        public IContentPathResolver normalPathModifier = new NormalContentResolver();
        volatile float progress;
        volatile bool running;

        /// <summary>
        /// Whether or not the queue is empty and all content is loaded.
        /// </summary>
        public bool Done { get { return !running && queue.Count == 0; } }

        /// <summary>
        /// The progress of the loading. 1 is complete while 0 is incomplete.
        /// </summary>
        public float Progress { get { return progress; } }

        /// <summary>
        /// Wraps the <see cref="ContentManager"/>.
        /// </summary>
        /// <param name="contentManager">The manager to wrap.</param>
        public ContentManagerController(ContentManager contentManager)
        {
            this.contentManager = contentManager;
            assets = new Dictionary<string, Object>();
            queue = new Queue<ContentData>();
            contentPathModifier = new Dictionary<Type, IContentPathResolver>();
        }

        private void Load(string assetName, Type type, bool usePathModifier)
        {
            Debug.WriteLine("Loading asset: " + assetName);
            string path = assetName;
            if (usePathModifier)
            {
                IContentPathResolver handler;
                if (contentPathModifier.ContainsKey(type))
                {
                    handler = contentPathModifier[type];
                }
                else
                {
                    handler = normalPathModifier;
                }
                path = handler.Modify(assetName);
            }
            assets.Add(assetName, contentManager.Load<Object>(path));

        }

        /// <summary>
        /// Gets the requested asset.
        /// </summary>
        /// <typeparam name="T">The type of the asset for an alternative way to cast.</typeparam>
        /// <param name="assetName">The name of the asset.</param>
        /// <returns>The asset casted to the type given with T.</returns>
        public T Get<T>(string assetName)
        {
            lock (queue)
            {
                return (T)assets[assetName];
            }
        }

        /// <summary>
        /// Queues an asset to be loaded.
        /// </summary>
        /// <typeparam name="T">The type of the asset to be queued.</typeparam>
        /// <param name="assetName">Name of asset to look for.</param>
        /// <param name="usePathModifier">Whether or not to use the path modifiers.</param>
        public void Queue<T>(string assetName, bool usePathModifier = true)
        {
            lock (queue)
            {
                if (!assets.ContainsKey(assetName))
                {
                    queue.Enqueue(new ContentData(assetName, typeof(T), usePathModifier));
                    Debug.WriteLine("Queued asset: " + assetName);
                }
                else
                {
                    throw new InvalidOperationException("Did not queue asset due to asset with same name being loaded: " + assetName);
                }
            }
        }

        /// <summary>
        /// Called whenever a batch of assets should be loaded from the queue. Safe to call once every frame.
        /// </summary>
        public void Update()
        {
            if (queue.Count > 0 && (thread == null || !thread.IsAlive))
            {
                thread = new Thread(LoadBatch);
                thread.Start();
            }
        }

        private void LoadBatch()
        {
            running = true;
            int totalTasks = queue.Count;
            int tasksCompleted = 0;
            while (queue.Count != 0)
            {
                lock (queue)
                {
                    ContentData content = queue.Dequeue();
                    Load(content.assetName, content.type, content.usePathModifier);
                    tasksCompleted++;
                    progress = (float)tasksCompleted / totalTasks;
                }
            }
            running = false;
        }
        /// <summary>
        /// Removes the asset from the list of assets in the system.
        /// Cannot remove from queue.
        /// </summary>
        /// <param name="name">the string name used to load the asset</param>
        public void Remove(string name)
        {
            lock (queue)
            {
                if (assets.ContainsKey(name))
                {
                    if (assets[name] is IDisposable)
                    {
                        ((IDisposable)assets[name]).Dispose();
                    }
                    assets.Remove(name);
                }
            }
        }

        /// <summary>
        /// Clears the queue.
        /// </summary>
        public void ClearQueue()
        {
            lock (queue)
            {
                queue.Clear();
            }
        }

        /// <summary>
        /// Unloads everything from both queue and loaded list while properly disposing of the assets loaded.
        /// </summary>
        public void UnloadAll()
        {
            lock (queue)
            {
                contentManager.Unload();
                assets.Clear();
                ClearQueue();
                Debug.WriteLine("Unloaded all assets.");
            }
        }


    }
}
