#region License
//
// The Open Toolkit Library License
//
// Copyright (c) 2006 - 2008 the Open Toolkit library, except where noted.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights to 
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#endregion

using System;
using System.Collections.Generic;
using System.Drawing;

using OpenTK.Graphics.OpenGL;

namespace OpenTK.Graphics.Text
{
    [Obsolete]
    abstract class GL1TextOutputProvider : ITextOutputProvider
    {
        #region Fields

        // Triangle lists, sorted by texture.
        Dictionary<Texture2D, List<Vector2>> active_lists = new Dictionary<Texture2D, List<Vector2>>();
        Queue<List<Vector2>> inactive_lists = new Queue<List<Vector2>>();

        #pragma warning disable 0649
        struct Viewport { public int X, Y, Width, Height; }
        #pragma warning restore 0649

        // Used to save the current state in Begin() and restore it in End()
        Stack<Matrix4> projection_stack = new Stack<Matrix4>();
        Stack<Matrix4> modelview_stack = new Stack<Matrix4>();
        Stack<Matrix4> texture_stack = new Stack<Matrix4>();
        Stack<Viewport> viewport_stack = new Stack<Viewport>();

        // Used as temporary storage when saving / restoring the current state.
        Viewport viewport = new Viewport();
        Matrix4 matrix = new Matrix4();

        // TextBlock - display list cache.
        // Todo: we need a cache eviction strategy.
        const int block_cache_capacity = 32;
        readonly Dictionary<int, int> block_cache = new Dictionary<int, int>(block_cache_capacity);

        bool disposed;

        #endregion

        #region Constructors

        public GL1TextOutputProvider()
        {
            inactive_lists.Enqueue(new List<Vector2>());
        }

        #endregion

        #region ITextOutputProvider Members

        #region Print

        public void Print(ref TextBlock block, Color color, IGlyphRasterizer rasterizer)
        {
            GL.PushAttrib(AttribMask.CurrentBit | AttribMask.TextureBit | AttribMask.EnableBit | AttribMask.ColorBufferBit | AttribMask.DepthBufferBit);

            GL.Enable(EnableCap.Texture2D);
            GL.Enable(EnableCap.Blend);
            SetBlendFunction();

            GL.Disable(EnableCap.DepthTest);

            GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (int)All.Modulate);
            GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvColor, new Color4(0, 0, 0, 0));

            GL.Disable(EnableCap.TextureGenQ);
            GL.Disable(EnableCap.TextureGenR);
            GL.Disable(EnableCap.TextureGenS);
            GL.Disable(EnableCap.TextureGenT);

            RectangleF position;

            SetColor(color);
            
            int block_hash = block.GetHashCode();
            if (block_cache.ContainsKey(block_hash))
            {
                GL.CallList(block_cache[block_hash]);
            }
            else
            {
                using (TextExtents extents = rasterizer.MeasureText(ref block))
                {
                    // Build layout
                    int current = 0;
                    foreach (Glyph glyph in block)
                    {
                        // Do not render whitespace characters or characters outside the clip rectangle.
                        if (glyph.IsWhiteSpace || extents[current].Width == 0 || extents[current].Height == 0)
                        {
                            current++;
                            continue;
                        }
                        else if (!Cache.Contains(glyph))
                            Cache.Add(glyph, rasterizer, TextQuality);

                        CachedGlyphInfo info = Cache[glyph];
                        position = extents[current++];

                        // Use the real glyph width instead of the measured one (we want to achieve pixel perfect output).
                        position.Size = info.Rectangle.Size;

                        if (!active_lists.ContainsKey(info.Texture))
                        {
                            if (inactive_lists.Count > 0)
                            {
                                List<Vector2> list = inactive_lists.Dequeue();
                                list.Clear();
                                active_lists.Add(info.Texture, list);
                            }
                            else
                            {
                                active_lists.Add(info.Texture, new List<Vector2>());
                            }
                        }

                        {
                            // Interleaved array: Vertex, TexCoord, Vertex, ...
                            List<Vector2> current_list = active_lists[info.Texture];
                            current_list.Add(new Vector2(info.RectangleNormalized.Left, info.RectangleNormalized.Top));
                            current_list.Add(new Vector2(position.Left, position.Top));
                            current_list.Add(new Vector2(info.RectangleNormalized.Left, info.RectangleNormalized.Bottom));
                            current_list.Add(new Vector2(position.Left, position.Bottom));
                            current_list.Add(new Vector2(info.RectangleNormalized.Right, info.RectangleNormalized.Bottom));
                            current_list.Add(new Vector2(position.Right, position.Bottom));

                            current_list.Add(new Vector2(info.RectangleNormalized.Right, info.RectangleNormalized.Bottom));
                            current_list.Add(new Vector2(position.Right, position.Bottom));
                            current_list.Add(new Vector2(info.RectangleNormalized.Right, info.RectangleNormalized.Top));
                            current_list.Add(new Vector2(position.Right, position.Top));
                            current_list.Add(new Vector2(info.RectangleNormalized.Left, info.RectangleNormalized.Top));
                            current_list.Add(new Vector2(position.Left, position.Top));
                        }
                    }
                }

                // Render
                int display_list = 0;
                if ((block.Options & TextPrinterOptions.NoCache) == 0)
                {
                    display_list = GL.GenLists(1);
                    // Mesa Indirect gerates an InvalidOperation error right after
                    // GL.EndList() when using ListMode.CompileAndExecute.
                    // Using ListMode.Compile as a workaround.
                    GL.NewList(display_list, ListMode.Compile);
                }
                foreach (Texture2D key in active_lists.Keys)
                {
                    List<Vector2> list = active_lists[key];

                    key.Bind();

                    GL.Begin(BeginMode.Triangles);

                    for (int i = 0; i < list.Count; i += 2)
                    {
                        GL.TexCoord2(list[i]);
                        GL.Vertex2(list[i + 1]);
                    }

                    GL.End();
                }
                if ((block.Options & TextPrinterOptions.NoCache) == 0)
                {
                    GL.EndList();
                    block_cache.Add(block_hash, display_list);
                    GL.CallList(display_list);
                }

                // Clean layout
                foreach (List<Vector2> list in active_lists.Values)
                {
                    //list.Clear();
                    inactive_lists.Enqueue(list);
                }

                active_lists.Clear();
            }
            
            GL.PopAttrib();
        }

        #endregion

        #region Clear

        public void Clear()
        {
            Cache.Clear();
			foreach (int display_list in block_cache.Keys)
				GL.DeleteLists(display_list, 1);
			block_cache.Clear();
        }

        #endregion

        #region Begin

        public void Begin()
        {
            if (disposed)
                throw new ObjectDisposedException(this.GetType().ToString());

            GraphicsContext.Assert();

            // Save the state of everything we are going to modify:
            // the current matrix mode, viewport state and the projection, modelview and texture matrices.
            // All these will be restored in the TextPrinter.End() method.
            int current_matrix;
            GL.GetInteger(GetPName.MatrixMode, out current_matrix);

            GL.GetInteger(GetPName.Viewport, out viewport.X);
            viewport_stack.Push(viewport);

            GL.GetFloat(GetPName.ProjectionMatrix, out matrix.Row0.X);
            projection_stack.Push(matrix);
            GL.GetFloat(GetPName.ModelviewMatrix, out matrix.Row0.X);
            modelview_stack.Push(matrix);
            GL.GetFloat(GetPName.TextureMatrix, out matrix.Row0.X);
            texture_stack.Push(matrix);

            // Prepare to draw text. We want pixel perfect precision, so we setup a 2D mode,
            // with size equal to the window (in pixels). 
            // While we could also render text in 3D mode, it would be very hard to get
            // pixel-perfect precision.
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();
            GL.Ortho(viewport.X, viewport.Width, viewport.Height, viewport.Y, -1.0, 1.0);

            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadIdentity();

            GL.MatrixMode(MatrixMode.Texture);
            GL.LoadIdentity();

            GL.MatrixMode((MatrixMode)current_matrix);
        }

        #endregion

        #region End

        public void End()
        {
            if (disposed)
                throw new ObjectDisposedException(this.GetType().ToString());

            GraphicsContext.Assert();

            int current_matrix;
            GL.GetInteger(GetPName.MatrixMode, out current_matrix);

            viewport = viewport_stack.Pop();
            GL.Viewport(viewport.X, viewport.Y, viewport.Width, viewport.Height);

            GL.MatrixMode(MatrixMode.Texture);
            matrix = texture_stack.Pop();
            GL.LoadMatrix(ref matrix);

            GL.MatrixMode(MatrixMode.Modelview);
            matrix = modelview_stack.Pop();
            GL.LoadMatrix(ref matrix);

            GL.MatrixMode(MatrixMode.Projection);
            matrix = projection_stack.Pop();
            GL.LoadMatrix(ref matrix);

            GL.MatrixMode((MatrixMode)current_matrix);
        }

        #endregion

        #endregion

        #region Protected Members

        protected abstract void SetBlendFunction();

        protected abstract void SetColor(Color color);

        protected abstract TextQuality TextQuality { get; }

        protected abstract GlyphCache Cache { get; }

        #endregion

        #region Static Members

        public static GL1TextOutputProvider Create(TextQuality quality)
        {
            if (!GL.SupportsExtension("Version12") || !GL.SupportsFunction("BlendColor") || quality == TextQuality.Low || quality == TextQuality.Medium)
                return new GL11TextOutputProvider(quality);
            else
                return new GL12TextOutputProvider(quality);
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (!disposed)
            {
                Cache.Dispose();
                disposed = true;
            }
        }

        #endregion
    }
}
