using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace VisimBox.Resources
{
    //Resource manager stores various objects in the memory and allows access to them. While most resources use generic resource manager, some may use specific type,
    //for example renderer resource managment.



    /// <summary>
    /// Each of the possible resource type. Use generic for unspecified objects
    /// </summary>
    public enum ResourceType : int
    {
        /// <summary>
        /// Generic object
        /// </summary>
        Generic = 0,
        /// <summary>
        /// String table - every entry has assigned array of strings
        /// </summary>       
        Strings = 1,
        /// <summary>
        /// System.Drawing Bitmap
        /// </summary>        
        Bitmap = 2,
        /// <summary>
        ///  System.Drawing Font
        /// </summary>         
        SystemFont = 3,            
        
        // renderer resources

        /// <summary>
        /// Texture in texture memory
        /// </summary>
        Texture = 10,               
        /// <summary>
        /// Vertex buffer
        /// </summary>
        VertexBuffer = 11,
        /// <summary>
        /// Index buffer
        /// </summary>
        IndexBuffer = 12,
        /// <summary>
        /// Complete mesh (vertex + index buffer)
        /// </summary>
        Mesh = 13,
        /// <summary>
        /// Font prepared for rendering
        /// </summary>
        Font = 14,
        /// <summary>
        /// Rendering material
        /// </summary>   
        Material = 15,          
        /// <summary>
        /// Shader effect
        /// </summary>
        Shader = 16,
        Light = 17
    };

    /// <summary>
    /// Type of the resource usage
    /// </summary>
    public enum ResourceUsage : int
    {
        /// <summary>
        /// Static resource, create once, use always
        /// </summary>
        Static = 0,                 
        /// <summary>
        /// Dynamic resource, may change over time
        /// </summary>
        Dynamic = 1,                
        /// <summary>
        /// Streamed resource, changes constantly, almost every frame
        /// </summary>
        Streamed = 2,
        /// <summary>
        /// Dynamic, user code can only read (output from renderer)
        /// </summary>
        ReadOnly = 3,               
        /// <summary>
        /// Dynamic, user code can only write (input for renderer)
        /// </summary>
        WriteOnly = 4,
        /// <summary>
        /// Streaming resource read only
        /// </summary>
        StreamedRead = 5,
        /// <summary>
        /// Streaming resource, write only
        /// </summary>
        StreamedWrite = 6
    };

    /// <summary>
    /// Interface for resource consumers - resource will call all of it's registered consumers on some events
    /// </summary>
    public interface IResourceConsumer
    {
        /// <summary>
        /// Callback for resource disposing - just before releasing itself from memory resource 
        /// will call all it's consumers to notify them it will be no longer valid
        /// </summary>
        /// <param name="resource">Resource which is being disposed</param>
        void ResourceDisposing(IResource resource);

        /// <summary>
        /// Resource was replaced by another resource. Consumers can decide to keep new resource instead or hold to original value.
        /// If consumers wants to hold new resource instead, it should change it's pointers and return true, references in resources will be automatically updated.
        /// If false, consumer is not interested in new resource - depending on the nature of replace it may then get ResourceDisposing callback.
        /// </summary>
        /// <param name="original">Original resource registered with consumer</param>
        /// <param name="newresource">New resource</param>
        /// <returns>Consumer must return true if it want's to change reference to newresource, otherwise false.</returns>
        bool ResourceReplaced(IResource original, IResource newresource);
    }

    /// <summary>
    /// Interface for resource
    /// </summary>
    public interface IResource : IDisposable
    {
        /// <summary>
        /// Generic type of the resource
        /// </summary>
        ResourceType ResourceType { get; }
        /// <summary>
        /// Type of the resource usage (static/dynamic)
        /// </summary>
        ResourceUsage Usage { get;}


        /// <summary>
        /// File used to create the resource (if it was loaded from the disk)
        /// </summary>
        string FileName {get; set;}


        /// <summary>
        /// Number of current references, 0 means no references
        /// </summary>
        int References { get;}

        /// <summary>
        /// Returns number of null generic references
        /// </summary>
        int NullReferences { get; }

        /// <summary>
        /// Adds reference to this resource
        /// </summary>
        /// <param name="consumer">Consumer of resource. Note, can be null for generic reference</param>
        void AddReference(IResourceConsumer consumer);

        /// <summary>
        /// Removes reference to this resource
        /// </summary>
        /// <param name="consumer">Consumer of resource. Note, can be null for generic reference.</param>
        void RemoveReference(IResourceConsumer consumer);

        /// <summary>
        /// Manager wants to replace this resource by another - resource should inform all of it's consumers that it's being replaced 
        /// and update references based on their response
        /// It should also check if new resource is of compatible type
        /// </summary>
        /// <param name="newresource">New resource</param>
        /// <returns>True if resource is compatible type and all consumers accepted resource changing, false otherwise</returns>
        bool ReplaceReferences(IResource newresource);

        /// <summary>
        /// If true, resource will stay in memory even if it's not referenced
        /// </summary>
        bool AlwaysStay { get; set; }

        #region future improvements

        /*
        // These properties are placeholders and reminders for future more advanced uses




        /// <summary>
        /// If true and resource si created from the disk file, it will be reloaded with every change
        /// </summary>
        bool AutoReload { get; set; }
        */

        #endregion
    }


    /// <summary>
    /// Class for wrapping generic resources
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Resource<T> : IResource
    {
        protected T _data;
        protected string _filename;

        public virtual T Data { get { return _data; } }

        protected Resource() 
        {
        }

        public Resource(T data) : this()
        {
            _data = data;
        }

        #region IResource Members

        /// <summary>
        /// If true, resource will stay in memory even if it's not referenced
        /// </summary>
        public virtual bool AlwaysStay { get; set; }

        /// <summary>
        /// Generic type of the resource
        /// </summary>
        public virtual ResourceType ResourceType
        {
            get { return ResourceType.Generic; }
        }

        /// <summary>
        /// Type of the resource usage (static/dynamic)
        /// </summary>
        public virtual ResourceUsage Usage
        {
            get { return ResourceUsage.Dynamic; }
        }

        /// <summary>
        /// File used to create the resource (if it was loaded from the disk)
        /// </summary>
        public virtual string FileName
        {
            get
            {
                return _filename;
            }
            set
            {
                _filename = value;
            }
        }

        protected List<IResourceConsumer> Consumers = new List<IResourceConsumer>();

        /// <summary>
        /// Number of current references
        /// </summary>
        public int References { get { return Consumers.Count; } }

        /// <summary>
        /// Returns number of null generic references
        /// </summary>
        public int NullReferences {
            get
            {
                int rval = 0;
                for (int i = 0; i < Consumers.Count; i++)
                {
                    if (Consumers[i] == null)
                        rval++;
                }
                return rval;
            }
        }

        /// <summary>
        /// Adds reference to this resource
        /// </summary>
        /// <param name="consumer">Consumer of resource</param>
        public virtual void AddReference(IResourceConsumer consumer)
        {
            Consumers.Add(consumer);
        }

        /// <summary>
        /// Removes reference to this resource
        /// </summary>
        /// <param name="consumer">Consumer of resource</param>
        public virtual void RemoveReference(IResourceConsumer consumer)
        {
            Consumers.Remove(consumer);
        }

        /// <summary>
        /// Manager wants to replace this resource by another - resource should inform all of it's consumers that it's being replaced 
        /// and update references based on their response
        /// It should also check if new resource is of compatible type
        /// </summary>
        /// <param name="newresource">New resource</param>
        /// <returns>True if resource is compatible type and all consumers accepted resource changing, false otherwise</returns>
        public virtual bool ReplaceReferences(IResource newresource)
        {
            bool rval = true;

            for (int i = 0; i < Consumers.Count; i++)
            {
                IResourceConsumer consumer = Consumers[i];
                if (consumer != null)
                {
                    bool consresponse = consumer.ResourceReplaced(this, newresource);

                    if (consresponse)
                    {
                        RemoveReference(consumer);

                        if (newresource != null)
                        {
                            newresource.AddReference(consumer);
                        }
                    }
                    else rval = false;
                }
            }

            return rval;
        }


        #endregion

        #region IDisposable Members

        public virtual void Dispose()
        {
            for (int i = 0; i < Consumers.Count; i++)
            {
                if (Consumers[i] != null)
                {
                    Consumers[i].ResourceDisposing(this);
                }
            }
            Consumers.Clear();
            
            if (_data is IDisposable)
            {
                IDisposable idis = _data as IDisposable;
                idis.Dispose();
            }
        }

        #endregion
    }

    /// <summary>
    /// Generic resource type with object
    /// </summary>
    public class GenericResource : Resource<object>
    {
        public GenericResource(object data) : base (data)
        {
        }

        public override ResourceType ResourceType
        {
            get
            {
                return ResourceType.Generic;
            }
        }
    }

    /// <summary>
    /// String table resource (one item is array of strings)
    /// </summary>
    public class StringTableResource : Resource<string[]>
    {
        public StringTableResource(string[] data) : base(data)
        {
        }
        public override ResourceType ResourceType
        {
            get
            {
                return ResourceType.Strings;
            }
        }
    }


    /*
/// <summary>
/// Interface for resource manager
/// </summary>
/// <typeparam name="T"></typeparam>
interface IResMan<T> : IDisposable
{
    public virtual void Add(string key, T value);
    public virtual void Remove(string key);
    public virtual string[] GetAllKeys();
    public virtual IEnumerator<KeyValuePair<string, T>> GetEnumerator();
    public virtual void ClearAll();
    public virtual T Load(string filename);
    public virtual T Load(string key, string filename);
}
 */

    /// <summary>
    /// Abstract resource manager, used as a base for all resource manager types
    /// </summary>
    public class AbstractResMan<T> : IDisposable, IResourceConsumer
        where T : IResource 
    {
        protected Dictionary<string, T> resources;
        protected bool disposing = false;

        public AbstractResMan()
        {
            resources = new Dictionary<string, T>();
        }

        public virtual void Dispose()
        {
            if (disposing)
                return;
            disposing = true;
            ClearAll();
            /*
            foreach (var data in resources)
            {
                data.Value.Dispose();
            }*/
        }

        /// <summary>
        /// Adds new key/value if the key is not already used (no duplicities allowed)
        /// </summary>
        /// <param name="key">key</param>
        /// <param name="value">value</param>
        public virtual void Add(string key, T value)
        {
            if (resources.ContainsKey(key))
                throw new ArgumentException(string.Format("Resource already contain key {0}", key));
            if (value == null)
                throw new SystemException("Attempt to add null value");

            resources.Add(key, value);
            value.AddReference(this);
        }

        /// <summary>
        /// Removes item with specified key and DISPOSES it!
        /// </summary>
        /// <param name="key">key to remove</param>
        public virtual void Remove(string key)
        {
            T res;

            if (!resources.TryGetValue(key, out res))
                return;

            res.RemoveReference(this);

            if ((res is IDisposable) && (res != null))
            {
                IDisposable dis = res as IDisposable;
                dis.Dispose();
            }

            resources.Remove(key);
        }

        /// <summary>
        /// Renames key to new value
        /// </summary>
        /// <param name="key">Original key</param>
        /// <param name="newkey">New key</param>
        /// <param name="uniquename">If unique set to true, unique newkwey will be found</param>
        /// <returns>New used key or null (if object not found)</returns>
        public virtual string Rename(string key, string newkey, bool uniquename = true)
        {
            if (!resources.ContainsKey(key))
                return null;

            T value = resources[key];

            if (uniquename)
                newkey = GetUniqueName(newkey);

            if (resources.ContainsKey(newkey))
                throw new ArgumentException(string.Format("Resource already contain key {0}", key));

            resources.Remove(key);
            resources.Add(newkey, value);
            return newkey;
        }

        List<string> CleanList = new List<string>();

        /// <summary>
        /// Performs cleanup of the resources. Should be called periodically or after big changes
        /// </summary>
        public virtual void PerformCleanup()
        {
            CleanList.Clear();
            foreach (var kvp in resources)
            {
                T val = kvp.Value;
                if ((!val.AlwaysStay) && (val.References < 2))
                {
                    //resource is not marked as persistent and it has only one reference, to the manager.
                    //kill it
                    CleanList.Add(kvp.Key);
                }
            }
            foreach (string key in CleanList)
            {
                this.Remove(key);
            }
        }

        /// <summary>
        /// Returns string array with all keys
        /// </summary>
        /// <returns></returns>
        public virtual string[] GetAllKeys()
        {
            List<string> ls = new List<string>(resources.Keys);
            return ls.ToArray();
        }


        /// <summary>
        /// Returns Enumerator from the resources collection.
        /// </summary>
        /// <returns></returns>
        public virtual IEnumerator<KeyValuePair<string, T>> GetEnumerator()
        {
            return resources.GetEnumerator();
        }

        /// <summary>
        /// Goes through the whole list and removes all items
        /// </summary>
        public virtual void ClearAll()
        {
            string[] keys = GetAllKeys();
            if (keys == null)
                return;

            foreach (string key in keys)
            {
                Remove(key);
            }
        }

        /// <summary>
        /// Returns data with associated filename from cache or loads them
        /// </summary>
        /// <param name="filename">Filename with data</param>
        /// <returns>loaded value</returns>
        public virtual T Load(string filename)
        {
            return Load(filename, filename);
        }

        /// <summary>
        /// Returns data with associated filename from cache or loads them
        /// </summary>
        /// <param name="filename">Filename with data</param>
        /// <param name="key">Key to use for loaded data</param>
        /// <returns>loaded value</returns>
        public virtual T Load(string key, string filename)
        {
            if (key == null)
                return default(T);
            if (resources.ContainsKey(key))
                return resources[key];
            return default(T);
        }

        public virtual T this[string index]
        {
            get
            {
                return resources[index];
            }
            set
            {
                //note!!! block if readonly?
                resources[index] = value;
            }
        }

        /// <summary>
        /// Returns true, if manager already contains value with specified index
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public bool Contains(string index)
        {
            return (resources.ContainsKey(index));
        }

        /// <summary>
        /// Returns unique name based on the name (adds numbers to the end)
        /// </summary>
        /// <param name="name">Base string</param>
        /// <returns>Unique name not contained in the manager</returns>
        public string GetUniqueName(string name)
        {
            if (!resources.ContainsKey(name))
                return name;

            string rval = String.Format("{0}0", name);
            int i = 0;

            while (resources.ContainsKey(rval))
            {
                i++;
                rval = String.Format("{0}{1}", name, i);
            }

            return rval;
        }


        #region IResourceConsumer Members

        public virtual void ResourceDisposing(IResource resource)
        {
            //somebody set up as a bomb! - somebody other then us called Dispose() on managed resource... to late to do anything about it, so remove it from the list                                   

            //and since c# is idiot and doesn't allow recasting resource as T even if T is IResource, we're not going to do anything but hope (and log this)
            Console.WriteLine("Resource manager " + this.GetType().Name + "lost control over resource " + resource.GetType().Name);
            throw new ArgumentException("Resources disposing in WRONG way!");
        }

        public virtual bool ResourceReplaced(IResource original, IResource newresource)
        {
            //ignoring this, we're supposed to keep all of this resource type
            return false;
        }

        #endregion
    } //AbstractResMan

    /// <summary>
    /// Resource manager for generic objects
    /// </summary>
    public class GenericResMan : AbstractResMan<GenericResource> { }
    /// <summary>
    /// Resource manager for strings
    /// </summary>
    public class StringResMan : AbstractResMan<StringTableResource> { }


    public class BitmapResource : Resource<System.Drawing.Bitmap>
    {
        public BitmapResource(System.Drawing.Bitmap data) : base(data)
        {
        }
    }

    public class SysFontResource : Resource<System.Drawing.Font>
    {
        public SysFontResource(Font data) : base(data)
        {
        }
    }

    /// <summary>
    /// Manager for bitmap objects.In most cases texture manager will load bitmap directly, but for some cases it might
    /// be usefull to hold bitmaps inside the memory (for example for dynamic textures)
    /// </summary>
    public class BitmapManager : AbstractResMan<BitmapResource>
    {
        public override BitmapResource Load(string key, string filename)
        {
            if (resources.ContainsKey(key))
                return resources[key];

            System.IO.Stream str = FileSystem.Instance.GetFile(filename);
            if (str == null)
                return null;

            Bitmap bm;
            try
            {
                bm = new Bitmap(str);
            }
            catch (System.ArgumentException)
            {
                return null;
            }

            BitmapResource bmr = new BitmapResource(bm);
            Add(key, bmr);
            return bmr;
        }

        public Bitmap LoadAndGetBitmap(string filename)
        {
            BitmapResource bmr = Load(filename);
            if (bmr != null)
            {
                return bmr.Data;
            }
            return null;
        }

        public bool LoadBitmap(string filename)
        {
            if (LoadAndGetBitmap(filename) != null) return true;
            return false;
        }


    }


    /// <summary>
    /// Simple system font manager, only storing functionality, create new fonts directly with new Font()
    /// </summary>
    public class SystemFontManager : AbstractResMan<SysFontResource>
    {
    }


}
