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

/*
 * Projet : TraKERS
 * Module : Front IDILL
 * Fichier : curvesDetector.js
 * 
 * Auteur : alexandre.bastien@iri.centrepompidou.fr
 * 
 * Fonctionnalités : Détecteur de courbes de recherche, appelé par la classe de création du canvas de recherche.
 */

/*
 * Détecteur de courbes (lignes droites, arcs de cercles et cercles).
 * Est appelé dans le fichier :
 * searchCanvas > fonction create.
*/
function CurvesDetector(divisions, sizeTreshold, dictionary, mosaic)
{
    //Précision de la détection de direction en divisions (par défaut 12, selon une modélisation horaire et non mathématique).
    this.divisions = divisions;
    //Taille limite pour un segment/courbe pour être considéré comme tel en px.
    this.sizeTreshold = sizeTreshold;
    
    this.dictionary = dictionary;
    
    this.mosaic = mosaic;
    
    //Code actuel généré. Il représente la structure de la courbe de recherche.
    this.actualCode = '';
	//Les éléments composant le code (correspondant aux courbes.
    this.MPCode = '';
    this.SPCode = '';
    
    this.joints = [];
    this.jInc = 0;
    
    //Longueur totale de la courbe.
    this.MPTotalDist = 0;
    this.SPTotalDist = 0;
    //Longueur de la partie actuelle (après début ou changement de direction).
    this.MPActualDist = 0;
    this.SPActualDist = 0;
    //Angles actuels.
    this.MPActualAngle = -1;
    this.SPActualAngle = -1;
    
    //Centre du repère du pointeur principal.
    this.MPrepX = null;
    this.MPrepY = null;
    //Centre du repère du pointeur secondaire.
    this.SPrepX = null;
    this.SPrepY = null;
    
    //Coordonnées actuelles du/des pointeur/s.
    this.MPx = 0;
    this.MPy = 0;
    this.SPx = 0;
    this.SPy = 0;
    //Coordonnées précédentes du/des pointeur/s.
    this.MPpx = 0;
    this.MPpy = 0;
    this.SPpx = 0;
    this.SPpy = 0;
    
    //Si les paramètres sont incorrects, on leur donne une valeur par défaut.
    if(isNaN(divisions) || divisions < 1 || divisions > 360)
    {
        this.divisions = 12;
    }
    if(sizeTreshold < 1)
    {
        sizeTreshold = 30;
    }
}

/*
 * Reinitialise les paramètres du détecteur.
 * Est appelé dans le fichier :
 * searchCanvas > fonction onPointerOut.
*/
CurvesDetector.prototype.reinit = function()
{
    this.MPrepX = 0;
    this.MPrepY = 0;
	this.SPrepX = 0;
	this.SPrepY = 0;
    this.MPpx = 0;
    this.MPpy = 0;
    this.SPpx = 0;
    this.SPpy = 0;
    this.MPActualAngle = -1;
    this.SPActualAngle = -1;
    this.MPCode = '';
    this.SPCode = '';
    this.actualCode = '';
    this.MPActualDist = 0;
    this.SPActualDist = 0;
    this.MPTotalDist = 0;
    this.SPTotalDist = 0;
}

/*
 * Met à jour les positions des pointeurs.
 * Est appelé dans le fichier :
 * searchCanvas > fonction onPointerMove.
*/
CurvesDetector.prototype.updatePos = function(mpx, mpy, spx, spy)
{
    //On met à jour les coordonnées récentes.
    this.MPx = mpx;
    this.MPy = mpy;
    
    //Si les coordonnées précédentes n'existent pas, alors on les met à jour.
    if(!this.MPpx)
    {
        this.MPpx = mpx;
    }
    if(!this.MPpy)
    {
        this.MPpy = mpy;
    }
    if(!this.MPrepX)
    {
        this.MPrepX = mpx;
    }
    if(!this.MPrepY)
    {
        this.MPrepY = mpy;
    }
    
    //Si on a un second pointeur.
    if(spx && spy)
    {
        //On met les coordonnées à jour.
        this.SPx = spx;
        this.SPy = spy;
        
        //Si les coordonnées précédentes n'existent pas, alors on les met à jour.
        if(!this.SPpx)
        {
            this.SPpx = spx;
        }
        if(!this.SPpy)
        {
            this.SPpy = spy;
        }
		if(!this.SPrepX)
		{
			this.SPrepX = spx;
		}
		if(!this.SPrepY)
		{
			this.SPrepY = spy;
		}
    }
    
    //On met à jour la distance des segments courants et on regarde les correspondances dans le dictionnaire.
    this.updateDists();
}

/*
 * Met à jour les distances parcourues.
 * Est appelé dans le fichier :
 * curvesDetector > fonction updatePos.
*/
CurvesDetector.prototype.updateDists = function()
{
	//Si on n'est pas en recherche pas courbes, on part.
	if(!this.mosaic.isSearchByCurvesOn)
	{
		return;
	}
	
    var foundGestures;
    
    //Si on a de quoi calculer les distances.
    if(this.MPx && this.MPy && this.MPpx && this.MPpy && this.MPrepX && this.MPrepY)
    {
        //Distance entre les deux derniers points.
        var MPDist = Math.floor(Math.sqrt((this.MPx - this.MPpx) * (this.MPx - this.MPpx) + (this.MPy - this.MPpy) * (this.MPy - this.MPpy)));
        //On met à jour la distance totale de la courbe.
        this.MPTotalDist += MPDist;
        //Et aussi la distance du segment en cours.
        this.MPActualDist += MPDist;
        
        //Angle courant initialisé à -1.
        var MPCurrentA = -1;
        
		//Seuil de considération d'un segment.
		var treshold = (this.mosaic.config.mouseInteractions ? this.mosaic.config.mouseUpDownDeltaTreshold : this.mosaic.config.kinectUpDownDeltaTreshold);
		
        //Si la distance actuelle du segment existe.
        if(MPDist > treshold)
        {
            //On calcule l'angle courant entre ce segment et 
            MPCurrentA = this.currentAngle(this.MPrepX, this.MPrepY, this.MPx, this.MPy, this.divisions);
        }
        
        //Si la distance du segment actuel excède le seuil de la config et qu'il y a un angle.
        if(this.MPActualDist > this.sizeTreshold && MPCurrentA != -1)
        {
            //Si l'angle affecté n'a pas encore de valeur ou si l'angle affecté est différent de l'angle calculé.
            if(this.MPActualAngle == -1 || this.MPActualAngle != MPCurrentA)
            {
                //On affecte le nouvel angle.
                this.MPActualAngle = MPCurrentA;
                //On construit le code correspondant à ce segment.
                this.MPCode += 'D' + MPCurrentA;
				
				if(this.mosaic.config.mouseInteractions)
				{
					this.actualCode = this.MPCode;
					//On affecte le code dans la mosaique.
					this.mosaic.actualCode = this.actualCode;
					//On recherche les gestures commencant par ce code.
					foundGestures = this.codeToGestures(this.actualCode);
					// console.log(this.actualCode);
					this.foundGesturesManagement(foundGestures);
				}
				
                //On réinitialise la distance entre les deux derniers points.
                this.MPActualDist = 0;
            }
            //Sinon si l'angle n'a pas changé dans le segment en cours.
            else
            {
                //On met à jour les dernières coordonnées du pointeur principal.
                this.MPrepX = this.MPpx;
                this.MPrepY = this.MPpy;
            }
            
            //Si l'angle affecté n'a pas encore de valeur.
            if(this.MPActualAngle == -1)
            {
                //On le met à jour.
                this.MPActualAngle = MPCurrentA;
            }
        }
        
        //On met à jour les coordonnées précédentes.
        if(this.MPpx != this.MPx)
        {
            this.MPpx = this.MPx;
        }
        if(this.MPpy != this.MPy)
        {
            this.MPpy = this.MPy;
        }
    }
	
    //Idem au cas où on aurait un deuxième pointeur.
    if(this.SPx && this.SPy && this.SPpx && this.SPpy && this.SPrepX && this.SPrepY)
    {
        //Distance entre les deux derniers points.
        var SPDist = Math.floor(Math.sqrt((this.SPx - this.SPpx) * (this.SPx - this.SPpx) + (this.SPy - this.SPpy) * (this.SPy - this.SPpy)));
        //On met à jour la distance totale de la courbe.
        this.SPTotalDist += SPDist;
        //Et aussi la distance du segment en cours.
        this.SPActualDist += SPDist;
        
		//Angle courant initialisé à -1.
        var SPCurrentA = -1;
		
		//Seuil de considération d'un segment.
		var treshold = (this.mosaic.config.mouseInteractions ? this.mosaic.config.mouseUpDownDeltaTreshold : this.mosaic.config.kinectUpDownDeltaTreshold);
		
        //Si la distance actuelle du segment est plus grande que le treshold.
        if(SPDist > treshold)
        {
            //On calcule l'angle courant entre ce segment en divisions horaires.
            SPCurrentA = this.currentAngle(this.SPrepX, this.SPrepY, this.SPx, this.SPy, this.divisions);
        }
		
		//Si la distance du segment actuel excède le seuil de la config et qu'il y a un angle.
        if(this.SPActualDist > this.sizeTreshold && SPCurrentA != -1)
        {
            //Si l'angle affecté n'a pas encore de valeur ou si l'angle affecté est différent de l'angle calculé.
            if(this.SPActualAngle == -1 || this.SPActualAngle != SPCurrentA)
            {
                //On affecte le nouvel angle.
                this.SPActualAngle = SPCurrentA;
                //On construit le code correspondant à ce segment.
                this.SPCode += 'D' + SPCurrentA;
				
                //On réinitialise la distance entre les deux derniers points.
                this.SPActualDist = 0;
            }
            //Sinon si l'angle n'a pas changé dans le segment en cours.
            else
            {
                //On met à jour les dernières coordonnées du pointeur principal.
                this.SPrepX = this.SPpx;
                this.SPrepY = this.SPpy;
            }
            
            //Si l'angle affecté n'a pas encore de valeur.
            if(this.SPActualAngle == -1)
            {
                //On le met à jour.
                this.SPActualAngle = SPCurrentA;
            }
        }
        
        //On met à jour les coordonnées précédentes.
        if(this.SPpx != this.SPx)
        {
            this.SPpx = this.SPx;
        }
        if(this.SPpy != this.SPy)
        {
            this.SPpy = this.SPy;
        }
    }
	
	if(!this.mosaic.config.mouseInteractions && (this.SPCode || this.MPCode))
	{
		this.actualCode = this.SPCode + ':' + this.MPCode;
		// console.log(this.actualCode);
		this.mosaic.actualCode = this.actualCode;
		//On recherche les gestures commencant par ce code.
		foundGestures = this.codeToGestures(this.actualCode);
		this.foundGesturesManagement(foundGestures);
	}
}

/*
 * Renvoie les noms de gestures du dictionnaire qui ont un code qui commence par le code en entrée.
 * Est appelé dans le fichier :
 * curvesDetector > fonction updateDists.
*/
CurvesDetector.prototype.codeToGestures = function(code)
{
    //Variable qui va stocker tous les noms trouvés.
    var retNames = '';
    
    //Pour tout le dictionnaire.
    for(var i = 0 ; i < this.dictionary.length ; i++)
    {
        //Pour touts les codes de chaque gesture du dictionnaire.
        for(var j = 0 ; j < this.dictionary[i].codes.length ; j++)
        {
			if(this.mosaic.config.mouseInteractions)
			{
				//Si le code en entrée est une partie début d'un des codes.
				if(this.dictionary[i].codes[j].indexOf(code) == 0)
				{
					//On ajoute le nom de la gesture et on passe à la gesture suivante.
					retNames += this.dictionary[i].name + ';';
					break;
				}
			}
			else
			{
				//On décompose le code dynamique et du dictionnaire en deux parties.
				var partsCode = code.split(':'), partsDicoCode = this.dictionary[i].codes[j].split(':');
				//Si les débuts des deux parties correspondent aux deux parties du code dynamique.
				if(partsDicoCode[0].indexOf(partsCode[0]) == 0 && partsDicoCode[1].indexOf(partsCode[1]) == 0)
				{
					retNames += this.dictionary[i].name + ';';
					break;
				}
			}
        }
    }
    //Comme on sépare chaque nom par un ;, il faut supprimer le dernier si au moins un nom a été trouvé.
    if(retNames.length > 0)
    {
        retNames = retNames.substring(0, retNames.length-1);
    }
    
    //On renvoit les noms.
    return retNames;
}

/*
 * Calcule l'angle emprunté par le morceau de segment actuel. On prend A va vers B.
 * Est appelé dans le fichier :
 * curvesDetector > fonction updateDists.
*/
CurvesDetector.prototype.currentAngle = function(xa, ya, xb, yb, divisions)
{
    //On calcule l'angle de la droite AB et des abscisses, et on effectue une rotation de 90° vers la gauche.
    var angleRad = Math.atan2((ya - yb), (xa - xb)) - Math.PI / 2;
    //On traduit les radians en divisions en passant de [-PI/2 ; PI/2] à [0 ; divisions - 1].
    var angleDiv = Math.floor((angleRad > 0 ? angleRad : (2*Math.PI + angleRad)) * divisions / (2*Math.PI));
    
    //L'angle initial est 0.
    if(angleDiv == divisions)
    {
        angleDiv = 0;
    }
    
    return angleDiv;
}

/*
 * Fonction de gestion des courbes trouvées.
*/
CurvesDetector.prototype.foundGesturesManagement = function(foundGestures)
{
	//S'il n'y a pas de gestures trouvées ou s'il y en a plus d'une.
	if(foundGestures.length == 0 || foundGestures.split(';').length != 1)
	{
		//On a trouvé quelque chose, même si ce qu'on a trouvé est vide ('').
		this.mosaic.curvesGesturesFound = true;
		//On notifie ce qu'on a trouvé. Dans le cas où c'est '', on affiche geste inconnu.
		this.mosaic.removeNotifications();
		this.mosaic.curvesGestures(foundGestures);
		
		//Si ce qu'on a trouvé est vide en mode d'interaction Kinect.
		if(foundGestures.length == 0 && !this.mosaic.config.mouseInteractions)
		{
			//Si on est en mode d'interaction Kinect, on désactive les courbes jusqu'à ce que l'utilisateur n'a plus les deux mains dans la zone (pour éviter les erreurs de manipulation).
			this.mosaic.mustTakeOutHands = true;
			
			/*//On affiche différentes aides en fonction de si on se trouve dans une vidéo ou non.
			if(this.mosaic.currentMode == 'SEARCH')
			{
				// this.mosaic.notifyHelp(false);
			}
			else if(this.mosaic.currentMode == 'FILTER')
			{
				// this.mosaic.notifyHelp(true);
			}*/
			foundGestures = '';
			
			//On enlève la recherche.
			this.mosaic.curvesGesturesFound = false;
			this.mosaic.isSearchByCurvesOn = false;
			this.mosaic.leaveSearch();
			
			//Si on était en mode filtrage de la mosaïque et qu'aucune gesture de filtrage n'avait été détectée avant ca, on revient en mode mosaïque.
			if(this.mosaic.currentMode == "FILTER" && this.mosaic.filterSearchedType == "")
			{
				this.mosaic.currentMode = "MOSAIC";
				this.mosaic.isMosaicFiltered = false;
			}
			//Sinon si on était en mode recherche dans une video et qu'aucune gesture n'avait été détectée avant ca, on revient en mode video.
			if(this.mosaic.currentMode == "SEARCH" && this.mosaic.currentSearchGesture[this.mosaic.centerId] == "")
			{
				this.mosaic.currentMode = "VIDEO";
			}
		}
		/*//Si l'aide est déjà affichée, on l'enlève.
		else if(foundGestures.split(';').length != 1 && this.mosaic.helpDisplayed && !this.mosaic.mustTakeOutHands)
		{
			// this.mosaic.removeHelp();
		}*/
	}
	//Si on a un seul résultat.
	else
	{
		//Si on est en mode d'interaction Kinect, on désactive les courbes jusqu'à ce que l'utilisateur n'a plus les deux mains dans la zone (pour éviter les erreurs de manipulation).
		this.mosaic.mustTakeOutHands = true;
		
		// On affecte la recherche.
		this.mosaic.currentSearchGesture[this.mosaic.centerId] = foundGestures;
		this.mosaic.isUserInSearchZone = false;
		
		//Si on est en mode recherche et que le player est prêt.
		if(this.mosaic.currentMode == "SEARCH" && this.mosaic.playerIsReady)
		{
			//On effectue la recherche.
			this.mosaic.player.widgets[0].searchByGesture(foundGestures);
			this.mosaic.isCurrentlyInASearchByGesture = this.mosaic.player.widgets[0].isCurrentlyInASearchByGesture;
			
			//On notifie.
			this.mosaic.removeNotifications();
			this.mosaic.searchGesture(foundGestures, 'valid');
			
			//S'il y a un marqueur trouvé au moins, on place le curseur sur le premier résultat.
			if(this.mosaic.player && this.mosaic.player.widgets[0] && this.mosaic.timeToGoAt[this.mosaic.centerId] === 0 && this.mosaic.player.widgets[0].atLeastOneSearchMarker(this.mosaic.currentSearchGesture[this.mosaic.centerId]))
			{
				this.mosaic.player.widgets[0].goToFirstSearchedMarker(this.mosaic.currentSearchGesture[this.mosaic.centerId]);
			}
			
			//On enlève a recherche par courbes.
			foundGestures = '';
			this.mosaic.curvesGesturesFound = false;
			
			this.mosaic.isSearchByCurvesOn = false;
			this.mosaic.leaveSearch();
		}
		//Si on est en filtrage.
		else if(this.mosaic.currentMode == "FILTER")
		{
			if(this.mosaic.isMosaicFiltered)
			{
				//On met à jour la gesture de filtrage.
				this.mosaic.filterSearchedType = foundGestures;
				//On filtre la mosaique.
				this.mosaic.searchFilter(foundGestures);
				this.mosaic.curvesGesturesFound = false;
				//On notifie.
				this.mosaic.removeNotifications();
				this.mosaic.filterGesture(foundGestures, 'valid');
				
				foundGestures = '';
				//On enlève la recherche par courbes.
				this.mosaic.isSearchByCurvesOn = false;
				this.mosaic.leaveSearch();
			}
		}
	}
}