using bbiwarg.Images;
using bbiwarg.Input.InputProviding;
using bbiwarg.Recognition.FingerRecognition;
using bbiwarg.Recognition.HandRecognition;
using bbiwarg.Recognition.PalmRecognition;
using bbiwarg.Recognition.TouchRecognition;
using bbiwarg.Utility;
using System;

namespace bbiwarg.Input.InputHandling
{
    public delegate void NewProcessedFrameEventHandler(object sender, NewProcessedFrameEventArgs e);

    public class NewProcessedFrameEventArgs
    {
        public FrameData FrameData { get; private set; }

        public NewProcessedFrameEventArgs(FrameData frameData)
        {
            FrameData = frameData;
        }
    }

    public class InputHandler
    {
        private InputProvider inputProvider;
        private bool resetFlag;

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

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

        public ImageSize ImageSize { get; private set; }
        public CoordinateConverter CoordinateConverter { get; private set; }
        public FrameData FrameData { get; private set; }

        public event NewProcessedFrameEventHandler NewProcessedFrameEvent;

        public InputHandler(InputProvider inputProvider)
        {
            this.inputProvider = inputProvider;
            initialize();

            inputProvider.NewFrameEvent += handleNewFrame;
            VideoInputProvider videoInputProvider = inputProvider as VideoInputProvider;
            if (videoInputProvider != null)
                videoInputProvider.MovieRestartedEvent += handleMovieRestart;
        }

        private void initialize()
        {
            ImageSize = new ImageSize(inputProvider.ImageWidth, inputProvider.ImageHeight);
            CoordinateConverter = new CoordinateConverter(ImageSize, inputProvider.HFOV, inputProvider.VFOV);
            resetFlag = false;

            fingerDetector = new FingerDetector(CoordinateConverter);
            handDetector = new HandDetector();
            palmDetector = new PalmDetector();
            touchDetector = new TouchDetector();

            fingerTracker = new FingerTracker(ImageSize);
            handTracker = new HandTracker(ImageSize);
            palmTracker = new PalmTracker(ImageSize);
            touchTracker = new TouchTracker(ImageSize);
        }

        public void reset()
        {
            touchTracker.reset();
            handTracker.reset();
            palmTracker.reset();
            fingerTracker.reset();
        }

        public void handleNewFrame(object sender, NewFrameEventArgs e)
        {
            Timer.start("InputHandler.handleNewFrame");

            FrameData frameData = new FrameData();

            frameData.FrameID = e.FrameID;
            frameData.ImageSize = ImageSize;

            // reset flag
            frameData.ResetFlag = resetFlag;
            resetFlag = false;

            // confidence image
            Timer.start("InputHandler.handleNewFrame::createConfidenceImage");
            frameData.ConfidenceImage = new ConfidenceImage(e.RawConfidenceData, ImageSize);
            Timer.stop("InputHandler.handleNewFrame::createConfidenceImage");

            // depth image
            Timer.start("InputHandler.handleNewFrame::createDepthImage");
            frameData.DepthImage = new DepthImage(e.RawDepthData, ImageSize, frameData.ConfidenceImage);
            Timer.stop("InputHandler.handleNewFrame::createDepthImage");

            // edge image
            Timer.start("InputHandler.handleNewFrame::createEdgeImage");
            frameData.EdgeImage = new EdgeImage(frameData.DepthImage);
            Timer.stop("InputHandler.handleNewFrame::createEdgeImage");

            // detect fingers
            Timer.start("InputHandler.handleNewFrame::detectFingers");
            fingerDetector.detectFingers(frameData);
            Timer.stop("InputHandler.handleNewFrame::detectFingers");

            // track fingers
            Timer.start("InputHandler.handleNewFrame::trackFingers");
            fingerTracker.trackFingers(frameData);
            Timer.stop("InputHandler.handleNewFrame::trackFingers");

            // detect hands
            Timer.start("InputHandler.handleNewFrame::detectHands");
            handDetector.detectHands(frameData);
            Timer.stop("InputHandler.handleNewFrame::detectHands");

            // track hands
            Timer.start("InputHandler.handleNewFrame::trackHands");
            handTracker.trackHands(frameData);
            Timer.stop("InputHandler.handleNewFrame::trackHands");

            // detect palms
            Timer.start("InputHandler.handleNewFrame::detectPalms");
            palmDetector.detectPalms(frameData);
            Timer.stop("InputHandler.handleNewFrame::detectPalms");

            // track palms
            Timer.start("InputHandler.handleNewFrame::trackPalms");
            palmTracker.trackPalms(frameData);
            Timer.stop("InputHandler.handleNewFrame::trackPalms");

            // detect touches
            Timer.start("InputHandler.handleNewFrame::detectTouches");
            touchDetector.detectTouches(frameData);
            Timer.stop("InputHandler.handleNewFrame::detectTouches");

            //track touches
            Timer.start("InputHandler.handleNewFrame::trackTouches");
            touchTracker.trackTouches(frameData);
            Timer.stop("InputHandler.handleNewFrame::trackTouches");

            Timer.start("InputHandler.handleNewFrame::exportResults");
            FrameData = frameData;
            if (NewProcessedFrameEvent != null)
                NewProcessedFrameEvent(this, new NewProcessedFrameEventArgs(frameData));
            Timer.stop("InputHandler.handleNewFrame::exportResults");

            Timer.stop("InputHandler.handleNewFrame");

            if (Parameters.LoggerTimerOutputEnabled)
                Timer.outputAll();
        }

        private void handleMovieRestart(object sender, EventArgs e)
        {
            reset();
            resetFlag = true;
        }
    }
}