/*===========================================================================
 
 Copyright (C) 2006-2012 Yves Renard, Julien Pommier, Jeremie Lasry.
 
 This file is a part of GETFEM++
 
 Getfem++  is  free software;  you  can  redistribute  it  and/or modify it
 under  the  terms  of the  GNU  Lesser General Public License as published
 by  the  Free Software Foundation;  either version 3 of the License,  or
 (at your option) any later version along with the GCC Runtime Library
 Exception either version 3.1 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 Lesser General Public
 License and GCC Runtime Library Exception for more details.
 You  should  have received a copy of the GNU Lesser General Public License
 along  with  this program;  if not, write to the Free Software Foundation,
 Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
 
===========================================================================*/

#include "crack_bilaplacian.h"
#include "getfem/getfem_regular_meshes.h"
#include "getfem/getfem_assembling.h" /* import assembly methods (and norms comp.) */
#include "getfem/getfem_fourth_order.h"
#include "getfem/getfem_model_solvers.h"
#include "getfem/getfem_superlu.h"

using std::endl; using std::cout; using std::cerr;
using std::ends; using std::cin;

scalar_type moment = - 1. ;
scalar_type bbeta = M_PI / 4. ;
//scalar_type epsilon = 1e-1 ;

base_matrix sol_moment(const base_node &x)
{ base_matrix m(x.size(), x.size()); 
  m(1,1) = moment;
  return m; }
  
base_small_vector sol_ff(const base_node &x)
{ base_small_vector res(x.size());
  if ( x[1] > 0.4999){
     res[1] = moment ; //* (-1./3.) * epsilon * epsilon;
  }
  if ( x[1] < -0.4999){
     res[1] = - moment ; //* (-1./3.) * epsilon * epsilon;
  }  
  res[0] = 0. ;
  return res ;}

scalar_type sol_beta(const base_node &x){
base_matrix m(2,2) ;
m(0,0) = m(1,1) = cos(bbeta) ;
m(0,1) = - sin(bbeta) ;
m(1,0) = sin(bbeta) ;
base_node z(2) ;
gmm::mult(m, x, z) ;
return - moment * cos(bbeta) * sin(bbeta) * z[0] * z[1] / 0.7 ; }

base_matrix moment_beta(const base_node &x)
{ base_matrix m(x.size(), x.size()); 
  m(1,0) = m(0,1) = moment * sin(bbeta) * cos(bbeta) ;
  return m; }

  
/***********************************/

size_type is_lagrange_dof_type(getfem::pdof_description dof){
  size_type displ_dof = 0 ;
  for (dim_type k = 0; k < 4; ++k) {
      if (dof == getfem::lagrange_dof(k)) 
         displ_dof = 1 ;
    }
return displ_dof ;
}

size_type is_dx_dof_type(getfem::pdof_description dof){
  char s[200];
  size_type dx_dof = 0 ;
    for (unsigned r = 0; r < 2; ++r) {
      if (dof == getfem::derivative_dof(2,0)){
         sprintf(s, "D_%c[%d]", "xyzuvw"[r], 2);
         dx_dof = 1 ;
	 }
  }
return dx_dof ;
}

size_type is_dy_dof_type(getfem::pdof_description dof){
  char s[200];
  size_type dy_dof = 0 ;
    for (unsigned r = 0; r < 2; ++r) {
      if (dof == getfem::derivative_dof(2,1)){
         sprintf(s, "D_%c[%d]", "xyzuvw"[r], 2);
         dy_dof = 1 ;
	 cout << "passage dans is_dy_dof_type OK \n" ;
	 }
    }
return dy_dof ;
}

bool bilaplacian_crack_problem::solve_moment(plain_vector &U) {
  size_type nb_dof_rhs = mf_rhs.nb_dof();

  // setting singularities 
  cout << "setting singularities \n" ;
  if (PARAM.int_value("SING_BASE_TYPE") == 0){
	std::vector<getfem::pglobal_function> ufunc(4);
	for (size_type i = 0 ; i < ufunc.size() ; ++i) {
	ufunc[i] = bilaplacian_crack_singular(i, ls, nu, 0.);
	}
  mf_sing_u.set_functions(ufunc);
  }
  if (PARAM.int_value("SING_BASE_TYPE") == 1){
	std::vector<getfem::pglobal_function> ufunc(2);
	for (size_type i = 0 ; i < ufunc.size() ; ++i) {
	ufunc[i] = bilaplacian_crack_singular(i + 4, ls, nu, 0.);
	}
  mf_sing_u.set_functions(ufunc);
  }
  
  
  // Setting the enrichment --------------------------------------------/
   
  switch(enrichment_option) {
  case 0 :  // No enrichment
    mf_u_sum.set_mesh_fems(mfls_u);
    break ;
  case 1 : 
    {
      cout << "\npointwise matching\n";
     /* first : selecting the convexes that are completly included in the enrichment area */ 
     for (dal::bv_visitor i(mesh.convex_index()); !i.finished(); ++i) {
       pm_convexes.add(i) ;
	/* For each element, we test all of its nodes. 
	   If all the nodes are inside the enrichment area,
	   then the element is completly inside the area too */ 
       for (unsigned j=0; j < mesh.nb_points_of_convex(i); ++j) {
	 if (gmm::sqr(mesh.points_of_convex(i)[j][0]) + 
	     gmm::sqr(mesh.points_of_convex(i)[j][1]) > 
	     gmm::sqr(enr_area_radius)) 
	   pm_convexes.sup(i); break;
       }
     }
      
      for (dal::bv_visitor cv(mf_sing_u.convex_index()); !cv.finished(); ++cv) {
	if (!pm_convexes.is_in(cv))
	  mf_sing_u.set_finite_element(cv, 0);
      }
      cout << "mf_sing_u: convex_index() = " << mf_sing_u.convex_index().card() << " convexes\n";

      //mf_u_sum.set_mesh_fems(mfls_u_ext, mf_pre_u); //_ext, mf_sing_u);
      mf_u_sum.set_smart_global_dof_linking(true);
      mf_u_sum.set_mesh_fems(mf_pre_u, mf_sing_u);


      cout << "mf_u_sum.nb_dof = " << mf_u_sum.nb_dof() << "\n";
      cout << "mfls_u.convex_index = " << mfls_u.convex_index() << "\nmf_sing_u: " << mf_sing_u.convex_index() << "\n";
      
    } break ;
  case 2 :  // standard XFEM on a fixed zone
    {
      dal::bit_vector enriched_dofs;
      plain_vector X(mf_partition_of_unity.nb_dof());
      plain_vector Y(mf_partition_of_unity.nb_dof());
      getfem::interpolation(ls.get_mesh_fem(), mf_partition_of_unity,
			    ls.values(1), X);    
      getfem::interpolation(ls.get_mesh_fem(), mf_partition_of_unity,
			    ls.values(0), Y);    
      for (size_type j = 0; j < mf_partition_of_unity.nb_dof(); ++j) {
            if (gmm::sqr(X[j]) + gmm::sqr(Y[j]) <= gmm::sqr(enr_area_radius))
	           enriched_dofs.add(j);
          }
      //cout << "enriched_dofs: " << enriched_dofs << "\n";
      if (enriched_dofs.card() < 3)
            GMM_WARNING0("There is " << enriched_dofs.card() <<
		   " enriched dofs for the crack tip");
      mf_u_product.set_enrichment(enriched_dofs);
      mf_u_sum.set_mesh_fems(mf_u_product, mfls_u);
      cout << "enrichment done \n" ;}
      break ;
  case 3 : // Integral matching (mortar)
    {
    cout << "\nIntegral Matching (Mortar)\n" ;    
    dal::bit_vector cvlist_in_area, cvlist_out_area;
    bool in_area = true;
    for (dal::bv_visitor cv(mesh.convex_index()); 
	   !cv.finished(); ++cv) {
	in_area = true;
	/* For each element, we test all of its nodes. 
	   If all the nodes are inside the enrichment area,
	   then the element is completly inside the area too */ 
	for (unsigned j=0; j < mesh.nb_points_of_convex(cv); ++j) {
	  if (gmm::sqr(mesh.points_of_convex(cv)[j][0] ) + 
	      gmm::sqr(mesh.points_of_convex(cv)[j][1] ) > 
	      gmm::sqr(enr_area_radius)) {
	          in_area = false; 
		  break;
	  }
	}

	/* "remove" the global function on convexes outside the enrichment
	   area */
	if (!in_area) {
	  cvlist_out_area.add(cv);
	  mf_sing_u.set_finite_element(cv, 0);
	  mf_u().set_dof_partition(cv, 1);
	} else cvlist_in_area.add(cv);
      }
       
       /* extract the boundary of the enrichment area, from the
	 "inside" point-of-view, and from the "outside"
	 point-of-view */
      getfem::mesh_region r_border, r_enr_out;
      getfem::outer_faces_of_mesh(mesh, r_border);

      getfem::outer_faces_of_mesh(mesh, cvlist_in_area, 
				  mesh.region(MORTAR_BOUNDARY_IN));
      getfem::outer_faces_of_mesh(mesh, cvlist_out_area, 
				  mesh.region(MORTAR_BOUNDARY_OUT));
      for (getfem::mr_visitor v(r_border); !v.finished(); ++v) {
	mesh.region(MORTAR_BOUNDARY_OUT).sup(v.cv(), v.f());
      }
      if (PARAM.int_value("MORTAR_WITHOUT_SINGUL"))
         mf_u_sum.set_mesh_fems(mfls_u);
      else
         mf_u_sum.set_mesh_fems(mf_sing_u, mfls_u);
      
      //cout << "cvlist_in_area: " << cvlist_in_area << "\n";
      cout << "mfls_u.nb_dof: " << mfls_u.nb_dof() << "\n";
      cout << "mf_u_sum.nb_dof: " << mf_u_sum.nb_dof() << "\n";
      //cout << "MORTAR_BOUNDARY_IN: " << mesh.region(MORTAR_BOUNDARY_IN) << "\n";
      //cout << "MORTAR_BOUNDARY_OUT: " << mesh.region(MORTAR_BOUNDARY_OUT) << "\n";
      
//       // an optional treatment : creating a representation of the enrichment area     
//       getfem::mesh_fem mf_enrich(mesh);
//       getfem::pfem pf_mef = getfem::classical_fem(mesh.trans_of_convex(mesh.convex_index().first_true()), 1 );
//       mf_enrich.set_finite_element(mesh.convex_index(), pf_mef) ;
//       std::vector<scalar_type> UU(mf_enrich.nb_dof()) ;
//       std::fill(UU.begin(), UU.end() ,0.) ;
//       cout << "exporting the enrichment zone: \n" ;
//       for (dal::bv_visitor i(cvlist_in_area) ; !i.finished() ; ++i){ 
// 	  for (unsigned int j = 0 ; j < mf_enrich.ind_dof_of_element(i).size() ; ++j )  
// 	  UU[mf_enrich.ind_dof_of_element(i)[j]] = 1. ;         
//       }
//       
//       cout << "exporting enrichment to " << "enrichment_zone.vtk" << "..\n";
//       getfem::vtk_export exp("enrichment_zone.vtk", false);
//       exp.exporting(mf_enrich); 
//       exp.write_point_data(mf_enrich, UU, "enrichment");
//       cout << "export done, you can view the data file with (for example)\n"
// 	"mayavi -d enrichment_zone.vtk -f "
// 	"WarpScalar -m BandedSurfaceMap -m Outline\n";
	
//       // Another optional treatment :
//       // Searching the elements that are both crossed by the crack
//       // and with one of their faces which constitutes a part of the 
//       // boundary between the enriched zone and the rest of the domain.
//       getfem::mesh_region &boundary = mesh.region(MORTAR_BOUNDARY_IN);
//       unsigned int cpt = 0 ;
//       for (dal::bv_visitor i(cvlist_in_area); !i.finished(); ++i) {
//          if (mls.is_convex_cut(i)){
// 	    // Among the faces of the convex, we search if some are
// 	    // part of the boundary
// 	    cpt = 0 ;
// 	    for (unsigned j=0; j < mesh.structure_of_convex(i) ->nb_faces(); ++j) {
// 	        if (boundary.is_in(i,j))
// 		   cpt += 1;
// 	    }
// 	    if (cpt) {
//                cout << "\n The convex number " << i << " is crossed by the crack :\n" ;
// 	       cout << "  it has : " << cpt << " face(s) among the boundary.\n \n " ;
// 	    }
// 	 }
//       }
 
    }
    break ;
  default : 
	GMM_ASSERT1(false, "Enrichment_option parameter is undefined");
	break ;  
	}
  mesh.write_to_file("toto.mesh");

  cout << "Number of dof for u: " << mf_u().nb_dof() << endl;

  // Bilaplacian brick.
  getfem::mdbrick_bilaplacian<> BIL(mim, mf_u());
  BIL.D().set(D);
  if (KL) { BIL.set_to_KL(); BIL.nu().set(nu); }

  size_type N = mesh.dim();
  plain_vector F(nb_dof_rhs);

  // Defining the prescribed momentum.
  gmm::resize(F, nb_dof_rhs*N*N);
  if (PARAM.int_value("SOL_REF")==1){
     getfem::interpolation_function(mf_rhs, F, sol_moment, MOMENTUM_BOUNDARY_NUM);
  }
  if (PARAM.int_value("SOL_REF")==2){
     getfem::interpolation_function(mf_rhs, F, moment_beta, MOMENTUM_BOUNDARY_NUM);
  }
//   getfem::mdbrick_normal_derivative_source_term<>
//   MOMENTUM(VOL_F, mf_rhs, F, MOMENTUM_BOUNDARY_NUM);
getfem::mdbrick_normal_derivative_source_term<>
  MOMENTUM(BIL, mf_rhs, F, MOMENTUM_BOUNDARY_NUM);

  // Normal derivative Dirichlet condition brick
  gmm::resize(F, nb_dof_rhs*N);
  gmm::clear(F);
  getfem::mdbrick_normal_derivative_Dirichlet<> 
	NDER_DIRICHLET(MOMENTUM, CLAMPED_BOUNDARY_NUM, mf_mult_d);
  NDER_DIRICHLET.set_constraints_type(dirichlet_version);
  NDER_DIRICHLET.rhs().set(mf_rhs, F);

  // Dirichlet condition brick.
  getfem::mdbrick_Dirichlet<>
    DIRICHLET(NDER_DIRICHLET, SIMPLE_SUPPORT_BOUNDARY_NUM, mf_mult);
  DIRICHLET.set_constraints_type(dirichlet_version);
  if (dirichlet_version == getfem::PENALIZED_CONSTRAINTS)
    DIRICHLET.set_penalization_parameter(PARAM.real_value("EPS_DIRICHLET_PENAL")) ;
  gmm::resize(F, nb_dof_rhs);
  gmm::clear(F) ;
  DIRICHLET.rhs().set(mf_rhs, F);

//   // model brick constraint for the integral matching
//   getfem::mdbrick_constraint<> &mortar = 
//       *(new getfem::mdbrick_constraint<>(NDER_DIRICHLET, 0));

  getfem::mdbrick_abstract<> *final_model = &DIRICHLET ;

  if (enrichment_option == 3 ) {
     /* add a constraint brick for the mortar junction between
       the enriched area and the rest of the mesh */

    getfem::mdbrick_constraint<> &mortar = 
      *(new getfem::mdbrick_constraint<>(DIRICHLET, 0));
    mortar.set_constraints_type(getfem::constraints_type(mortar_version));
    if (mortar_version == getfem::PENALIZED_CONSTRAINTS)
      mortar.set_penalization_parameter(PARAM.real_value("EPS_MORTAR_PENAL")) ;

    plain_vector R(1) ;
    sparse_matrix H(1, mf_u().nb_dof());
    (*this).set_matrix_mortar(H) ;

    /* because of the discontinuous partition of mf_u(), some levelset 
       enriched functions do not contribute any more to the
       mass-matrix (the ones which are null on one side of the
       levelset, when split in two by the mortar partition, may create
       a "null" dof whose base function is all zero.. */
    sparse_matrix M2(mf_u().nb_dof(), mf_u().nb_dof());
    getfem::asm_mass_matrix(M2, mim, mf_u(), mf_u());
    //gmm::HarwellBoeing_IO::write("M2.hb", M2);
    cout << "PARAM.real_value(\"SEUIL\") : " << PARAM.real_value("SEUIL") << "\n" ;
    for (size_type d = 0; d < mf_u().nb_dof(); ++d) {
      //       if (M2(d,d) < 1e-7) cout << "  weak mf_u() dof " << d << " @ " << 
      // 	  mf_u().point_of_dof(d) << " M2(d,d) = " << M2(d,d) << "\n";
      if (M2(d,d) < PARAM.real_value("SEUIL")) {
	cout << "removed\n";	
	size_type n = gmm::mat_nrows(H);
	gmm::resize(H, n+1, gmm::mat_ncols(H));
	H(n, d) = 1;
      }
    }  
    gmm::resize(R, gmm::mat_nrows(H)); 
    mortar.set_constraints(H,R);
    final_model = &mortar;
    gmm::Harwell_Boeing_save("H.hb", H);

  }

    cout << "Total number of variables : " << final_model->nb_dof() << endl;
    getfem::standard_model_state MS(*final_model);
    gmm::iteration iter(residual, 1, 40000);
    getfem::standard_solve(MS, *final_model, iter /* , p*/);  
    // getfem::useful_types<getfem::standard_model_state>::plsolver_type p;
    // p.reset(new getfem::linear_solver_cg_preconditioned_ildlt<sparse_matrix,plain_vector>);
    // Solution extraction
    gmm::resize(U, mf_u().nb_dof());
    gmm::copy(BIL.get_solution(MS), U); 

  return true;  
}

void bilaplacian_crack_problem::compute_error_beta(plain_vector &U) {
  GMM_ASSERT1(!mf_rhs.is_reduced(), "To be adapted");
  std::vector<scalar_type> V(mf_rhs.nb_dof());
  getfem::interpolation(mf_u(), mf_rhs, U, V);
  for (size_type i = 0; i < mf_rhs.nb_dof(); ++i)
    V[i] -= sol_beta(mf_rhs.point_of_basic_dof(i));
  cout.precision(8);
//   cout  << "L2 error = " << getfem::asm_L2_norm(mim, mf_rhs, V)  << endl
//         << "H1 error = " << getfem::asm_H1_norm(mim, mf_rhs, V)  << endl
//         << "H2 error = " << getfem::asm_H2_norm(mim, mf_rhs, V)  << endl
//         /*<< "Linfty error = " << gmm::vect_norminf(V)  << endl*/; 
//   cout  << "semi-norme H1 = " << getfem::asm_H1_semi_norm(mim, mf_rhs, V)  << endl 
//         << "semi-norme H2 = " << getfem::asm_H2_semi_norm(mim, mf_rhs, V)  << endl ;
cout << "erreur L2 / erreur H1 / erreur H2 / semi-H1 / semi-H2 :\n" << getfem::asm_L2_norm(mim, mf_rhs, V) << " ";
cout << getfem::asm_H1_norm(mim, mf_rhs, V) << " " << getfem::asm_H2_norm(mim, mf_rhs, V) << " ";
cout << getfem::asm_H1_semi_norm(mim, mf_rhs, V) << " " <<getfem::asm_H2_semi_norm(mim, mf_rhs, V) << endl ;
  if(PARAM.real_value("NORM_EXACT") != 0. ){
    for (size_type i = 0; i < mf_rhs.nb_dof(); ++i)
        V[i] = sol_beta(mf_rhs.point_of_basic_dof(i));
    cout << "display exact solution: \n" ; 
    cout << "L2 norm: " <<  getfem::asm_L2_norm(mim, mf_rhs, V) << endl ;
    cout << "H1 norm: " <<  getfem::asm_H1_norm(mim, mf_rhs, V) << endl ;
    cout << "H2 norm: " <<  getfem::asm_H2_norm(mim, mf_rhs, V) << endl ;
    }
}
