using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VisimBox.Resources;

namespace VisimBox.Renderer
{

    /// <summary>
    /// Abstract base class for HW buffers
    /// </summary>
    /// <typeparam name="T">Type of element in the buffer (uint for index buffer, IVertex structure for vertex buffer)</typeparam>
    public abstract class HWBuffer<T> : IHWBuffer<T>
    {
        protected T[] _cache;
        protected bool _cached = false;
        protected int _count;
        protected ResourceUsage _usage;
        protected uint _internalID;

        #region Constructors

        /// <summary>
        /// No parameterless constructor allowed
        /// </summary>
        private HWBuffer()
        {
        }

        protected HWBuffer(ResourceUsage usage)
        {
            _usage = usage;
        }

        /// <summary>
        /// Initializes buffer with specified number of basic elements
        /// </summary>
        /// <param name="count">Count of elements</param>
        /// <param name="cache">If true, cache is constructed and used</param>
        /// <param name="usage">Usage of the buffer, default is static (create once)</param>
        protected HWBuffer(int count, bool cache = false, ResourceUsage usage = ResourceUsage.Static) : this(usage)
        {
            if (count<=0)
                throw new ArgumentException("HWBuffer - can't initialize with zero elements!", "count");
            ConstructBuffer(count);
            if (cache)
            {
                _cached = true;
                _count = count;
                CreateCache();
            }
            else
            {
                _cache = null;
                _cached = false;
            }
        }

        /// <summary>
        /// Initialized buffer from array of values
        /// </summary>
        /// <param name="values">Source values</param>
        /// <param name="cache">If true, cache is constructed and used</param>
        /// <param name="useArrayAsCache">If true, hw buffer will store reference to the source array as cache (so any direct read/write from/to array will be reflected in cache. 
        /// Note, automatically sets cache using to true!</param>
        /// <param name="usage">Usage of the buffer, default is static (create once)</param>
        protected HWBuffer(T[] values, bool cache = false, bool useArrayAsCache = false, ResourceUsage usage = ResourceUsage.Static) : this(usage)
        {
            
            if (values == null)
                throw new ArgumentException("HWBuffer - can't initialize with null array!", "values");
            if (values.Length <=0)
                throw new ArgumentException("HWBuffer - can't initialize with array of zero elements!", "values");

            ConstructBuffer(values.Length, true);
            _cache = values;
            WriteCache();

            if (useArrayAsCache)
            {
                _cached = true;
            }
            else
            {
                if (cache)
                {
                    _cached = true;
                    _count = values.Length;
                    CreateCache();
                    Array.Copy(values, _cache, values.Length);
                }
                else
                {
                    _cached = false;
                    _cache = null;
                }
            }
        }


        /// <summary>
        /// Constructs buffer from collection of values
        /// </summary>
        /// <param name="values">Source values</param>
        /// <param name="cache">If true, cache is constructed and used</param>
        /// <param name="usage">Usage of the buffer, default is static (create once)</param>
        protected HWBuffer(ICollection<T> values, bool cache = false, ResourceUsage usage = ResourceUsage.Static) : this(usage)
        {
            if (values == null)
                throw new ArgumentException("HWBuffer - can't initialize with null array!", "values");
            if (values.Count <= 0)
                throw new ArgumentException("HWBuffer - can't initialize with array of zero elements!", "values");
            ConstructBuffer(values.Count, true);
            _cache = values.ToArray();
            _cached = true;
            WriteCache();
            if (cache)
            {
                _cached = true;
            }
            else
            {
                DisposeCache();
            }
        }

        #endregion

        /// <summary>
        /// Will construct buffer with specified number of elements.
        /// If HWbuffer already has some buffer assigned, it will be deleted.
        /// </summary>
        /// <param name="count">Required number of elements</param>
        /// <param name="justPrepade">Only prepares buffer without setting it's size</param>
        protected abstract void ConstructBuffer(int count, bool justPrepare = false);

        #region Cache methods

        /// <summary>
        /// Sets entire cache to true or false (recreates cache from buffer data or deletes it)
        /// </summary>
        /// <param name="value">cache on or off?</param>
        protected virtual void SetCache(bool value)
        {
            if (value)
            {
                if (!_cached)
                {
                    CreateCache();
                    ReadCache();
                }
            }
            else
            {
                DisposeCache();
            }
            _cached = true;
        }

        /// <summary>
        /// Creates cache
        /// </summary>
        protected virtual void CreateCache()
        {
            _cache = new T[_count];
        }

        /// <summary>
        /// Disposes cache
        /// </summary>
        protected virtual void DisposeCache()
        {
            if (_cache == null)
                return;
            _cached = false;
            if (typeof(T) is IDisposable)
            {
                foreach (T t in _cache)
                {
                    IDisposable id = t as IDisposable;
                    id.Dispose();
                }
            }
            _cache = null;
        }

        #endregion

        #region IHWBuffer<T> Members

        /// <summary>
        /// Allows access to buffer elements array style. If caching is enabled, reads/writes from cache, otherwise uses direct (and usually slow) access.
        /// </summary>
        /// <param name="index">index of the element</param>
        /// <returns></returns>
        public virtual T this[int index]
        {
            get
            {
                if (index >= _count)
                {
                    throw new ArgumentException("Indexing out of the HW buffer!", "index");
                }
                if (_cached && _cache != null)
                {
                    return _cache[index];
                }
                else
                {
                    T rval = ReadElement(index);
                    return rval;
                }
            }
            set
            {
                if (index >= _count)
                {
                    throw new ArgumentException("Indexing out of the HW buffer!", "index");
                }
                if (_cached && _cache != null)
                {
                    _cache[index] = value;
                }
                else
                {
                    WriteElement(index, ref value);
                }
            }
        }

        /// <summary>
        /// Number of elements in the buffer
        /// </summary>
        public virtual int Count
        {
            get
            {
                return _count;
            }
        }

        /// <summary>
        /// Internal ID for the buffer
        /// </summary>
        public abstract uint InternalID { get; }

        /// <summary>
        /// If cached, index buffer will keep copy of the data in the system memory.
        /// All user read / write operations will be routed to cached copy. 
        /// Use ReadCache / WriteCache to update buffer
        /// </summary>
        public virtual bool Cached {
            get
            {
                return _cached;
            }
            set
            {
                SetCache(value);
            }
        }


        /// <summary>
        /// Reads content of the buffer to cache
        /// </summary>
        public virtual void ReadCache()
        {
            ReadCache(0, _count);
        }

        /// <summary>
        /// Reads part of the buffer to cache
        /// </summary>
        /// <param name="start">starting index</param>
        /// <param name="count">number of elements to be read from the buffer</param>
        public virtual void ReadCache(int start, int count)
        {
            if ((start + count) > _count)
            {
                throw new ArgumentException("HWBuffer ReadCache - trying to read to many elements!", "count");
            }

            if (_cache == null)
                throw new NullReferenceException("HWBuffer: cache is not initialized!");

            for (int i = start; i < start + count; i++)
            {
                //_cache[i] = ReadElements(
            }
        }


        /// <summary>
        /// Writes content of the cache to index buffer
        /// </summary>
        public virtual void WriteCache()
        {
            WriteCache(0, _count);
        }

        /// <summary>
        /// Writes content of the cache to the buffer
        /// </summary>
        /// <param name="start">starting index</param>
        /// <param name="count">number of elements to be written to the buffer</param>
        public abstract void WriteCache(int start, int count);


        /// <summary>
        /// Directly reads one element from the buffer (might be very slow)
        /// </summary>
        /// <param name="position">Index of the element to write</param>
        /// <returns>Element on the position</returns>
        public abstract T ReadElement(int position);

        /// <summary>
        /// Directly reads several elements from the buffer (might be very slow)
        /// </summary>
        /// <param name="position">Starting position</param>
        /// <param name="count">Number of elements</param>
        /// <returns>Array of elements from the buffer</returns>
        public virtual T[] ReadElements(int position, int count)
        {
            T[] rval = null;
            ReadElements(position, count, ref rval, 0);
            return rval;
        }

        /// <summary>
        /// Directly reads several elements from the buffer (might be very slow) to specified array
        /// </summary>
        /// <param name="position">Starting position</param>
        /// <param name="count">Number of elements</param>
        /// <param name="dest">Destination array</param>
        /// <param name="start">Starting index in the array </param>
        public virtual void ReadElements(int position, int count, ref T[] dest, int start = 0)
        {
            if (count > (position + _count))
                throw new ArgumentException("HWBuffer: Attempt to read too much data from buffer!", "count");
            if (dest == null)
            {
                dest = new T[count + start];
            }

            for (int i = 0; i < count; i++)
            {
                dest[start + i] = ReadElement(position + i);
            }
        }


        /// <summary>
        /// Directly writes element to the buffer
        /// </summary>
        /// <param name="position">Position of element</param>
        /// <param name="value">New value for buffer</param>
        public abstract void WriteElement(int position, ref T value);

        /// <summary>
        /// Directly writes array of elements to the buffer)
        /// </summary>
        /// <param name="position">Position of element</param>
        /// <param name="values">Array of elements</param>
        public virtual void WriteElements(int position, T[] values)
        {
            WriteElements(position, values, 0, values.Length);
        }

        /// <summary>
        /// Directly writes part of the array of elements to the buffer)
        /// </summary>
        /// <param name="position">Position of element</param>
        /// <param name="values">Array of elements</param>
        /// <param name="count">Number of elements to write</param>
        /// <param name="start">Starting position in the source array</param>
        public virtual void WriteElements(int position, T[] values, int start, int count)
        {
            if (count > (position + _count))
                throw new ArgumentException("HWBuffer: Attempt to read too much data from buffer!", "count");

            for (int i = 0; i < count; i++)
            {
                WriteElement(position + i, ref values[start + i]);
            }
        }

        #endregion

        #region IResource Members

        /// <summary>
        /// Generic type of the resource
        /// </summary>
        public abstract ResourceType ResourceType { get; }

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

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

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


        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)
        {
            //todo: add type checking?!
            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;
        }

        protected virtual void DisposeConsumers()
        {
            for (int i = 0; i < Consumers.Count; i++)
            {
                if (Consumers[i] != null)
                {
                    Consumers[i].ResourceDisposing(this);
                }
            }
            Consumers.Clear();
        }

        #region IDisposable Members

        public virtual void Dispose()
        {
            DisposeConsumers();
            DisposeCache();
        }

        #endregion

    }
}
