
// 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 "Debug.h"

#include "ElFrontiere.h"
#include "ParaAlgoControle.h"
#include "Util.h"

//----------------------------------------------------------------
// def des donnees commune a tous les elements
//----------------------------------------------------------------
unsigned int ElFrontiere::nrand = 0;

    // CONSTRUCTEURS :
    // par defaut
ElFrontiere::ElFrontiere () :
  tabNoeud(),ddlElem(),tabfront(),encomb_min(),encomb_max()
  { };
    // fonction du tableau des noeuds sommet
ElFrontiere::ElFrontiere ( const Tableau <Noeud *>& tab, const DdlElement& TddlElem,int nbnoeud ) :
 tabNoeud(tab),ddlElem(TddlElem),tabfront()
 ,encomb_min(ParaGlob::Dimension()),encomb_max(ParaGlob::Dimension())
  {
    #ifdef MISE_AU_POINT	 	 
    	if  ((tabNoeud.Taille() != nbnoeud) || (ddlElem.NbNoeud() != nbnoeud))
			{ cout << "\nErreur : mauvais dimensionnement de tableau"
			       << " \n nbnoeud = " << nbnoeud << ", dim tableau de noeud " 
			       << tabNoeud.Taille()
			       << " , dim ddlelem " << ddlElem.NbNoeud() ;
			  cout << "\nElFrontiere::ElFrontiere (int nbnoeud,Tableau <Noeud *>&"
			       << " tab,DdlElement& TddlElem,int nbnoeud ) " << endl;
			  Sortie(1);
			};
    #endif
    // définition de la boite d'encombrement
    AjourBoiteEncombrement();
  };
  // de copie
ElFrontiere::ElFrontiere( const ElFrontiere& a) :
 tabNoeud(a.tabNoeud),ddlElem(a.ddlElem),tabfront(a.tabfront)
 ,encomb_min(a.encomb_min),encomb_max(a.encomb_max)
   {  };  
    // DESTRUCTEUR :
ElFrontiere::~ElFrontiere ()
  {};

// surcharge de l'affectation
ElFrontiere& ElFrontiere::operator = ( const ElFrontiere& a)
 { this->tabNoeud = a.tabNoeud;
   this->ddlElem = a.ddlElem;
   this->tabfront = a.tabfront;
   encomb_min=a.encomb_min;encomb_max=a.encomb_max;
   return *this;
  };
  
  
// surcharge des tests
// ne concerne que le tableau de noeud de l'élément frontière !! 
bool ElFrontiere::operator == ( const ElFrontiere& a) const 
  { // on ne teste pas l'encombrement car il dépend des points donc a priori en découle
    // test ok si même maillage et même type d'élément
    if ( (this->TypeFrontiere() == a.TypeFrontiere())
        && (tabNoeud(1)->Num_Mail() == a.tabNoeud(1)->Num_Mail()))
      {int ta = this->tabNoeud.Taille(); // nb de noeud
       switch (ta)
       	{ case 1 : // cas d'une frontière point, on simplifie
       		  if (this->tabNoeud(1) == a.tabNoeud(1)) {return true;} else {return false;}
       	    break;
       	  case 2 : // cas où la frontière est à 2 noeuds uniquement
       	   {if (this->tabNoeud(1) == a.tabNoeud(1))
       	      {if (this->tabNoeud(2) == a.tabNoeud(2)) {return true;} else {return false;}}
       	    else if (this->tabNoeud(1) == a.tabNoeud(2))  
       	      {if (this->tabNoeud(2) == a.tabNoeud(1)) {return true;} else {return false;}}
       	    else {return false;}; 
       	    break;
       	  	}
       	  default : // les autres cas	
           {// récup de l'élément géométrique
            const ElemGeomC0 & elemgeom = this->ElementGeometrique();
            const Tableau <int> & ind = elemgeom.Ind(); // récup du tableau des tranches
            int nb_tranche = ind.Taille(); // le nombre d'intervalle de numéros de noeuds constituant la numérotation
                     // vérification que la taille de toutes les tranches est au moins supérieure à 1
            #ifdef MISE_AU_POINT
             if (nb_tranche == 0)
              {cout << "\n *** erreur, pas de tranche de nb de noeud definie "
                    << "\n ElFrontiere::MemeNoeud(... " << endl;
               Sortie(1);
              };
             for (int i1=1;i1<=nb_tranche;i1++)
               if (ind(i1) < 1)
                 {cout << "\n *** erreur, une tranche de nb de noeud est nulle "
                       << " tableau ind: " << ind
                       << "\n ElFrontiere::MemeNoeud(... " << endl;
                  Sortie(1);
                 };
            #endif
            // on balaie chaque intervalle
            int deb_intervalle = 0; int fin_intervalle = 0; // init
            for (int iter =1;iter<= nb_tranche;iter++)
             { int tranche = ind(iter);
               deb_intervalle = fin_intervalle+1; // mise à jour des bornes de l'intervalle de scrutation
               fin_intervalle += tranche;   //      "           "
               // on commence par chercher un premier noeud identique dans l'intervalle
               bool res = false;
               int nd; // indice du cote this
               Noeud * ptNoeud_a = a.tabNoeud(deb_intervalle);
               for (nd=deb_intervalle; nd<= fin_intervalle; nd++)
                {if  (this->tabNoeud(nd) == ptNoeud_a)
                  { res = true; break; };
                };
               if (!res)          // on arrête de suite si
                 {return false;}; // on n'a pas trouvé de premier noeud !!
               // s'il n'y a qu'un seule noeud dans la tranche, on a finit cette tranche sinon on continue
               if (tranche > 1)
                { // on regarde dans quel sens il faut tourner
                  int ndplus = nd + 1; if (ndplus > fin_intervalle) ndplus -= tranche;
                  int ndmoins = nd - 1; if (ndmoins < deb_intervalle) ndmoins += tranche;
                  if  (this->tabNoeud(ndplus) == a.tabNoeud(deb_intervalle+1))
                    {// on est dans le bon sens en augmentant, et c'est ok pour le 2ième noeud,
                     // continue que s'il y a plus de 2 noeuds dans la tranche
                     if (tranche > 2)
                      { for (int i=1;i<= (tranche-2);i++)
                          { ndplus++; if (ndplus > fin_intervalle) ndplus -= tranche;
                            if (this->tabNoeud(ndplus) != a.tabNoeud(deb_intervalle+i+1))
                              return false;
                          };
                      };
                     // sinon ok, on vient de balayer tous les noeuds, on continue
                    }
                  // le sens 1 ne marche pas , on regarde l'autre sens
                  else if  (this->tabNoeud(ndmoins) == a.tabNoeud(deb_intervalle+1))
                    {// le bon sens est finalement en diminuant
                     // continue que s'il y a plus de 2 noeuds dans la tranche
                     if (tranche > 2)
                      { for (int i=1;i<= (tranche-2);i++)
                         { ndmoins--; if (ndmoins < deb_intervalle) ndmoins += tranche;
                           if (this->tabNoeud(ndmoins) != a.tabNoeud(deb_intervalle+i+1))
                             return false;
                         };
                      };
                     // sinon ok, on vient de balayer tous les noeuds, on continue
                    }
                  else  // sinon ne marche pas dans les deux sens
                    { return false;};
                };
             }; // fin de la boucle sur les tranches
				  
           }; // fin du cas courant (default du switch)
       	}; // fin du switch sur le nombre de noeuds
      }
    else // le type est different et ou le numéro de maillage est différent                     
       return false; 
    // si on arrive ici, cela veut dire que toutes les égalités sont bonnes pour un cas autre que
    // point ou frontière à 2 noeuds
    return true;
  };  
  
// retourne un element frontiere ayant une orientation opposee
ElFrontiere * ElFrontiere::Oppose() const 
  { int tail = tabNoeud.Taille();  // dim du tableau de noeud
    Tableau <Noeud *> tab(tail);
    // récup de l'élément géométrique attaché à l'élément frontière
    const ElemGeomC0  & elemgeom = this->ElementGeometrique();
    // récup du tableau de connection de l'inverse de l'élément
    const Tableau<int>  t_inv = elemgeom.InvConnec();
    // on definit le tableau tab
    for (int i=1;i<= tail;i++)
      tab(i) = tabNoeud(t_inv(i));
    // construction des ddl de l'élément  
    Tableau <int> tt(tail);
    for (int j=1;j<= tail;j++)
      tt(j) = ddlElem.NbDdl(t_inv(j));
    DdlElement dElem(tail,tt);
    for (int k=1;k<= tail;k++)
       dElem.Change_un_ddlNoeudElement(k,ddlElem(t_inv(k)));
    // création de l'élément frontière inverse   
    ElFrontiere * pt = this->NevezElemFront(tab,dElem);  
    return pt; 
   };
    
// calcul éventuel de la normale à un noeud
// ce calcul existe pour les éléments 2D, 1D axi, et aussi pour les éléments 1D
// qui possède un repère d'orientation
// en retour coor = la normale si coor.Dimension() est = à la dimension de l'espace
// si le calcul n'existe pas -->  coor.Dimension() = 0
// ramène un entier :
//    == 1 : calcul normal
//    == 0 : problème de calcul -> coor.Dimension() = 0
//    == 2 : indique que le calcul n'est pas licite pour le noeud passé en paramètre
//           mais il n'y a pas d'erreur, c'est seulement que l'élément n'est pas ad hoc pour
//           calculer la normale à ce noeud là
// temps: indique  à quel moment on veut le calcul
int ElFrontiere::CalculNormale_noeud(Enum_dure temps,const Noeud& noe,Coordonnee& coor)
  {
     int retour = 1; // init du retour : on part d'un bon a priori
     Enum_type_geom enutygeom =  Type_geom_front();
     int dima = ParaGlob::Dimension();
     // on exclue les cas à pb
     if (  ((dima == 3) && (!ParaGlob::AxiSymetrie()) && ((enutygeom == LIGNE)||(enutygeom == POINT_G)))
         || ((dima == 3) && (ParaGlob::AxiSymetrie()) && (enutygeom == POINT_G))
         || ((dima == 2) && (enutygeom == POINT_G))
        )
       // on n'a pas d'information particulière pour calculer la normale
       // donc on ne peut pas calculer
       { retour = 2;
       }
     else // sinon le calcul est possible
       { // on commence par repérer le noeud dans la numérotation locale
         int nuoe=0;
         int borne_nb_noeud=tabNoeud.Taille()+1;
         for (int i=1;i< borne_nb_noeud;i++)
           {Noeud& noeloc = *tabNoeud(i);
            if (   (noe.Num_noeud() == noeloc.Num_noeud())
                && (noe.Num_Mail() == noeloc.Num_Mail())
               )
              {nuoe = i; break;
              };
            };
          // on ne continue que si on a trouvé le noeud
          if (nuoe != 0)
           { ElemGeomC0& elemgeom = ElementGeometrique(); // récup de la géométrie
             // récup des coordonnées locales du noeuds
             const Coordonnee& theta_noeud = elemgeom.PtelemRef()(nuoe);
             // récup de la métrique associée à l'élément
             Met_abstraite * met = this->Metrique();

             // récup des phi et dphi au noeud
            const Vecteur & phi = elemgeom.Phi_point(theta_noeud);
             const Mat_pleine& dphi = elemgeom.Dphi_point(theta_noeud);
             switch (temps)
              {case TEMPS_0 :
                 {const BaseB& baseB = met->BaseNat_0(tabNoeud,dphi,phi);
                  coor = Util::ProdVec_coor( baseB.Coordo(1), baseB.Coordo(2));
                  coor.Normer();
                  break;
                 }
               case TEMPS_t :
                {const BaseB& baseB = met->BaseNat_t(tabNoeud,dphi,phi);
                 coor = Util::ProdVec_coor( baseB.Coordo(1), baseB.Coordo(2));
                 coor.Normer();
                 break;
                }
               case TEMPS_tdt :
                {const BaseB& baseB = met->BaseNat_tdt(tabNoeud,dphi,phi);
                 coor = Util::ProdVec_coor( baseB.Coordo(1), baseB.Coordo(2));
                 coor.Normer();
                 break;
                }
               default :
                cout << "\nErreur : valeur incorrecte du  temps demande = "
                     << Nom_dure(temps) << " !\n";
                cout << "\n ElFrontiere::CalculNormale_noeud(Enum_dure temps... \n";
                retour = 0;
                Sortie(1);
              };
           }
          else
           {cout << "\n *** erreur le noeud demande num= "<<noe.Num_noeud()
                 << " du maillage "<< noe.Num_Mail()
                 << " ne fait pas parti de l'element de frontiere FrontSegLine "
                 << "contenant les noeuds: \n";
            for (int i=1;i< borne_nb_noeud;i++)
              {Noeud& noeloc = *tabNoeud(i);
               cout << " num "<< noe.Num_noeud() << " mail " << noeloc.Num_Mail() << ", ";
              };
            cout << " on ne peut pas calculer la normale au noeud !!"
                 << "\n ElFrontiere::CalculNormale_noeud(...";
            retour = 0;
            Sortie(1);
           };
        };
     // retour
     return retour;
  };

// effacement de la place memoire des frontieres de l'element frontiere
void ElFrontiere::EffaceFrontiere()
 { for (int i=1; i<= tabfront.Taille(); i++)
     delete (tabfront(i));
   tabfront.Libere();  
  };
    
// cas d'un élément frontière surface:
// ramène une surface approximative de l'élément (toujours > 0) : calculée à l'aide
// d'une triangulation, puis des produits vectoriels
// ramène une valeur nulle, s'il n'y a pas de surface
double ElFrontiere::SurfaceApprox()
  { // si on est en dimension 1, les frontières ne peuvent pas être des surfaces
    if (ParaGlob::Dimension() == 1)
       return 0;
    // on commence par récupérer la triangulation
    ElemGeomC0 const & elemGeom = this->ElementGeometrique();
    // si c'est un élément de dimension 1, il n'y a également pas de notion de surface
    if (elemGeom.Dimension() == 1)
       return 0;
    // maintenant il ne reste que les frontières de dimension 2 dans un espace 2 ou 3
    const Tableau<Tableau<Tableau<int> > >& tab_tri_lin = elemGeom.Trian_lin();
    const Tableau<Tableau<int> >& tab_tri_lin_1 = tab_tri_lin(1); // car l'élément a une face
    int nb_tri = tab_tri_lin.Taille();
    double surface=0.; // init
    for (int i=1; i<= nb_tri; i++)
      { // calcul des deux vecteurs représentants les cotés du triangle
        Coordonnee AB = tabNoeud(tab_tri_lin_1(i)(2))->Coord2() - tabNoeud(tab_tri_lin_1(i)(1))->Coord2();
        Coordonnee AC = tabNoeud(tab_tri_lin_1(i)(3))->Coord2() - tabNoeud(tab_tri_lin_1(i)(1))->Coord2();
        // surface de la facette
        surface += 0.5 * Dabs(Util::Determinant(AB,AC));
    	};
     return surface;    		
    };
    
    
// ramène la distance maxi entre deux noeuds de l'élément à tdt
double ElFrontiere::MaxDiagonale_tdt()
  { double maxdiagonale = 0;
    int nbnoeud=tabNoeud.Taille();
    for (int i=1;i<=nbnoeud;i++)
      for (int j=i+1; j<= nbnoeud; j++)
        { double dist= (tabNoeud(i)->Coord2() - tabNoeud(j)->Coord2()).Norme();
          maxdiagonale = MaX(maxdiagonale,dist);
        };
	   return maxdiagonale;
	 };

// met à jour la boite d'encombrement 
void ElFrontiere::AjourBoiteEncombrement()
  { int tab_taille= tabNoeud.Taille();
    for (int i=1;i<=tab_taille;i++)
       { if (tabNoeud(i)->ExisteCoord1())
          // on travaille sur la taille actuelle
          { Coordonnee co=tabNoeud(i)->Coord1();
            encomb_min.Modif_en_min(co);encomb_max.Modif_en_max(co);
           }
         else
          // on travaille avec la taille initiale
          { Coordonnee co=tabNoeud(i)->Coord0();
            encomb_min.Modif_en_min(co);
            encomb_max.Modif_en_max(co);
           }
        }                
    // maintenant on tiend compte d'un facteur majorant pour incertitude
    Coordonnee delta=(encomb_max- encomb_min) 
                        * (ParaGlob::param->ParaAlgoControleActifs().Extra_boite_prelocalisation()-1.);
    // dans le cas de surface plane on peut avoir une boite avec une épaisseur nulle,
    // comme il s'agit d'une boite d'encombrement assez grossière on ajoute une valeur par défaut
    // par défaut on prend une valeur par défaut du max de delta
    double miniajout = (ParaGlob::param->ParaAlgoControleActifs().Rapport_Extra_boite_mini_prelocalisation())
                       * delta.Max_val_abs();
    delta.Ajout_meme_valeur(miniajout);
    // mise à jour de l'encombrement
    encomb_min -= delta; encomb_max += delta;
   };

// calcul de la projection normale d'un point sur la frontière
// ramène true si la projection est effective, si elle est hors de l'élément
// ramène false
// P : retour du point projeté (s'il existe)
bool ElFrontiere::Projection_normale(const Coordonnee& A, Coordonnee& P)
 { int dim = A.Dimension(); // dimension des points
   bool retour = false; // init par défaut
   if (this->TypeFrontiere() == "FrontPointF")
     {// cas où la frontière est un point
      // par défaut la projection est le point frontière
      // il n'y a qu'un seul noeud d'ou le pointeur du noeud frontière
      Noeud * noe = TabNoeud()(1);
      if (tabNoeud(1)->ExisteCoord2())
        {P = noe->Coord2();}
      else if (tabNoeud(1)->ExisteCoord1())
        {P = noe->Coord1();}
      else
        {P = noe->Coord2();};
      retour = true;
     }
   else
    {// cas d'un plan ou d'une droite
     Plan pl(dim); Droite dr(dim); int indic; // def des variables de tangence
     // constitution d'un plan tangent ou de la droite tangente au point de reference
     this->TangentRef(dr,pl,indic);
     // cas général : algorithme de recherche de projection
     //  - initialisation
     Coordonnee M(dim); // le point cherché d'intersection avec la surface
     Coordonnee M1; // le point courant de la surface
     int compteur = 0; double nm = 0.; // grandeurs de contrôle
     double max_compteur = ParaGlob::param->ParaAlgoControleActifs().Nb_boucle_newton_position_sur_frontiere();
     // distance maxi entre le point d'inter du plan et le point correspondant de la surface
     double dismini = ParaGlob::param->ParaAlgoControleActifs().Precision_pt_sur_front();
     //---------------------------------------
     // recherche du point d'intersection M
     //---------------------------------------
     do
      {// calcul de l'intersection
       if (indic == 1)
       // cas d'une droite
         { // calcul du projeté d'un point sur la doite
           M =  dr.Projete(A);
         }
       else
       // cas d'un plan
         { // calcul du projeté d'un point sur le plan
           M = pl.Projete(A);
         };
        // calcul du point M1 de la surface correspondant  a M
        // et en même temps calcul de dr ou pl au point M1
        this->Tangent(M,M1,dr,pl,indic);
        nm = (M1 - M).Norme();
       
        // test d'arret si l'on est en dehors de la boite d'encombrement
  //   cout << "\n"; M1.Affiche();elfront->Encom_mini_FR().Affiche();elfront->Encom_maxi_FR().Affiche();cout << endl;
//        if (!(In_boite_emcombrement(M1)))
//           break;
        compteur++;
      }
     while ((compteur <= max_compteur) && (nm >= dismini)) ;
     // ici on recherche avec une précision donnée
     double extra_in_surface = ParaGlob::param->ParaAlgoControleActifs().PointInternePrecThetaiInterne();
     // si le point appartient à l'élément c'est ok sinon non
     if (InSurf(extra_in_surface))
      {// retour du point d'intersection
       P = M1;retour = true;
      }
     else
      {retour = false;};
   };
 // retour
 return retour;
};

//----- lecture écriture base info -----
// pour l'instant on ne sauvegarde rien car ce n'est pas une bonne idée, et cela ne servira a rien

// lecture base info
void ElFrontiere::Lecture_base_info_ElFrontiere(ifstream& ent)
  { /*// lecture du type et vérification
    string nom_type;
    ent >> nom_type;
    if (nom_type != "ElFrontiere")
      Sortie(1);
    // le tableau de noeud associé à l'élément frontière
    int taille ;
    ent >> nom_type >> taille;
    tabNoeud.Change_taille(taille);
    int num_noeud,num_mail;
    for (int i=1;i<=taille;i++)
      { // l' indicateur pour le numéro de maillage et le numéro de noeud
        ent >> num_mail >> num_noeud;
        tabNoeud(i)->Change_num_Mail(num_mail); 
        tabNoeud(i)->Change_num_noeud(num_noeud); 
       }
    // les ddléléments associés   
    ent >>  ddlElem;
    // le tableau d'éléments frontières
    ent >> nom_type >> taille;
    tabfront.Change_taille(taille);
    for (int i=1;i<=taille;i++)
      tabfront(i)->Lecture_base_info_ElFrontiere(ent); 
    // la boite d'encombrement
    ent >> nom_type >> encomb_min >>  encomb_max ;
	 */        
   };

//   écriture base info   
void ElFrontiere::Ecriture_base_info_ElFrontiere(ofstream& sort )
  { /*// un identificateur de type
    sort << "ElFrontiere \n";
    // le tableau de noeud associé à l'élément frontière
    int taille = tabNoeud.Taille();
    sort << "taille_noeud= " << taille << " \n";
    for (int i=1;i<=taille;i++)
      { // un indicateur pour le numéro de maillage et le numéro de noeud
        sort << tabNoeud(i)->Num_Mail() << " " << tabNoeud(i)->Num_noeud() << " "; 
       }
    // les ddléléments associés   
    sort <<  ddlElem;
    // le tableau d'éléments frontières
    taille = tabfront.Taille();
    sort << "taille_tabfront= " << taille << " \n";
    for (int i=1;i<=taille;i++)
      tabfront(i)->Ecriture_base_info_ElFrontiere(sort);
    // la boite d'encombrement
    sort << "encombrement " << encomb_min << " " << encomb_max << " \n";
	 */
             
   };
//-------------------- méthode interne --------
	// test si le point passé en argument appartient à la boite d'encombrement de la frontière
// tous les points sont supposées avoir la même dimension
bool ElFrontiere::In_boite_emcombrement(const Coordonnee& M) const
{ // on va essayer de faire efficace, pour cela on test le minimum
  #ifdef MISE_AU_POINT
  if ((M.Dimension() != ParaGlob::Dimension())|| (M.Dimension() != encomb_min.Dimension()))
   { cout << "\n *** pb de dimensions non coherente !! "
          << "\n ElFrontiere::In_boite_emcombrement(...";
     Sortie(1);
   };
  #endif
  switch (ParaGlob::Dimension())
   { case 3: if ((M(3)<encomb_min(3)) || (M(3) > encomb_max(3))) return false;
     case 2: if ((M(2)<encomb_min(2)) || (M(2) > encomb_max(2))) return false;
     case 1: if ((M(1)<encomb_min(1)) || (M(1) > encomb_max(1))) return false;
   };
  // si on arrive ici c'est que le point est interne
  return true;
};


