// This code was written for the OpenTK library and has been released
// to the Public Domain.
// It is provided "as is" without express or implied warranty of any kind.

using System;
using System.Drawing;
using System.Drawing.Imaging;

using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;

namespace Examples.Tutorial
{

    [Example("Texture Matrix Wormhole", ExampleCategory.OpenGL, "1.x", Documentation = "TextureMatrix")]

    class TextureMatrix : GameWindow
    {
        Vector2 orientation;

        public TextureMatrix()
            : base(800, 600, new GraphicsMode(32, 16, 0, 4))
        {
            VSync = VSyncMode.On;
        }

        int Texture;
        int list;

        protected override void OnLoad(EventArgs e)
        {
            GL.ClearColor(0f, 0f, 0f, 0f);
            GL.Enable(EnableCap.DepthTest);
            GL.Enable(EnableCap.CullFace);

            Texture = LoadTexture("Data/Textures/logo-dark.jpg");

            GL.Enable(EnableCap.Texture2D);

            list = GL.GenLists(1);

            GL.NewList(list, ListMode.Compile);
            {
                const int slices = 32;
                const float distance = 0.25f;

                GL.Begin(PrimitiveType.Quads);

                for (float scale = 0.26f; scale < 5f; scale += distance)
                    for (int i = 0; i < slices; i++)
                    {
                        Vector3 MiddleCenter = new Vector3((float)(Math.Sin((double)i / slices * 2 * Math.PI) * scale),
                                                           (float)(Math.Cos((double)i / slices * 2 * Math.PI) * scale),
                                                           (float)(1f / scale));
                        Vector3 MiddleRight = new Vector3((float)(Math.Sin((double)(i + 1) / slices * 2 * Math.PI) * scale),
                                                          (float)(Math.Cos((double)(i + 1) / slices * 2 * Math.PI) * scale),
                                                          (float)(1f / scale));
                        Vector3 BottomRight = new Vector3((float)(Math.Sin((double)(i + 1) / slices * 2 * Math.PI) * (scale - distance)),
                                                          (float)(Math.Cos((double)(i + 1) / slices * 2 * Math.PI) * (scale - distance)),
                                                          (float)(1f / (scale - distance)));
                        Vector3 BottomCenter = new Vector3((float)(Math.Sin((double)i / slices * 2 * Math.PI) * (scale - distance)),
                                                           (float)(Math.Cos((double)i / slices * 2 * Math.PI) * (scale - distance)),
                                                           (float)(1f / (scale - distance)));

                        GL.TexCoord2(1f, 0f);
                        GL.Vertex3(MiddleCenter);
                        GL.TexCoord2(0f, 0f);
                        GL.Vertex3(MiddleRight);
                        GL.TexCoord2(0f, 1f);
                        GL.Vertex3(BottomRight);
                        GL.TexCoord2(1f, 1f);
                        GL.Vertex3(BottomCenter);
                    }

                GL.End();
            }
            GL.EndList();
        }

        protected override void OnResize(EventArgs e)
        {
            GL.Viewport(this.ClientRectangle);

            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, Width / (float)Height, 1.0f, 50.0f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref projection);
        }

        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            var keyboard = OpenTK.Input.Keyboard.GetState();
            if (keyboard[Key.Escape])
                Exit();

            var mouse = OpenTK.Input.Mouse.GetState();
            orientation = new Vector2(mouse.X, mouse.Y);
        }

        protected override void OnRenderFrame(FrameEventArgs e)
        {
            GL.Clear(ClearBufferMask.ColorBufferBit |
                     ClearBufferMask.DepthBufferBit);

            GL.MatrixMode(MatrixMode.Texture);
            GL.Translate(e.Time / 2, -e.Time, 0f);

            Matrix4 modelview = Matrix4.LookAt(Vector3.Zero, Vector3.UnitZ, Vector3.UnitY);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadMatrix(ref modelview);

            GL.Translate(0f, 0f, 1.5f);

            GL.Rotate(orientation.X, Vector3.UnitY);
            GL.Rotate(orientation.Y, Vector3.UnitX);

            GL.BindTexture(TextureTarget.Texture2D, Texture);
            GL.CallList(list);

            SwapBuffers();
        }

        public static int LoadTexture(string filename)
        {
            TextureTarget Target = TextureTarget.Texture2D;

            int texture;
            GL.GenTextures(1, out texture);
            GL.BindTexture(Target, texture);

            Version version = new Version(GL.GetString(StringName.Version).Substring(0, 3));
            Version target = new Version(1, 4);
            if (version >= target)
            {
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.GenerateMipmap, (int)All.True);
                GL.TexParameter(Target, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear);
            }
            else
            {
                GL.TexParameter(Target, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
            }
            GL.TexParameter(Target, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);

            GL.TexParameter(Target, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
            GL.TexParameter(Target, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);

            Bitmap bitmap = new Bitmap(filename);
            BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            GL.TexImage2D(Target, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
            GL.Finish();
            bitmap.UnlockBits(data);

            if (GL.GetError() != ErrorCode.NoError)
                throw new Exception("Error loading texture " + filename);

            return texture;
        }

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            using (TextureMatrix example = new TextureMatrix())
            {
                // Get the title and category  of this example using reflection.
                ExampleAttribute info = ((ExampleAttribute)example.GetType().GetCustomAttributes(false)[0]);
                example.Title = String.Format("OpenTK | {0} {1}: {2}", info.Category, info.Difficulty, info.Title);
                example.Run(30.0, 0.0);
            }
        }
    }
}