//#include "Debug.h"

// This file is part of the Herezh++ application.
//
// The finite element software Herezh++ is dedicated to the field
// of mechanics for large transformations of solid structures.
// It is developed by Gérard Rio (APP: IDDN.FR.010.0106078.000.R.P.2006.035.20600)
// INSTITUT DE RECHERCHE DUPUY DE LÔME (IRDL) <https://www.irdl.fr/>.
//
// Herezh++ is distributed under GPL 3 license ou ultérieure.
//
// Copyright (C) 1997-2022 Université Bretagne Sud (France)
// AUTHOR : Gérard Rio
// E-MAIL  : gerardrio56@free.fr
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
// For more information, please consult: <https://herezh.irdl.fr/>.

#include <iostream>

//#define BOOST_STACKTRACE_LINK
//#include <stdio.h>
//#include <stdlib.h>
//#include <string.h>
//#include <dlfcn.h>
//
//#include "unwind.h"

//#include <boost/stacktrace.hpp>
#include <execinfo.h>
#include <stdio.h>
//#include <stacktrace>

#include "Droite.h"
#include "ConstMath.h"
#include "MathUtil.h"
#include "Util.h"

//----------------------------------------------------------------
// def des donnees commune a toutes les droites
//----------------------------------------------------------------
int Droite::alea = 0; 

    // CONSTRUCTEURS :
// par defaut
// par defaut définit une droit // à x et passant par 0, et de dimension celle de l'espace de travail
Droite::Droite () :
 A(ParaGlob::Dimension()), U(ParaGlob::Dimension())
  { // on met une valeur 1 par défaut sur U
    U(1)=1.;
	};
// avec les datas
Droite::Droite ( const Coordonnee& B, const Coordonnee& vec) :
 A(B), U(vec)
  { 
    #ifdef MISE_AU_POINT	 	 
     if  (B.Dimension() != vec.Dimension())
      { cout << "\nErreur : les dimensions du point et du vecteur ne sont pas identique !";
        cout <<"\ndim point = " <<B.Dimension() <<", dim vecteur =" << vec.Dimension();
        cout << "\nDroite::Droite (Coordonnee& B,Coordonnee& vec)" << endl;
//        std::cout << boost::stacktrace::stacktrace();
//       // essai
//       void* callstack[128];
//       int i, frames = backtrace(callstack, 128);
//       char** strs = backtrace_symbols(callstack, frames);
//       for (i = 0; i < frames; ++i) {
//           printf("%s\n", strs[i]);
//       }
//       free(strs);
//        // fin essai
        Sortie(1);
      };
    #endif 
    double d = U.Norme();
    if (d <= ConstMath::trespetit)
	     { cout << "\nErreur : la norme du vecteur de def de la droite est trop petite !";
	       cout <<"\nnorme = " << d;
		      cout << "\nDroite::Droite (Coordonnee& B,Coordonnee& vec)" << endl;
//// essai
//        void* callstack[128];
//        int i, frames = backtrace(callstack, 128);
//        char** strs = backtrace_symbols(callstack, frames);
//        for (i = 0; i < frames; ++i) {
////            printf("%s\n", strs[i]);
//            cout << "\n " << strs[i];
//        }
//        free(strs);
//// fin essai
        
        
		      Sortie(1);
		    };
    U /= d;  // U est ainsi norme  
   };
// avec la dimension
// def par défaut d'une droite // à x
Droite::Droite (int dim) :
 A(dim), U(dim)
  { // on met une valeur 1 par défaut sur U
    U(1)=1.;
  };
    // de copie
Droite::Droite ( const Droite& a) :
  A(a.A), U(a.U)
   {};
    // DESTRUCTEUR :
Droite::~Droite ()  {};
    // surcharge des operator
Droite& Droite::operator = ( const Droite & P)
  { this->A = P.A; this->U = P.U; 
    return *this;
  };
        
    // METHODES PUBLIQUES :
    
    // change le point de ref de la droite
void Droite::Change_ptref( const Coordonnee& B)
 {
    #ifdef MISE_AU_POINT	 	 
     if  (B.Dimension() != U.Dimension())
	     { cout << "\nErreur : les dimensions du point et du vecteur ne sont pas identique !";
	       cout <<"\ndim point = " <<B.Dimension() <<", dim vecteur =" << U.Dimension();
		      cout << "\nDroite::Change_ptref(Coordonnee& B)" << endl;
		      Sortie(1);
		    };
    #endif 
    A = B;
  };
    // change le vecteur de la droite
void Droite::Change_vect( const Coordonnee& vec)
 {
    #ifdef MISE_AU_POINT	 	 
     if  (A.Dimension() != vec.Dimension())
	     { cout << "\nErreur : les dimensions du point et du vecteur ne sont pas identique !";
	       cout <<"\ndim point = " <<A.Dimension() <<", dim vecteur =" << vec.Dimension();
		      cout << "\nDroite::Change_vect(Coordonnee& vec)" << endl;
		      Sortie(1);
		    };
    #endif 
    double d = vec.Norme();
//    if (d <= ConstMath::trespetit)
    if (d <= ConstMath::petit) // *****pour test
	    { cout << "\nErreur : la norme du vecteur est trop petite !";
	      cout <<"\nnorme = " << d << " vec= " << vec ;
		     cout << "\nDroite::Change_vect( const Coordonnee& vec)" << endl;
//       // essai
//               void* callstack[128];
//               int i, frames = backtrace(callstack, 128);
//               char** strs = backtrace_symbols(callstack, frames);
//               cout << "\n frames= "<<frames;
//               for (i = 0; i < frames; ++i) {
//                   //            printf("%s\n", strs[i]);
//                   cout << "\n i= "<< i << " strs[i]= " << strs[i];
//               }
//               free(strs);
//       // fin essai
		     Sortie(1);
     };
    U = vec / d;  // U est ainsi norme   
  };
    // change toutes les donnees
void Droite::change_donnees( const Coordonnee& B, const Coordonnee& vec)
 {
    #ifdef MISE_AU_POINT	 	 
     if  (B.Dimension() != vec.Dimension())
	     { cout << "\nErreur : les dimensions du point et du vecteur ne sont pas identique !";
	       cout <<"\ndim point = " <<B.Dimension() <<", dim vecteur =" << vec.Dimension();
		      cout << "\nDroite::change_donnees( const Coordonnee& B, const Coordonnee& vec)" << endl;
		      Sortie(1);
		    };
    #endif
    A = B;
    double d = vec.Norme();
    if (d <= ConstMath::trespetit)
	     { cout << "\nErreur : la norme du vecteur est trop petite !";
	       cout <<"\nnorme = " << d;
		      cout << "\nDroite::change_donnees( const Coordonnee& B, const Coordonnee& vec)" << endl;
//        // essai
//                void* callstack[128];
//                int i, frames = backtrace(callstack, 128);
//                char** strs = backtrace_symbols(callstack, frames);
//                for (i = 0; i < frames; ++i) {
//                    printf("%s\n", strs[i]);
//                }
//                free(strs);
//        // fin essai
		      Sortie(1);
		    };
    U = vec / d;  // U est ainsi norme    
  };
 
    // calcul l'intersection M de la droite avec une autre droite, ramene  0 s'il n'y
    // a pas d'intersection, ramene -1 si l'intersection ne peut pas etre calculee
    // et 1 s'il y a un point d'intersection
    // NB: en axisymétrique, on considère que les droites sont dans le plan xy
int Droite::Intersection( const Droite & D,Coordonnee& M)
  { const Coordonnee & B = D.PointDroite(); // par commodite d'ecriture
    const Coordonnee & V = D.VecDroite(); //          ""
    Coordonnee AB = B - A;double Nab = AB.Norme();
	   // test en fonction de la dimension (a priori 2 ou 3 !!)
    int dima = A.Dimension();
    // en axi on calcule comme en 2D
    if (ParaGlob::AxiSymetrie())
       dima--;
	   switch (dima) //(A.Dimension())
	    {case 3:
       { Coordonnee W = Util::ProdVec_coor(U,V);
         double d = W.Norme();
//         if (Dabs(d) <= ConstMath::pasmalpetit)
         if (Dabs(d) <= ConstMath::petit)
          { // cas de droite // ou confondues           
//            if (Dabs(Nab) <= ConstMath::pasmalpetit)
            if (Dabs(Nab) <= ConstMath::petit)
              // les droites sont confondues et ont meme point de reference
               return -1; // il n'y a pas d'Intersection identifiee
            AB /= Nab; // norme AB    
            Coordonnee V1 = Util::ProdVec_coor(AB,V);double Nv1= V1.Norme();
            if (Dabs(Nv1) <= ConstMath::petit)
				          // produit vectoriel nul: les droites sont confondues, tous les points conviennent
					         // mais on ne peut pas calculer un seul point d'intersection, on choisit par exemple le point de ref:
					         { M= B; return -1;}
				        else
             // les droites sont distinctes: // mais avec des points de reference differents
              {return -1;} // il n'y a pas d'interection identifiee
           }
         else
           // on calcul la distance entre les droites
           { W /= d;
             double h = AB * W; // h = la distance
             if (Dabs(h) >= ConstMath::petit) //pasmalpetit)
               // trop de distance entre les deux droites -> pas d'Intersection
               return 0;
            }     
        }
		     break;
	     case 2:
       { // on regarde si les droites ne sont pas //
		       if ( Dabs(1.-Dabs(U*V)) < ConstMath::petit) //pasmalpetit)
          // soit les droites sont distinctes soit elles sont confondues
          // mais dans tous les cas, on ne peut pas calculer un seul point d'intersection
				     return 0;
       }
		     break;
      case 1:
       { // en 1D on considère que l'on ne peut pas calculer un seul point d'intersection
         return 0;
       }
       break;
		    default:
		      cout << "\n erreur, on ne devrait pas passer par ici !!! "
			          << "\n *** Droite::Intersection( const Droite & D,Coordonnee& M) " << endl;
        Sortie(1);
	   };
    // cas ou l'Intersection existe
    // détail du calcul du point M = intersection
    // on a: vec(AB) = vec(AM) + vec(MB) = alpha U + beta V
    // d'où : (1): AB*U = alpha - beta U*V et (2): AB*V = alpha U*V - beta
    // on fait (1) * (-U*V) + (2) -> - (AB*U) (U*V) + (AB*V) = beta ((U*V)^2 -1)
    // et BM = beta V
   
   
    double uv = U * V;
    double beta = -((AB * U) * uv - (AB * V)) / (uv * uv -1.);
    M = B + beta * V;
////--- debug
//cout << "\n debug Droite::Intersection( ";
//A.Affiche(cout, 10); M.Affiche(cout, 10); cout << "\n" << endl;
//cout << "\n uv= " << uv << ", V: " << V(1) << " " << V(2) << " V= " << sqrt(V(1)*V(1)+V(2)*V(2))
//     << " B: " << B(1) << " " << B(2) << ", AB: " << AB(1) << " " << AB(2) << ", beta= " << beta;
//double diff = (B-M)*(A-M); cout << " (B-M)*(A-M) "<< diff;
//   
//cout << "\n A: " << A(1) << " " << A(2) << ", U: " << U(1) << " " << U(2)
//     << ", M: " << M(1) << " " << M(2) << endl;
////--- fin debug
   
    return 1;       
   };
    
// calcul si un point donné appartient ou non à la droite (à la précision donnée)
bool Droite::Appartient_a_la_droite(const Coordonnee& B)
  { // le point B appartient à la droite lorsque AB est // à U donc que le produit
    // vectoriel de ces deux vecteurs est nul en 3D, en 2D c'est le déterminant et en 1D c'est toujours ok
    Coordonnee AB = B-A;
    switch (AB.Dimension())
     { case 1 : // la collinéarité est obligatoire ici
         return true; break;
       case 2 : // examen du déterminant 
//         if (Abs(Util::Determinant(U,AB)) <= ConstMath::pasmalpetit) return true; else return false; 
         if (Abs(Util::Determinant(U,AB)) <= ConstMath::petit) return true; else return false; 
         break; 
       case 3 :   
//         if((Util::ProdVec_coor(U,AB)).Norme() <= ConstMath::pasmalpetit) return true; else return false; 
         if((Util::ProdVec_coor(U,AB)).Norme() <= ConstMath::petit) return true; else return false; 
         break;
	      default :
			      cout << "\nErreur : la dimension de AB est nulle !, suite des calculs impossible\n";
			      cout << "Droite::Appartient_a_la_droite... \n";
			      Sortie(1);
    };
    // ne doit jamais passer ici mais pour taire le compilo ....
    Sortie(1);
    return false;    
 };
    
// ramene une normale, en 2D il y a une seule solution, en 3D a chaque appel on ramene
// une nouvelle normale calculée aleatoirement
// en 3D axi ramène la normale dans le plan xy : solution unique si elle peut-être calculée
Coordonnee Droite::UneNormale()
  { // on considere la dimension
    int dima = A.Dimension();
    // en axi on calcule comme en 2D mais il faut initialiser la 3ième coordonnée
    // donc on utilise un dima == 4
    if (ParaGlob::AxiSymetrie())
       dima++; //

    switch (dima) //(A.Dimension())
	    { case 2:
         // cas ou l'on est en dimension 2  -> une seule solution pour la normale
         { Coordonnee ve(2);
           ve(1) = -U(2);
           ve(2) = U(1);
           return ve;
         }
         break;
	      case 3: // cas où l'on est en dimension 3
        { Coordonnee ve(3);
          Coordonnee v1(3),v2(3);
          // on cherche un vecteur du plan normal a la droite : v1
          v2(1) = 1.;
          v1 = Util::ProdVec_coor(U,v2);
          double r = v1.Norme();
          if (r <= ConstMath::petit)
            // cas U // v2
            { v2(2) = 1.; v2(1) = 0;
              v1 = Util::ProdVec_coor(U,v2);
            };
          // maintenant le second
          v2 = Util::ProdVec_coor(U,v1);
          // on genere un angle aleatoirement -> un cos
          double cos = ((double)rand())/RAND_MAX;
          // et le vecteur correspondant
          ve = cos * v1 + (1.-cos) * v2;
          // update le nombre aleatoire
          alea++; srand((unsigned) alea);
          return ve;
       }   
		       break;
       case 4:
         // cas ou l'on est en dimension  axi -> une seule solution pour la normale
         // dans le plan xy car on travaille uniquement dans ce plan
         { Coordonnee ve(3); // init à 0. des 3 composantes
           ve(1) = -U(2);
           ve(2) = U(1);
           return ve;
         }
         break;

	    }
 };
    
// calcul la distance d'un point à la droite
double Droite::Distance_a_la_droite(const Coordonnee& M) const
	{ int dima = ParaGlob::Dimension();
	  Coordonnee AM(M-A);
	  Coordonnee HM(AM-(AM * U)*U);
	  return HM.Norme();
	};
               
// fonction valable uniquement en 2D !!!, sinon erreur
// ramène true si les deux points sont du même coté de la droite, false sinon
bool Droite::DuMemeCote(const Coordonnee& M1, const Coordonnee& M2) const
	{ if (ParaGlob::Dimension() == 2)
    { 
  // erreur 12 avril 2011: correction, il faut utiliser le déterminant et non le produit vectoriel
  //		return ((Util::ProdVec_coor(U,(M1-A)) * Util::ProdVec_coor(U,(M2-A))) >= 0.);
      return ((Util::Determinant(U,(M1-A)) * Util::Determinant(U,(M2-A))) >= 0.);
    }
	  else
	  	{ cout << "\n erreur, la fonction Droite::DuMemeCote(.., n'est pas utilisable pour une dimension "
	  	       << " differente de 2D ";
	  	  Sortie(1);
	  	  return false; // pour éviter un warning     
	  	}	
	};           
    
// surcharge de l'operateur de lecture
istream & operator >> (istream & entree, Droite & dr)
  { // vérification du type
    string nom;
    entree >> nom;
    #ifdef MISE_AU_POINT
     if (nom != "_droite_")
      {	cout << "\nErreur, en lecture d'une instance  Droite "
             << " on attendait _droite_ et on a lue: " << nom ;
        cout << "istream & operator >> (istream & entree, Droite & dr)\n";
        Sortie(1);
      };
    #endif
    // puis lecture des différents éléments
    entree >> nom >> dr.A >> nom >> dr.U; 
    return entree;      
  };
  
// surcharge de l'operateur d'ecriture 
ostream & operator << ( ostream & sort,const  Droite & dr)
  { // tout d'abord un indicateur donnant le type
    sort << " _droite_ " ; 
    // puis les différents éléments
    sort << "\n A= " << dr.A << " U= " << dr.U << " "; 
    return sort;      
 };