using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using bbiwarg.Images;
using bbiwarg.Detectors.TouchDetection;
using bbiwarg.Utility;

using Emgu.CV;
using Emgu.CV.Structure;

namespace bbiwarg.Graphics
{
    class OutputWindow : GameWindow
    {
        private VideoHandle videoHandle;
        private uint textureId;
        private bool paused = false;
        private long timeSpacePressed, timeLeftPressed, timeRightPressed;
        private Stopwatch watch;

        public OutputWindow(VideoHandle videoHandle)
            : base((int) (Constants.OutputScaleFactor * Math.Max(1, Math.Min(Constants.OutputNumImagesPerRow, videoHandle.OutputImages.Length)) * videoHandle.Width), 
                   (int) (Constants.OutputScaleFactor * (1 + (videoHandle.OutputImages.Length - 1) / Constants.OutputNumImagesPerRow) * videoHandle.Height))
        {
            this.videoHandle = videoHandle;
            watch = new Stopwatch();
            watch.Start();
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            Title = "BBIWARG - Output";
            GL.ClearColor(Color.Black);

            // transparency
            GL.Enable(EnableCap.Blend);
            GL.BlendEquation(BlendEquationMode.Max);

            //Depth Test 
            GL.Enable(EnableCap.DepthTest);

            // Texture
            GL.Enable(EnableCap.Texture2D);

            GL.GenTextures(1, out textureId);
            GL.BindTexture(TextureTarget.Texture2D, textureId);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
        }

        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            
            int screenWidth = ClientRectangle.Width;
            int screenHeight = ClientRectangle.Height;

            int imageWidth = videoHandle.Width;
            int imageHeight = videoHandle.Height;
            float imageAspectRatio = (float)imageWidth / (float)imageHeight;

            int numImages = videoHandle.OutputImages.Length;
            int numRows = 1 + (numImages - 1) / Constants.OutputNumImagesPerRow;
            int numCols = Math.Min(videoHandle.OutputImages.Length, Constants.OutputNumImagesPerRow);
            int heightForWidth = (int)((float)screenWidth / ((float)numCols * imageAspectRatio) * (float)numRows);
            
            GL.Viewport(0, (screenHeight - heightForWidth) / 2, screenWidth, heightForWidth);

            // top left at (0,0) every image from (i, j) to (i + 1, j + 1)
            Matrix4 projection = Matrix4.CreateOrthographicOffCenter(0, numCols, numRows, 0, 0.001f, 10f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref projection);
        }

        protected override void OnRenderFrame(FrameEventArgs e)
        {
            Timer.start("onRenderFrame");
   
            base.OnRenderFrame(e);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            Matrix4 modelview = Matrix4.LookAt(Vector3.Zero, -Vector3.UnitZ, Vector3.UnitY);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadMatrix(ref modelview);

            const int autoRepeatDelay = 100; // ms
            if (videoHandle.sourceIsMovie())
            {
                // pause and unpause with space
                long elapsed = watch.ElapsedMilliseconds;
                if (OpenTK.Input.Keyboard.GetState().IsKeyDown(OpenTK.Input.Key.Space) && (elapsed - timeSpacePressed) >= autoRepeatDelay)
                {
                    timeSpacePressed = elapsed;
                    if (paused)
                        videoHandle.unpauseMovie();
                    else
                        videoHandle.pauseMovie();
                    paused = !paused;
                }

                // when paused go to next / previous frame with right / left keys
                if (paused)
                {
                    if (OpenTK.Input.Keyboard.GetState().IsKeyDown(OpenTK.Input.Key.Right) && (elapsed - timeRightPressed) >= autoRepeatDelay)
                    {
                        timeRightPressed = elapsed;
                        videoHandle.unpauseMovie();
                        videoHandle.nextFrame();
                        videoHandle.pauseMovie();
                    }
                    else if (OpenTK.Input.Keyboard.GetState().IsKeyDown(OpenTK.Input.Key.Left) && (elapsed - timeLeftPressed) >= autoRepeatDelay)
                    {
                        timeLeftPressed = elapsed;
                        videoHandle.unpauseMovie();
                        videoHandle.reversePlay();
                        videoHandle.nextFrame();
                        videoHandle.reversePlay();
                        videoHandle.pauseMovie();
                    }
                }
                else
                {
                    videoHandle.nextFrame();
                }
            }
            else
            {
                videoHandle.nextFrame();
            }

            if (videoHandle.sourceIsMovie())
                Title = "BBIWARG - Output (Frame " + videoHandle.CurrentFrame + ")";

            Timer.start("buildTextures");
            GL.Enable(EnableCap.Texture2D);
                
            int imageIndex = 0;
            foreach (OutputImage image in videoHandle.OutputImages)
            {
                int x = imageIndex % Constants.OutputNumImagesPerRow;
                int y = imageIndex / Constants.OutputNumImagesPerRow;
                GL.BindTexture(TextureTarget.Texture2D, textureId);
                GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, videoHandle.Width, videoHandle.Height, 0, PixelFormat.Rgb, PixelType.UnsignedByte, image.Image.MIplImage.imageData);
                GL.Begin(PrimitiveType.Quads);
                GL.Color3(1.0, 1.0, 1.0);
                GL.TexCoord2(0, 0); GL.Vertex3(0 + x, 0 + y, -1);
                GL.TexCoord2(1, 0); GL.Vertex3(1 + x, 0 + y, -1);
                GL.TexCoord2(1, 1); GL.Vertex3(1 + x, 1 + y, -1);
                GL.TexCoord2(0, 1); GL.Vertex3(0 + x, 1 + y, -1);
                GL.End();
                ++imageIndex;
            }

            Timer.stop("buildTextures");

            Timer.start("swapBuffers");
            SwapBuffers();
            Timer.stop("swapBuffers");

            Timer.stop("onRenderFrame");
            
            if(Constants.OutputTimerEnabled)
                Timer.outputAll();
        }

    }
}