/*
 DatasetReader application (C#, .NET Framework)
 for the DATASET ID# 2023-GESTURES-IFAD.

 	  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:
 
    Radu-Daniel Vatavu. (2023). iFAD Gestures: Understanding Users' Gesture Input 
    Performance with Index-Finger Augmentation Devices. In Proceedings of the 
    2023 CHI Conference on Human Factors in Computing Systems (CHI '23). 
    ACM, New York, NY, USA, 17 pages. https://doi.org/10.1145/3544548.3580928

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

 Copyright (c) 2023, 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 Geometry;

namespace GestureSharpToolkit
{
    /// <summary>
    /// Implements a processor for the points of a gesture.
    /// Some processors have arguments, which can be specified using the *args* parameter
    /// when instantiating the corresponding delegate function.
    /// </summary>
    public class GestureProcessor
    {
        /// <summary>
        /// Specifies a processor for the points of a gesture
        /// </summary>
        public delegate Point[] GesturePointProcessor(Point[] points, double[] args);

        private readonly GesturePointProcessor m_processor = null;
        private readonly double[] m_args = null;

        #region constructor

        /// <summary>
        /// Creates a new gesture processor
        /// </summary>
        public GestureProcessor(GesturePointProcessor processor, params double[] args)
        {
            this.m_processor = processor;
            this.m_args = new double[args.Length];
            for (int i = 0; i < args.Length; i++)
                this.m_args[i] = args[i];
        }

        #endregion

        #region point processors

        /// <summary>
        /// Applies the processor to the *points* array
        /// </summary>
        public Point[] Apply(Point[] points)
        {
            return m_processor(points, m_args);
        }

        /// <summary>
        /// Resamples the array of points
        /// </summary>
        public static Point[] ResampleUniformLength(Point[] points, double[] args = null)
        {
            int samplingRate = ((args == null) || (args.Length < 1)) ? GestureConstants.DEFAULT_SAMPLING_RATE : (int)args[0];
            if (samplingRate < 2) throw new ArgumentException();
            return PointSet.Resample(points, samplingRate, false);
        }

        /// <summary>
        /// Recales the array of points
        /// </summary>
        public static Point[] ScaleToUnitBox(Point[] points, double[] args = null)
        {
            return PointSet.Scale(points);
        }

        /// <summary>
        /// Translates the array of points so that the new centroid becomes 0
        /// </summary>
        public static Point[] TranslateToOrigin(Point[] points, double[] args = null)
        {
            return PointSet.TranslateBy(points, PointSet.Centroid(points));
        }

        /// <summary>
        /// Rotates the array of points counterclockwise with *angle* (in radians) 
        /// around the centroid
        /// </summary>
        public static Point[] RotateBy(Point[] points, double[] args = null)
        {
            return PointSet.RotateBy(points, args[0]);
        }

        /// <summary>
        /// Resamples the array of points
        /// </summary>
        public static Point[] ResampleNonUniformTime(Point[] points, double[] args = null)
        {
            double time = points[points.Length - 1].T - points[0].T;
            return PointSet.Resample(points, (int)(time * args[0]), true);
        }

        /// <summary>
        /// For accelerated motion, this filter removes the contribution of gravity:
        /// https://developer.android.com/reference/android/hardware/SensorEvent.html
        /// </summary>
        public static Point[] GravityRemoval(Point[] points, double[] args = null)
        {
            double alpha = ((args == null) || (args.Length < 1)) ? 0.8 : args[0];
            
            Point[] newPoints = new Point[points.Length];
            Point gravity = new Point(0, 0, 0);
            for (int i = 0; i < points.Length; i++)
            {
                gravity = new Point(
                    alpha * gravity.X + (1 - alpha) * points[i].X,
                    alpha * gravity.Y + (1 - alpha) * points[i].Y,
                    alpha * gravity.Z + (1 - alpha) * points[i].Z,
                    points[i].T,
                    points[i].StrokeID
                );
                newPoints[i] = points[i] - gravity;
            }
            return newPoints;
        }

        #endregion
    }
}
