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

namespace VisimBox.Renderer
{

    /// <summary>
    /// Basic types of lights
    /// </summary>
    public enum LightTypes
    {
        /// <summary>
        /// Ambient all encompassing light
        /// </summary>
        Ambient = 0,
        /// <summary>
        /// Directional light coming from the inifinite distant source (sun)
        /// </summary>
        Directional = 1,
        /// <summary>
        /// Positional light coming from point source
        /// </summary>
        Position = 2,
        /// <summary>
        /// Spot light (cone of light)
        /// </summary>
        Spot = 3,
    }


    public interface ILight : IResource, IXmlSerializable
    {
        #region light parameters

        /// <summary>
        /// name of the light in manager
        /// </summary>
        string Name { get; set; }

        /// <summary>
        /// Ambient light
        /// </summary>
        ColorByte Ambient { get; set; }
        /// <summary>
        /// Difuse light color
        /// </summary>
        ColorByte Diffuse{ get; set; }
        /// <summary>
        /// Specular light color
        /// </summary>
        ColorByte Specular{ get; set; }

        /// <summary>
        /// type of the light
        /// </summary>
        LightTypes LightType{ get; set; }

        /// <summary>
        /// Position of the light, used for positional and spot light and for radius calculation.
        /// </summary>
        Vector3 Position{ get; set; }
        /// <summary>
        /// Normalized direction of the light for spot and directional
        /// </summary>
        Vector3 Direction{ get; set; }

        /// <summary>
        /// Angle of the cone light for spot lighting (in degrees!!!)
        /// </summary>
        float SpotAngle{ get; set; }


        /// <summary>
        /// Spot light intensity distribution. Must be 0 - 128. 0 is uniformly distributed light, 128 focused beam
        /// </summary>
        float SpotIntensity { get; set; }

        /// <summary>
        /// Light attenuation (X = constant, Y = linear, Z = quadratic), defines how the light weakens with distance.
        /// Default value is (1,0,0) meaning no attenuation (note - using attenuation induces slowdowns)
        /// </summary>
        Vector3 Attenuation { get; set; }


        /// <summary>
        /// SQUARED radius of the light where it's active (0 everywhere). Used if active area is null
        /// </summary>
        float Radius { get; set; }

        /// <summary>
        /// Area where the light is active (can be null, then it's active everywhere)
        /// </summary>
        IGeometry ActiveArea { get; set; }


        #endregion

        #region Methods

        ILight GetCopy();

        /// <summary>
        /// Activates light on specified HW position
        /// </summary>
        /// <param name="light">light index</param>
        void Activate(int light);

        /// <summary>
        /// Returns true, if light is active in the specified area
        /// </summary>
        /// <param name="area">Geometry area (use bounding boxes!)</param>
        /// <returns>True if light is active in the area (and should be set for all objects there)</returns>
        bool IsActive(IGeometry area);
        
        /// <summary>
        /// Returns true if light is active on specified position
        /// </summary>
        /// <param name="pos">Position</param>
        /// <returns>True if light is active in the area (and should be set for all objects there)</returns>
        bool IsActive(Vector3 pos);



        #endregion

    }


    public abstract class Light : ILight
    {
        protected string _Name;
        protected bool _typeChanged = false;
        protected LightTypes _lType;

        #region Light parameters

        /// <summary>
        /// Ambient light
        /// </summary>
        public ColorByte Ambient{ get; set; }
        /// <summary>
        /// Difuse light color
        /// </summary>
        public ColorByte Diffuse { get; set; }
        /// <summary>
        /// Specular light color
        /// </summary>
        public ColorByte Specular { get; set; }

        /// <summary>
        /// type of the light
        /// </summary>
        public LightTypes LightType { 
            get {return _lType;}
            set
            {
                if (_lType != value)
                {
                    _lType = value;
                    _typeChanged = true;
                }
            }
            }

        /// <summary>
        /// Position of the light, used for positional and spot light and for radius calculation.
        /// </summary>
        public Vector3 Position { get; set; }
        /// <summary>
        /// Normalized direction of the light for spot and directional
        /// </summary>
        public Vector3 Direction { get; set; }

        /// <summary>
        /// Angle of the cone light for spot lighting
        /// </summary>
        public float SpotAngle { get; set; }

        /// <summary>
        /// Spot light intensity distribution. Must be 0 - 128. 0 is uniformly distributed light, 128 focused beam
        /// </summary>
        public float SpotIntensity { get; set; }

        /// <summary>
        /// Light attenuation (X = constant, Y = linear, Z = quadratic), defines how the light weakens with distance.
        /// Default value is (1,0,0) meaning no attenuation (note - using attenuation induces slowdowns)
        /// </summary>
        public Vector3 Attenuation { get; set; }

        /// <summary>
        /// name of the light in manager
        /// </summary>
        public string Name { get { return _Name; } set { _Name = value; } }


        //Active area

        /// <summary>
        /// SQUARED radius of the light where it's active (0 everywhere). Used if active area is null (required set position!)
        /// </summary>
        public float Radius { get; set; }

        /// <summary>
        /// Area where the light is active (can be null, then it's active everywhere)
        /// </summary>
        public IGeometry ActiveArea { get; set; }

        #endregion

        #region Methods


        /// <summary>
        /// Activates light on specified HW position
        /// </summary>
        /// <param name="light">light index</param>
        public abstract void Activate(int light);


        public Light()
        {
            Attenuation = new Vector3(1, 0, 0);
            SpotAngle = 45;
        }

        /// <summary>
        /// Returns true, if light is active in the specified area
        /// </summary>
        /// <param name="area">Geometry area (use bounding boxes!)</param>
        /// <returns>True if light is active in the area (and should be set for all objects there)</returns>
        public virtual bool IsActive(IGeometry area)
        {
            if (ActiveArea != null)
            {
                return ActiveArea.Intersects(area);
            }
            else if (Radius != 0)
            {
                float dst = area.Distance(Position);
                if ((dst*dst) < Radius)
                    return true;
                return false;
            }
            return true;
        }

        /// <summary>
        /// Returns true if light is active on specified position
        /// </summary>
        /// <param name="pos">Position</param>
        /// <returns>True if light is active in the area (and should be set for all objects there)</returns>
        public virtual bool IsActive(Vector3 pos)
        {
            if (ActiveArea != null)
            {
                return ActiveArea.ContainsPosition(pos);
            }
            else if (Radius != 0)
            {
                if (Position.DistanceSquared(ref pos) < Radius)
                    return true;
                return false;
            }
            return true;
        }



        #endregion

        #region IResource Members

        public ResourceType  ResourceType
        {
            get { return Resources.ResourceType.Light; }
        }

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

        public string  FileName
        {
	        get 
	            {
                    return null;
	            }
	        set 
	            { 
	            }
        }

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

        public abstract ILight GetCopy();

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


        #endregion

        #region IDisposable Members

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

        #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 virtual void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteAttributeString("type", LightType.ToString());
            writer.WriteAttributeString("name", _Name);
            switch (LightType)
            {
                case LightTypes.Ambient:
                    Ambient.WriteXml(writer, "Ambient", true);
                    Position.WriteXml(writer, "Position");
                    XMLHelper.WriteFloat(writer, "Radius", Radius);
                    break;
                case LightTypes.Directional:
                    Ambient.WriteXml(writer, "Ambient", true);
                    Diffuse.WriteXml(writer, "Diffuse", true);
                    Specular.WriteXml(writer, "Specular", true);
                    Position.WriteXml(writer, "Position");
                    Direction.WriteXml(writer, "Direction");
                    XMLHelper.WriteFloat(writer, "Radius", Radius);
                    break;
                case LightTypes.Position:
                    Ambient.WriteXml(writer, "Ambient", true);
                    Diffuse.WriteXml(writer, "Diffuse", true);
                    Specular.WriteXml(writer, "Specular", true);
                    Position.WriteXml(writer, "Position");
                    Attenuation.WriteXml(writer, "Attenuation");
                    XMLHelper.WriteFloat(writer, "Radius", Radius);
                    break;
                case LightTypes.Spot:
                    Ambient.WriteXml(writer, "Ambient", true);
                    Diffuse.WriteXml(writer, "Diffuse", true);
                    Specular.WriteXml(writer, "Specular", true);
                    Position.WriteXml(writer, "Position");
                    Direction.WriteXml(writer, "Direction");
                    Attenuation.WriteXml(writer, "Attenuation");
                    XMLHelper.WriteFloat(writer, "SpotAngle", SpotAngle);
                    XMLHelper.WriteFloat(writer, "SpotIntensity", SpotIntensity);
                    XMLHelper.WriteFloat(writer, "Radius", Radius);
                    break;
            }

            if (ActiveArea != null)
            {
                writer.WriteStartElement("ActiveArea");
                ActiveArea.WriteXml(writer);
                writer.WriteEndElement();
            }
        }

        #endregion
    }

    /// <summary>
    /// Abstract light manager
    /// </summary>
    public abstract class LightManager : AbstractResMan<ILight>
    {
        public override void Add(string key, ILight value)
        {
            value.Name = key;
            base.Add(key, value);
        }
        
        /// <summary>
        /// Maximal number of currently active lights
        /// </summary>
        public abstract int MaxLights { get; }

        /// <summary>
        /// Deactivates HW light with specified index
        /// </summary>
        /// <param name="index"></param>
        public abstract void DeactiveLight(int index);

        /// <summary>
        /// Deactivates all lights
        /// </summary>
        public virtual void DeactiveLights()
        {
            int max = MaxLights;
            for (int i = 0; i < max; i++)
                DeactiveLight(i);
        }

        /// <summary>
        /// Enables (or disables) lighting
        /// </summary>
        /// <param name="value">Lighting</param>
        public abstract void EnableLighting(bool value = true);

        /// <summary>
        /// Activates lights for specified area (or all possible if parameter is null)
        /// </summary>
        /// <param name="area">Geometric area for lights (should be simple bounding object!)</param>
        public virtual void ActivateLights(IGeometry area)
        {
            DeactiveLights();
            int lindex = 0;

            foreach (var light in resources)
            {
                if (light.Value.IsActive(area))
                {
                    light.Value.Activate(lindex);
                    lindex++;
                    if (lindex > MaxLights)
                        break;
                }
            }
        }

        /// <summary>
        /// Activates lights which are active on specified position
        /// </summary>
        /// <param name="position"></param>
        public virtual void ActivateLights(Vector3 position)
        {
            DeactiveLights();
            int lindex = 0;

            foreach (var light in resources)
            {
                if (light.Value.IsActive(position))
                {
                    light.Value.Activate(lindex);
                    lindex++;
                    if (lindex > MaxLights)
                        break;
                }
            }
        }


        /// <summary>
        /// Adds new ambient light to the manager and returns pointer to it.
        /// Note - all other light types may contain ambient value
        /// </summary>
        /// <param name="name">Name for the light (may be changed if not unique)</param>
        /// <param name="color"></param>
        /// <returns></returns>
        public ILight AddAmbient(string name, ColorByte color)
        {
            ILight rval = CreateAmbient(color);
            name = GetUniqueName(name);
            Add(name, rval);
            return rval;
        }


        /// <summary>
        /// Adds new directional light to the manager (and returns pointer) without any settings
        /// </summary>
        /// <param name="name">Name for the light (may be changed if not unique)</param>
        /// <returns>New light</returns>
        public ILight AddDirectional(string name)
        {
            ILight rval = CreateDirectional();
            name = GetUniqueName(name);
            Add(name, rval);
            return rval;
        }

        /// <summary>
        /// Adds new directional light to the manager (and returns pointer)
        /// No specular light
        /// </summary>
        /// <param name="name">Name for the light (may be changed if not unique)</param>
        /// <param name="color">Diffuse color</param>
        /// <param name="direction">Directional (normalized!)vector</param>
        /// <returns>New light</returns>
        public ILight AddDirectional(string name, Vector3 direction, ColorByte color)
        {
            return AddDirectional(name, direction, ColorByte.Black, color, ColorByte.Black);
        }

        /// <summary>
        /// Adds new directional light to the manager (and returns pointer)
        /// No specular light
        /// </summary>
        /// <param name="name">Name for the light (may be changed if not unique)</param>
        /// <param name="diffuse">Diffuse color</param>
        /// <param name="ambient">Ambient light color</param>
        /// <param name="direction">Directional (normalized!)vector</param>
        /// <returns>New light</returns>
        public ILight AddDirectional(string name, Vector3 direction, ColorByte ambient, ColorByte diffuse)
        {
            return AddDirectional(name, direction, ambient, diffuse, ColorByte.Black);
        }

        /// <summary>
        /// Adds new directional light to the manager (and returns pointer)
        /// </summary>
        /// <param name="name">Name for the light (may be changed if not unique)</param>
        /// <param name="diffuse">Diffuse color</param>
        /// <param name="ambient">Ambient light color</param>
        /// <param name="specular">Specular light color</param>
        /// <param name="direction">Directional (normalized!)vector</param>
        /// <returns>New light</returns>
        public ILight AddDirectional(string name, Vector3 direction, ColorByte ambient, ColorByte diffuse, ColorByte specular)
        {
            ILight rval = CreateDirectional(direction, ambient, diffuse, specular);
            name = GetUniqueName(name);
            Add(name, rval);
            return rval;
        }

        /// <summary>
        /// Adds uninitalized spot light to manager and returns it's value
        /// </summary>
        /// <param name="name">Name for the light (may be changed if not unique)</param>
        /// <returns>New Light</returns>
        public ILight AddSpot(string name)
        {
            ILight rval = CreateSpot();
            name = GetUniqueName(name);
            Add(name, rval);
            return rval;
        }


        /// <summary>
        /// Adds spot light to manager and returns its value.
        /// </summary>
        /// <param name="name">Name for the light (may be changed if not unique)</param>
        /// <param name="position">Position of the light</param>
        /// <param name="direction">Normalized direction of the light</param>
        /// <param name="angle">Viewing angle in degrees (default is 180 meaning light shining in all directions)</param>
        /// <param name="color">Color used for both diffuse and specular</param>
        /// <returns>New Light</returns>
        public ILight AddSpot(string name, Vector3 position, Vector3 direction, float angle, ColorByte color)
        {
            ILight rval = CreateSpot(position, direction, angle, ColorByte.Black, color, color);
            name = GetUniqueName(name);
            Add(name, rval);
            return rval;
        }


        /// <summary>
        /// Adds spot light to manager and returns its value.
        /// </summary>
        /// <param name="name">Name for the light (may be changed if not unique)</param>
        /// <param name="position">Position of the light</param>
        /// <param name="direction">Normalized direction of the light</param>
        /// <param name="angle">Viewing angle in degrees (default is 180 meaning light shining in all directions)</param>
        /// <param name="ambient">Ambient light</param>
        /// <param name="diffuse">Diffuse light</param>
        /// <param name="specular">Specular light</param>
        /// <returns>New Light</returns>
        public ILight AddSpot(string name, Vector3 position, Vector3 direction, float angle, ColorByte ambient, ColorByte diffuse, ColorByte specular)
        {
            ILight rval = CreateSpot(position, direction, angle, ambient, diffuse, specular);
            name = GetUniqueName(name);
            Add(name, rval);
            return rval;
        }


        /// <summary>
        /// Adds uninitalized position light to manager and returns it's value
        /// </summary>
        /// <param name="name">Name for the light (may be changed if not unique)</param>
        /// <returns>New Light</returns>
        public ILight AddPositional(string name)
        {
            ILight rval = CreatePositional();
            name = GetUniqueName(name);
            Add(name, rval);
            return rval;
        }

        /// <summary>
        /// Adds positional light to manager and returns its value.
        /// </summary>
        /// <param name="name">Name for the light (may be changed if not unique)</param>
        /// <param name="position">Position of the light</param>
        /// <param name="color">Color used for both diffuse and specular</param>
        /// <returns>New Light</returns>
        public ILight AddPositional(string name, Vector3 position, ColorByte color)
        {
            ILight rval = CreatePositional(position, ColorByte.Black, color, color);
            name = GetUniqueName(name);
            Add(name, rval);
            return rval;
        }

        /// <summary>
        /// Adds positional light to manager and returns its value.
        /// </summary>
        /// <param name="name">Name for the light (may be changed if not unique)</param>
        /// <param name="position">Position of the light</param>
        /// <param name="ambient">Ambient light</param>
        /// <param name="diffuse">Diffuse light</param>
        /// <param name="specular">Specular light</param>
        /// <returns>New Light</returns>
        public ILight AddPositional(string name, Vector3 position, ColorByte ambient, ColorByte diffuse, ColorByte specular)
        {
            ILight rval = CreatePositional(position, ambient, diffuse, specular);
            name = GetUniqueName(name);
            Add(name, rval);
            return rval;
        }

        /// <summary>
        /// Attempts to read light data from xml stream, creates new object and returns it
        /// </summary>
        /// <param name="reader">XML reader</param>
        /// <returns>Valid light object or null</returns>
        public abstract ILight ReadFromXml(System.Xml.XmlReader reader);


        /// <summary>
        /// Creates exact copy of the light source
        /// </summary>
        /// <param name="source">Source light object</param>
        /// <returns>Copy of the light source</returns>
        public abstract ILight CreateCopy(ILight source);



        #region protected

        /// <summary>
        /// Creates new ambient light
        /// </summary>
        /// <param name="color">Required color</param>
        /// <returns>New light</returns>
        protected abstract ILight CreateAmbient(ColorByte color);

        /// <summary>
        /// Creates new directional light (without setting any parameters)
        /// </summary>
        /// <returns>New light</returns>
        protected abstract ILight CreateDirectional();
        /// <summary>
        /// Creates new directional light (without setting any parameters)
        /// </summary>
        /// <param name="ambient"> Ambient light</param>
        /// <param name="diffuse">Diffuse light</param>
        /// <param name="specular">Specular light</param>
        /// <param name="direction">Normalized Direction</param>
        /// <returns>New light</returns>
        protected abstract ILight CreateDirectional(Vector3 direction, ColorByte ambient, ColorByte diffuse, ColorByte specular);
        
        
        /// <summary>
        /// Creates positional light without setting parameters
        /// </summary>
        /// <returns>New light</returns>
        protected abstract ILight CreatePositional();
        /// <summary>
        /// Creates spot light.
        /// </summary>
        /// <param name="position">Position</param>
        /// <param name="direction">Normalized direction</param>
        /// <param name="angle">Angle of spot light in degrees</param>
        /// <param name="ambient">Ambient color</param>
        /// <param name="diffuse">Diffuse color</param>
        /// <param name="specular">Specular color</param>
        /// <returns>New light</returns>
        protected abstract ILight CreatePositional(Vector3 position, ColorByte ambient, ColorByte diffuse, ColorByte specular);

        /// <summary>
        /// Creates parameterless spot light
        /// </summary>
        /// <returns></returns>
        protected abstract ILight CreateSpot();
        /// <summary>
        /// Creates spot light.
        /// </summary>
        /// <param name="position">Position</param>
        /// <param name="direction">Normalized direction</param>
        /// <param name="angle">Angle of spot light in degrees</param>
        /// <param name="ambient">Ambient color</param>
        /// <param name="diffuse">Diffuse color</param>
        /// <param name="specular">Specular color</param>
        /// <returns>New light</returns>
        protected abstract ILight CreateSpot(Vector3 position, Vector3 direction, float angle, ColorByte ambient, ColorByte diffuse, ColorByte specular);


        #endregion
    }
}
