using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VisimBox.Resources;
using VisiMath;
using System.Xml.Serialization;
using System.ComponentModel;

namespace VisimBox.Renderer
{
    /// <summary>
    /// Type of the render material
    /// </summary>
    public enum RenderMaterialType
    {
        /// <summary>
        /// Very simple color material, just color (for unlighted things)
        /// </summary>
        Simple,
        /// <summary>
        /// Very simple color material, just color (for unlighted things) + texture
        /// </summary>
        SimpleTextured,
        /// <summary>
        /// Simple color material 
        /// </summary>
        Colored,
        /// <summary>
        /// Textured color material
        /// </summary>
        Textured,
        /// <summary>
        /// Enhanced material, per pixel lightning, normalmaps and other standard shader functions
        /// </summary>
        Enhanced,
        /// <summary>
        /// User specified material (uses custom shader)
        /// </summary>
        User
    }


    /// <summary>
    /// Interface for render material
    /// </summary>
    public interface IRenderMaterial : IResource, IXmlSerializable
    {
        /// <summary>
        /// Basic type of material
        /// </summary>
        RenderMaterialType MaterialType { get;}

        /// <summary>
        /// Name of the material
        /// </summary>
        string Name { get; set; }

        /// <summary>
        /// Description of the material
        /// </summary>
        string Description {get;set;}


        /// <summary>
        /// Creates copy of self with new name
        /// </summary>
        /// <param name="newname">name for the copy</param>
        /// <returns>new material</returns>
        IRenderMaterial CreateCopy(string newname);


        /// <summary>
        /// Does material contain alpha values? (transparency)
        /// Required for renderer for correct drawing (alpha materials must be drawn last, in reverse order back to front as opposed to normal materials)
        /// </summary>
        bool HasAlpha { get; set; }


        /// <summary>
        /// Returns true if material contains specified color element
        /// </summary>
        /// <param name="element">element type</param>
        /// <returns>True if material has this type of element</returns>
        bool HasColorElement(MaterialElement element);
        /// <summary>
        /// Returns true if material contains specified texture element
        /// </summary>
        /// <param name="element">element type</param>
        /// <returns>True if material has this type of element</returns>
        bool HasTextureElement(MaterialElement element);
        
        /// <summary>
        /// Returns value of specified color element (or white if it doesn't contain this)
        /// </summary>
        /// <param name="element">Element type</param>
        /// <returns>Color value</returns>
        ColorByte GetColorElement(MaterialElement element);
        /// <summary>
        /// Returns texture on specified element (or null)
        /// </summary>
        /// <param name="element">Element type</param>
        /// <returns>Texture object</returns>
        Texture GetTextureElement(MaterialElement element);

        /// <summary>
        /// Sets value of specified color element (if supported by material)
        /// </summary>
        /// <param name="element">Element type</param>
        /// <param name="color">Color value</param>
        void SetColorElement(MaterialElement element, ColorByte color);
        /// <summary>
        /// Sets value of specified texture element (if supported by material)
        /// </summary>
        /// <param name="element">Element type</param>
        /// <param name="tex">Texture value</param>
        void SetTextureElement(MaterialElement element, Texture tex);


        /// <summary>
        /// Activate material for rendering (called by the renderer when using material)
        /// </summary>
        /// <param name="args">Renderer arguments</param>
        void Activate(RendererEventArgs args);
        
        /// <summary>
        /// This event is fired on the activation of the material - for user code for time changing materials
        /// </summary>
        event RendererEvent Activating;
    }

    /// <summary>
    /// Color elements
    /// </summary>
    public enum MaterialElement
    {
        /// <summary>
        /// Ambient color (for ambient lightning)
        /// </summary>
        Ambient = 0,
        /// <summary>
        /// Basic diffuse color
        /// </summary>
        Diffuse = 1,
        /// <summary>
        /// Specular color
        /// </summary>
        Specular = 2,
        /// <summary>
        /// Emmissive color (self lighting)
        /// </summary>
        Emmissive = 3
    }


    /// <summary>
    /// Standard color material descriptor
    /// </summary>
    public struct StandardColorValues
    {
        public ColorByte Emissive, Ambient, Diffuse, Specular;
        public float Shininess;
        public bool Alpha;

        /// <summary>
        /// Creates material with just ambient and diffuse lightning, optionally with alpha value
        /// </summary>
        /// <param name="color">Base material color</param>
        /// <param name="alpha">Does material have alpha? (taken from Diffuse color)</param>
        public StandardColorValues(ColorByte color, bool alpha = false)
        {
            Ambient = Diffuse =  color;
            Specular = Emissive = ColorByte.Black;
            Shininess = 0;
            Alpha = alpha;
        }

        public StandardColorValues(ColorByte color, ColorByte specular, ColorByte emissive, float shiny = 1, bool alpha = false)
        {
            Ambient = Diffuse = color;
            Specular = specular;
            Emissive = emissive;
            Shininess = shiny;
            Alpha = alpha;
        }

        /// <summary>
        /// Returns default white material
        /// </summary>
        /// <returns></returns>
        public static StandardColorValues Default()
        {
            return new StandardColorValues(ColorByte.White);
        }
    }

    /// <summary>
    /// Base rendering material
    /// </summary>
    public abstract class RenderMaterial : IRenderMaterial
    {
        #region private fields
        protected string _name;
        protected string _filename;
        protected string _description;
        protected bool _alpha;
        protected RenderMaterialType _materialType;

        #endregion


        /// <summary>
        /// No direct constructor allowd
        /// </summary>
        protected RenderMaterial()
        {
        }

        protected RenderMaterial(string name, RenderMaterialType type)
        {
            _name = name;
            _materialType = type;
        }


        #region IResource Members

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

        public ResourceType ResourceType
        {
            get { return ResourceType.Material; }
        }

        public ResourceUsage Usage
        {
            get { return ResourceUsage.Dynamic; }
        }

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

        public string Name
        {
            get
            {
                return _name;
            }

            set
            {
                _name = value;
            }
        }

        public 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)
        {
            //safety check

            if ((newresource as IRenderMaterial) == null)
                return false;

            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();
        }



        #endregion

        #region IRenderMaterial Members

        /// <summary>
        /// Description of the material
        /// </summary>
        public string Description
        {
            get
            {
                return _description;
            }
            set
            {
                _description = value;
            }
        }

        /// <summary>
        /// Does material contain alpha values? (transparency)
        /// Required for renderer for correct drawing (alpha materials must be drawn last, in reverse order back to front as opposed to normal materials)
        /// </summary>
        public virtual bool HasAlpha
        {
            get
            {
                return _alpha;
            }
            set
            {
                _alpha = value;
            }
        }

        public RenderMaterialType MaterialType
        {
            get
            {
                return _materialType;
            }
        }
        
        /// <summary>
        /// Activate material (called from Activate)
        /// </summary>
        /// <param name="args">Rendering arguments</param>
        public abstract void OnActivate(RendererEventArgs args);

        /// <summary>
        /// Activate material for rendering (called by the renderer when using material)
        /// Calls registered events and then OnActivate
        /// </summary>
        /// <param name="args">Renderer arguments</param>
        public virtual void Activate(RendererEventArgs args)
        {
            if (Activating != null)
                Activating(this, args);
            OnActivate(args);
        }

        /// <summary>
        /// This event is fired on the activation of the material - for user code for time changing materials
        /// </summary>
        public event RendererEvent Activating;


        /// <summary>
        /// Returns true if material contains specified color element
        /// </summary>
        /// <param name="element">element type</param>
        /// <returns>True if material has this type of element</returns>
        public abstract bool HasColorElement(MaterialElement element);
        /// <summary>
        /// Returns true if material contains specified texture element
        /// </summary>
        /// <param name="element">element type</param>
        /// <returns>True if material has this type of element</returns>
        public abstract bool HasTextureElement(MaterialElement element);

        /// <summary>
        /// Returns value of specified color element (or white if it doesn't contain this)
        /// </summary>
        /// <param name="element">Element type</param>
        /// <returns>Color value</returns>
        public abstract ColorByte GetColorElement(MaterialElement element);
        /// <summary>
        /// Returns texture on specified element (or null)
        /// </summary>
        /// <param name="element">Element type</param>
        /// <returns>Texture object</returns>
        public abstract Texture GetTextureElement(MaterialElement element);

        /// <summary>
        /// Sets value of specified color element (if supported by material)
        /// </summary>
        /// <param name="element">Element type</param>
        /// <param name="color">Color value</param>
        public abstract void SetColorElement(MaterialElement element, ColorByte color);
        /// <summary>
        /// Sets value of specified texture element (if supported by material)
        /// </summary>
        /// <param name="element">Element type</param>
        /// <param name="tex">Texture value</param>
        public abstract void SetTextureElement(MaterialElement element, Texture tex);

        /// <summary>
        /// Creates copy of self with new name
        /// </summary>
        /// <param name="newname">name for the copy</param>
        /// <returns>new material</returns>
        public abstract IRenderMaterial CreateCopy(string newname);


        #endregion

        #region IXmlSerializable Members

        /// <summary>
        /// Reqired by IXMLserializable
        /// </summary>
        /// <returns>Null</returns>
        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

        /// <summary>
        /// Deserializes object from XML
        /// </summary>
        /// <param name="reader">XML reader</param>
        public abstract void ReadXml(System.Xml.XmlReader reader);

        /// <summary>
        /// Serializes object to XML
        /// </summary>
        /// <param name="writer">XML writer to save values</param>
        public abstract void WriteXml(System.Xml.XmlWriter writer);

        #endregion
    }


}
