// -*- c++ -*- (enables emacs c++ mode)
//===========================================================================
//
// Copyright (C) 1999-2009 Yves Renard
//
// 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 <queue>
#include "getfem/dal_singleton.h"
#include "getfem/getfem_mesh_fem.h"

namespace getfem {

  void mesh_fem::update_from_context(void) const {
    for (dal::bv_visitor i(fe_convex); !i.finished(); ++i) {
      if (linked_mesh_->convex_index().is_in(i)) {
	if (v_num_update < linked_mesh_->convex_version_number(i)) {
	  if (auto_add_elt_pf != 0)
	    const_cast<mesh_fem *>(this)
	      ->set_finite_element(i, auto_add_elt_pf);
	  else
	  if (auto_add_elt_K != dim_type(-1))
	    const_cast<mesh_fem *>(this)
	      ->set_classical_finite_element(i, auto_add_elt_K);
	  else
	    const_cast<mesh_fem *>(this)
	      ->set_finite_element(i, 0);
	}
      }
      else const_cast<mesh_fem *>(this)->set_finite_element(i, 0);
    }
    for (dal::bv_visitor i(linked_mesh_->convex_index());
	 !i.finished(); ++i) {
      if (!fe_convex.is_in(i)
	  && v_num_update < linked_mesh_->convex_version_number(i)) {
	if (auto_add_elt_pf != 0)
	  const_cast<mesh_fem *>(this)
	    ->set_finite_element(i, auto_add_elt_pf);
	else if (auto_add_elt_K != dim_type(-1))
	  const_cast<mesh_fem *>(this)
	    ->set_classical_finite_element(i, auto_add_elt_K);
      }
    }
    if (!dof_enumeration_made) enumerate_dof();
    v_num = v_num_update = act_counter();
  }
  
  dal::bit_vector mesh_fem::basic_dof_on_region(const mesh_region &b) const {
    // cout << "dof on region for " << b << endl;
    context_check(); if (!dof_enumeration_made) this->enumerate_dof();
    dal::bit_vector res;
    for (getfem::mr_visitor v(b,linked_mesh()); !v.finished(); ++v) {
      size_type cv = v.cv();
      if (convex_index().is_in(cv)) {
	if (v.is_face()) {
	  short_type f = short_type(v.f());
	  size_type nbb =
	    dof_structure.structure_of_convex(cv)->nb_points_of_face(f);
	  for (size_type i = 0; i < nbb; ++i) {
	    size_type n = Qdim/fem_of_element(cv)->target_dim();
	    for (size_type ll = 0; ll < n; ++ll)
	      res.add(dof_structure.ind_points_of_face_of_convex(cv,f)[i]+ll);
	  }
	} else {
	  size_type nbb =
	    dof_structure.structure_of_convex(cv)->nb_points();
	  for (size_type i = 0; i < nbb; ++i) {
	    size_type n = Qdim/fem_of_element(cv)->target_dim();
	    for (size_type ll = 0; ll < n; ++ll)
	      res.add(dof_structure.ind_points_of_convex(cv)[i]+ll);
	  }
	}
      }
    }
    return res;
  }

  template <typename V>
  static void add_e_line__(const V &v, dal::bit_vector &r) {
    typedef typename gmm::linalg_traits<V>::value_type T;
    typename gmm::linalg_traits<V>::const_iterator it = gmm::vect_begin(v);
    typename gmm::linalg_traits<V>::const_iterator ite = gmm::vect_end(v);
    for (; it != ite; ++it) if (*it != T(0)) r.add(it.index());
  }

  dal::bit_vector mesh_fem::dof_on_region(const mesh_region &b) const {
    dal::bit_vector res = basic_dof_on_region(b);
    if (is_reduced()) {
      dal::bit_vector basic = res;
      res.clear();
      for (dal::bv_visitor i(basic); !i.finished(); ++i)
	add_e_line__(gmm::mat_row(E_, i), res);
    }
    return res;
  }

   
  void mesh_fem::set_finite_element(size_type cv, pfem pf) {
    GMM_ASSERT1(linked_mesh_ != 0, "Uninitialized mesh_fem");
    context_check();
    if (pf == 0) {
      if (fe_convex.is_in(cv)) {
	fe_convex.sup(cv);
	dof_enumeration_made = false;
	touch(); v_num = act_counter();
      }
    }
    else {
      GMM_ASSERT1(linked_mesh_->structure_of_convex(cv)->basic_structure() 
		  == pf->basic_structure(cv),
		  "Incompatibility between fem " << name_of_fem(pf) << 
		  " and mesh element " <<
		  name_of_geometric_trans(linked_mesh_->trans_of_convex(cv)));
      GMM_ASSERT1((Qdim % pf->target_dim()) == 0 || pf->target_dim() == 1,
		  "Incompatibility between Qdim=" << int(Qdim) <<
		  " and target_dim " << int(pf->target_dim()) << " of " <<
		  name_of_fem(pf));
      if (!fe_convex.is_in(cv) || f_elems[cv] != pf) {
	fe_convex.add(cv);
	f_elems[cv] = pf;
	dof_enumeration_made = false;  
	touch(); v_num = act_counter();
      }
    }
  }

  void mesh_fem::set_finite_element(const dal::bit_vector &cvs, pfem ppf) { 
    for (dal::bv_visitor cv(cvs); !cv.finished(); ++cv)
      set_finite_element(cv, ppf);
  }

  void mesh_fem::set_finite_element(pfem ppf)
  { set_finite_element(linked_mesh().convex_index(), ppf); set_auto_add(ppf); }
  
  void mesh_fem::set_classical_finite_element(size_type cv,
					      dim_type fem_degree) {
    pfem pf = getfem::classical_fem(linked_mesh().trans_of_convex(cv), 
				    fem_degree);
    set_finite_element(cv, pf);
  }

  void mesh_fem::set_classical_finite_element(const dal::bit_vector &cvs, 
					      dim_type fem_degree) {
    for (dal::bv_visitor cv(cvs); !cv.finished(); ++cv) {
      pfem pf = getfem::classical_fem(linked_mesh().trans_of_convex(cv), 
				      fem_degree);
      set_finite_element(cv, pf);
    }
  }

  void mesh_fem::set_classical_finite_element(dim_type fem_degree) {
    set_classical_finite_element(linked_mesh().convex_index(), fem_degree);
    set_auto_add(fem_degree);
  }

  void mesh_fem::set_classical_discontinuous_finite_element
  (const dal::bit_vector &cvs, dim_type fem_degree, scalar_type alpha) {
    for (dal::bv_visitor cv(cvs); !cv.finished(); ++cv) {
      pfem pf = getfem::classical_discontinuous_fem
	(linked_mesh().trans_of_convex(cv), fem_degree, alpha);
      set_finite_element(cv, pf);
    }
  }

  void mesh_fem::set_classical_discontinuous_finite_element
  (dim_type fem_degree, scalar_type alpha) { 
    set_classical_discontinuous_finite_element(linked_mesh().convex_index(),
					       fem_degree,alpha);
  }

  base_node mesh_fem::point_of_basic_dof(size_type cv, size_type i) const {
    context_check(); if (!dof_enumeration_made) enumerate_dof();
    pfem pf = f_elems[cv];
    return linked_mesh().trans_of_convex(cv)->transform
      (pf->node_of_dof(cv, i * pf->target_dim() / Qdim),
       linked_mesh().points_of_convex(cv));
  }

  base_node mesh_fem::point_of_basic_dof(size_type d) const {
    context_check(); if (!dof_enumeration_made) enumerate_dof();
    for (size_type i = d; i != d - Qdim && i != size_type(-1); --i) {
      size_type cv = dof_structure.first_convex_of_point(i);
      if (cv != size_type(-1)) {
	pfem pf = f_elems[cv];
	return linked_mesh().trans_of_convex(cv)->transform
	  (pf->node_of_dof(cv, dof_structure.ind_in_convex_of_point(cv, i)),
	   linked_mesh().points_of_convex(cv));
      }
    }
    GMM_ASSERT1(false, "Inexistent dof");
  }

  dim_type mesh_fem::basic_dof_qdim(size_type d) const {
    context_check(); if (!dof_enumeration_made) enumerate_dof();
    size_type cv = first_convex_of_basic_dof(d);
    GMM_ASSERT1(cv != size_type(-1), "Inexistent dof");
    size_type tdim = f_elems[cv]->target_dim();
    return dim_type(dof_structure.ind_in_convex_of_point(cv, d) % (Qdim/tdim));
  }

  size_type mesh_fem::first_convex_of_basic_dof(size_type d) const {
    context_check();
    for (size_type i = d; i != d - Qdim && i != size_type(-1); --i) {
      size_type j = dof_structure.first_convex_of_point(i);
      if (j != size_type(-1)) return j;
    }
    return size_type(-1);
  }

  const mesh::ind_cv_ct &mesh_fem::convex_to_basic_dof(size_type d) const {
    for (size_type i = d; i != d - Qdim && i != size_type(-1); --i) {
      size_type j = dof_structure.first_convex_of_point(i);
      if (j != size_type(-1)) return dof_structure.convex_to_point(i);
    }
    GMM_ASSERT1(false, "Inexistent dof");
  }

  struct fem_dof {
    size_type ind_node;
    pdof_description pnd;
    size_type part;
  };

  struct dof_comp_ { 
    bool operator()(const fem_dof& m, const fem_dof& n) const {
      if (m.ind_node < n.ind_node) return true;
      if (m.ind_node > n.ind_node) return false;
      if (m.part == n.part)
	return dof_description_compare(m.pnd, n.pnd) < 0;
      else if (m.part < n.part) return true;
      else /*if (m.part > n.part)*/ return false;
    }
  };

  typedef std::map<fem_dof, size_type, dof_comp_> dof_sort_type;

  /// Enumeration of dofs
  void mesh_fem::enumerate_dof(void) const {
    GMM_ASSERT1(linked_mesh_ != 0, "Uninitialized mesh_fem");
    context_check();
    if (fe_convex.card() == 0) {
      dof_enumeration_made = true;
      nb_total_dof = 0;
      return;
    }
    const std::vector<size_type> &cmk = linked_mesh().cuthill_mckee_ordering();
    
    bgeot::node_tab dof_nodes(scalar_type(100000));
    dof_sort_type dof_sort;
    dal::bit_vector encountered_global_dof;
    dal::dynamic_array<size_type> ind_global_dof;
    std::vector<size_type> tab;
    base_node P;
    size_type nbdof = 0;

    size_type cv = fe_convex.first_true();
    fem_dof fd;

    dof_structure.clear();
    encountered_global_dof.clear();

    bgeot::pstored_point_tab pspt_old = 0;
    bgeot::pgeometric_trans pgt_old = 0;
    bgeot::pgeotrans_precomp pgp = 0;
    for (size_type icv=0; icv < cmk.size(); ++icv) {
      cv = cmk[icv];
      if (!fe_convex.is_in(cv)) continue;
      pfem pf = fem_of_element(cv);
      bgeot::pgeometric_trans pgt = linked_mesh().trans_of_convex(cv);
      bgeot::pstored_point_tab pspt = pf->node_tab(cv);
      if (pgt != pgt_old || pspt != pspt_old)
	pgp = bgeot::geotrans_precomp(pgt, pspt, pf);
      pgt_old = pgt; pspt_old = pspt;
      
      size_type nbd = pf->nb_dof(cv); 
      pdof_description andof = global_dof(pf->dim());
      tab.resize(nbd);

      for (size_type i = 0; i < nbd; i++) {
	P.resize(linked_mesh().dim()); 
	fd.pnd = pf->dof_types()[i];
	fd.part = get_dof_partition(cv);
	if (fd.pnd == andof) {
	  size_type num = pf->index_of_global_dof(cv, i);
	  if (!(encountered_global_dof[num])) {
	    ind_global_dof[num] = nbdof;
	    // cout << "Global dof " << num << " is numbered " << nbdof <<endl;
	    nbdof += Qdim / pf->target_dim();
	    encountered_global_dof[num] = true;
	  }
	  tab[i] = ind_global_dof[num];
	} else if (!dof_linkable(fd.pnd)) {
	  tab[i] = nbdof;
	  nbdof += Qdim / pf->target_dim();
	} else {
	  pgp->transform(linked_mesh().points_of_convex(cv), i, P);
	  fd.ind_node = dof_nodes.add_node(P);

	  std::pair<dof_sort_type::iterator, bool>
	    pa = dof_sort.insert(std::make_pair(fd, nbdof));
	  if (pa.second) {
	    tab[i] = nbdof;
	    nbdof += Qdim / pf->target_dim();
	  }
	  else { tab[i] = pa.first->second; }
	}
      }
      dof_structure.add_convex_noverif(pf->structure(cv), tab.begin(), cv);
    }
    
    dof_enumeration_made = true;
    nb_total_dof = nbdof;
  }

  void mesh_fem::reduce_to_basic_dof(const dal::bit_vector &kept_dof) {
    gmm::row_matrix<gmm::rsvector<scalar_type> >
      RR(kept_dof.card(), nb_basic_dof());
    size_type j = 0;
    for (dal::bv_visitor i(kept_dof); !i.finished(); ++i, ++j)
      RR(j, i) = scalar_type(1);
    set_reduction_matrices(RR, gmm::transposed(RR));
  }

  void mesh_fem::reduce_to_basic_dof(const std::set<size_type> &kept_dof) {
    gmm::row_matrix<gmm::rsvector<scalar_type> >
      RR(kept_dof.size(), nb_basic_dof());
    size_type j = 0;
    for (std::set<size_type>::iterator it = kept_dof.begin();
	 it != kept_dof.end(); ++it, ++j)
      RR(j, *it) = scalar_type(1);
    set_reduction_matrices(RR, gmm::transposed(RR));
  }

  void mesh_fem::clear(void) {
    fe_convex.clear();
    dof_enumeration_made = false;
    touch(); v_num = act_counter();
    dof_structure.clear();
    use_reduction = false;
    R_ = REDUCTION_MATRIX();
    E_ = EXTENSION_MATRIX();
  }

  void mesh_fem::init_with_mesh(const mesh &me, dim_type Q) {
    GMM_ASSERT1(linked_mesh_ == 0, "Mesh level set already initialized");
    dof_enumeration_made = false;
    auto_add_elt_pf = 0;
    auto_add_elt_K = dim_type(-1);
    Qdim = Q;
    QdimM = 1;
    QdimN = 1;
    linked_mesh_ = &me;
    use_reduction = false;
    this->add_dependency(me);
    v_num = v_num_update = act_counter();
  }

  mesh_fem::mesh_fem(const mesh &me, dim_type Q)
  { linked_mesh_ = 0; init_with_mesh(me, Q); }

  mesh_fem::mesh_fem(void) 
  { linked_mesh_ = 0; dof_enumeration_made = false; }

  mesh_fem::~mesh_fem() { }

  void mesh_fem::read_from_file(std::istream &ist) {
    GMM_ASSERT1(linked_mesh_ != 0, "Uninitialized mesh_fem");
    gmm::stream_standard_locale sl(ist);
    dal::bit_vector npt;
    dal::dynamic_array<double> tmpv;
    std::string tmp, tmp2;
    bool dof_read = false;
    gmm::col_matrix< gmm::wsvector<scalar_type> > RR, EE;
    ist.precision(16);
    clear();
    ist.seekg(0);ist.clear();
    bgeot::read_until(ist, "BEGIN MESH_FEM");

    while (true) {
      ist >> std::ws; bgeot::get_token(ist, tmp);
      if (bgeot::casecmp(tmp, "END")==0) {
	break;
      } else if (bgeot::casecmp(tmp, "CONVEX")==0) {
	bgeot::get_token(ist, tmp);
	size_type ic = atoi(tmp.c_str());
	GMM_ASSERT1(linked_mesh().convex_index().is_in(ic), "Convex " << ic <<
		    " does not exist, are you sure "
		    "that the mesh attached to this object is right one ?");
	
	int rgt = bgeot::get_token(ist, tmp);
	if (rgt != 3) { // for backward compatibility
	  char c; ist.get(c);
	  while (!isspace(c)) { tmp.push_back(c); ist.get(c); }
	}
	getfem::pfem fem = getfem::fem_descriptor(tmp);
	GMM_ASSERT1(fem, "could not create the FEM '" << tmp << "'");
	set_finite_element(ic, fem);
      } else if (bgeot::casecmp(tmp, "BEGIN")==0) {
	bgeot::get_token(ist, tmp);
	if (bgeot::casecmp(tmp, "DOF_PARTITION") == 0) {
	  for (dal::bv_visitor cv(convex_index()); !cv.finished(); ++cv) {
	    size_type d; ist >> d; set_dof_partition(cv, unsigned(d));
	  }
	  ist >> bgeot::skip("END DOF_PARTITION");
	} else if (bgeot::casecmp(tmp, "DOF_ENUMERATION") == 0) {
	  dal::bit_vector doflst;
	  dof_structure.clear(); dof_enumeration_made = false;
	  touch(); v_num = act_counter();
	  while (true) {
	    bgeot::get_token(ist, tmp);
	    if (bgeot::casecmp(tmp, "END")==0) { 
	      break;
	    }
	    bgeot::get_token(ist, tmp2);

	    size_type ic = atoi(tmp.c_str());
	    std::vector<size_type> tab;
	    if (convex_index().is_in(ic) && tmp.size() &&
		isdigit(tmp[0]) && tmp2 == ":") {
	      tab.resize(nb_basic_dof_of_element(ic));
	      for (size_type i=0; i < fem_of_element(ic)->nb_dof(ic);
		   i++) {
		ist >> tab[i];
		for (size_type q=0; q < size_type(get_qdim())
		       / fem_of_element(ic)->target_dim(); ++q)
		  doflst.add(tab[i]+q);
	      }
	      dof_structure.add_convex_noverif
		(fem_of_element(ic)->structure(ic), tab.begin(), ic);
	    } else GMM_ASSERT1(false, "Missing convex or wrong number "
			       << "in dof enumeration: '" 
			       << tmp << "' [pos="
			       << std::streamoff(ist.tellg())<<"]");
	    /*bgeot::get_token(ist, tmp);
	      cerr << " tok: '" << tmp << "'\n";*/
	  } 
	  dof_read = true;
	  this->dof_enumeration_made = true;
	  touch(); v_num = act_counter();
	  this->nb_total_dof = doflst.card();
	  ist >> bgeot::skip("DOF_ENUMERATION");
	} else if  (bgeot::casecmp(tmp, "REDUCTION_MATRIX")==0) {
	  bgeot::get_token(ist, tmp);
	  GMM_ASSERT1(bgeot::casecmp(tmp, "NROWS")==0,
		      "Missing number of rows");
	  size_type nrows; ist >> nrows;
	  bgeot::get_token(ist, tmp);
	  GMM_ASSERT1(bgeot::casecmp(tmp, "NCOLS")==0,
		      "Missing number of columns");
	  size_type ncols; ist >> ncols;
	  bgeot::get_token(ist, tmp);
	  GMM_ASSERT1(bgeot::casecmp(tmp, "NNZ")==0,
		      "Missing number of nonzero elements");
	  size_type nnz; ist >> nnz;
	  gmm::clear(RR); gmm::resize(RR, nrows, ncols);
	  for (size_type i = 0; i < ncols; ++i) {
	    bgeot::get_token(ist, tmp);
	    GMM_ASSERT1(bgeot::casecmp(tmp, "COL")==0,
			"Missing some columns");
	    size_type nnz_col; ist >> nnz_col;
	    for (size_type j=0; j<nnz_col; ++j) {
	      size_type ind; ist >> ind;
	      scalar_type val; ist >> val;
	      RR(ind, i) = val; // can be optimized using a csc matrix
	    }
	  }
	  R_ = REDUCTION_MATRIX(nrows, ncols);
	  gmm::copy(RR, R_);
	  use_reduction = true;
	  ist >> bgeot::skip("END");
	  ist >> bgeot::skip("REDUCTION_MATRIX");
	} else if  (bgeot::casecmp(tmp, "EXTENSION_MATRIX")==0) {
	  bgeot::get_token(ist, tmp);
	  GMM_ASSERT1(bgeot::casecmp(tmp, "NROWS")==0,
		      "Missing number of rows");
	  size_type nrows; ist >> nrows;
	  bgeot::get_token(ist, tmp);
	  GMM_ASSERT1(bgeot::casecmp(tmp, "NCOLS")==0,
		      "Missing number of columns");
	  size_type ncols; ist >> ncols;
	  bgeot::get_token(ist, tmp);
	  GMM_ASSERT1(bgeot::casecmp(tmp, "NNZ")==0,
		      "Missing number of nonzero elements");
	  size_type nnz; ist >> nnz;
	  gmm::clear(EE); gmm::resize(EE, nrows, ncols);
	  for (size_type i = 0; i < nrows; ++i) {
	    bgeot::get_token(ist, tmp);
	    GMM_ASSERT1(bgeot::casecmp(tmp, "ROW")==0,
			"Missing some rows");
	    size_type nnz_row; ist >> nnz_row;
	    for (size_type j=0; j < nnz_row; ++j) {
	      size_type ind; ist >> ind;
	      scalar_type val; ist >> val;
	      EE(i, ind) = val; // can be optimized using a csc matrix
	    }
	  }
	  E_ = EXTENSION_MATRIX(nrows, ncols);
	  gmm::copy(EE, E_);
	  use_reduction = true;
	  ist >> bgeot::skip("END");
	  ist >> bgeot::skip("EXTENSION_MATRIX");
	}
	else if (tmp.size())
	  GMM_ASSERT1(false, "Syntax error in file at token '"
		      << tmp << "' [pos=" << std::streamoff(ist.tellg())
		      << "]");
      } else if (bgeot::casecmp(tmp, "QDIM")==0) {
	GMM_ASSERT1(!dof_read, "Can't change QDIM after dof enumeration");
	bgeot::get_token(ist, tmp);
	int q = atoi(tmp.c_str());
	GMM_ASSERT1(q > 0 && q <= 250, "invalid qdim: " << q);
	set_qdim(dim_type(q));
      } else if (tmp.size()) {
	GMM_ASSERT1(false, "Unexpected token '" << tmp <<
		    "' [pos=" << std::streamoff(ist.tellg()) << "]");
      } else if (ist.eof()) {
	GMM_ASSERT1(false, "Unexpected end of stream "
		    << "(missing BEGIN MESH_FEM/END MESH_FEM ?)");	
      }
    }
  }

  void mesh_fem::read_from_file(const std::string &name) { 
    std::ifstream o(name.c_str());
    GMM_ASSERT1(o, "Mesh_fem file '" << name << "' does not exist");
    read_from_file(o);
  }

  template<typename VECT> static void
  write_col(std::ostream &ost, const VECT &v) {
    typename gmm::linalg_traits<VECT>::const_iterator it = v.begin();
    ost << gmm::nnz(v);
    for (; it != v.end(); ++it)
      ost << " " << it.index() << " " << *it;
    ost << "\n";
  }

  void mesh_fem::write_reduction_matrices_to_file(std::ostream &ost) const {
    if (use_reduction) {
      ost.precision(16);
      ost << " BEGIN REDUCTION_MATRIX " << '\n';
      ost << "  NROWS " <<  gmm::mat_nrows(R_) << '\n'; 
      ost << "  NCOLS " <<  gmm::mat_ncols(R_) << '\n';
      ost << "  NNZ " << gmm::nnz(R_) << '\n';
      for (size_type i = 0; i < gmm::mat_ncols(R_); ++i) {
	ost << "  COL ";
	write_col(ost, gmm::mat_col(R_, i));
      }
      ost << " END REDUCTION_MATRIX " << '\n';
      ost << " BEGIN EXTENSION_MATRIX " << '\n';
      ost << "  NROWS " <<  gmm::mat_nrows(E_) << '\n'; 
      ost << "  NCOLS " <<  gmm::mat_ncols(E_) << '\n';
      ost << "  NNZ " << gmm::nnz(E_) << '\n';
      for (size_type i = 0; i < gmm::mat_nrows(E_); ++i) {
	ost << "  ROW ";
	write_col(ost, gmm::mat_row(E_, i));
      }
      ost << " END EXTENSION_MATRIX " << '\n';
    }
  }

  void mesh_fem::write_basic_to_file(std::ostream &ost) const {
    ost << "QDIM " << size_type(get_qdim()) << '\n';
    for (dal::bv_visitor cv(convex_index()); !cv.finished(); ++cv) {
      ost << " CONVEX " << cv;
      ost << " \'" << name_of_fem(fem_of_element(cv));
      ost << "\'\n";
    }
    if (!dof_partition.empty()) {
      ost << " BEGIN DOF_PARTITION\n";
      unsigned i = 0;
      for (dal::bv_visitor cv(convex_index()); !cv.finished(); ++cv) {
	ost << " " << get_dof_partition(cv); if ((++i % 20) == 0) ost << "\n";
      }
      ost << "\n";
      ost << " END DOF_PARTITION\n";
    }

    ost << " BEGIN DOF_ENUMERATION " << '\n';
    for (dal::bv_visitor cv(convex_index()); !cv.finished(); ++cv) {
      ost << "  " << cv << ": ";
      ind_dof_ct::const_iterator it = ind_basic_dof_of_element(cv).begin();
      while (it != ind_basic_dof_of_element(cv).end()) {
	ost << " " << *it;
	// skip repeated dofs for "pseudo" vector elements
	for (size_type i=0;
	     i < size_type(get_qdim())/fem_of_element(cv)->target_dim();
	     ++i) ++it;
      }
      ost << '\n';
    }
    ost << " END DOF_ENUMERATION " << '\n';
  }

  void mesh_fem::write_to_file(std::ostream &ost) const {
    context_check();
    gmm::stream_standard_locale sl(ost);
    ost << '\n' << "BEGIN MESH_FEM" << '\n' << '\n';
    write_basic_to_file(ost);
    write_reduction_matrices_to_file(ost);
    ost << "END MESH_FEM" << '\n';
  }

  void mesh_fem::write_to_file(const std::string &name, bool with_mesh) const {
    std::ofstream o(name.c_str());
    GMM_ASSERT1(o, "impossible to open file '" << name << "'");
    o << "% GETFEM MESH_FEM FILE " << '\n';
    o << "% GETFEM VERSION " << GETFEM_VERSION << '\n' << '\n' << '\n';
    if (with_mesh) linked_mesh().write_to_file(o);
    write_to_file(o);
  }

  struct mf__key_ : public context_dependencies {
    const mesh *pmesh;
    dim_type order, qdim;
    mf__key_(const mesh &msh, dim_type o, dim_type q)
      : pmesh(&msh), order(o), qdim(q)  { add_dependency(msh); }
    bool operator <(const mf__key_ &a) const {
      if (pmesh < a.pmesh) return true;
      else if (pmesh > a.pmesh) return false;
      else if (order < a.order) return true; 
      else if (order > a.order) return false;
      else if (qdim < a.qdim) return true;
      return false;
    }
    void update_from_context(void) const {}
    mf__key_(const mf__key_ &mfk) {
      pmesh = mfk.pmesh; order = mfk.order; qdim = mfk.qdim;
      add_dependency(*pmesh);
    }
  private :
    mf__key_& operator=(const mf__key_ &mfk);
  };


  class classical_mesh_fem_pool {

    typedef const mesh_fem * pmesh_fem;
    typedef std::map<mf__key_, pmesh_fem> mesh_fem_tab;

    mesh_fem_tab mfs;

  public :

    const mesh_fem &operator()(const mesh &msh, dim_type o, dim_type qdim) {
      for (mesh_fem_tab::iterator itt = mfs.begin(); itt != mfs.end(); ++itt)
	if (!(itt->first.is_context_valid())) 
	  { delete itt->second; mfs.erase(itt); }

      mf__key_ key(msh, o, qdim);
      mesh_fem_tab::iterator it = mfs.find(key);
      assert(it == mfs.end() || it->second->is_context_valid());
      
      if (it == mfs.end()) {
	mesh_fem *pmf = new mesh_fem(msh);
	pmf->set_classical_finite_element(o);
	pmf->set_auto_add(o);
	pmf->set_qdim(qdim);
	return *(mfs[key] = pmf);
      }
      else return *(it->second);
    }
    
  };

  const mesh_fem &classical_mesh_fem(const mesh &msh,
				     dim_type order, dim_type qdim) {
    classical_mesh_fem_pool &pool
      = dal::singleton<classical_mesh_fem_pool>::instance();
    return pool(msh, order, qdim);
  }

  struct dummy_mesh_fem_ {
    mesh m;
    mesh_fem mf;
    dummy_mesh_fem_() : mf(m) {}
  };

  const mesh_fem &dummy_mesh_fem(void)
  { return dal::singleton<dummy_mesh_fem_>::instance().mf; }




}  /* end of namespace getfem.                                             */


