/*
* This file is part of the TraKERS\Middleware package.
*
* (c) IRI <http://www.iri.centrepompidou.fr/>
*
* For the full copyright and license information, please view the LICENSE_MIDDLEWARE
* file that was distributed with this source code.
*/

/*
 * Projet : TraKERS
 * Module : MIDDLEWARE
 * Sous-Module : Search
 * Classe : Segmenter
 * 
 * Auteur : alexandre.bastien@iri.centrepompidou.fr
 * 
 * Fonctionnalités : Permet d'extraire à la volée les segments du tracé en cours.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Media3D;

namespace Trakers.Tracking.Search
{
    public class Segmenter
    {
        //Stocke les positions d'une main au cours du temps.
        private List<Point3D> handPointerHistory;
        //Point précédent.
        private Point3D prevPt;
        //Ratio : on prend 1/ratio points.
        private int ratio;
        //Indique le numéro du point actuellement enregistré (pour le ratio).
        private int currentPointNumber;
        //Extremums enregistrés parmi les 3 axes pour détecter les changements de sens.
        private double Xmax, Ymax, Zmax;
        private double Xmin, Ymin, Zmin;
        //On établit une distance en nombre de points entre le point actuel l'extremum 
        //local détecté dans les 3 axes (pour copier le segment : points de 0 à id de l'extremum).
        private int distToXmax, distToYmax, distToZmax;
        private int distToXmin, distToYmin, distToZmin;
        //Points critiques.
        private Point3D minPt, maxPt;
        //Tendance actuelle du tracé.
        private bool XtowardRight, YtowardUp, ZtowardFront;
        //Limites de différences à partir desquelles .
        private int directionChangeTresholdXY;
        private float directionChangeTresholdZ;

        /*
        *  Initialisation du segmenter.
        *  Coupe une courbe en deux dès qu'il repère un changement important de trajectoire.
        */
        public Segmenter(int _ratio)
        {
            //directionChangeTresholdXY = kinectMain.getDirectionChangeTresholdXY();
            //directionChangeTresholdZ = kinectMain.getDirectionChangeTresholdZ();
            handPointerHistory = new List<Point3D>();
            ratio = _ratio;
            currentPointNumber = 1;
            prevPt = new Point3D(0, 0, 0);

            Xmax = Ymax = Zmax = Xmin = Ymin = Zmin = -1;
            distToXmax = distToYmax = distToZmax = distToXmin = distToYmin = distToZmin = -1;
        }

        /*
         * Getters et setters.
         */
        public void SetRatio(int _ratio)
        {
            ratio = _ratio;
        }
        public void SetDirectionChangeTresholdXY(int _directionChangeTresholdXY)
        {
            directionChangeTresholdXY = _directionChangeTresholdXY;
        }
        public void SetDirectionChangeTresholdZ(float _directionChangeTresholdZ)
        {
            directionChangeTresholdZ = _directionChangeTresholdZ;
        }

        public int GetRatio()
        {
            return ratio;
        }
        public int SetDirectionChangeTresholdXY()
        {
            return directionChangeTresholdXY;
        }
        public float SetDirectionChangeTresholdZ()
        {
            return directionChangeTresholdZ;
        }

        /*
         * On charge tous les paramètres d'un coup.
         */
        public void setParams(int _ratio, int _directionChangeTresholdXY, float _directionChangeTresholdZ)
        {
            ratio = _ratio;
            directionChangeTresholdXY = _directionChangeTresholdXY;
            directionChangeTresholdZ = _directionChangeTresholdZ;
        }

        /*
         * Enregistre le point passé en paramètre d'après le ratio.
         */
        public void RecordPoint(Point3D pt)
        {
            //Indique l'ID du point à couper.
            int whereToCut;
            //Si le ratio est excédé, on peut enregistrer.
            if (currentPointNumber > ratio)
                currentPointNumber = 1;
            
            //Si le point précédent est à une position différente du point actuel.
            if(prevPt.X != pt.X || prevPt.Y != pt.Y || prevPt.Z != pt.Z)
            {
                //Si le numéro est 1 (début ou ratio atteint), on enregistre.
                if(currentPointNumber == 1)
                    handPointerHistory.Add(pt);

                //Si le point précédent a été initialisé avec un vrai point.
                if (prevPt.X > 0 && prevPt.Y > 0 && prevPt.Z > 0)
                {
                    //Appel aux detecteurs d'extremums.
                    if ((whereToCut = DetectDirectionChangeAtXAxis(pt)) > 0)
                        Segment(whereToCut);
                    else if((whereToCut = DetectDirectionChangeAtYAxis(pt)) > 0)
                        Segment(whereToCut);
                    else if ((whereToCut = DetectDirectionChangeAtZAxis(pt)) > 0)
                        Segment(whereToCut);
                }
                //On met à jour le point précédent.
                prevPt = pt;
                //On passe au numéro suivant (jusqu'à atteindre le ration plus tard).
                currentPointNumber++;
            }
        }

        /*
         * Détecte un changement de sens (extremum) dans l'axe X.
         */
        public int DetectDirectionChangeAtXAxis(Point3D pt)
        {
            //ID où couper en cas d'extremum.
            int whereToCut = -1;

            //Mise à jour des extremums.
            if (Xmax == -1 || Xmax < pt.X)
            {
                //Si le point est plus grand en X que Xmax, alors il est remplacé et on réinitialise la
                //distance au dernier maximum en X.
                Xmax = pt.X;
                maxPt = pt;
                distToXmax = 0;
            }
            else
                distToXmax++;
            if (Xmin == -1 || Xmin > pt.X)
            {
                //Si le point est plus petit en X que Xmin, alors il est remplacé et on réinitialise la
                //distance au dernier minimum en X.
                Xmin = pt.X;
                minPt = pt;
                distToXmin = 0;
            }
            else
                distToXmin++;

            //Si X max est plus grand que la position actuelle additionnée du seuil
            //et que l'extremum n'est pas le premier point sauvegardé.
            if (Xmax > pt.X + directionChangeTresholdXY && maxPt != handPointerHistory.First())
                whereToCut = currentPointNumber - distToXmax;
            //Si X min est plus petit que la position actuelle à laquelle on a retiré le seuil
            //et que l'extremum n'est pas le premier point sauvegardé.
            if (Xmin < pt.X - directionChangeTresholdXY && minPt != handPointerHistory.First())
                whereToCut = currentPointNumber - distToXmin;

            return whereToCut;
        }

        /*
         * Détecte un changement de sens (extremum) dans l'axe Y.
         */
        public int DetectDirectionChangeAtYAxis(Point3D pt)
        {
            //ID où couper en cas d'extremum.
            int whereToCut = -1;

            //Mise à jour des extremums.
            if (Ymax == -1 || Ymax < pt.Y)
            {
                //Si le point est plus grand en Y que Ymax, alors il est remplacé et on réinitialise la
                //distance au dernier maximum en Y.
                Ymax = pt.Y;
                maxPt = pt;
                distToYmax = 0;
            }
            else
                distToYmax++;
            if (Ymin == -1 || Ymin > pt.Y)
            {
                //Si le point est plus petit en Y que Ymin, alors il est remplacé et on réinitialise la
                //distance au dernier minimum en Y.
                Ymin = pt.Y;
                minPt = pt;
                distToYmin = 0;
            }
            else
                distToYmin++;

            //Si Y max est plus grand que la position actuelle additionnée du seuil
            //et que l'extremum n'est pas le premier point sauvegardé.
            if (Ymax > pt.Y + directionChangeTresholdXY && maxPt != handPointerHistory.First())
                whereToCut = currentPointNumber - distToYmax;
            //Si Y min est plus petit que la position actuelle à laquelle on a retiré le seuil
            //et que l'extremum n'est pas le premier point sauvegardé.
            if (Ymin < pt.Y - directionChangeTresholdXY && minPt != handPointerHistory.First())
                whereToCut = currentPointNumber - distToYmin;

            return whereToCut;
        }

        /*
         * Détecte un changement de sens (extremum) dans l'axe Z.
         */
        public int DetectDirectionChangeAtZAxis(Point3D pt)
        {
            //ID où couper en cas d'extremum.
            int whereToCut = -1;

            //Mise à jour des extremums.
            if (Zmax == -1 || Zmax < pt.Z)
            {
                //Si le point est plus grand en Z que Ymax, alors il est remplacé et on réinitialise la
                //distance au dernier maximum en Z.
                Zmax = pt.Z;
                maxPt = pt;
                distToZmax = 0;
            }
            else
                distToZmax++;
            if (Zmin == -1 || Zmin > pt.Z)
            {
                //Si le point est plus petit en Z que Zmin, alors il est remplacé et on réinitialise la
                //distance au dernier minimum en Z.
                Zmin = pt.Z;
                minPt = pt;
                distToZmin = 0;
            }
            else
                distToZmin++;

            //Si Z max est plus grand que la position actuelle additionnée du seuil
            //et que l'extremum n'est pas le premier point sauvegardé.
            if (Zmax > pt.Z + directionChangeTresholdXY && maxPt != handPointerHistory.First())
                whereToCut = currentPointNumber - distToZmax;
            //Si Z min est plus petit que la position actuelle à laquelle on a retiré le seuil
            //et que l'extremum n'est pas le premier point sauvegardé.
            if (Zmin < pt.Z - directionChangeTresholdXY && minPt != handPointerHistory.First())
                whereToCut = currentPointNumber - distToZmin;

            return whereToCut;
        }

        /*
         * Découpe le tracé en cours en segment allant du premier point au premier extremum trouvé.
         */
        public void Segment(int whereToCut)
        {
            handPointerHistory.RemoveRange(0, whereToCut);
            currentPointNumber -= whereToCut;
        }
    }
}
