using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Kinect.Toolkit;
using Microsoft.Kinect;
using Microsoft.Kinect.Toolkit.Interaction;
using System.Net;
using System.Net.Sockets;
using System.ComponentModel;
using System.IO;
using Microsoft.Samples.Kinect.SwipeGestureRecognizer;
using System.Globalization;
using System.Timers;


namespace RIMSI_Audio_multiplayer
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private double ACTIVE_OFFSET_Z = 0.2;

        
       
        private KinectSensorChooser sensorChooser;
        private KinectSensor sensor;
        private InteractionStream interaction;
        private UserInfo[] interactionData;
        private Skeleton[] skeletons = new Skeleton[0];
        private Ellipse[] circles = new Ellipse[6];

        private SocketManager socketWriter;
        private SpeechRecognizer speechRecognizer;

        private Recognizer activeRecognizer;

        private Image cursorImage;

        private int anestLastId = -1;
        private int surgeonLastId = -1;
        private int nurseLastId = -1;
        


        private double audioAngle = 0.0;

        private Image ColorImageElement;

        private GameStatus gameStatus;

        private long lastActiveCharacterChange = 0;
        private long ACTIVE_CHARACTER_TIME_INTERVAL = 2000;

        //private SocketWriter socketWriter;

        public MainWindow()
        {
            InitializeComponent();
            Loaded += OnLoaded;

            

            ColorImageElement = new Image();
            gameStatus = GameStatus.Instance;
           

        }

        private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
        {
            this.sensorChooser = new KinectSensorChooser();
            this.sensorChooser.KinectChanged += SensorChooserOnKinectChanged;
            this.sensorChooserUi.KinectSensorChooser = this.sensorChooser;
            this.sensorChooser.Start();

            circles[0] = ellipse1;
            circles[1] = ellipse2;
            circles[2] = ellipse3;
            circles[3] = ellipse4;
            circles[4] = ellipse5;
            circles[5] = ellipse6;

            //socketWriter = new SocketWriter("127.0.0.1",9999);
            socketWriter = SocketManager.Instance;
            //socketWriter.Connect("127.0.0.1", 9999);
            socketWriter.Connect("150.217.35.225", 9999);
            activeRecognizer = this.CreateRecognizer();
            
        }

        private void SensorChooserOnKinectChanged(object sender, KinectChangedEventArgs args)
        {
            bool error = false;
            if (args.OldSensor != null)
            {
                try
                {
                    args.OldSensor.DepthStream.Range = DepthRange.Default;
                    args.OldSensor.SkeletonStream.EnableTrackingInNearRange = false;
                    args.OldSensor.ColorStream.Disable();
                    args.OldSensor.DepthStream.Disable();
                    args.OldSensor.SkeletonStream.Disable();
                }
                catch (InvalidOperationException)
                {
                    // KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
                    // E.g.: sensor might be abruptly unplugged.
                    error = true;
                }
            }

            if (args.NewSensor != null)
            {
                try
                {
                    args.NewSensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);
                    args.NewSensor.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
                    args.NewSensor.SkeletonStream.Enable();

                    try
                    {
                        args.NewSensor.DepthStream.Range = DepthRange.Near;
                        args.NewSensor.SkeletonStream.EnableTrackingInNearRange = true;
                        args.NewSensor.SkeletonStream.TrackingMode = SkeletonTrackingMode.Seated;
                    }
                    catch (InvalidOperationException)
                    {
                        // Non Kinect for Windows devices do not support Near mode, so reset back to default mode.
                        args.NewSensor.DepthStream.Range = DepthRange.Default;
                        args.NewSensor.SkeletonStream.EnableTrackingInNearRange = false;
                        //error = true;
                    }
                }
                catch (InvalidOperationException)
                {
                    error = true;
                    // KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
                    // E.g.: sensor might be abruptly unplugged.
                }
            }

            if (!error)
            {
                //kinectRegion.KinectSensor = args.NewSensor;
                this.sensor = args.NewSensor;
                this.sensor.DepthFrameReady += this.Sensor_DepthFrameReady;
                this.sensor.ColorFrameReady += this.Sensor_ColorFrameReady;

                Stream audioStream = this.sensor.AudioSource.Start();
                this.sensor.AudioSource.SoundSourceAngleChanged += this.AudioSourceSoundSourceAngleChanged;


                this.sensor.SkeletonStream.AppChoosesSkeletons = true;
                this.sensor.SkeletonFrameReady += OnSkeletonFrameReady;
                

                this.speechRecognizer = new SpeechRecognizer(this.sensor);

                this.interaction = new InteractionStream(sensor, new InteractionClient());
                this.interaction.InteractionFrameReady += interaction_InteractionFrameReady;


                this.kinectRegion.KinectSensor = this.sensor;

                //cursorImage = handPointer;
                /*BackgroundWorker bw1 = new BackgroundWorker();
                bw1.RunWorkerCompleted += (a, b) => bw1.RunWorkerAsync();
                bw1.DoWork += delegate { SendFrame(); };
                bw1.RunWorkerAsync();
               */

            }
        }





        private void Sensor_DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e)
        {
            using (var frame = e.OpenDepthImageFrame())
            {
                if (frame != null)
                {
                    short[] pixelData = new short[frame.PixelDataLength];
                    frame.CopyPixelDataTo(pixelData);
                    int depthBytePerPixel = 2;
                    int stride = frame.Width * depthBytePerPixel;

                    int playerIndex;
                    
                    byte[] enhPixelData = new byte[frame.Height * stride];
                    for(int i = 0, j = 0; i < pixelData.Length; i++, j += depthBytePerPixel)
                    {
                        playerIndex = pixelData[i] & DepthImageFrame.PlayerIndexBitmask;
                        if(playerIndex == 0)
                        {
                            enhPixelData[j] = 0xFF;
                            enhPixelData[j + 1] = 0xFF;
                            //enhPixelData[j + 2] = 0xFF;
                        }
                        else
                        {
                            enhPixelData[j] = 0x00;
                            enhPixelData[j + 1] = 0x00;
                            //enhPixelData[j + 2] = 0x00;
                        }
                    }
                        //this._EnhDepthImage.WritePixels(this._EnhDepthImageRect, enhPixelData,
                        //this._EnhDepthImageStride, 0);
                        

                    DepthImage.Source = BitmapSource.Create(frame.Width, frame.Height, 96, 96,
                    PixelFormats.Gray16, null,
                    enhPixelData, stride);

                    interaction.ProcessDepth(frame.GetRawPixelData(), frame.Timestamp);

                    /*DepthImage.Source = BitmapSource.Create(frame.Width, frame.Height, 96, 96,
                    PixelFormats.Gray16, null,
                    pixelData, stride);*/
                }
            }
            updateCommandLabels();
        }

        private void Sensor_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
        {
            using (var frame = e.OpenColorImageFrame())
            {
                if (frame != null)
                {
                    try
                    {
                        byte[] pixelData = new byte[frame.PixelDataLength];
                        frame.CopyPixelDataTo(pixelData);
                        ColorImageElement.Source = BitmapImage.Create(frame.Width, frame.Height, 96, 96, PixelFormats.Bgr32, null, pixelData,frame.Width * frame.BytesPerPixel);
                        
                    }
                    catch (InvalidOperationException) { }
                }
            }
        }

        void OnSkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            if (null != sensor)
            {
                using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())
                {
                    if (skeletonFrame != null)
                    {
                        if (skeletons.Length != skeletonFrame.SkeletonArrayLength)
                        {
                            skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength];
                        }

                        skeletonFrame.CopySkeletonDataTo(skeletons);

                        //reset characters skeleton index
                        anestLastId = surgeonLastId = nurseLastId = -1;
                        
                        for (int i = 0; i < skeletons.Length; i++)
                        {
                            if (skeletons[i].TrackingState != SkeletonTrackingState.NotTracked)
                            {
                                //update UI
                                circles[i].Visibility = System.Windows.Visibility.Visible;
                                Point pos = ScalePosition(skeletons[i].Position);
                                Canvas.SetLeft(circles[i], Canvas.GetLeft(DepthImage) + pos.X);
                                Canvas.SetTop(circles[i], Canvas.GetTop(DepthImage) + pos.Y);
                                if (skeletons[i].TrackingState == SkeletonTrackingState.PositionOnly) circles[i].Fill = Brushes.Yellow;
                                if (skeletons[i].TrackingState == SkeletonTrackingState.Tracked) circles[i].Fill = Brushes.Green;

                                //checkSkeletonPosition
                                checkCharacter(pos, skeletons[i].Position.Z, skeletons[i].TrackingId);

                                
                            }
                            else
                            {
                                circles[i].Visibility = System.Windows.Visibility.Hidden;
                            }
                        }

                        this.activeRecognizer.Recognize(sender, skeletonFrame, skeletons);

                        updateCharactersState(ref gameStatus.anestState, anestLastId);
                        updateCharactersState(ref gameStatus.surgeonState, surgeonLastId);
                        updateCharactersState(ref gameStatus.nurseState, nurseLastId);

                        checkActivePlayer();

                        //if (gameStatus.isHandTrackingActive() == true)
                            interaction.ProcessSkeleton(skeletons, sensor.AccelerometerGetCurrentReading(), skeletonFrame.Timestamp);
  
                          
                       
                    }
                }
            }
        }

        private void checkCharacter(Point position, double Z, int skeletonId) 
        {
            if (position.X < 213)
            {
                anestLastId = skeletonId;
                gameStatus.anestState.Z = Z;
                labelAnes.FontWeight = FontWeights.Bold;
            }
            else if (position.X < 426)
            {
                surgeonLastId = skeletonId;
                gameStatus.surgeonState.Z = Z;
                labelChir.FontWeight = FontWeights.Bold;
            }
            else
            {
                nurseLastId = skeletonId;
                gameStatus.nurseState.Z = Z;
                labelInf.FontWeight = FontWeights.Bold;
            }
        }

        private void updateCharactersState(ref GameStatus.CharacterState character, int currentSkeletonId)
        {
            if (currentSkeletonId == -1)
            {
                if (character.isPresent)
                {
                    socketWriter.WriteOnSocket("avatarState@{\"player\" : \"" + character.name + "\", \"state\" : \"off\"}");
                }
                character.isPresent = false;
                character.skeletonId = -1;
                character.Z = double.MaxValue;
            }
            else
            {
                if (!character.isPresent)
                {
                    socketWriter.WriteOnSocket("avatarState@{\"player\" : \"" + character.name + "\", \"state\" : \"on\"}");
                }
                character.isPresent = true;
                character.skeletonId = currentSkeletonId;
            }

            
        }

        private void checkActivePlayer()
        {
            GameStatus.CharacterState newActiveCharacter = new GameStatus.CharacterState();
            newActiveCharacter.name = GameStatus.NO_CHARACHTER;



            if (gameStatus.anestState.Z < gameStatus.surgeonState.Z - ACTIVE_OFFSET_Z && gameStatus.anestState.Z < gameStatus.nurseState.Z - ACTIVE_OFFSET_Z)
            { 
                //set active player
                newActiveCharacter = gameStatus.anestState;

                this.sensor.SkeletonStream.ChooseSkeletons(gameStatus.anestState.skeletonId);
                activePlayerMarker.Visibility = Visibility.Visible;
                Canvas.SetLeft(activePlayerMarker, Canvas.GetLeft(DepthImage) + 140);
            }
            else if (gameStatus.surgeonState.Z < gameStatus.anestState.Z - ACTIVE_OFFSET_Z && gameStatus.surgeonState.Z < gameStatus.nurseState.Z - ACTIVE_OFFSET_Z)
            { 
                //set active player
                newActiveCharacter = gameStatus.surgeonState;
                this.sensor.SkeletonStream.ChooseSkeletons(gameStatus.surgeonState.skeletonId);
                 activePlayerMarker.Visibility = Visibility.Visible;
                 Canvas.SetLeft(activePlayerMarker, Canvas.GetLeft(DepthImage) + 355);
            }
            else if (gameStatus.nurseState.Z < gameStatus.surgeonState.Z - ACTIVE_OFFSET_Z && gameStatus.nurseState.Z < gameStatus.anestState.Z - ACTIVE_OFFSET_Z)
            {
                //set active player
                newActiveCharacter = gameStatus.nurseState;
                this.sensor.SkeletonStream.ChooseSkeletons(gameStatus.nurseState.skeletonId);
                activePlayerMarker.Visibility = Visibility.Visible;
                Canvas.SetLeft(activePlayerMarker, Canvas.GetLeft(DepthImage) + 580);
            }
            else 
            {
                activePlayerMarker.Visibility = Visibility.Hidden;    
            }


            if (newActiveCharacter.name != gameStatus.activeCharacter.name && lastActiveCharacterChange < getTime() + ACTIVE_CHARACTER_TIME_INTERVAL)
            {
                gameStatus.activeCharacter = newActiveCharacter;
                socketWriter.WriteOnSocket("activeAvatar@" + gameStatus.activeCharacter.name);
                lastActiveCharacterChange = getTime();
            }

        }
       
        private Point ScalePosition(SkeletonPoint skeletonPoint)
        {
            DepthImagePoint depthPoint = this.sensor.CoordinateMapper.MapSkeletonPointToDepthPoint(skeletonPoint, DepthImageFormat.Resolution640x480Fps30);

            return new Point(depthPoint.X, depthPoint.Y);
        }

        /*public void SendFrame()
        {
            Socket sListen = new Socket(AddressFamily.InterNetwork,
                                        SocketType.Stream,
                                        ProtocolType.Tcp);

            IPAddress IP = IPAddress.Parse("127.0.0.1");
            IPEndPoint IPE = new IPEndPoint(IP, 4321);

            sListen.Bind(IPE);
            sListen.Listen(2);

            while (true)
            {
                Socket clientSocket;
                clientSocket = sListen.Accept();

                var converter = new System.Drawing.ImageConverter();
                this.Dispatcher.Invoke((Action)(() =>
                {
                    byte[] buffer = (byte[])converter.ConvertTo(ColorImageElement.Source, typeof(byte[]));
                    clientSocket.Send(buffer, buffer.Length, SocketFlags.None);
                }));
                
                
            }
        }*/

       private void AudioSourceSoundSourceAngleChanged(object sender, SoundSourceAngleChangedEventArgs e)
          {
   
            // Rotate gradient to match angle
            audioAngle = -e.Angle;
            audioAngleLabel.Text = audioAngle.ToString();

            if (audioAngle <  -12) 
            {
                //right
                rectangleLeft.Opacity = 0.1;
                rectangleCenter.Opacity = 0.1;
                rectangleRight.Opacity = 0.5;
                gameStatus.talkingPlayer = GameStatus.ANESTH_NAME;
            }
            else if (audioAngle < 12)
            {
                //center
                rectangleLeft.Opacity = 0.1;
                rectangleCenter.Opacity = 0.5;
                rectangleRight.Opacity = 0.1;
                gameStatus.talkingPlayer = GameStatus.SURGEON_NAME;
            }
            else
            {
                //left
                rectangleLeft.Opacity = 0.5;
                rectangleCenter.Opacity = 0.1;
                rectangleRight.Opacity = 0.1;
                gameStatus.talkingPlayer = GameStatus.NURSE_NAME;
            }

        }

        
        private Recognizer CreateRecognizer()
        {
       
            var recognizer = new Recognizer();

        
            recognizer.SwipeRightDetected += (s, e) =>
            {
                if (e.Skeleton.TrackingId == gameStatus.activeCharacter.skeletonId)
                {
                    SocketManager.Instance.WriteOnSocket("swipe@in");
                }
            };

            // Wire-up swipe left to manually reverse picture.
            recognizer.SwipeLeftDetected += (s, e) =>
            {
                if (e.Skeleton.TrackingId == gameStatus.activeCharacter.skeletonId)
                {
                    SocketManager.Instance.WriteOnSocket("swipe@out");
                }
            };

            return recognizer;
        }

        private long previousMsgTimestamp;

        private void interaction_InteractionFrameReady(object sender, InteractionFrameReadyEventArgs e)
        {
            using (var frame = e.OpenInteractionFrame())
            {
                if (frame != null)
                {

                    if ((interactionData == null) || (interactionData.Length != InteractionStream.FrameUserInfoArrayLength))
                    {
                        interactionData = new UserInfo[InteractionStream.FrameUserInfoArrayLength];
                    }
                    frame.CopyInteractionDataTo(interactionData);

                    foreach (var ui in interactionData)
                    {
                        foreach (var hp in ui.HandPointers)
                        {
                            if (hp.IsTracked && hp.IsPrimaryForUser && hp.IsInteractive)
                            {
                                if (frame.Timestamp - previousMsgTimestamp > 50)
                                {
                                    if (hp.PressExtent < 0.1) SocketManager.Instance.WriteOnSocket("handposition@" + hp.X.ToString("0.0000", CultureInfo.InvariantCulture) + "," + (1 - hp.Y).ToString("0.0000", CultureInfo.InvariantCulture) + ",0");
                                    previousMsgTimestamp = frame.Timestamp;
                                }
                                //handPointer.Visibility = Visibility.Hidden;
                                //handPointerPush.Visibility = Visibility.Hidden;
                                //cursorImage.Visibility = Visibility.Visible;

                                //Canvas.SetLeft(cursorImage, hp.X * mainCanvas.Width);
                                //Canvas.SetTop(cursorImage, hp.Y * mainCanvas.Height);

                                /*Timer t = new Timer(1000);

                                if (hp.PressExtent > 0.9)
                                {

                                    if (cursorImage != handPointerPush)
                                    {
                                        cursorImage = handPointerPush;
                                        SocketManager.Instance.WriteOnSocket("push@push");
                                        t.Start();
                                        t.Elapsed += (ev, argg) =>
                                        {
                                            cursorImage = handPointer;

                                        };
                                    }
                                }
                                 */
                                
                                
                            }
                        }
                    }
                }
            }
        }

        public void updateCommandLabels() 
        {
            labelCommandID.Content = gameStatus.expectedCommandID;
            labelCommandPlayer.Content = gameStatus.expectedCharacterName;
            labelCommandType.Content = gameStatus.commandType;
            labelCommandAnswer.Content = gameStatus.expectedCommand.ToString();

        }

        public class InteractionClient : IInteractionClient
        {
            public InteractionInfo GetInteractionInfoAtLocation(
                int skeletonTrackingId,
                InteractionHandType handType,
                double x, double y)
            {
                return new InteractionInfo();
            }
        }
      
        //utilities
        private static DateTime JanFirst1970 = new DateTime(1970, 1, 1);
        public static long getTime()
        {
            return (long)((DateTime.Now.ToUniversalTime() - JanFirst1970).TotalMilliseconds + 0.5);
        }

        private void pushButton_Click(object sender, RoutedEventArgs e)
        {

        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            gameStatus.notifyAnswer(true);
        }

        private void labelAnes_Click(object sender, RoutedEventArgs e)
        {


            this.sensor.SkeletonStream.ChooseSkeletons(gameStatus.anestState.skeletonId);
            activePlayerMarker.Visibility = Visibility.Visible;
            Canvas.SetLeft(activePlayerMarker, 140);

            gameStatus.activeCharacter = gameStatus.anestState;
            socketWriter.WriteOnSocket("activeAvatar@" + gameStatus.activeCharacter.name);
            lastActiveCharacterChange = getTime();
        }

        private void labelChir_Click(object sender, RoutedEventArgs e)
        {
            
            this.sensor.SkeletonStream.ChooseSkeletons(gameStatus.surgeonState.skeletonId);
            activePlayerMarker.Visibility = Visibility.Visible;
            Canvas.SetLeft(activePlayerMarker, 355);

            gameStatus.activeCharacter = gameStatus.surgeonState;
            socketWriter.WriteOnSocket("activeAvatar@" + gameStatus.activeCharacter.name);
            lastActiveCharacterChange = getTime();
        }

        private void labelInf_Click(object sender, RoutedEventArgs e)
        {
             
            this.sensor.SkeletonStream.ChooseSkeletons(gameStatus.nurseState.skeletonId);
            activePlayerMarker.Visibility = Visibility.Visible;
            Canvas.SetLeft(activePlayerMarker, 580);

            gameStatus.activeCharacter = gameStatus.nurseState;
            socketWriter.WriteOnSocket("activeAvatar@" + gameStatus.activeCharacter.name);
            lastActiveCharacterChange = getTime();

        }

        private void KinectTileButton_Click(object sender, RoutedEventArgs e)
        {
            SocketManager.Instance.WriteOnSocket("push@push");
        }

        
        
    }
}