using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using bbiwarg.InputProviders;
using bbiwarg.Images;
using bbiwarg.Recognition.FingerRecognition;
using bbiwarg.Recognition.HandRecognition;
using bbiwarg.Recognition.PalmRecognition;
using bbiwarg.Recognition.TouchRecognition;
using bbiwarg.Recognition.Tracking;
using bbiwarg.Server;
using bbiwarg.Graphics;
using bbiwarg.Utility;
using Emgu.CV;
using Emgu.CV.Structure;

namespace bbiwarg
{
    class InputHandler
    {
        public OutputImage[] OutputImages { get; private set; }

        private InputProvider inputProvider;
        private InputFrame currentInputFrame;
        private int currentFrameID;
        private int lastFrameID;

        private DepthImage depthImage;
        private EdgeImage edgeImage;
        private ConfidenceImage confidenceImage;

        private FingerDetector fingerDetector;
        private HandDetector handDetector;
        private PalmDetector palmDetector;
        private TouchDetector touchDetector;

        private FingerTracker fingerTracker;
        private HandTracker handTracker;
        private PalmTracker palmTracker;
        private TouchTracker touchTracker;

        private TouchEventVisualizer touchEventVisualizer;
        private TuioCommunicator tuioCommunicator;

        public InputHandler(InputProvider inputProvider)
        {
            this.inputProvider = inputProvider;
            this.currentFrameID = inputProvider.CurrentFrameID;
            this.lastFrameID = this.currentFrameID - 1;

            initializeConsistentObjects();
        }

        private void initializeConsistentObjects()
        {
            fingerTracker = new FingerTracker();
            handTracker = new HandTracker();
            palmTracker = new PalmTracker();
            touchTracker = new TouchTracker();

            touchEventVisualizer = new TouchEventVisualizer(Parameters.ImageWidth, Parameters.ImageHeight);
            touchTracker.TouchDown += touchEventVisualizer.touchDown;
            touchTracker.TouchMove += touchEventVisualizer.touchMove;
            touchTracker.TouchUp += touchEventVisualizer.touchUp;


            if (Parameters.TuioEnabled)
            {
                tuioCommunicator = new TuioCommunicator(Parameters.TuioIP.ToString(), Parameters.TuioPort);
                touchTracker.TouchDown += tuioCommunicator.touchDown;
                touchTracker.TouchMove += tuioCommunicator.touchMove;
                touchTracker.TouchUp += tuioCommunicator.touchUp;
            }
        }

        private void resetConsistentObjects()
        {
            touchTracker.reset();
            handTracker.reset();
            palmTracker.reset();
            fingerTracker.reset();

            touchEventVisualizer.reset();

            if (Parameters.TuioEnabled)
                tuioCommunicator.reset();
        }

        public void updateFrame()
        {
            Timer.start("updateFrame");

            if (currentFrameID != inputProvider.CurrentFrameID)
            {
                beforeUpdateFrame();

                if (lastFrameID - currentFrameID > 20)
                    resetConsistentObjects();

                createConfidenceImage();
                createDepthImage();
                createEdgeImage();

                detectFingers();
                trackFingers();

                detectHands();
                trackHands();

                detectPalm();
                trackPalm();

                detectTouchEvents();
                trackTouchEvents();

                afterUpdateFrame();
            }

            Timer.stop("updateFrame");
        }

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

            currentInputFrame = inputProvider.CurrentFrame;

            lastFrameID = currentFrameID;
            currentFrameID = currentInputFrame.FrameID;
            Logger.CurrentFrame = currentFrameID;

            if (Parameters.TuioEnabled)
                tuioCommunicator.initFrame();

            Timer.stop("beforeUpdateFrame");
        }

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

            if (Parameters.TuioEnabled)
                tuioCommunicator.commitFrame();

            createOutputImages();

            Timer.stop("afterUpdateFrame");
        }

        private void createConfidenceImage()
        {
            Timer.start("createCnfdncImg");
            Image<Gray, Int16> rawConfidenceImage = new Image<Gray, Int16>(Parameters.ImageWidth, Parameters.ImageHeight, Parameters.ImageWidth * 2, currentInputFrame.RawConfidenceData);
            confidenceImage = new ConfidenceImage(rawConfidenceImage);
            Timer.stop("createCnfdncImg");
        }

        private void createDepthImage()
        {
            Timer.start("createDepthImage");
            Image<Gray, Int16> rawDepthImage = new Image<Gray, Int16>(Parameters.ImageWidth, Parameters.ImageHeight, Parameters.ImageWidth * 2, currentInputFrame.RawDepthData);
            rawDepthImage = rawDepthImage.Or((1 - confidenceImage.Mask).Convert<Gray, Int16>().Mul(Int16.MaxValue));
            depthImage = new DepthImage(rawDepthImage);
            Timer.stop("createDepthImage");
        }

        private void createEdgeImage()
        {
            Timer.start("createEdgeImage");
            edgeImage = new EdgeImage(depthImage);
            Timer.stop("createEdgeImage");
        }

        private void detectFingers()
        {
            Timer.start("detectFingers");
            fingerDetector = new FingerDetector(depthImage, edgeImage);
            Timer.stop("detectFingers");
        }

        private void trackFingers()
        {
            Timer.start("trackFingers");
            fingerTracker.updateFrame(fingerDetector.Fingers);
            Timer.stop("trackFingers");
        }

        private void detectHands()
        {
            Timer.start("detectHands");
            handDetector = new HandDetector(depthImage, edgeImage, fingerTracker.Fingers);
            Timer.stop("detectHands");
        }

        private void trackHands()
        {
            Timer.start("trackHands\t");
            handTracker.updateFrame(handDetector.Hands);
            Timer.stop("trackHands\t");
        }

        private void detectPalm()
        {
            Timer.start("detectPalm\t");
            palmDetector = new PalmDetector(handTracker.Hands);
            Timer.stop("detectPalm\t");
        }

        private void trackPalm()
        {
            Timer.start("trackPalm\t");
            palmTracker.updateFrame(palmDetector.Palms);
            Timer.stop("trackPalm\t");
        }

        private void detectTouchEvents()
        {
            Timer.start("detectTouchEvents");
            touchDetector = new TouchDetector(depthImage, fingerTracker.Fingers, palmTracker.OptimizedPalms);
            Timer.stop("detectTouchEvents");
        }

        private void trackTouchEvents()
        {
            Timer.start("trackTouchEvents");
            touchTracker.updateFrame(touchDetector.TouchEvents);
            Timer.stop("trackTouchEvents");
        }

        private void createOutputImages()
        {
            Timer.start("createOutputImages");
            int numImages = Parameters.OutputNumImages;
            OutputImages = new OutputImage[numImages];
            for (int i = 0; i < numImages; i++)
            {
                OutputImages[i] = new OutputImage(Parameters.ImageWidth, Parameters.ImageHeight);
            }

            //image0
            OutputImages[0].drawImage((depthImage.MaxDepth - depthImage.MinDepth) - depthImage.Image, Parameters.DepthImageColor);
            foreach (Finger f in fingerTracker.Fingers)
            {
                OutputImages[0].fillCircle(f.TipPoint.IntX, f.TipPoint.IntY, 5, Parameters.FingerTipColor);
            }

            foreach (TrackedFinger tf in fingerTracker.TrackedObjects)
            {
                if (tf.CurrentState == TrackingState.Tracked)
                {
                    OutputImages[0].fillCircle(tf.TipPointPrediction.IntX, tf.TipPointPrediction.IntY, 3, Parameters.FingerPointsPredictionColor);
                    OutputImages[0].fillCircle(tf.HandPointPrediction.IntX, tf.HandPointPrediction.IntY, 3, Parameters.FingerPointsPredictionColor);
                    Vector2D mid = (tf.TipPointPrediction+tf.HandPointPrediction) /2 ;
                    OutputImages[0].drawLineSegment(new Utility.LineSegment2D(mid, mid + Parameters.FingerTrackerNumDirectionsForMeanDirection*tf.MeanDirection), Parameters.FingerMeanDirectionColor);
                }
            }

            //image1
            OutputImages[1].drawImage(edgeImage.Image.ThresholdBinary(new Gray(0), new Gray(255)), Parameters.EdgeImageColor);
            foreach (Finger f in fingerTracker.Fingers)
            {
                for (int i = 0; i < f.SliceTrail.NumSlices; i++)
                    OutputImages[1].drawLineSegment(f.SliceTrail[i].LineSegment, Parameters.FingerSliceColor);
                OutputImages[1].drawContour(f.Contour, Parameters.FingerContourColor);
                OutputImages[1].drawLineSegment(f.LineSegment, Parameters.FingerTrackedColor);
                OutputImages[1].drawText(f.MidPoint.IntX, f.MidPoint.IntY, f.TrackID.ToString(), Parameters.FingerIDColor);
            }

            //image2
            foreach (Hand h in handTracker.Hands)
            {
                OutputImages[2].drawImage(h.Mask.ThresholdBinary(new Gray(0), new Gray(255)), Parameters.HandColor[h.TrackID % Parameters.HandNumColors]);
                OutputImages[2].fillCircle(h.Centroid.IntX, h.Centroid.IntY, 5, Parameters.HandCentroidColor);
                OutputImages[2].drawText(h.Centroid.IntX, h.Centroid.IntY, h.TrackID.ToString(), Parameters.HandIDColor);

                if (h.ThumbDefect != null)
                    OutputImages[2].drawDefect(h.ThumbDefect, Parameters.HandThumbDefectPointColor, Parameters.HandThumbDefectLineColor);
            }

            //image3
            OutputImages[3].drawImage((depthImage.MaxDepth - depthImage.MinDepth) - depthImage.Image.Or(255 - handDetector.HandMask.ThresholdBinary(new Gray(0), new Gray(255))), Parameters.DepthImageColor);
            foreach (TrackedTouchEvent tte in touchTracker.TrackedObjects)
            {
                Vector2D position = tte.AbsolutePositionPrediction;
                OutputImages[3].fillCircle(position.IntX, position.IntY, 5, Parameters.TouchEventTrackedColor);
            }
            foreach (Palm p in palmTracker.OptimizedPalms)
            {
                OutputImages[3].drawQuadrangleGrid(p.Quad, Parameters.PalmQuadColor, Parameters.PalmGridColor, Parameters.PalmGridNumRows, Parameters.PalmGridNumColumns);
            }

            //image4
            touchEventVisualizer.updateImage();
            OutputImages[4] = touchEventVisualizer.OutputImage;


            //borders
            for (int i = 0; i < numImages; i++)
            {
                OutputImages[i].drawRectangle(0, 0, Parameters.ImageWidth - 1, Parameters.ImageHeight - 1, Parameters.OutputImageBorderColor);
            }

            Timer.stop("createOutputImages");
        }

    }
}