using System;
using System.Collections.Generic;
using System.Linq;
using NAudio.Wave;
using System.Threading;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Forms;

namespace MotorSkill
{
    public partial class MainWindow : Window
    {
        #region Member Data
        // layout constants
        public static readonly double screenWidth = System.Windows.SystemParameters.PrimaryScreenWidth;
        public static readonly double screenHeight = System.Windows.SystemParameters.PrimaryScreenHeight;
        public static readonly double deltaWidth = screenWidth / 20;
        public static readonly double deltaHeight = screenHeight / 20;
        public static readonly double touchAreaBorderWidth = 5;
        public static readonly int commonFontSize = ((int)screenWidth / 80);
        public static readonly int wordFontSize = commonFontSize * 2;
        public static readonly int wpfDPI = 96;
        public static readonly int winDPI_X = (int)(typeof(SystemParameters).GetProperty("DpiX", BindingFlags.NonPublic | BindingFlags.Static)).GetValue(null, null);
        public static readonly int winDPI_Y = (int)(typeof(SystemParameters).GetProperty("Dpi", BindingFlags.NonPublic | BindingFlags.Static)).GetValue(null, null);
        public static readonly double winToWpf_X = (double)wpfDPI / winDPI_X;
        public static readonly double winToWpf_Y = (double)wpfDPI / winDPI_Y;
        public static readonly double wpfToWin_X = 1 / winToWpf_X;
        public static readonly double wpfToWin_Y = 1 / winToWpf_Y;
        public static readonly string fontFamily = "Tahoma";
        public static readonly SolidColorBrush neutralStateColor = new SolidColorBrush(Colors.Black);
        public static readonly SolidColorBrush errorStateColor = new SolidColorBrush(Colors.Red);
        public static readonly SolidColorBrush okStateColor = new SolidColorBrush(Colors.Green);
        public static readonly SolidColorBrush errorStateBkColor = new SolidColorBrush(Colors.Yellow);
        public static readonly SolidColorBrush okStateBkColor = new SolidColorBrush(Colors.LightGray);

        // path constants
        public static readonly string configFileName = @"config\config.xml";
        public static readonly string imagesPath = @"img\";
        public static string experimentPath = @"experiment\";

        // audio constants
        public static readonly int sampleRate = 16000;
        public static readonly int channels = 1;

        // gui components
        private System.Windows.Controls.ComboBox lstSubjects;
        private TextBlock tbkMicrophoneState;
        private TextBlock tbkTouchScreenState;
        private TextBlock tbkEyeTrackerState;
        private TextBlock tbkEEGState;
        private System.Windows.Controls.Image imgModel;
        private System.Windows.Shapes.Rectangle rctTouchArea;
        private System.Windows.Controls.Label lbWord;
        private System.Windows.Controls.Label lbProgress;
        private System.Windows.Controls.ProgressBar prgProgress;

        // points and strokes stuff
        public static readonly int maxStrokes = 10000;
        private List<Point>[] strokes = new List<Point>[maxStrokes];
        private int strokesCount = 0;
        public static readonly int pointWidth = 5;
        public static readonly SolidColorBrush pointColor = System.Windows.Media.Brushes.Black;
        public static readonly SolidColorBrush activeTouchAreaBorderColor = System.Windows.Media.Brushes.Green;
        public static readonly SolidColorBrush pasiveTouchAreaBorderColor = System.Windows.Media.Brushes.Gray;
        private IDictionary<int, int> indexOfStroke = new Dictionary<int, int>();

        // data streams 
        private MicrophoneDataStream microphoneDataStream = null;
        private EyeTrackerDataStream eyeTrackerDataStream = null;
        private int eyeTrackerFramesCount = 0;
        private double eyeTrackerValidFramesRatio = 0;
        private EEGDataStream eegDataStream = null;
        private static ReaderWriterLockSlim eyeTrackerStreamLock = new ReaderWriterLockSlim();
        private static ReaderWriterLockSlim eegStreamLock = new ReaderWriterLockSlim();
        private static ReaderWriterLockSlim microphoneStreamLock = new ReaderWriterLockSlim();
        private static ReaderWriterLockSlim touchScreenStreamLock = new ReaderWriterLockSlim();

        // data files
        public string experimentMainFileName = "";
        private WaveFileWriter microphoneFile;
        private StreamWriter eyeTrackerFile;
        private StreamWriter touchScreenFile;
        private StreamWriter eegFile;

        // isAvailableFlags
        private bool isMicrophoneAvailable = true;
        private bool isTouchScreenAvailable = true;
        private bool isEyeTrackerAvailable = true;
        private bool isEEGAvailable = true;

        // experiment
        private ExperimentConfig experimentConfig;
        private int experimentCounter;

        // testing
        private bool isTesting = false;
        private Ellipse eyeTrackerTesting_ellipse;
        private System.Windows.Threading.DispatcherTimer eyeTrackerTesting_timer;
        private int eyeTrackerTesting_counter;
        private List<EyeTrackerDataFrame> eyeTrackerTesting_list;
        private int eyeTrackerTesting_counterFixation;
        private List<EyeTrackerFixationDataFrame> eyeTrackerTesting_listFixation;

        #endregion

        #region Initialisation
        public MainWindow()
        {
            InitializeComponent();
            loadConfigData();
            initializeCustomGUIComponents();
        }

        private void loadConfigData()
        {
            using (var stream = File.OpenRead(configFileName))
            {
                var serializer = new XmlSerializer(typeof(ExperimentConfig));
                experimentConfig = (ExperimentConfig)serializer.Deserialize(stream);
            }
            experimentConfig.loadTrials();
        }
        private void initializeCustomGUIComponents()
        {
            WindowStyle = WindowStyle.None;
            ResizeMode = ResizeMode.NoResize;
            WindowStartupLocation = WindowStartupLocation.Manual;
            Top = 0;
            Left = 0;
            Width = screenWidth;
            Height = screenHeight;

            TouchDown += new EventHandler<TouchEventArgs>(onTouchDown);
            TouchUp += new EventHandler<TouchEventArgs>(onTouchUp);
            TouchMove += new EventHandler<TouchEventArgs>(onTouchMove);

            root.Uid = nameof(root);
            Canvas.SetTop(root, 0);
            Canvas.SetLeft(root, 0);
            root.Height = screenHeight;
            root.Width = screenWidth;
            
            KeyDown += new System.Windows.Input.KeyEventHandler(onKeyDown);
            Closed += new EventHandler(onFormClosed);

            lstSubjects = new System.Windows.Controls.ComboBox
            {
                Uid = nameof(lstSubjects),
                Width = 4 * deltaWidth,
                Height = deltaHeight,
                FontSize = commonFontSize,
                IsEditable = true,
                IsReadOnly = true
            };
            Canvas.SetLeft(lstSubjects, 0);
            Canvas.SetTop(lstSubjects, 0);

            root.Children.Add(lstSubjects);

            foreach (Subject s in experimentConfig.subjects)
            {
                lstSubjects.Items.Add(s.name);
            }
            lstSubjects.SelectionChanged += new SelectionChangedEventHandler(changeSubject);

            tbkMicrophoneState = new TextBlock
            {
                Uid = nameof(tbkMicrophoneState),
                Width = deltaWidth,
                Height = deltaHeight,
                FontSize = wordFontSize,
                Foreground = neutralStateColor,
                FontWeight = FontWeights.Bold,
            };
            tbkMicrophoneState.Text = "MC";
            Canvas.SetLeft(tbkMicrophoneState, 4 * deltaWidth);
            Canvas.SetTop(tbkMicrophoneState, 0);
            root.Children.Add(tbkMicrophoneState);
            try
            {
                MicrophoneDataStream.testAvailability();
            }
            catch (Exception e)
            {
                tbkMicrophoneState.Foreground = errorStateColor;
                isMicrophoneAvailable = false;
            }

            tbkTouchScreenState = new TextBlock
            {
                Uid = nameof(tbkTouchScreenState),
                Width = deltaWidth,
                Height = deltaHeight,
                FontSize = wordFontSize,
                Foreground = neutralStateColor,
                FontWeight = FontWeights.Bold,
            };
            tbkTouchScreenState.Text = "TS";
            Canvas.SetLeft(tbkTouchScreenState, 5 * deltaWidth);
            Canvas.SetTop(tbkTouchScreenState, 0);
            root.Children.Add(tbkTouchScreenState);
            try
            {
                testTouchScreenAvailability();
            }
            catch (Exception e)
            {
                tbkTouchScreenState.Foreground = errorStateColor;
                isTouchScreenAvailable = false;
            }
            tbkEyeTrackerState = new TextBlock
            {
                Uid = nameof(tbkEyeTrackerState),
                Width = deltaWidth,
                Height = deltaHeight,
                FontSize = wordFontSize,
                Foreground = neutralStateColor,
                FontWeight = FontWeights.Bold,
            };
            tbkEyeTrackerState.Text = "ET";
            Canvas.SetLeft(tbkEyeTrackerState, 6 * deltaWidth);
            Canvas.SetTop(tbkEyeTrackerState, 0);
            root.Children.Add(tbkEyeTrackerState);
            try
            {
                EyeTrackerDataStream.testAvailability();
            }
            catch (Exception e)
            {
                tbkEyeTrackerState.Foreground = errorStateColor;
                isEyeTrackerAvailable = false;
            }

            tbkEEGState = new TextBlock
            {
                Uid = nameof(tbkEEGState),
                Width = deltaWidth,
                Height = deltaHeight,
                FontSize = wordFontSize,
                Foreground = neutralStateColor,
                FontWeight = FontWeights.Bold,
            };
            tbkEEGState.Text = "EG";
            Canvas.SetLeft(tbkEEGState, 7 * deltaWidth);
            Canvas.SetTop(tbkEEGState, 0);
            root.Children.Add(tbkEEGState);

            if (!testEEGAvailability())
            {
                tbkEEGState.Foreground = errorStateColor;
                isEEGAvailable = false;
            }

            imgModel = new System.Windows.Controls.Image
            {
                Uid = nameof(imgModel),
                Width = 7 * deltaWidth,
                Height = 16 * deltaHeight,
                ///HorizontalAlignment = HorizontalAlignment.Stretch,
                VerticalAlignment = VerticalAlignment.Stretch,
            };
            Canvas.SetLeft(imgModel, 0);
            Canvas.SetTop(imgModel, deltaWidth);
            root.Children.Add(imgModel);

            lbWord = new System.Windows.Controls.Label
            {
                Uid = nameof(lbWord),
                Width = 8 * deltaWidth,
                Height = 2 * deltaHeight,
                Background = System.Windows.Media.Brushes.Aquamarine,
                FontSize = wordFontSize,
                Visibility = Visibility.Hidden,
            };
            Canvas.SetLeft(lbWord, 0);
            Canvas.SetTop(lbWord, 17 * deltaHeight);
            root.Children.Add(lbWord);

            lbProgress = new System.Windows.Controls.Label
            {
                Uid = nameof(lbProgress),
                Width = deltaWidth,
                Height = deltaHeight,
                FontSize = commonFontSize,
            };
            Canvas.SetLeft(lbProgress, 0);
            Canvas.SetTop(lbProgress, 19 * deltaHeight);
            root.Children.Add(lbProgress);

            prgProgress = new System.Windows.Controls.ProgressBar
            {
                Uid = nameof(prgProgress),
                Width = 7 * deltaWidth,
                Height = deltaHeight,
                Maximum = experimentConfig.trials.Length,
            };
            Canvas.SetLeft(prgProgress, deltaWidth);
            Canvas.SetTop(prgProgress, 19 * deltaHeight);
            root.Children.Add(prgProgress);

            rctTouchArea = new System.Windows.Shapes.Rectangle
            {
                Uid = nameof(rctTouchArea),
                Stroke = pasiveTouchAreaBorderColor,
                StrokeThickness = touchAreaBorderWidth,
                Height = 18 * deltaHeight,
                Width = 12 * deltaWidth,
            };
            Canvas.SetTop(rctTouchArea, deltaHeight - touchAreaBorderWidth / 2);
            Canvas.SetLeft(rctTouchArea, 8 * deltaWidth - touchAreaBorderWidth / 2);
            root.Children.Add(rctTouchArea);
            if (isTesting)
            {
                eyeTrackerTesting_ellipse = new Ellipse();
                eyeTrackerTesting_ellipse.Width = 150;
                eyeTrackerTesting_ellipse.Height = 150;
                eyeTrackerTesting_ellipse.Stroke = System.Windows.Media.Brushes.Black;
                eyeTrackerTesting_ellipse.Fill = Brushes.LightGray;
                root.Children.Add(eyeTrackerTesting_ellipse);
            }
        }
        private void mirrorGUI()
        {
            Canvas.SetLeft(lstSubjects, screenWidth - Canvas.GetLeft(lstSubjects) - lstSubjects.Width);
            Canvas.SetLeft(tbkMicrophoneState, screenWidth - Canvas.GetLeft(tbkMicrophoneState) - tbkMicrophoneState.Width);
            Canvas.SetLeft(tbkEEGState, screenWidth - Canvas.GetLeft(tbkEEGState) - tbkEEGState.Width);
            Canvas.SetLeft(tbkTouchScreenState, screenWidth - Canvas.GetLeft(tbkTouchScreenState) - tbkTouchScreenState.Width);
            Canvas.SetLeft(tbkEyeTrackerState, screenWidth - Canvas.GetLeft(tbkEyeTrackerState) - tbkEyeTrackerState.Width);
            Canvas.SetLeft(imgModel, screenWidth - Canvas.GetLeft(imgModel) - imgModel.Width);
            Canvas.SetLeft(rctTouchArea, screenWidth - Canvas.GetLeft(rctTouchArea) - rctTouchArea.Width);
            Canvas.SetLeft(lbWord, screenWidth - Canvas.GetLeft(lbWord) - lbWord.Width);
            Canvas.SetLeft(lbProgress, screenWidth - Canvas.GetLeft(lbProgress) - lbProgress.Width);
            Canvas.SetLeft(prgProgress, screenWidth - Canvas.GetLeft(prgProgress) - prgProgress.Width);
        }
        #endregion

        #region User Interaction

        private void changeSubject(object sender, SelectionChangedEventArgs args)
        {
            lstSubjects.IsEnabled = false;
            experimentCounter = 0;
            string baseName = lstSubjects.SelectedItem.ToString() + " - " + ExperimentConfig.getMsDateTime();
            experimentPath = @"experiment\" + baseName + @"\";
            System.IO.Directory.CreateDirectory(experimentPath);
            experimentMainFileName = "_" + baseName + ".xml";
            log("", ExperimentConfig.convertObjectToXML(GuiObjectLayout.getGuiObjectLayoutForUIElement(root)));
            foreach (UIElement guiElement in root.Children)
            {
                log("", ExperimentConfig.convertObjectToXML(GuiObjectLayout.getGuiObjectLayoutForUIElement(guiElement)));
            }
            log("select subject", lstSubjects.SelectedItem.ToString());
        }

        private void onKeyDown(object sender, System.Windows.Input.KeyEventArgs args)
        {
            if (args.Key == Key.E)
            {
                startEyeTrackerTesting();
            }
            if (args.Key == Key.M)
            {
                mirrorGUI();
            }
            if (lstSubjects.SelectedIndex > -1)
            {
                if (args.Key == Key.Right)
                {
                    goNext();
                }
                else if (args.Key == Key.Down)
                {
                    repeat();
                }
            }
        }

        private void onFormClosed(object sender, EventArgs args)
        {
            stopRecording();
            log("stop app", "");
        }

        #endregion

        #region Generic Actions

        private void goNext()
        {
            stopRecording();
            if (experimentCounter < experimentConfig.trials.Length)
            {
                prgProgress.Value = experimentCounter + 1;
                lbProgress.Content = experimentCounter + 1 + "/" + experimentConfig.trials.Length;
                Trial trial = experimentConfig.trials[experimentCounter];
                if (trial.state == Trial.EXPLANATORY)
                {
                    runExplanationPhase();
                    trial.state = Trial.ACTIVE;
                }
                else if (trial.state == Trial.ACTIVE)
                {
                    runActivePhase();
                    trial.state = Trial.FINISHED;
                }
                else if (trial.state == Trial.FINISHED)
                {
                    experimentCounter++;
                    goNext();
                }
            }
        }

        private void repeat()
        {
            if (experimentCounter < experimentConfig.trials.Length)
            {
                Trial trial = experimentConfig.trials[experimentCounter];
                if (trial.state == Trial.FINISHED)
                {
                    trial.state = Trial.ACTIVE;
                    trial.repetitionCounter++;
                    goNext();
                }
            }
        }

        private void runExplanationPhase()
        {
            Trial trial = experimentConfig.trials[experimentCounter];
            BitmapImage img = new BitmapImage(new Uri(imagesPath + trial.condition.image, UriKind.Relative));
            double tmp = img.Height;
            imgModel.Source = img;

            lbWord.Content = trial.condition.word;
            imgModel.Visibility = Visibility.Visible;
            lbWord.Visibility = Visibility.Visible;

            startRecording();
        }

        private void runActivePhase()
        {
            Trial trial = experimentConfig.trials[experimentCounter];
            imgModel.Visibility = Visibility.Hidden;
            lbWord.Visibility = Visibility.Hidden;

            startRecording();
        }

        private void startRecording()
        {
            log("start recording", getCurrentFileName(""));
            if (isMicrophoneAvailable)
            {
                tbkMicrophoneState.Foreground = okStateColor;
                tbkMicrophoneState.Background = errorStateBkColor;
                startMicrophoneRecording();
            }
            if (isEyeTrackerAvailable)
            {
                tbkEyeTrackerState.Foreground = okStateColor;
                startEyeTrackerRecording();
            }
            if (isEEGAvailable)
            {
                tbkEEGState.Foreground = okStateColor;
                tbkEEGState.Background = errorStateBkColor;
                startEEGRecording();
            }
            if (isTouchScreenAvailable && experimentConfig.trials[experimentCounter].state == Trial.ACTIVE)
            {
                tbkTouchScreenState.Foreground = okStateColor;
                tbkTouchScreenState.Background = errorStateBkColor;
                startTouchScreenRecording();
            }
        }

        private void stopRecording()
        {
            log("stop recording", "");
            if (isMicrophoneAvailable)
            {
                tbkMicrophoneState.Foreground = neutralStateColor;
                stopMicrophoneRecording();
            }
            if (isEyeTrackerAvailable)
            {
                tbkEyeTrackerState.Foreground = neutralStateColor;
                stopEyeTrackerRecording();
            }
            if (isEEGAvailable)
            {
                tbkEEGState.Foreground = neutralStateColor;
                stopEEGRecording();
            }
            if (isTouchScreenAvailable)
            {
                tbkTouchScreenState.Foreground = neutralStateColor;
                stopTouchScreenRecording();
            }
        }

        #endregion

        #region TouchScreen

        private void testTouchScreenAvailability()
        {
            bool isTouchAvailable = false;
            foreach (TabletDevice tabletDevice in Tablet.TabletDevices)
            {
                //Only detect if it is a touch Screen not how many touches (i.e. Single touch or Multi-touch)
                if (tabletDevice.Type == TabletDeviceType.Touch)
                {
                    isTouchAvailable = true;
                    break;
                }
            }
            if (!isTouchAvailable)
            {
                throw new Exception("No touch screen available");
            }
        }

        private void startTouchScreenRecording()
        {
            rctTouchArea.Stroke = activeTouchAreaBorderColor;
            touchScreenFile = new StreamWriter(experimentPath + getCurrentFileName("gesture") + ".xml");
        }

        private void stopTouchScreenRecording()
        {
            rctTouchArea.Stroke = pasiveTouchAreaBorderColor;
            if (isFileEnableForWriting(touchScreenFile))
            {
                touchScreenFile.Close();
                int n = root.Children.Count;
                for (int i = n - 1; i >= 0; i--)
                {
                    if (root.Children[i].GetType().Name == "Line")
                    {
                        root.Children.Remove(root.Children[i]);
                    }
                }
            }
        }

        private void onTouchDown(object sender, TouchEventArgs args)
        {
            // create a new stroke
            indexOfStroke[args.TouchDevice.Id] = strokesCount;
            strokes[strokesCount] = new List<Point>();
            addTouchScreenDataFrameToStroke(args.GetTouchPoint(this), strokesCount, ExperimentConfig.getMsDateTime());
            strokesCount++;
        }

        private void onTouchUp(object sender, TouchEventArgs args)
        {
            saveStroke(indexOfStroke[args.TouchDevice.Id]);
            // remove deviceID from dictionary
            indexOfStroke.Remove(args.TouchDevice.Id);
        }

        private void onTouchMove(object sender, TouchEventArgs args)
        {
            if (isFileEnableForWriting(touchScreenFile))
            {
                int deviceId = args.TouchDevice.Id;
                int strokeIndex = indexOfStroke[deviceId];
                TouchPointCollection points = args.GetIntermediateTouchPoints(this);
                double lastTime = strokes[strokeIndex].Last().T;
                double nowTime = ExperimentConfig.getMsDateTime();
                double msDeltaTotal = nowTime - lastTime;
                double msDelta = (msDeltaTotal *1.0 / (points.Count + 1));
                foreach (TouchPoint point in points)
                {
                    drawTouchPoint(point, strokeIndex);
                    lastTime += msDelta;
                    addTouchScreenDataFrameToStroke(point, indexOfStroke[deviceId], (long)lastTime);
                }
                drawTouchPoint(args.GetTouchPoint(this), strokeIndex);
                addTouchScreenDataFrameToStroke(args.GetTouchPoint(this), indexOfStroke[deviceId], ExperimentConfig.getMsDateTime());
            }
        }

        private void addTouchScreenDataFrameToStroke(TouchPoint tp, int strokeIndex, long timestamp)
        {
            double baseX = Canvas.GetLeft(rctTouchArea) + touchAreaBorderWidth;
            double baseY = Canvas.GetTop(rctTouchArea) + touchAreaBorderWidth;
            strokes[strokeIndex].Add(new Point(timestamp, Math.Round(tp.Position.X - baseX,2), Math.Round(tp.Position.Y - baseY,2), Math.Round(tp.Bounds.Width,2), Math.Round(tp.Bounds.Height,2)));
            tbkTouchScreenState.Background = okStateBkColor;
        }

        private void drawTouchPoint(TouchPoint p, int strokeIndex)
        {
            double baseX = Canvas.GetLeft(rctTouchArea) + touchAreaBorderWidth;
            double baseY = Canvas.GetTop(rctTouchArea) + touchAreaBorderWidth;
            Line l = new Line();
            l.Stroke = pointColor;
            l.StrokeThickness = (pointWidth == 0 ? 5 : pointWidth);
            l.X1 = strokes[strokeIndex].Last().X + baseX;
            l.X2 = p.Position.X;
            l.Y1 = strokes[strokeIndex].Last().Y + baseY;
            l.Y2 = p.Position.Y;
            root.Children.Add(l);
        }

        private void saveStroke(int strokeIndex)
        {
            if (isFileEnableForWriting(touchScreenFile))
            {
                touchScreenStreamLock.EnterWriteLock(); 
                touchScreenFile.WriteLine("<Stroke id=\"" + strokeIndex + "\">");
                strokes[strokeIndex].ForEach((touchScreenDataFrame) =>
                    touchScreenFile.WriteLine(ExperimentConfig.convertObjectToXML(touchScreenDataFrame)));
                touchScreenFile.WriteLine("</Stroke>");
                touchScreenFile.Flush();
                touchScreenStreamLock.ExitWriteLock();
            }
            else///
            {
                Console.WriteLine("gesture");
            }
        }

        #endregion

        #region Microphone

        private void startMicrophoneRecording()
        {
            try
            {
                microphoneDataStream = new MicrophoneDataStream(onMicrophoneData, sampleRate, channels);
                microphoneDataStream.Open();
                /// test if it is open
                microphoneFile = new WaveFileWriter(experimentPath + getCurrentFileName("audio") + ".wav", new WaveFormat(sampleRate, channels));
            }
            catch (Exception e)
            {
                log("error", e.Message);
                tbkMicrophoneState.Foreground = errorStateColor;
            }
        }

        private void onMicrophoneData(MicrophoneDataFrame audioDataFrame)
        {
            if (microphoneFile != null && microphoneFile.CanWrite)
            {
                microphoneStreamLock.EnterWriteLock();
                microphoneFile.Write(audioDataFrame.data, audioDataFrame.offset, audioDataFrame.count);
                tbkMicrophoneState.Background = okStateBkColor;
                microphoneFile.Flush();
                microphoneStreamLock.ExitWriteLock();
            }
            else///
            {
                Console.WriteLine("audio");
            }
        }

        private void stopMicrophoneRecording()
        {
            if (microphoneDataStream != null)
            {
                microphoneDataStream.Close();
                microphoneDataStream = null;
                if (microphoneFile != null)
                {
                    microphoneFile.Close();
                }
            }
        }

        #endregion

        #region EyeTracker

        private void startEyeTrackerRecording()
        {
            try
            {
                eyeTrackerFramesCount = 0;
                eyeTrackerValidFramesRatio = 1;
                eyeTrackerDataStream = new EyeTrackerDataStream(onEyeTrackerData, onEyeTrackerFixationData);
                eyeTrackerDataStream.Open();
                eyeTrackerFile = new StreamWriter(experimentPath + getCurrentFileName("eyeTracker") + ".xml");
            }
            catch (Exception e)
            {
                log("error", e.Message);
                tbkEyeTrackerState.Foreground = errorStateColor;
            }
        }

        private void onEyeTrackerData(EyeTrackerDataFrame eyeTrackerDataFrame)
        {
            try {
                if (isFileEnableForWriting(eyeTrackerFile))
                {
                    eyeTrackerStreamLock.EnterWriteLock();
                    eyeTrackerDataFrame.left_gaze_x *= winToWpf_X;
                    eyeTrackerDataFrame.left_gaze_y *= winToWpf_Y;
                    eyeTrackerDataFrame.right_gaze_x *= winToWpf_X;
                    eyeTrackerDataFrame.right_gaze_y *= winToWpf_Y;
                    int validFrame;
                    if(eyeTrackerDataFrame.left_gaze_x == 0 && eyeTrackerDataFrame.left_gaze_y == 0 && eyeTrackerDataFrame.right_gaze_x == 0 && eyeTrackerDataFrame.right_gaze_y == 0)
                    {
                        validFrame = 0;
                    }
                    else
                    {
                        validFrame = 1;
                    }
                    eyeTrackerValidFramesRatio = (eyeTrackerValidFramesRatio * eyeTrackerFramesCount + validFrame) / (eyeTrackerFramesCount + 1);
                    eyeTrackerFramesCount++;
                    eyeTrackerFile.WriteLine(ExperimentConfig.convertObjectToXML(eyeTrackerDataFrame));
                    eyeTrackerFile.Flush();
                    eyeTrackerStreamLock.ExitWriteLock();

                    if (isTesting) {
                        System.Windows.Application.Current.Dispatcher.Invoke((Action)delegate {
                            eyeTrackerTesting_ellipse.SetValue(Canvas.LeftProperty, (eyeTrackerDataFrame.left_gaze_x + eyeTrackerDataFrame.right_gaze_x) / 2 - eyeTrackerTesting_ellipse.Width / 2);
                            eyeTrackerTesting_ellipse.SetValue(Canvas.TopProperty, (eyeTrackerDataFrame.left_gaze_y + eyeTrackerDataFrame.right_gaze_y) / 2 - eyeTrackerTesting_ellipse.Height / 2);
                        });
                    }
                }
                else///
                {
                    Console.WriteLine("eye tracker");
                }
            }catch(Exception e)
            {
                Console.WriteLine(e.Message);
                log("error", e.Message);
            }
        }

        private void onEyeTrackerFixationData(EyeTrackerFixationDataFrame eyeTrakerFixationDataFrame)
        {
            try {
                if (isFileEnableForWriting(eyeTrackerFile))
                {
                    eyeTrackerStreamLock.EnterWriteLock();
                    eyeTrackerFile.WriteLine(ExperimentConfig.convertObjectToXML(eyeTrakerFixationDataFrame));
                    eyeTrackerFile.Flush();
                    eyeTrackerStreamLock.ExitWriteLock();
                }
                else///
                {
                    Console.WriteLine("eye tracker fixation data");
                }
            }catch(Exception e)
            {
                Console.WriteLine(e.Message);
                log("error", e.Message);
            }
        }

        private void stopEyeTrackerRecording()
        {
            if (eyeTrackerDataStream != null)
            {
                eyeTrackerDataStream.Close();
                eyeTrackerDataStream = null;
                Thread.Sleep(100);

                if (eyeTrackerFile != null)
                {
                    if(eyeTrackerFile.BaseStream.Length == 0 || eyeTrackerValidFramesRatio < 0.5)
                    {
                        this.Dispatcher.Invoke(() =>
                        {
                            tbkEyeTrackerState.Background = errorStateBkColor;
                        });
                    }
                    else
                    {
                        this.Dispatcher.Invoke(() =>
                        {
                            tbkEyeTrackerState.Background = okStateBkColor;
                        });
                    }
                    eyeTrackerFile.Close();
                }
            }
        }

        private void startEyeTrackerTesting()
        {
            eyeTrackerTesting_ellipse = new Ellipse();
            eyeTrackerTesting_ellipse.Width = 150;
            eyeTrackerTesting_ellipse.Height = 150;
            root.Children.Add(eyeTrackerTesting_ellipse);
            OpenFileDialog fileDialog = new OpenFileDialog();
            fileDialog.Filter = "Cursor Files|*.xml";
            fileDialog.Title = "Select an EyeTracker data XML file";
            if (fileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                eyeTrackerTesting_list = new List<EyeTrackerDataFrame>();
                eyeTrackerTesting_counter = 0;
                eyeTrackerTesting_listFixation = new List<EyeTrackerFixationDataFrame>();
                eyeTrackerTesting_counterFixation = 0;

                string[] lines = File.ReadAllLines(fileDialog.FileName);
                int linesNumber = lines.Count();
                for (int i = 0; i < linesNumber; i++)
                {
                    object o = ExperimentConfig.convertXMLToObject("MotorSkill", lines[i]);
                    if (o.GetType().Name.Equals("EyeTrackerDataFrame"))
                    {
                        eyeTrackerTesting_list.Add((EyeTrackerDataFrame)o);
                    }
                    if (o.GetType().Name.Equals("EyeTrackerFixationDataFrame"))
                    {
                        if (((EyeTrackerFixationDataFrame)o).duration > 0)
                        {
                            eyeTrackerTesting_listFixation.Add((EyeTrackerFixationDataFrame)o);
                        }
                    }
                }
                eyeTrackerTesting_timer = new System.Windows.Threading.DispatcherTimer();
                eyeTrackerTesting_timer.Interval = TimeSpan.FromMilliseconds(10);
                eyeTrackerTesting_timer.Tick += eyeTrackerTesting_timer_Tick;
                eyeTrackerTesting_timer.Start();
            }
        }

        private void eyeTrackerTesting_timer_Tick(object sender, EventArgs e)
        {
            int eyeTrackerTesting_listCount = eyeTrackerTesting_list.Count();
            int eyeTrackerTesting_listFixationCount = eyeTrackerTesting_listFixation.Count();

            if (eyeTrackerTesting_listCount > eyeTrackerTesting_counter + 1)
            {
                bool isFixation = false;
                EyeTrackerDataFrame current = eyeTrackerTesting_list.ElementAt(eyeTrackerTesting_counter);
                EyeTrackerDataFrame next = eyeTrackerTesting_list.ElementAt(eyeTrackerTesting_counter + 1);
                eyeTrackerTesting_ellipse.SetValue(Canvas.LeftProperty, (current.left_gaze_x + current.right_gaze_x) / 2 - eyeTrackerTesting_ellipse.Width / 2);
                eyeTrackerTesting_ellipse.SetValue(Canvas.TopProperty, (current.left_gaze_y + current.right_gaze_y) / 2 - eyeTrackerTesting_ellipse.Height / 2);
                eyeTrackerTesting_timer.Interval = TimeSpan.FromMilliseconds(next.timestamp - current.timestamp);
                eyeTrackerTesting_counter++;
                for (int i = eyeTrackerTesting_counterFixation; i < eyeTrackerTesting_listFixationCount; i++)
                {
                    EyeTrackerFixationDataFrame frame = eyeTrackerTesting_listFixation.ElementAt(i);
                    if (current.timestamp >= frame.timestamp-frame.duration && current.timestamp<=frame.timestamp)
                    {
                        isFixation = true;
                        eyeTrackerTesting_counterFixation = i;
                        break;
                    }
                }
                if (isFixation)
                {
                    eyeTrackerTesting_ellipse.Stroke = System.Windows.Media.Brushes.Red;
                    eyeTrackerTesting_ellipse.Fill = Brushes.LightSalmon;
                }
                else
                {
                    eyeTrackerTesting_ellipse.Stroke = System.Windows.Media.Brushes.Black;
                    eyeTrackerTesting_ellipse.Fill = Brushes.LightGray;
                }
            }
            else
            {
                eyeTrackerTesting_timer.Stop();
                root.Children.Remove(eyeTrackerTesting_ellipse);
            }
        }

        #endregion

        #region EEG

        private bool testEEGAvailability()
        {
            // Test #1: EDK availability.
            if (!File.Exists("edk.dll"))
            {
                Console.WriteLine("__________ Failure: no EDK dll found");
                log("error", "Failure: no EDK dll found!");
                return false;
            }

            // Test #2: BT dongle availability (subsystem is on but no data comes in big slots of time).
            try
            {
                eegDataStream = new EEGDataStream(onEEGRawData, onEEGEmoData);
                eegDataStream.Open();
                Thread.Sleep(4000);
                if (eegDataStream.GetContinousInvalidUserCounter() > 25)
                {
                    log("error", "Failure: userID was invalid more than 25 times in a row; something is wrong with the handset access subsystem!");
                    return false;
                }
                if (eegDataStream.GetContinousNullDataPacksCounter() > 25)
                {
                    log("error", "Failure: data packs were null more than 25 times in a row; something is wrong with the handset pooling!");
                    return false;
                }
                eegDataStream.Close();
            }
            catch (Exception e)
            {
                log("error", e.Message);
                return false;
            }

            return true;
        }

        private void startEEGRecording()
        {
            try
            {
                eegDataStream = new EEGDataStream(onEEGRawData, onEEGEmoData);
                eegDataStream.Open();
                eegFile = new StreamWriter(experimentPath + getCurrentFileName("EEG") + ".xml");
            }
            catch (Exception e)
            {
                log("error", e.Message);
                tbkEEGState.Foreground = errorStateColor;
            }
        }

        private void onEEGRawData(EEGRawDataFrame eegRawDataFrame)
        {
            if (isFileEnableForWriting(eegFile))
            {
                eegStreamLock.EnterWriteLock();
                eegFile.WriteLine(ExperimentConfig.convertObjectToXML(eegRawDataFrame));
                eegFile.Flush();
                eegStreamLock.ExitWriteLock();
                this.Dispatcher.Invoke(() =>
                {
                    tbkEEGState.Background = okStateBkColor;
                });
            }
            else
            {
                Console.WriteLine("EEG Raw");
            }
        }

        private void onEEGEmoData(EEGEmoDataFrame eegEmoDataFrame)
        {
            if (isFileEnableForWriting(eegFile))
            {
                eegStreamLock.EnterWriteLock();
                eegFile.WriteLine(ExperimentConfig.convertObjectToXML(eegEmoDataFrame));
                eegFile.Flush();
                eegStreamLock.ExitWriteLock();
            }
            else
            {
                Console.WriteLine("EEG Emo");
            }
        }

        private void stopEEGRecording()
        {
            if (eegDataStream != null)
            {
                eegDataStream.Close();
                eegDataStream = null;
                if (eegFile != null)
                {
                    eegFile.Close();
                }
            }
        }

        #endregion

        #region Miscellaneous

        public string getCurrentFileName(string device)
        {
            Trial trial = experimentConfig.trials[experimentCounter];
            string name = lstSubjects.SelectedItem.ToString() + " - ";
            name += experimentCounter + " - ";
            name += trial.condition.name + " - ";
            name += trial.trialNumberInCondition + " - ";
            name += trial.state + " - ";
            name += trial.repetitionCounter + " - ";
            name += device + " - ";
            name += ExperimentConfig.getMsDateTime();
            return name;
        }

        public void log(string action, string value)
        {
            if (experimentMainFileName.Length > 0)
            {
                if (action != "")
                {
                    File.AppendAllText(experimentPath + experimentMainFileName, Environment.NewLine + ExperimentConfig.convertObjectToXML(new Log(action, value, ExperimentConfig.getMsDateTime())));
                }
                else
                {
                    File.AppendAllText(experimentPath + experimentMainFileName, Environment.NewLine + value);
                }
            }
        }

        public bool isFileEnableForWriting(StreamWriter file)
        {
            return (file != null && file.BaseStream != null && file.BaseStream.CanWrite);
        }

        #endregion
    }

    public class GuiObjectLayout
    {
        public string id { get; set; }
        public double top { get; set; }
        public double left { get; set; }
        public double height { get; set; }
        public double width { get; set; }

        public static GuiObjectLayout getGuiObjectLayoutForUIElement(UIElement uiElement)
        {
            GuiObjectLayout guiObjectLayout= new GuiObjectLayout();
            guiObjectLayout.id = uiElement.Uid;
            guiObjectLayout.top = Canvas.GetTop(uiElement);
            guiObjectLayout.left = Canvas.GetLeft(uiElement);
            guiObjectLayout.height = ((FrameworkElement)uiElement).Height;
            guiObjectLayout.width = ((FrameworkElement)uiElement).Width;
            return guiObjectLayout; 
        }
    }
}