using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VisimBox.Renderer;
using OpenTK.Graphics.OpenGL;
using VisiMath;
using VisiMath.Geometry;

namespace VisimRenderGL
{
    class GLLight : Light
    {
        protected float[] ambient;
        protected float[] diffuse;
        protected float[] specular;
        protected float[] position;
        protected float[] direction;
        protected float[] zeroes = new float[4];

        public GLLight()
        {
            position = new float[4];
            position[3] = 1;
            direction = new float[4];
            direction[3] = 0;
        }

        public GLLight(GLLight copy) : this()
        {
            this._lType = copy._lType;
            this._Name = copy.Name;
            if (copy.ActiveArea != null)
                this.ActiveArea = copy.ActiveArea.GetCopy();
            this._typeChanged = true;
            Ambient = copy.Ambient;
            Diffuse = copy.Diffuse;
            Specular = copy.Specular;
            Position = copy.Position;
            Direction = copy.Direction;
            SpotAngle = copy.SpotAngle;
            SpotIntensity = copy.SpotIntensity;
            Attenuation = copy.Attenuation;
            Radius = copy.Radius;
        }


        public void Update()
        {
            ambient =  Ambient.GetFloats();
            diffuse =  Diffuse.GetFloats();
            specular = Specular.GetFloats();

            if ((LightType == LightTypes.Position) || (LightType == LightTypes.Spot))
            {
                position[0] = Position.X;
                position[1] = Position.Y;
                position[2] = Position.Z;
            }

            if ((LightType == LightTypes.Directional) || (LightType == LightTypes.Spot))
            {
                direction[0] = Direction.X;
                direction[1] = Direction.Y;
                direction[2] = Direction.Z;
            }
        }

        public override ILight GetCopy()
        {
            return new GLLight(this);
        }

        public override void Activate(int light)
        {
            Update();
            LightName lname;

            switch (light)
            {
                case 0:
                    GL.Enable(EnableCap.Light0);
                    lname = LightName.Light0;
                    break;
                case 1:
                    GL.Enable(EnableCap.Light1);
                    lname = LightName.Light1;
                    break;
                case 2:
                    GL.Enable(EnableCap.Light2);
                    lname = LightName.Light2;
                    break;
                case 3:
                    GL.Enable(EnableCap.Light3);
                    lname = LightName.Light3;
                    break;
                case 4:
                    GL.Enable(EnableCap.Light4);
                    lname = LightName.Light4;
                    break;
                case 5:
                    GL.Enable(EnableCap.Light5);
                    lname = LightName.Light5;
                    break;
                case 6:
                    GL.Enable(EnableCap.Light6);
                    lname = LightName.Light6;
                    break;
                case 7:
                    GL.Enable(EnableCap.Light7);
                    lname = LightName.Light7;
                    break;
                default:
                    return;
            }

            if (_typeChanged)
            {
                _typeChanged = false;
                //we're already setting all states, so we can ignore this
            }


            //todo: spot light and positional light parameters!
            switch (LightType)
            {
                case LightTypes.Ambient:
                    GL.Light(lname, LightParameter.Ambient, ambient);
                    GL.Light(lname, LightParameter.Diffuse, zeroes);
                    GL.Light(lname, LightParameter.Specular, zeroes);
                    GL.Light(lname, LightParameter.ConstantAttenuation, 1);
                    GL.Light(lname, LightParameter.LinearAttenuation, 0);
                    GL.Light(lname, LightParameter.QuadraticAttenuation, 0);
                    GL.Light(lname, LightParameter.SpotCutoff, 180);
                    GL.Light(lname, LightParameter.SpotExponent, 0);
                    GL.Light(lname, LightParameter.SpotDirection, zeroes);
                    GL.Light(lname, LightParameter.Position, zeroes);
                    break;
                case LightTypes.Directional:
                    GL.Light(lname, LightParameter.Ambient, ambient);
                    GL.Light(lname, LightParameter.Diffuse, diffuse);
                    GL.Light(lname, LightParameter.Specular, specular);
                    GL.Light(lname, LightParameter.ConstantAttenuation, 1);
                    GL.Light(lname, LightParameter.LinearAttenuation, 0);
                    GL.Light(lname, LightParameter.QuadraticAttenuation, 0);
                    GL.Light(lname, LightParameter.SpotCutoff, 180);
                    GL.Light(lname, LightParameter.SpotExponent, 0);
                    GL.Light(lname, LightParameter.SpotDirection, zeroes);
                    GL.Light(lname, LightParameter.Position, direction);

                    break;
                case LightTypes.Position:
                    GL.Light(lname, LightParameter.Ambient, ambient);
                    GL.Light(lname, LightParameter.Diffuse, diffuse);
                    GL.Light(lname, LightParameter.Specular, specular);                    
                    GL.Light(lname, LightParameter.ConstantAttenuation, Attenuation.X);
                    GL.Light(lname, LightParameter.LinearAttenuation, Attenuation.Y);
                    GL.Light(lname, LightParameter.QuadraticAttenuation, Attenuation.Z);
                    GL.Light(lname, LightParameter.SpotCutoff, 180);
                    GL.Light(lname, LightParameter.SpotExponent, 0);
                    GL.Light(lname, LightParameter.SpotDirection, zeroes);

                    GL.Light(lname, LightParameter.Position, position);

                    break;
                case LightTypes.Spot:
                    GL.Light(lname, LightParameter.Ambient, ambient);
                    GL.Light(lname, LightParameter.Diffuse, diffuse);
                    GL.Light(lname, LightParameter.Specular, specular);
                    GL.Light(lname, LightParameter.ConstantAttenuation, Attenuation.X);
                    GL.Light(lname, LightParameter.LinearAttenuation, Attenuation.Y);
                    GL.Light(lname, LightParameter.QuadraticAttenuation, Attenuation.Z);
                    GL.Light(lname, LightParameter.Position, position);
                    GL.Light(lname, LightParameter.SpotDirection, direction);
                    GL.Light(lname, LightParameter.SpotCutoff, SpotAngle);
                    GL.Light(lname, LightParameter.SpotExponent, SpotIntensity);

                    break;
            }
        }

        public override void ReadXml(System.Xml.XmlReader reader)
        {
            string type = reader["type"];

            if (type == LightTypes.Ambient.ToString())
            {
                LightType = LightTypes.Ambient;
            }
            else if (type == LightTypes.Directional.ToString())
            {
                LightType = LightTypes.Directional;
            }
            else if (type == LightTypes.Position.ToString())
            {
                LightType = LightTypes.Position;
            }
            else if (type == LightTypes.Spot.ToString())
            {
                LightType = LightTypes.Spot;
            }
            else
            {
                VisimBox.Console.WriteLine("GLLight ReadXML: unknown type of light, assuming ambient");
                LightType = LightTypes.Ambient;
            }


            _Name = reader["name"];
            if (_Name == null)
            {
                VisimBox.Console.WriteLine("GLLight ReadXML: no name for light, using default");
                _Name = "DefaultLight";
            }

            while (reader.Read())
            {
                if (reader.IsStartElement())
                {
                    if (reader.Name == "Ambient")
                    {
                        Ambient = new ColorByte(reader);
                    }
                    else if (reader.Name == "Diffuse")
                    {
                        Diffuse = new ColorByte(reader);
                    }
                    else if (reader.Name == "Specular")
                    {
                        Specular = new ColorByte(reader);
                    }
                    else if (reader.Name == "Radius")
                    {
                        Radius = XMLHelper.ReadAsFloat(reader);
                    }
                    else if (reader.Name == "Position")
                    {
                        Position = XMLHelper.ReadVector3(reader);
                    }
                    else if (reader.Name == "Direction")                        
                    {
                        Direction = XMLHelper.ReadVector3(reader);
                    }
                    else if (reader.Name == "Attenuation")
                    {
                        Attenuation = XMLHelper.ReadVector3(reader);
                    }
                    else if (reader.Name == "SpotAngle")
                    {
                        float val = XMLHelper.ReadAsFloat(reader);
                        MathTools.Clamp(ref val, 0, 180);   //!!!
                        SpotAngle = val;
                    }
                    else if (reader.Name == "SpotIntensity")
                    {
                        float val = XMLHelper.ReadAsFloat(reader);
                        MathTools.Clamp(ref val, 0, 128);   //!!!
                        SpotIntensity = val;
                    }
                    else if (reader.Name == "ActiveArea")
                    {
                        IGeometry GeoObject = GeoFactory.ReadXml(reader);
                        if (GeoObject == null)
                        {
                            VisimBox.Console.DebugLine("Light loading: loading geometry failed!");
                        }
                        else ActiveArea = GeoObject;
                    }
                    //todo - active area reading
                }
                else return;
            }
        }
    }



    class GLLightMan : LightManager
    {

        /// <summary>
        /// Creates exact copy of the light source
        /// </summary>
        /// <param name="source">Source light object</param>
        /// <returns>Copy of the light source</returns>
        public override ILight CreateCopy(ILight source)
        {
            ILight il = source.GetCopy();
            string name = GetUniqueName(il.Name);
            Add(name, il);
            return il;
        }


        public override ILight ReadFromXml(System.Xml.XmlReader reader)
        {
            GLLight rval = new GLLight();
            rval.ReadXml(reader);
            string name = GetUniqueName(rval.Name);
            rval.Name = name;
            Add(rval.Name, rval);
            return rval as ILight;
        }

        public override void EnableLighting(bool value = true)
        {
            if (value)
                GL.Enable(EnableCap.Lighting);
            else
                GL.Disable(EnableCap.Lighting);
        }


        public static EnableCap GetLightEnable(int index)
        {
            switch (index)
            {
                case 0:
                    return EnableCap.Light0;
                case 1:
                    return EnableCap.Light1;
                case 2:
                    return EnableCap.Light2;
                case 3:
                    return EnableCap.Light3;
                case 4:
                    return EnableCap.Light4;
                case 5:
                    return EnableCap.Light5;
                case 6:
                    return EnableCap.Light6;
                case 7:
                    return EnableCap.Light7;
            }
            throw new ArgumentException("GetLight name - index out of bounds");
        }
        
        public override int MaxLights
        {
            get { return 8; } //note!! check this!
        }

        public override void DeactiveLight(int index)
        {
            GL.Disable(GetLightEnable(index));
        }

        protected override ILight CreateAmbient(VisiMath.ColorByte color)
        {
            ILight rval = new GLLight();
            rval.LightType = LightTypes.Ambient;
            rval.Ambient = color;
            return rval;
        }

        protected override ILight CreateDirectional()
        {
            ILight rval = new GLLight();
            rval.LightType = LightTypes.Directional;
            return rval;
        }

        protected override ILight CreateDirectional(VisiMath.Vector3 direction, VisiMath.ColorByte ambient, VisiMath.ColorByte diffuse, VisiMath.ColorByte specular)
        {
            ILight rval = new GLLight();
            rval.LightType = LightTypes.Directional;
            rval.Direction = direction;
            rval.Ambient = ambient;
            rval.Diffuse = diffuse;
            rval.Specular = specular;
            return rval;
        }

        protected override ILight CreatePositional()
        {
            ILight rval = new GLLight();
            rval.LightType = LightTypes.Position;
            return rval;
        }

        protected override ILight CreatePositional(VisiMath.Vector3 position, VisiMath.ColorByte ambient, VisiMath.ColorByte diffuse, VisiMath.ColorByte specular)
        {
            ILight rval = new GLLight();
            rval.LightType = LightTypes.Position;
            rval.Position = position;
            rval.Ambient = ambient;
            rval.Diffuse = diffuse;
            rval.Specular = specular;
            return rval;
        }

        protected override ILight CreateSpot()
        {
            ILight rval = new GLLight();
            rval.LightType = LightTypes.Spot;
            return rval;
        }

        protected override ILight CreateSpot(VisiMath.Vector3 position, VisiMath.Vector3 direction, float angle, VisiMath.ColorByte ambient, VisiMath.ColorByte diffuse, VisiMath.ColorByte specular)
        {
            ILight rval = new GLLight();
            rval.LightType = LightTypes.Spot;
            rval.Position = position;
            rval.Direction = direction;
            rval.Ambient = ambient;
            rval.Diffuse = diffuse;
            rval.Specular = specular;
            rval.SpotAngle = angle;
            return rval;
        }
    }


}
