/* DatasetReader application (C#, .NET Framework)
   for the DATASET ID# 2024-HAND-CHAIR-GESTURES.
      
   Radu-Daniel Vatavu, Ph.D.
   MintViz Lab, MANSiD Research Center
   Stefan cel Mare University of Suceava
   Suceava 720229, Romania
   radu.vatavu @usm.ro
   http://www.eed.usv.ro/~vatavu

 The academic publication for this work, and what should be used to cite it, is:
  
	Alexandru-Tudor Andrei, Laura-Bianca Bilius, Radu-Daniel Vatavu. (2024).
    Take a Seat, Make a Gesture: Charting User Preferences for On-Chair and 
	From-Chair Gesture Input. Proceedings of the CHI Conference on Human Factors 
	in Computing Systems. New York, NY, USA: ACM, 17 pages.
    https://doi.org/10.1145/3613904.3642028

 This software is distributed under the "New BSD License" agreement:

 Copyright(c) 2024, Radu-Daniel Vatavu. All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
    *Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the Stefan cel Mare University of Suceava, nor 
      the names of its contributors may be used to endorse or promote products 
      derived from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Radu-Daniel Vatavu BE LIABLE FOR 
 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
 USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.IO;
using System.Collections.Generic;
using System.Windows.Forms;
using GestureSharpToolkit;
using System.Linq;
using Geometry;

namespace DatasetReader
{
    class Program
    {
        const int NUM_PARTICIPANTS = 54;
        string[] CHAIR_TYPES = new string[] { "Stool", "Office", "Armchair" };
        string[] PARTICIPANT_TO_CHAIR_TYPE = new string[] {
                "Stool", "Office", "Armchair", "Stool", "Armchair", "Stool", "Office", "Office", "Armchair",
                "Office", "Armchair", "Office", "Stool", "Armchair", "Stool", "Office", "Stool", "Stool",
                "Armchair", "Armchair", "Office", "Armchair", "Office", "Stool", "Office", "Armchair", "Stool",
                "Armchair", "Stool", "Office", "Armchair", "Stool", "Office", "Armchair", "Stool", "Armchair",
                "Office", "Stool", "Stool", "Armchair", "Office", "Office", "Armchair", "Office", "Office",
                "Stool", "Armchair", "Office", "Office", "Stool", "Armchair", "Stool", "Armchair", "Stool"
            };

        [STAThread]
        static void Main()
        {
            new Program();
        }

        public Program()
        {
            try
            {
                List<List<Gesture>> dataset = ReadGestureDataset(String.Format("{0}\\Data", Application.StartupPath));
                WriteGestureFeaturesToFile(dataset, "output.csv");
            }
            catch (Exception exc)
            {
                Console.WriteLine(exc.ToString());
            }

            Console.WriteLine("done.");
            Console.ReadLine();
        }


        /// <summary>
        /// Reads the gesture dataset
        /// </summary>
        private List<List<Gesture>> ReadGestureDataset(string folderName)
        {
            List<List<Gesture>> dataset = new List<List<Gesture>>();
            int numTotalGestures = 0;
            for (int indexParticipant = 1; indexParticipant <= NUM_PARTICIPANTS; indexParticipant++)
            {
                List<Gesture> gestures = GestureIO.Read(folderName + "\\" + indexParticipant + ".xml");
                numTotalGestures += gestures.Count;
                dataset.Add(gestures);
                Console.WriteLine("reading data for participant #{0}: {1} gestures", indexParticipant, gestures.Count);
            }
            Console.WriteLine("Total records: {0}.", numTotalGestures);
            return dataset;
        }


        /// <summary>
        /// Computes gesture features and writes the results to a file
        /// </summary>
        private void WriteGestureFeaturesToFile(List<List<Gesture>> dataset, string fileName)
        {
            using (StreamWriter writer = new StreamWriter(fileName))
            {
                writer.WriteLine("Participant,ChairType,GestureLocale,Referent,NumPoints,ProductionTime,MeanAccelerationLH,MeanAccelerationRH,MeanAccelerationBimanual,NumAxesMovementLH,NumAxesMovementRH,NumAxesMovementBimanual");

                for (int idxParticipant = 0; idxParticipant < dataset.Count; idxParticipant++)
                    foreach (Gesture gesture in dataset[idxParticipant])
                    {
                        // preprocess finger movements
                        foreach (MovementPath fingerMovement in gesture)
                        {
                            fingerMovement.ApplyProcessor(new GestureProcessor(GestureProcessor.GravityRemoval));
                            fingerMovement.ApplyProcessor(new GestureProcessor(GestureProcessor.TrimLeftFixed, 300));
                        }
                        Trim(gesture, PointSource.Processed, 0.1);
                        foreach (MovementPath fingerMovement in gesture)
                            fingerMovement.ApplyProcessor(new GestureProcessor(GestureProcessor.ResampleNonUniformTime, 100.0 / 1000.0));

                        // compute gesture measures
                        Finger[] fingersLH = new Finger[] { Finger.LHThumb, Finger.LHIndex, Finger.LHMiddle, Finger.LHRing, Finger.LHLittle };
                        Finger[] fingersRH = new Finger[] { Finger.RHThumb, Finger.RHIndex, Finger.RHMiddle, Finger.RHRing, Finger.RHLittle };
                        Finger[] fingersAll = new Finger[] { Finger.LHThumb, Finger.LHIndex, Finger.LHMiddle, Finger.LHRing, Finger.LHLittle, Finger.RHThumb, Finger.RHIndex, Finger.RHMiddle, Finger.RHRing, Finger.RHLittle };

                        writer.WriteLine(
                            "{0},{1},{2},{3},{4},{5},{6:.0000},{7:.0000},{8:.0000},{9:.0000},{10:.0000},{11:.0000}",
                            idxParticipant + 1,
                            PARTICIPANT_TO_CHAIR_TYPE[idxParticipant], gesture.Locale, gesture.Name,
                            gesture[0].GetPoints(PointSource.Processed).Length,
                            ComputeGestureFeature(gesture, PointSource.Processed, GestureFeatures.ProductionTime, fingersAll),
                            ComputeGestureFeature(gesture, PointSource.Processed, GestureFeatures.MeanAcceleration, fingersLH) * 9.80665, // in m/s^2
                            ComputeGestureFeature(gesture, PointSource.Processed, GestureFeatures.MeanAcceleration, fingersRH) * 9.80665, // in m/s^2
                            ComputeGestureFeature(gesture, PointSource.Processed, GestureFeatures.MeanAcceleration, fingersAll) * 9.80665, // in m/s^2
                            ComputeGestureFeature(gesture, PointSource.Processed, GestureFeatures.NumAxesOfMovement, fingersLH),
                            ComputeGestureFeature(gesture, PointSource.Processed, GestureFeatures.NumAxesOfMovement, fingersRH),
                            ComputeGestureFeature(gesture, PointSource.Processed, GestureFeatures.NumAxesOfMovement, fingersAll)
                        );
                    }
            }
        }

        /// <summary>
        /// Compute a gesture feature averaged across all specified [fingers]
        /// </summary>
        private double ComputeGestureFeature(Gesture gesture, PointSource pointSource, GestureFeature featureFunc, Finger[] fingers)
        {
            double feature = 0;
            foreach (Finger finger in fingers)
                feature += featureFunc(gesture[(int)finger], pointSource);
            feature /= fingers.Length;
            return feature;
        }

        /// <summary>
        /// Trims the [gesture] by a specified percentage, e.g., 10% to the left and 
        /// right, with respect to the total norm of linear accelerations
        /// </summary>
        private void Trim(Gesture gesture, PointSource pointSource, double percentage = 0.1)
        {
            // empty gesture?
            if (gesture == null || gesture.Count < 1) return;

            // compute total norm of linear accelerations
            double totalNorm = 0;
            foreach (MovementPath fingerMovement in gesture)
                totalNorm += fingerMovement.GetPoints(pointSource).Sum(p => p.Norm);
            double threshold = percentage * totalNorm;

            // compute left and right indexes to trim the gesture
            int indexLeft = 0, indexRight = gesture[0].GetPoints(pointSource).Length - 1;
            double s = 0;
            while ((indexLeft < indexRight) && (s < threshold))
            {
                double normLeft = 0, normRight = 0;
                foreach (MovementPath fingerMovement in gesture)
                {
                    Point[] points = fingerMovement.GetPoints(pointSource);
                    normLeft += points[indexLeft].Norm;
                    normRight += points[indexRight].Norm;
                }
                if (normLeft < normRight) { s += normLeft; indexLeft++; }
                else { s += normRight; indexRight--; }
            }

            // perform trimming
            for (int i = 0; i < gesture.Count; i++)
                gesture[i] = new MovementPath(PointSet.Subset(gesture[i].GetPoints(pointSource), indexLeft, indexRight));
        }
    }
}