using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using bbiwarg.Utility;
using bbiwarg.Detectors.Fingers;
using bbiwarg.Detectors.Palm;
using bbiwarg.Detectors.Touch;
using bbiwarg.Images;
using bbiwarg.InputProviders;
using Emgu.CV;
using Emgu.CV.Structure;
using bbiwarg.Graphics;

namespace bbiwarg
{
    class VideoHandle
    {
        private IInputProvider inputProvider;
        private InputFrame inputFrame;

        public int Width { get; private set; }
        public int Height { get; private set; }

        public int CurrentFrame
        {
            get
            {
                if (sourceIsMovie())
                    return inputProvider.getCurrentMovieFrame();
                else
                    return videoFrame;
            }
        }

        public OutputImage[] OutputImages { get; private set; }
        
        private DepthImage depthImage;
        private EdgeImage edgeImage;
        
        private FingerDetector fingerDetector;
        private PalmDetector palmDetector;
        private TouchDetector touchDetector;
        private PalmTouchDetector palmTouchDetector;

        private FingerTracker fingerTracker;
        private TouchTracker touchTracker;

        private TouchEventVisualizer touchEventVisualizer;

        private int videoFrame = 0;

        public VideoHandle(IInputProvider inputProvider)
        {
            this.inputProvider = inputProvider;

            //initialize trackers
            touchTracker = new TouchTracker();
            fingerTracker = new FingerTracker();
        }

        public void start()
        {
            palmDetector = new PalmDetector();

            inputProvider.init();
            inputProvider.start();
            inputProvider.updateFrame();
            processFrameUpdate();
        }

        public void stop()
        {
            inputProvider.stop();
        }

        public bool sourceIsMovie()
        {
            return inputProvider.sourceIsMovie();
        }

        public void reversePlay()
        {
            inputProvider.reversePlay();
        }

        public void pauseMovie()
        {
            inputProvider.pauseMovie();
        }

        public void unpauseMovie()
        {
            inputProvider.unpauseMovie();
        }

        public void nextFrame()
        {
            if (inputProvider.isActive())
            {
                inputProvider.releaseFrame();
                inputProvider.updateFrame();
                processFrameUpdate();
            }
            else
            {
                inputProvider.stop();
            }
            ++videoFrame;
        }

        public List<PalmTouchEvent> getPalmTouchEvents()
        {
            return palmTouchDetector.PalmTouchEvents;
        }

        private void processFrameUpdate()
        {
            Timer.start("processFrameUpdate");

            //read data from inputProvider
            Timer.start("readInputData");
            inputFrame = inputProvider.getInputFrame();
            Width = inputFrame.Width;
            Height = inputFrame.Height;
            Timer.stop("readInputData");

            //create output images
            Timer.start("createOtherImages");
            int numImages = 4;
            OutputImages = new OutputImage[numImages];
            for (int i = 0; i < numImages; i++) {
                OutputImages[i] = new OutputImage(Width, Height);
                    
            }
            Timer.stop("createOtherImages");

            //create depthImage
            Timer.start("createDepthImage");
            Image<Gray, Int16> image = new Image<Gray, Int16>(Width, Height, Width * 2, inputFrame.RawDepthData);
            depthImage = new DepthImage(image);
            Image<Gray, byte> tmpDepth = (depthImage.MaxDepth - depthImage.MinDepth) - depthImage.Image;
            OutputImages[0].Image[0] = OutputImages[0].Image[1] = OutputImages[0].Image[2] = tmpDepth;
            Timer.stop("createDepthImage");

            // create edge image
            Timer.start("createEdgeImage");
            edgeImage = new EdgeImage(depthImage);
            OutputImages[1].Image[2] = edgeImage.Image.ThresholdBinary(new Gray(0), new Gray(1)).Mul(255);
            Timer.stop("createEdgeImage");

            //detect fingers
            Timer.start("fingerDetection");
            fingerDetector = new FingerDetector(depthImage, edgeImage, OutputImages[1]);
            Timer.stop("fingerDetection");

            //track fingers
            Timer.start("fingerTracking");
            fingerTracker.setDetectedTouchEventsThisFrame(fingerDetector.Fingers, OutputImages[1]);
            Timer.stop("fingerTracking");

            //remove background noise
            Timer.start("removeBackground");
            depthImage.removeBackground(fingerTracker.TrackedFingers);
            edgeImage = new EdgeImage(depthImage);
            OutputImages[2].Image[0] = OutputImages[2].Image[1] = OutputImages[2].Image[2] = (depthImage.MaxDepth - depthImage.MinDepth) - depthImage.Image;
            Timer.stop("removeBackground");

            //detect palm
            Timer.start("palmDetection");
            if (fingerTracker.TrackedFingers.Count >= 2)
                palmDetector.findPalmQuad(depthImage, edgeImage, OutputImages[2], fingerTracker.TrackedFingers);
            if (CurrentFrame == 0)
                palmDetector.resetFilters();
            Timer.stop("palmDetection");

            //detect touchEvents
            Timer.start("touchDetection");
            touchDetector = new TouchDetector(fingerTracker.TrackedFingers, depthImage, OutputImages[0]);
            if (palmDetector.PalmQuad != null)
                palmTouchDetector = new PalmTouchDetector(touchDetector.TouchEvents, palmDetector.PalmQuad);
            Timer.stop("touchDetection");

            //track touchEvents
            Timer.start("touchTracking");
            touchTracker.setDetectedTouchEventsThisFrame(touchDetector.TouchEvents, OutputImages[2]);
            Timer.stop("touchTracking");

            // touch event visualizer
            if (touchEventVisualizer == null)
                touchEventVisualizer = new TouchEventVisualizer(Width, Height);
            if (CurrentFrame == 0)
                touchEventVisualizer.Reset();
            if (palmTouchDetector != null)
            {
                foreach (PalmTouchEvent e in palmTouchDetector.PalmTouchEvents)
                    touchEventVisualizer.addPalmTouchEvent(e, CurrentFrame);
                touchEventVisualizer.updateImage();
                OutputImages[3] = touchEventVisualizer.OutputImage;
            }

            // add borders
            for (int i = 0; i < numImages; i++)
            {
                OutputImages[i].drawRectangle(0, 0, Width - 1, Height - 1, Color.White);

            }

            Timer.stop("processFrameUpdate");
        }
    }
}
