// -*- c++ -*- (enables emacs c++ mode)
//===========================================================================
//
// Copyright (C) 2007-2008 Yves Renard, Julien Pommier.
//
// 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 2.1 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 Lesser General Public
// License 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 "getfem/getfem_modeling.h"

#include "getfem/getfem_modeling.h"
#include "getfem/getfem_import.h"
#include "getfem/getfem_inter_element.h"
#include "getfem/getfem_export.h"
#include "gmm/gmm_superlu_interface.h"

using bgeot::base_small_vector; /* special class for small (dim<16) vectors */
using bgeot::base_node;  /* geometrical nodes(derived from base_small_vector)*/
using bgeot::scalar_type; /* = double */
using bgeot::size_type;   /* = unsigned long */
using bgeot::base_matrix; /* small dense matrices. */

/* definition of some matrix/vector types. These ones are built
 * using the predefined types in Gmm++
 */
typedef getfem::modeling_standard_sparse_vector sparse_vector;
typedef getfem::modeling_standard_sparse_matrix sparse_matrix;
typedef getfem::modeling_standard_plain_vector  plain_vector;


class mass_matrix_jump : public getfem::compute_on_inter_element {

 protected :

  sparse_matrix &M;
  scalar_type coeff_;
  
  virtual void compute_on_gauss_point
    (getfem::fem_interpolation_context ctx1, getfem::pfem pf1,
     getfem::fem_interpolation_context ctx2, getfem::pfem pf2,
     getfem::papprox_integration pai1) {
    
    scalar_type w = pai1->integration_coefficients()[ctx1.ii()];
    base_small_vector up(mf.linked_mesh().dim());
    const base_matrix& B = ctx1.B();
    gmm::mult(B, pgt1->normals()[f1], up);
    scalar_type norm = gmm::vect_norm2(up);
    scalar_type J = ctx1.J() * norm;

    size_type cv1 = ctx1.convex_num();
    size_type cv2 = ctx2.convex_num();
    getfem::base_tensor t1, t2;
    pf1->real_base_value(ctx1, t1);
    pf2->real_base_value(ctx2, t2);

    for (size_type i = 0; i < pf1->nb_dof(cv1); ++i)
      for (size_type j = 0; j < pf1->nb_dof(cv1); ++j)
	M(mf.ind_basic_dof_of_element(cv1)[i],
	  mf.ind_basic_dof_of_element(cv1)[j])
	  += coeff_ * J * w * t1[i] * t1[j];
       
    for (size_type i = 0; i < pf2->nb_dof(cv2); ++i)
      for (size_type j = 0; j < pf2->nb_dof(cv2); ++j)
	M(mf.ind_basic_dof_of_element(cv2)[i],
	  mf.ind_basic_dof_of_element(cv2)[j])
	  += coeff_ * J * w * t2[i] * t2[j];
    
    for (size_type i = 0; i < pf1->nb_dof(cv1); ++i)
      for (size_type j = 0; j < pf2->nb_dof(cv2); ++j) {
	M(mf.ind_basic_dof_of_element(cv1)[i],
	  mf.ind_basic_dof_of_element(cv2)[j])
	  -= coeff_ * J * w * t1[i] * t2[j];
	M(mf.ind_basic_dof_of_element(cv2)[j],
	  mf.ind_basic_dof_of_element(cv1)[i])
	  -= coeff_ * J * w * t1[i] * t2[j];
      }
  }

 public :
  
  mass_matrix_jump(sparse_matrix &MM, const getfem::mesh_im &mmim,
		   const getfem::mesh_fem &mmf, scalar_type coeff = 1)
    : compute_on_inter_element(mmim, mmf), M(MM), coeff_(coeff) {
    GMM_ASSERT1(mf.get_qdim() <= 1,
	       "Vectorial elements not taken into account ... to be done");
  }

};

/*! post_proc

  A short post_proc class for simple post-processing operations.
*/
class post_proc {

protected:
  const getfem::mesh_fem *_mf; //!< Mesh fem structure for postprocessing.

public:
  /*!
    \brief A simple constructor. Just linking the mesh fem structure.
    \param mf the mesh fem structure for post processing.
  */
  post_proc(const getfem::mesh_fem &mf){ _mf = &mf; }; 
    
  /*!
    \brief Return the difference of potential V(x2)-V(x1).
    \param X the vector to represent.
    \param x1 the coordinates of the first point.
    \param x2 the coordinates of the second point.
    \param DDP the value of the difference of potential computed.
  */
  template <class typeX, class Scalar>
  void emf(const typeX &X,
	   const getfem::base_node &x1,
	   const getfem::base_node &x2,
	   Scalar &DDP) {

    getfem::base_node vecdir(x2-x1);
    getfem::slicer_half_space a0(x1, vecdir, 0);
    getfem::slicer_half_space a1(x1, getfem::base_node
				 (vecdir[1], -vecdir[0]), 0);
    getfem::slicer_half_space a2(x2, vecdir, 0);
    // We use here two parallel 1D slicers and one perpendicular to the
    // others.
    getfem::stored_mesh_slice sl, sl2;
    getfem::slicer_build_stored_mesh_slice a3(sl), a4(sl2);
    getfem::mesh_slicer slicer((this->_mf)->linked_mesh()),
      slicer2((this->_mf)->linked_mesh());
    slicer.push_back_action(a2);
    slicer.push_back_action(a1);
    slicer.push_back_action(a3);
    slicer.exec(1);
    
    typeX Xinterp(sl.nb_points(), 0.);
    sl.interpolate(*(this->_mf), X, Xinterp); // Data are interpolated on the
					      // 0D slice.
    slicer2.push_back_action(a0);
    slicer2.push_back_action(a1);
    slicer2.push_back_action(a4);
    slicer2.exec(1);
    
    typeX Xinterp2(sl2.nb_points(), 0.);
    sl2.interpolate(*(this->_mf), X, Xinterp2); // Data are interpolated on
						// the 0D slice.
    DDP = Xinterp[0] - Xinterp2[0];
  }

};

int main(void) {

  getfem::mesh m;
  // Import mesh from gmsh.
  getfem::import_mesh("gmshv2:square.msh", m);
  // 2d problem.
  getfem::maybe_remove_last_dimension(m);

  // Mesh fem variables for the solution and the data.
  getfem::mesh_fem mf(m), mfd(m);
  mf.set_finite_element(m.convex_index(),
			getfem::fem_descriptor("FEM_PK(2,2)"));
  mfd.set_finite_element(m.convex_index(),
			 getfem::fem_descriptor("FEM_PK_DISCONTINUOUS(2,0)"));

  // Dissocation between the upper and the lower region.
  int LOWER_REGION = 1, UPPER_REGION = 3,
    LOWER_BOUNDARY = 10, UPPER_BOUNDARY = 30;
  getfem::mesh_region rg = m.region(UPPER_REGION);
  for (getfem::mr_visitor i(rg); !i.finished(); ++i) {
    mf.set_dof_partition(i.cv(), UPPER_REGION);
  }
  rg = m.region(LOWER_REGION);
  for (getfem::mr_visitor i(rg); !i.finished(); ++i) {
    mf.set_dof_partition(i.cv(), LOWER_REGION);
  }

  // Extraction of the boundary separating the upper and lower region.
  getfem::mesh_region r_border;
  getfem::outer_faces_of_mesh(m, r_border);
  
  getfem::outer_faces_of_mesh(m, m.region(UPPER_REGION).index(), 
			      m.region(UPPER_BOUNDARY));
  getfem::outer_faces_of_mesh(m, m.region(LOWER_REGION).index(), 
			      m.region(LOWER_BOUNDARY));
  for (getfem::mr_visitor v(r_border); !v.finished(); ++v) {
    m.region(UPPER_BOUNDARY).sup(v.cv(), v.f());
  }
  
  // Selection of the integration method.
  getfem::mesh_im mim(m);
  mim.set_integration_method(m.convex_index(), 
			     getfem::int_method_descriptor("IM_TRIANGLE(5)"));

  // Creation of the matrix for the problem assembly.
  sparse_matrix M(mf.nb_dof(), mf.nb_dof());
  // Creation of the RHS and of the solution vectors.
  plain_vector B(mf.nb_dof(), 0.), X(mf.nb_dof(), 0.);
  // Creation of a vector containing the medium coefficients.
  plain_vector A(mfd.nb_dof(), 0.);
  
  // Characteristic constants for the different regions.
  double slow(1.), supper(1.);
  rg = m.region(1);
  GMM_ASSERT1(!mfd.is_reduced(), "To be adapted");
  for (getfem::mr_visitor i(rg); !i.finished(); ++i) {
    A[ mfd.ind_basic_dof_of_element( i.cv() )[0] ] = slow;
  }
  rg = m.region(3);
  for (getfem::mr_visitor i(rg); !i.finished(); ++i) {
    A[ mfd.ind_basic_dof_of_element( i.cv() )[0] ] = supper;
  }

  // Assembly of the stiffness matrix for the laplacian.
  getfem::asm_stiffness_matrix_for_laplacian(M, mim, mf, mfd, A);

  // Assembly of the jump on in the interface.  
  GMM_ASSERT1(!mf.is_reduced(), "To be adapted");
  mass_matrix_jump MMJ(M, mim, mf, 2.);
  getfem::mesh_region interface = m.region(UPPER_BOUNDARY);
  for (getfem::mr_visitor v(interface); !v.finished(); ++v)
    MMJ.compute_on_face(v.cv(), v.f());

  // Set the Dirichlet constraints.
  for (unsigned int j = 0; j < mf.nb_dof(); j++) {
    if ( mf.point_of_basic_dof(j)[1] == .5 ) X[j] = 1.;
  }
  getfem::assembling_Dirichlet_condition(M, B, mf, 1010, X);

  // Solution of the linear system.
  double condest;
  gmm::SuperLU_solve(M, X, B, condest);

  // Post-processing of the solution.
  post_proc sortie(mf);
  // The emf is computed between the points x1 and x2.
  getfem::base_node x1(0., -0.0001), x2(0., .0001);
  scalar_type ddp;
  sortie.emf(X, x1, x2, ddp);
  std::cout << "Jump of the potential V on the interface :" << std::endl;
  std::cout << "V" << x2 << " - " << "V" << x1
       << " = " << ddp << std::endl;
}
