using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VisimBox.Renderer.RenderObjects;
using VisiMath;

namespace VisimBox.Renderer
{
    public struct RenderQueueSettings
    {
        /// <summary>
        /// Sort alphas immediately when adding new item
        /// </summary>
        public bool ImmediateAlphaSort;
        /// <summary>
        /// Resort alphas before rendering
        /// </summary>
        public bool AlphaSort;

        public static RenderQueueSettings Default()
        {
            RenderQueueSettings rval = new RenderQueueSettings();
            rval.ImmediateAlphaSort = true;
            rval.AlphaSort = false;
            return rval;
        }
    }


    /// <summary>
    /// rendering queue
    /// </summary>
    public class RenderQueue
    {
        protected List<RenderBufferItem> Items;
        protected List<RenderBufferItem> AlphaItems;    //todo, uuse sorted dictionary?
        protected int _CurrentIndex;
        protected int _CurrentAlphaIndex;
        protected IRenderContext Owner;

        protected RenderQueueSettings settings;



        /// <summary>
        /// Current capacity of the renderqueue
        /// </summary>
        public int Capacity
        {
            get
            {
                return Items.Capacity;
            }
            set
            {
                Items.Capacity = value;
            }
        }

        /// <summary>
        /// Current index of processed item (everything before was processed)
        /// </summary>
        public int CurrentIndex
        {
            get
            {
                return _CurrentIndex;
            }
        }

        /// <summary>
        /// Current index of start of the transparent items
        /// </summary>
        public int CurrentAlphaIndex
        {
            get
            {
                return _CurrentAlphaIndex;
            }
        }

        /// <summary>
        /// Returns number of remaining items in the queue
        /// </summary>
        public int Remaining
        {
            get
            {
                if (CurrentIndex < Items.Count)
                    return Items.Count - CurrentIndex;
                return AlphaItems.Count - (Items.Count + CurrentIndex);
            }
        }


        /// <summary>
        /// Creates renderqueue with starting capacity
        /// </summary>
        /// <param name="capacity">Requested starting capacity of the queue</param>
        /// <param name="owner">Associated rendering context</param>
        public RenderQueue(IRenderContext owner, int capacity = 100)
        {
            Items = new List<RenderBufferItem>(capacity);
            AlphaItems = new List<RenderBufferItem>(capacity);
            Owner = owner;
            settings = RenderQueueSettings.Default();
        }

        /// <summary>
        /// Adds render item to the renderer queue
        /// </summary>
        /// <param name="item">Render buffer item</param>
        /// <remarks>Adds item to either normal or alpha list. Depending on used rendering strategy (and used multithreading) may resort list, or directly render the item.</remarks>
        public void AddItem(RenderBufferItem item)
        {
            if (item.Alpha)
            {
                if (settings.ImmediateAlphaSort)
                {
                    float dst = item.CameraDistance;
                    int idx = AlphaItems.Count;
                    if (idx == 0)
                    {
                        AlphaItems.Add(item);
                    }
                    else
                    {
                        while (AlphaItems[idx - 1].CameraDistance > dst)
                        {
                            idx--;
                            if (idx == 0)
                            {
                                break;
                            }
                        }
                        AlphaItems.Insert(idx, item);
                    }                
                    
                }
                else
                {
                    AlphaItems.Add(item);
                }
            }
            else
            {
                Items.Add(item);
            }
        }

        /// <summary>
        /// Clears render queue (stops rendering, removes unrendered items)
        /// </summary>
        public void Clear()
        {
            AlphaItems.Clear();
            Items.Clear();
            _CurrentIndex = 0;
        }

        /// <summary>
        /// Starts rendering queue (should be cleared before).
        /// Queue now accepts (and based on used strategy) renders items
        /// </summary>
        public void Start()
        {            
            //do nothing (in future start thread)
        }

        protected void RenderItem(RenderBufferItem item, RendererEventArgs args)
        {
            Owner.SetTransformation(Matrix4.Identitity);

            //set material

            if (item.Material != null)
            {
                item.Material.Activate(args);

                LightManager lm = args.Renderer.LightMan;

                if ((item.Material.MaterialType != RenderMaterialType.Simple) &&
                    (item.Material.MaterialType != RenderMaterialType.SimpleTextured))
                {
                    //set lights. NOTE!!! this really SHOULD be optimized somehow....
                    lm.EnableLighting(true);

                    VisiMath.Geometry.IGeometry bound = item.Item.GetBoundingObject();
                    if (bound != null)
                    {
                        lm.ActivateLights(bound);
                    }
                    else
                    {
                        lm.ActivateLights(item.Item.GetCenter());
                    }
                }
                else
                {
                    lm.EnableLighting(false);
                }
            }

            //set transformation
            if (item.TransValid)
                Owner.SetTransformation(ref item.Trans);

            //render geometry
            item.Item.RenderGeometry(args);
        }

        /// <summary>
        /// Finishes rendering of the queue (first normal objects, then sorted alphas)
        /// </summary>
        public void Finish()
        {
            //first render remaining normal items
            RendererEventArgs args = new RendererEventArgs(Owner.Renderer, Owner, Owner.MainCamera);

            if (_CurrentIndex < Items.Count)
            {

                for (int i = _CurrentIndex; i < Items.Count; i++)
                {
                    RenderItem(Items[i], args);
                }
            }

            //sort alphas
            if (settings.AlphaSort)
                AlphaItems.Sort(CompareDistances);

            //render alphas

            for (int i = 0; i < AlphaItems.Count; i++)
            {
                RenderItem(AlphaItems[i], args);
            }

            _CurrentIndex = Items.Count + AlphaItems.Count;
        }

        protected static int CompareDistances(RenderBufferItem r1, RenderBufferItem r2)
        {
            float dst = r1.CameraDistance - r2.CameraDistance;
            if (MathTools.IsAlmostZero(dst))
                return 0;

            if (dst < 0)
                return -1;
            return 1;
        }
             


    }
}
