// Copyright (c) 2010-2022, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.

#include "mesquite.hpp"

#ifdef MFEM_USE_MESQUITE

#include "../fem/fem.hpp"
#include <iostream>

namespace mfem
{

using namespace std;

MesquiteMesh::MeshTags::TagData::~TagData()
{
   if (elementData)
   {
      free(elementData);
   }
   if (vertexData)
   {
      free(vertexData);
   }
   if (defaultValue)
   {
      free(defaultValue);
   }
}


void MesquiteMesh::MeshTags::clear()
{
   for (std::vector<TagData*>::iterator iter = tagList.begin();
        iter != tagList.end(); ++iter)
      if (*iter)
      {
         delete *iter;
      }

   tagList.clear();
}

size_t MesquiteMesh::MeshTags::size_from_tag_type( Mesh::TagType type )
{
   switch ( type )
   {
      case Mesh::BYTE:   return 1;
      case Mesh::BOOL:   return sizeof(bool);
      case Mesh::DOUBLE: return sizeof(double);
      case Mesh::INT:    return sizeof(int);
      case Mesh::HANDLE: return sizeof(void*);
      case Mesh::LONG_LONG: return sizeof(long long);
      default: assert(0); return 0;
   }
}

size_t MesquiteMesh::MeshTags::create( const std::string& name,
                                       Mesh::TagType type,
                                       unsigned length,
                                       const void* defval,
                                       MsqError& err )
{
   size_t h = handle( name, err );
   if (h)
   {
      MSQ_SETERR(err)(name, MsqError::TAG_ALREADY_EXISTS);
      return 0;
   }

   if (length == 0 || size_from_tag_type(type) == 0)
   {
      MSQ_SETERR(err)(MsqError::INVALID_ARG);
      return 0;
   }

   TagData* tag = new TagData( name, type, length );
   h = tagList.size();
   tagList.push_back(tag);

   if (defval)
   {
      tag->defaultValue = malloc( tag->desc.size );
      memcpy( tag->defaultValue, defval, tag->desc.size );
   }

   return h+1;
}

size_t MesquiteMesh::MeshTags::create( const MfemTagDescription& desc,
                                       const void* defval,
                                       MsqError& err )
{
   size_t h = handle( desc.name.c_str(), err );
   if (h)
   {
      MSQ_SETERR(err)(desc.name.c_str(), MsqError::TAG_ALREADY_EXISTS);
      return 0;
   }

   err.clear();
   if (desc.size == 0 || (desc.size % size_from_tag_type(desc.type)) != 0)
   {
      MSQ_SETERR(err)(MsqError::INVALID_ARG);
      return 0;
   }

   TagData* tag = new TagData( desc );
   h = tagList.size();
   tagList.push_back(tag);

   if (defval)
   {
      tag->defaultValue = malloc( tag->desc.size );
      memcpy( tag->defaultValue, defval, tag->desc.size );
   }

   return h+1;
}

void MesquiteMesh::MeshTags::destroy( size_t tag_index, MsqError& err )
{
   --tag_index;
   if (tag_index >= tagList.size() || 0 == tagList[tag_index])
   {
      MSQ_SETERR(err)(MsqError::TAG_NOT_FOUND);
      return ;
   }

   delete tagList[tag_index];
   tagList[tag_index] = 0;
}

size_t MesquiteMesh::MeshTags::handle( const std::string& name,
                                       MsqError& err ) const
{
   for (size_t i = 0; i < tagList.size(); ++i)
      if (tagList[i] && tagList[i]->desc.name == name)
      {
         return i+1;
      }

   return 0;
}

const MesquiteMesh::MfemTagDescription& MesquiteMesh::MeshTags::properties(
   size_t tag_index, MsqError& err ) const
{
   static MfemTagDescription dummy_desc;
   --tag_index;

   if (tag_index >= tagList.size() || !tagList[tag_index])
   {
      MSQ_SETERR(err)("Invalid tag handle", MsqError::INVALID_ARG);
      return dummy_desc;
   }

   return tagList[tag_index]->desc;
}


void MesquiteMesh::MeshTags::set_element_data( size_t tag_index,
                                               size_t num_indices,
                                               const size_t* index_array,
                                               const void* values,
                                               MsqError& err )
{
   size_t i;
   char* data;
   --tag_index;
   if (tag_index >= tagList.size() || !tagList[tag_index])
   {
      MSQ_SETERR(err)("Invalid tag handle", MsqError::INVALID_ARG);
      return;
   }

   TagData* tag = tagList[tag_index];

   // Get highest element index
   size_t total = tag->elementCount;
   for (i = 0; i < num_indices; ++i)
      if (index_array[i] >= total)
      {
         total = index_array[i] + 1;
      }

   // If need more space
   if (total > tag->elementCount)
   {
      // allocate more space
      tag->elementData = realloc( tag->elementData, tag->desc.size * total );
      // if a default value, initialize new space with it
      if (tag->defaultValue)
      {
         data = ((char*)tag->elementData) + tag->elementCount * tag->desc.size;
         for (i = tag->elementCount; i < total; ++i)
         {
            memcpy( data, tag->defaultValue, tag->desc.size );
            data += tag->desc.size;
         }
      }
      else
      {
         memset( (char*)tag->elementData + tag->elementCount * tag->desc.size, 0,
                 (total - tag->elementCount) * tag->desc.size );
      }
      tag->elementCount = total;
   }

   // Store passed tag values
   data = (char*)tag->elementData;
   const char* iter = (const char*)values;
   for (i = 0; i < num_indices; ++i)
   {
      memcpy( data + index_array[i]*tag->desc.size, iter, tag->desc.size );
      iter += tag->desc.size;
   }
}

void MesquiteMesh::MeshTags::get_element_data( size_t tag_index,
                                               size_t num_indices,
                                               const size_t* index_array,
                                               void* values,
                                               MsqError& err ) const
{
   --tag_index;
   if (tag_index >= tagList.size() || !tagList[tag_index])
   {
      MSQ_SETERR(err)("Invalid tag handle", MsqError::INVALID_ARG);
      return;
   }

   TagData* tag = tagList[tag_index];

   char* iter = (char*)values;
   const char* data = (const char*)tag->elementData;

   for (size_t i = 0; i < num_indices; ++i)
   {
      const void* ptr;
      size_t index = index_array[i];
      if (index >= tag->elementCount)
      {
         ptr = tag->defaultValue;
         if (!ptr)
         {
            MSQ_SETERR(err)(MsqError::TAG_NOT_FOUND);
            return;
         }
      }
      else
      {
         ptr = data + index * tag->desc.size;
      }

      memcpy( iter, ptr, tag->desc.size );
      iter += tag->desc.size;
   }
}

void MesquiteMesh::MeshTags::set_vertex_data( size_t tag_index,
                                              size_t num_indices,
                                              const size_t* index_array,
                                              const void* values,
                                              MsqError& err )
{
   size_t i;
   char* data;
   --tag_index;
   if (tag_index >= tagList.size() || !tagList[tag_index])
   {
      MSQ_SETERR(err)("Invalid tag handle", MsqError::INVALID_ARG);
      return;
   }

   TagData* tag = tagList[tag_index];

   // Get highest element index
   size_t total = tag->vertexCount;
   for (i = 0; i < num_indices; ++i)
      if (index_array[i] >= total)
      {
         total = index_array[i] + 1;
      }

   // If need more space
   if (total > tag->vertexCount)
   {
      // allocate more space
      tag->vertexData = realloc( tag->vertexData, tag->desc.size * total );
      // if a default value, initialize new space with it
      if (tag->defaultValue)
      {
         data = ((char*)tag->vertexData) + tag->vertexCount * tag->desc.size;
         for (i = tag->vertexCount; i < total; ++i)
         {
            memcpy( data, tag->defaultValue, tag->desc.size );
            data += tag->desc.size;
         }
      }
      else
      {
         memset( (char*)tag->vertexData + tag->vertexCount * tag->desc.size, 0,
                 (total - tag->vertexCount) * tag->desc.size );
      }
      tag->vertexCount = total;
   }

   // Store passed tag values
   data = (char*)tag->vertexData;
   const char* iter = (const char*)values;
   for (i = 0; i < num_indices; ++i)
   {
      memcpy( data + index_array[i]*tag->desc.size, iter, tag->desc.size );
      iter += tag->desc.size;
   }
}

void MesquiteMesh::MeshTags::get_vertex_data( size_t tag_index,
                                              size_t num_indices,
                                              const size_t* index_array,
                                              void* values,
                                              MsqError& err ) const
{
   --tag_index;
   if (tag_index >= tagList.size() || !tagList[tag_index])
   {
      MSQ_SETERR(err)("Invalid tag handle", MsqError::INVALID_ARG);
      return;
   }

   TagData* tag = tagList[tag_index];

   char* iter = (char*)values;
   const char* data = (const char*)tag->vertexData;

   for (size_t i = 0; i < num_indices; ++i)
   {
      const void* ptr;
      size_t index = index_array[i];
      if (index >= tag->vertexCount)
      {
         ptr = tag->defaultValue;
         if (!ptr)
         {
            MSQ_SETERR(err)(MsqError::TAG_NOT_FOUND);
            return;
         }
      }
      else
      {
         ptr = data + index * tag->desc.size;
      }

      memcpy( iter, ptr, tag->desc.size );
      iter += tag->desc.size;
   }
}

bool MesquiteMesh::MeshTags::tag_has_vertex_data( size_t tag_index,
                                                  MsqError& err )
{
   --tag_index;
   if (tag_index >= tagList.size() || !tagList[tag_index])
   {
      MSQ_SETERR(err)("Invalid tag handle", MsqError::INVALID_ARG);
      return false;
   }

   TagData* tag = tagList[tag_index];
   return 0 != tag->vertexData || tag->defaultValue;
}

bool MesquiteMesh::MeshTags::tag_has_element_data( size_t tag_index,
                                                   MsqError& err )
{
   --tag_index;
   if (tag_index >= tagList.size() || !tagList[tag_index])
   {
      MSQ_SETERR(err)("Invalid tag handle", MsqError::INVALID_ARG);
      return false;
   }

   TagData* tag = tagList[tag_index];
   return 0 != tag->elementData || tag->defaultValue;
}

MesquiteMesh::MeshTags::TagIterator MesquiteMesh::MeshTags::tag_begin()
{
   size_t index = 0;
   while (index < tagList.size() && tagList[index] == NULL)
   {
      ++index;
   }
   return TagIterator( this, index );
}

MesquiteMesh::MeshTags::TagIterator
MesquiteMesh::MeshTags::TagIterator::operator++()
{
   ++index;
   while (index < tags->tagList.size() && NULL == tags->tagList[index])
   {
      ++index;
   }
   return TagIterator( tags, index );
}

MesquiteMesh::MeshTags::TagIterator
MesquiteMesh::MeshTags::TagIterator::operator--()
{
   --index;
   while (index < tags->tagList.size() && NULL == tags->tagList[index])
   {
      --index;
   }
   return TagIterator( tags, index );
}

MesquiteMesh::MeshTags::TagIterator
MesquiteMesh::MeshTags::TagIterator::operator++(int)
{
   size_t old = index;
   ++index;
   while (index < tags->tagList.size() && NULL == tags->tagList[index])
   {
      ++index;
   }
   return TagIterator( tags, old );
}

MesquiteMesh::MeshTags::TagIterator
MesquiteMesh::MeshTags::TagIterator::operator--(int)
{
   size_t old = index;
   --index;
   while (index < tags->tagList.size() && NULL == tags->tagList[index])
   {
      --index;
   }
   return TagIterator( tags, old );
}

//
// MesquiteMesh implementation follows
//
MesquiteMesh::MesquiteMesh(mfem::Mesh *mfem_mesh)
   :   myTags( new MeshTags )
{
   mesh = mfem_mesh;
   nelems = mesh->GetNE();
   nodes = mesh->GetNodes();

   if (nodes)
   {
      fes = nodes->FESpace();
      ndofs = fes->GetNDofs();
      fes->BuildElementToDofTable();
      const Table *elem_dof = &fes->GetElementToDofTable();
      dof_elem = new Table;
      Transpose(*elem_dof, *dof_elem, ndofs);
   }
   else
   {
      ndofs = mesh->GetNV();
      dof_elem = mesh->GetVertexToElementTable();
   }

   mByte    = vector<char>(ndofs);
   mFixed   = vector<bool>(ndofs, false);

   // By default, flag all boundary nodes as fixed
   Array<int> bdofs;
   for (int i = 0; i < mesh->GetNBE(); i++)
   {
      if (nodes)
      {
         fes->GetBdrElementDofs(i, bdofs);
      }
      else
      {
         mesh->GetBdrElementVertices(i, bdofs);
      }
      for (int j = 0; j < bdofs.Size(); j++)
      {
         mFixed[bdofs[j]] = true;
      }
   }

}

int MesquiteMesh::get_geometric_dimension(Mesquite::MsqError &err)
{
   return mesh->Dimension();
}

void MesquiteMesh::get_all_elements(std::vector<ElementHandle>& elements,
                                    Mesquite::MsqError& err)
{
   elements.resize(nelems);
   for (int i = 0; i < nelems; i++)
   {
      elements[i] = (ElementHandle) i;
   }
}

void MesquiteMesh::get_all_vertices(std::vector<VertexHandle>& vertices,
                                    Mesquite::MsqError& err)
{
   vertices.resize(ndofs);
   for (int i = 0; i < ndofs; i++)
   {
      vertices[i] = (VertexHandle) i;
   }
}

void MesquiteMesh::vertices_get_coordinates(const VertexHandle vert_array[],
                                            MsqVertex* coordinates,
                                            size_t num_vtx,
                                            MsqError &err)
{

   const size_t *indices = (const size_t*) vert_array;
   double coords[3];
   for (int i = 0; i < num_vtx; i++)
   {
      mesh->GetNode(indices[i], coords);
      coordinates[i].x(coords[0]);
      coordinates[i].y(coords[1]);
      if (mesh->Dimension() == 3 )
      {
         coordinates[i].z(coords[2]);
      }
      else
      {
         coordinates[i].z(0.0);
      }
   }
}

void MesquiteMesh::vertex_set_coordinates(VertexHandle vertex,
                                          const Vector3D &coordinates,
                                          MsqError &err)
{
   double coords[3];
   coords[0] = coordinates.x();
   coords[1] = coordinates.y();
   coords[2] = coordinates.z();
   mesh->SetNode((size_t) vertex, coords);
}

void MesquiteMesh::vertex_set_byte(VertexHandle vertex,
                                   unsigned char byte,
                                   MsqError &err)
{
   size_t index = (size_t) vertex;
   mByte[index] = byte;
}

void MesquiteMesh::vertices_set_byte(const VertexHandle *vert_array,
                                     const unsigned char *byte_array,
                                     size_t array_size,
                                     MsqError &err)
{
   const size_t* indices = (const size_t*) vert_array;
   for (int i = 0; i < array_size; i++)
   {
      mByte[indices[i]] = byte_array[i];
   }
}

void MesquiteMesh::vertex_get_byte(const VertexHandle vertex,
                                   unsigned char *byte,
                                   MsqError &err )
{
   *byte = mByte[(const size_t) vertex];
}

void MesquiteMesh::vertices_get_byte(const VertexHandle *vertex,
                                     unsigned char *byte_array,
                                     size_t array_size,
                                     MsqError &err )
{
   const size_t* indices = (const size_t*) vertex;
   for (int i = 0; i < array_size; i++)
   {
      byte_array[i] = mByte[indices[i]];
   }
}

void MesquiteMesh::vertices_get_fixed_flag(const VertexHandle vert_array[],
                                           std::vector<bool>& fixed_flag_array,
                                           size_t num_vtx,
                                           MsqError &err )
{
   fixed_flag_array.resize(num_vtx + 1);
   const size_t* indices = (const size_t*) vert_array;
   for (int i = 0; i < num_vtx; i++)
   {
      fixed_flag_array[i] = mFixed[indices[i]];
   }
}

void MesquiteMesh::vertices_set_fixed_flag(const VertexHandle vert_array[],
                                           const std::vector< bool > &fixed_flag_array,
                                           size_t num_vtx,
                                           MsqError &err )
{
   const size_t* indices = (const size_t*) vert_array;
   for (int i = 0; i < num_vtx; i++)
   {
      mFixed[indices[i]] = fixed_flag_array[i];
   }
}

void MesquiteMesh::elements_get_attached_vertices(const ElementHandle
                                                  *elem_handles,
                                                  size_t num_elems,
                                                  std::vector<VertexHandle>& vert_handles,
                                                  std::vector<size_t>& offsets,
                                                  MsqError &err)
{
   const size_t* indices = (const size_t*) elem_handles;
   vert_handles.clear();
   offsets.resize(num_elems + 1);

   Array<int> elem_dofs;

   for (int i = 0; i < num_elems; i++)
   {
      offsets[i] = vert_handles.size();
      elem = mesh->GetElement(indices[i]);
      if (nodes)
      {
         fes->GetElementDofs(indices[i],elem_dofs);
      }
      else
      {
         elem->GetVertices(elem_dofs);
      }

      for (int j = 0; j < elem_dofs.Size(); j++)
      {
         // Ordering of this matters!!!
         // We are good for triangles, quads and hexes. What about tets?
         vert_handles.push_back( (VertexHandle) elem_dofs[j] );
      }
   }
   offsets[num_elems] = vert_handles.size();
}

void MesquiteMesh::vertices_get_attached_elements(const VertexHandle*
                                                  vertex_array,
                                                  size_t num_vertex,
                                                  std::vector<ElementHandle>& elements,
                                                  std::vector<size_t>& offsets,
                                                  MsqError& err)
{
   const size_t* indices = (const size_t*) vertex_array;
   elements.clear();
   offsets.resize(num_vertex + 1);

   for (int i = 0; i < num_vertex; i++)
   {
      offsets[i] = elements.size();
      int* vertex_elems = dof_elem->GetRow(indices[i]);
      for (int j = 0; j < dof_elem->RowSize(indices[i]); j++)
      {
         elements.push_back( (ElementHandle) vertex_elems[j] );
      }
   }
   offsets[num_vertex] = elements.size();
}

void MesquiteMesh::elements_get_topologies(const ElementHandle
                                           *element_handle_array,
                                           EntityTopology *element_topologies,
                                           size_t num_elements,
                                           MsqError &err)
{
   // In MESQUITE:
   // TRIANGLE      = 8
   // QUADRILATERAL = 9
   // TETRAHEDRON   = 11
   // HEXAHEDRON    = 12

   // In MFEM:
   // POINT         = 0
   // SEGMENT       = 1
   // TRIANGLE      = 2
   // QUADRILATERAL = 3
   // TETRAHEDRON   = 4
   // HEXAHEDRON    = 5
   // BISECTED      = 6
   // QUADRISECTED  = 7
   // OCTASECTED    = 8

   int mfem_to_mesquite[9] = {0,0,8,9,11,12,0,0,0};
   const size_t* indices = (const size_t*) element_handle_array;
   for (int i = 0; i < num_elements; i++)
   {
      element_topologies[i] = (EntityTopology) mfem_to_mesquite[mesh->GetElementType(
                                                                   indices[i])];
   }
}

MesquiteMesh::~MesquiteMesh()
{
   MsqPrintError err(mfem::err);
   delete myTags;

   delete dof_elem;
}

void
MesquiteMesh::tag_attributes()
{
   MsqError err;

   // create a tag for a single integer value
   TagHandle attributeTagHandle = tag_create( "material", Mesh::INT, 1, 0, err );

   int *materialValues = new int[nelems];
   for (int i=0; i<nelems; i++)
   {
      materialValues[i] = mesh->GetAttribute(i);
   }

   //
   // now put these values into an element tag
   //
   std::vector<ElementHandle> elements;
   get_all_elements(elements, err );

   tag_set_element_data( attributeTagHandle, nelems, arrptr(elements),
                         (const void*)(materialValues), err );

   delete[] materialValues;
}

TagHandle MesquiteMesh::tag_create( const std::string& name,
                                    TagType type,
                                    unsigned length,
                                    const void* defval,
                                    MsqError& err )
{

   size_t size = MeshTags::size_from_tag_type( type );
   MfemTagDescription desc( name, type, length*size );
   size_t index = myTags->create( desc, defval, err ); MSQ_ERRZERO(err);
   return (TagHandle)index;
}

void MesquiteMesh::tag_delete( TagHandle handle, MsqError& err )
{
   myTags->destroy( (size_t)handle, err ); MSQ_CHKERR(err);
}

TagHandle MesquiteMesh::tag_get( const std::string& name, MsqError& err )
{
   size_t index = myTags->handle( name, err ); MSQ_ERRZERO(err);
   if (!index)
   {
      MSQ_SETERR(err)( MsqError::TAG_NOT_FOUND, "could not find tag \"%s\"",
                       name.c_str() );
   }
   return (TagHandle)index;
}

void MesquiteMesh::tag_properties( TagHandle handle,
                                   std::string& name,
                                   TagType& type,
                                   unsigned& length,
                                   MsqError& err )
{
   const MfemTagDescription& desc
      = myTags->properties( (size_t)handle, err ); MSQ_ERRRTN(err);

   name = desc.name;
   type = desc.type;
   length = (unsigned)(desc.size / MeshTags::size_from_tag_type( desc.type ));
}


void MesquiteMesh::tag_set_element_data( TagHandle handle,
                                         size_t num_elems,
                                         const ElementHandle* elem_array,
                                         const void* values,
                                         MsqError& err )
{
   myTags->set_element_data( (size_t)handle,
                             num_elems,
                             (const size_t*)elem_array,
                             values,
                             err );  MSQ_CHKERR(err);
}

void MesquiteMesh::tag_get_element_data( TagHandle handle,
                                         size_t num_elems,
                                         const ElementHandle* elem_array,
                                         void* values,
                                         MsqError& err )
{
   myTags->get_element_data( (size_t)handle,
                             num_elems,
                             (const size_t*)elem_array,
                             values,
                             err );  MSQ_CHKERR(err);
}

void MesquiteMesh::tag_set_vertex_data(  TagHandle handle,
                                         size_t num_elems,
                                         const VertexHandle* elem_array,
                                         const void* values,
                                         MsqError& err )
{
   myTags->set_vertex_data( (size_t)handle,
                            num_elems,
                            (const size_t*)elem_array,
                            values,
                            err );  MSQ_CHKERR(err);
}

void MesquiteMesh::tag_get_vertex_data(  TagHandle handle,
                                         size_t num_elems,
                                         const VertexHandle* elem_array,
                                         void* values,
                                         MsqError& err )
{
   myTags->get_vertex_data( (size_t)handle,
                            num_elems,
                            (const size_t*)elem_array,
                            values,
                            err );  MSQ_CHKERR(err);
}

static void BoundaryPreservingOptimization(mfem::MesquiteMesh &mesh)
{

   MsqDebug::enable(1);
   MsqPrintError err(mfem::err);

   int pOrder = 2;
   int mNumInterfaceSmoothIters = 5;
   bool mFixBndryInInterfaceSmooth = true; //this fixes exterior surface nodes
   bool project_gradient= false;
   double cos_crease_angle=0.2;

   // get all vertices
   std::vector<Mesquite::Mesh::VertexHandle> vertices;
   mesh.get_all_vertices(vertices, err);
   if (MSQ_CHKERR(err)) {mfem::out << err << std::endl; exit(EXIT_FAILURE);}
   int num_vertices = vertices.size();

   // get application fixed vertices
   std::vector<bool> app_fixed(num_vertices);

   mesh.vertices_get_fixed_flag(&(vertices[0]), app_fixed, num_vertices, err);
   if (MSQ_CHKERR(err)) {mfem::out << err << std::endl; exit(EXIT_FAILURE);}
   int num_app_fixed = 0;

   for (int i = 0; i < num_vertices; i++)
   {
      if (app_fixed[i]) { num_app_fixed++; }
   }

   mfem::out << "mesh has " << num_vertices << " vertices and " << num_app_fixed <<
             " are app fixed. ";

   // create planar domain for interior and assessor queues
   Mesquite::PlanarDomain geom( PlanarDomain::XY );

   // tag the underlying mesh material attributes.
   mesh.tag_attributes();

   // create boundary domain and find mesh boundary
   Mesquite::MeshBoundaryDomain2D* mesh_domain = new
   Mesquite::MeshBoundaryDomain2D( MeshBoundaryDomain2D::XY, 0.0, project_gradient,
                                   Mesquite::MeshBoundaryDomain2D::QUADRATIC);
   mesh_domain->skin_area_mesh(&mesh,cos_crease_angle,"material");

   std::vector<Mesquite::Mesh::VertexHandle> theBoundaryVertices;
   mesh_domain->get_boundary_vertices( theBoundaryVertices );

   int num_boundary_vertices = theBoundaryVertices.size();

   std::vector<Mesquite::Mesh::VertexHandle> theBoundaryEdges;
   mesh_domain->get_boundary_edges( theBoundaryEdges );

   //     int num_boundary_edges = theBoundaryEdges.size();
   std::vector<bool> fixed_flags_boundary(num_boundary_vertices);

   // get application fixed boundary vertices
   std::vector<bool> app_fixed_boundary(num_boundary_vertices);
   mesh.vertices_get_fixed_flag(&(theBoundaryVertices[0]),app_fixed_boundary,
                                num_boundary_vertices, err);

   int num_app_fixed_boundary = 0;
   for (int i = 0; i < num_boundary_vertices; i++)
   {
      if (app_fixed_boundary[i]) { num_app_fixed_boundary++; }
   }

   mfem::out << "mesh has " << num_boundary_vertices << " boundary vertices and "
             << num_app_fixed_boundary << " are app fixed" << std::endl;


   // only fix  boundary vertices along corners
   int num_fixed_boundary_flags = 0;

   std::vector<Mesquite::Mesh::VertexHandle> theCornerVertices;
   mesh_domain->get_corner_vertices( theCornerVertices );


   // fix only vertices that are classified as corners
   for (int i = 0; i < num_boundary_vertices; i++)
   {
      if (!mFixBndryInInterfaceSmooth)
      {
         fixed_flags_boundary[i] = false;
      }
      else
      {
         fixed_flags_boundary[i] = app_fixed_boundary[i];
      }

      for (int j = 0; j < theCornerVertices.size(); j++)
      {
         // printf("theCornerVertices[%d]=%lu\n",j, (size_t)(theCornerVertices[j]));
         if (theCornerVertices[j] == theBoundaryVertices[i])
         {
            fixed_flags_boundary[i] = true;
            num_fixed_boundary_flags++;
            break;
         }
      }
   }
   printf("fixed %d of %d boundary vertices (those classified corner)\n",
          num_fixed_boundary_flags, num_boundary_vertices);


   // creates three instruction queues
   Mesquite::InstructionQueue boundary_queue;
   Mesquite::InstructionQueue interior_queue;


   boundary_queue.set_slaved_ho_node_mode(Settings::SLAVE_ALL);
   interior_queue.set_slaved_ho_node_mode(Settings::SLAVE_ALL);

   TShapeB1 targetMetric;

   IdealShapeTarget tc;

   TQualityMetric metric( &tc, &targetMetric );

   Mesquite::LPtoPTemplate* obj_func = new Mesquite::LPtoPTemplate(&metric, pOrder,
                                                                   err);
   if (MSQ_CHKERR(err)) {mfem::out << err << std::endl; exit(EXIT_FAILURE);}

   Mesquite::QuasiNewton* boundary_alg = new Mesquite::QuasiNewton( obj_func);
   boundary_alg->use_element_on_vertex_patch();

   Mesquite::QuasiNewton* interior_alg = new Mesquite::QuasiNewton( obj_func );
   interior_alg->use_global_patch();

   // **************Set stopping criterion**************
   double grad_norm = 1e-5;
   double successiveEps = 1e-5;
   int boundary_outer = 5;
   int boundary_inner = 3;

   // for boundary
   Mesquite::TerminationCriterion* boundaryTermInner = new
   Mesquite::TerminationCriterion();
   Mesquite::TerminationCriterion* boundaryTermOuter = new
   Mesquite::TerminationCriterion();
   // boundaryTermInner->add_absolute_gradient_L2_norm(grad_norm);
   // boundaryTermInner->add_relative_successive_improvement(successiveEps);
   boundaryTermOuter->add_relative_successive_improvement(successiveEps);
   boundaryTermOuter->add_iteration_limit(boundary_outer);
   boundaryTermInner->add_iteration_limit(boundary_inner);

   ostringstream bndryStream;
   bndryStream<<"boundary_"<<targetMetric.get_name()<<"_p"<<pOrder;
   boundaryTermOuter->write_mesh_steps(bndryStream.str().c_str());
   boundary_alg->set_outer_termination_criterion(boundaryTermOuter);
   boundary_alg->set_inner_termination_criterion(boundaryTermInner);

   // for interior
   Mesquite::TerminationCriterion* interiorTermInner = new
   Mesquite::TerminationCriterion();
   Mesquite::TerminationCriterion* interiorTermOuter = new
   Mesquite::TerminationCriterion();
   interiorTermInner->add_absolute_gradient_L2_norm(grad_norm);
   interiorTermInner->add_relative_successive_improvement(successiveEps);
   // interiorTermInner->add_iteration_limit(3); // for element_on_vertex_patch mode
   interiorTermInner->add_iteration_limit(100); // for global_patch mode

   ostringstream interiorStream;
   interiorStream<<"interior_"<<targetMetric.get_name()<<"_p"<<pOrder;
   interiorTermOuter->write_mesh_steps(interiorStream.str().c_str());
   interiorTermOuter->add_iteration_limit(1);
   interior_alg->set_outer_termination_criterion(interiorTermOuter);
   interior_alg->set_inner_termination_criterion(interiorTermInner);

   // ConditionNumberQualityMetric qm_metric;
   // QualityAssessor boundary_assessor,interior_assessor;

   // boundary_assessor.add_quality_assessment( &metric, 10 );
   // boundary_assessor.add_quality_assessment( &qm_metric );

   // interior_assessor.add_quality_assessment( &metric, 10 );
   // interior_assessor.add_quality_assessment( &qm_metric );


   // set the boundary instruction queue
   // boundary_queue.add_quality_assessor( &boundary_assessor, err );
   boundary_queue.set_master_quality_improver(boundary_alg, err);
   // boundary_queue.add_quality_assessor( &boundary_assessor, err );
   if (MSQ_CHKERR(err)) {mfem::out << err << std::endl; exit(EXIT_FAILURE);}

   // set the interior instruction queue
   // interior_queue.add_quality_assessor( &interior_assessor, err );
   interior_queue.set_master_quality_improver(interior_alg, err);
   // interior_queue.add_quality_assessor( &interior_assessor, err );
   if (MSQ_CHKERR(err)) {mfem::out << err << std::endl; exit(EXIT_FAILURE);}

   err.clear();

   std::vector<bool> fixed_flags(num_vertices);

   for (int j=0; j<mNumInterfaceSmoothIters; j++)
   {

      mfem::out<<" Boundary + Interior smoothing pass "<< j<<"....."<<endl;

      // smooth boundary only
      for (int i = 0; i < num_vertices; i++) { fixed_flags[i] = true; }
      mesh.vertices_set_fixed_flag(&(vertices[0]),fixed_flags,num_vertices, err);
      if (MSQ_CHKERR(err)) {mfem::out << err << std::endl; exit(EXIT_FAILURE);}

      mesh.vertices_set_fixed_flag(&(theBoundaryVertices[0]),fixed_flags_boundary,
                                   num_boundary_vertices, err);
      if (MSQ_CHKERR(err)) {mfem::out << err << std::endl; exit(EXIT_FAILURE);}

      // -----------------------------------------------------
      // debug
      //
      mesh.vertices_get_fixed_flag(&(vertices[0]),fixed_flags,num_vertices, err);
      if (MSQ_CHKERR(err)) {mfem::out << err << std::endl; exit(EXIT_FAILURE);}
      int num_fixed = 0;
      for (int i = 0; i < num_vertices; i++)
      {
         if (fixed_flags[i]) { num_fixed++; }
      }

      mfem::out << "   For Boundary smooth, mesh has " << num_vertices <<
                " vertices and " << num_fixed << " are fixed. "<<endl;
      //
      // debug
      // -----------------------------------------------------


      boundary_queue.run_instructions(&mesh, mesh_domain, err);
      if (MSQ_CHKERR(err)) {mfem::out << err << std::endl; exit(EXIT_FAILURE);}
      mfem::out<<" boundary smooth completed in "<<boundaryTermOuter->get_iteration_count()
               <<" outer and "<<boundaryTermInner->get_iteration_count()
               <<" inner iterations."<<endl;


      // smooth interior only

      if (!mFixBndryInInterfaceSmooth)
      {
         //
         // let all interior vertices float during the interior smooth.
         //
         for (int i = 0; i < num_vertices; i++) { fixed_flags[i] = false; }
         mesh.vertices_set_fixed_flag(&(vertices[0]),fixed_flags,num_vertices, err);
      }
      else
      {
         //
         // use the app_fixed settings for the fixed state of boundary vertices.
         //
         mesh.vertices_set_fixed_flag(&(vertices[0]),app_fixed,num_vertices, err);
      }
      if (MSQ_CHKERR(err)) {mfem::out << err << std::endl; exit(EXIT_FAILURE);}
      for (int i = 0; i < num_boundary_vertices; i++) { fixed_flags[i] = true; }

      mesh.vertices_set_fixed_flag(&(theBoundaryVertices[0]),fixed_flags,
                                   num_boundary_vertices, err);
      if (MSQ_CHKERR(err)) {mfem::out << err << std::endl; exit(EXIT_FAILURE);}

      // -----------------------------------------------------
      // debug
      //
      mesh.vertices_get_fixed_flag(&(vertices[0]),fixed_flags,num_vertices, err);
      if (MSQ_CHKERR(err)) {mfem::out << err << std::endl; exit(EXIT_FAILURE);}
      num_fixed = 0;
      for (int i = 0; i < num_vertices; i++)
      {
         if (fixed_flags[i]) { num_fixed++; }
      }

      mfem::out << "    For Interior smooth, mesh has " << num_vertices <<
                " vertices and " << num_fixed << " are fixed. "<<endl;
      //
      // debug
      // -----------------------------------------------------

      interior_queue.run_instructions(&mesh, &geom, err);
      if (MSQ_CHKERR(err)) {mfem::out << err << std::endl; exit(EXIT_FAILURE);}
      mfem::out<<" interior smooth completed in "<<interiorTermOuter->get_iteration_count()
               <<" outer and "<<interiorTermInner->get_iteration_count()
               <<" inner iterations."<<endl;

   }


   delete mesh_domain;
   delete interiorTermOuter;
   delete interiorTermInner;
   delete boundaryTermOuter;
   delete boundaryTermInner;
   delete interior_alg;
   delete boundary_alg;
   delete obj_func;

}


// Implementation of Mesh::MesquiteSmooth method

void mfem::Mesh::MesquiteSmooth(const int mesquite_option)
{
   mfem::MesquiteMesh msq_mesh(this);
   MsqDebug::enable(1);
   MsqPrintError err(mfem::err);

   Wrapper *method;

   const double vert_move_tol = 1e-3;

   switch (mesquite_option)
   {
      case 0:  method = new LaplaceWrapper(); break;
      case 1:  method = new UntangleWrapper(); break;
      case 2:  method = new ShapeImprover(); break;
      case 3:  method = new PaverMinEdgeLengthWrapper(vert_move_tol); break;
   }

   if ( mesquite_option < 4 )
   {
      // Specify SLAVE_NONE for high order node positions
      method->set_slaved_ho_node_mode(Settings::SLAVE_NONE);

      if ( this->Dimension() == 3 )
      {
         method->run_instructions(&msq_mesh, err);
      }
      else
      {
         Vector3D normal(0,0,1);
         Vector3D  point(0,0,0);
         PlanarDomain mesh_plane(normal, point);

         method->run_instructions(&msq_mesh, &mesh_plane, err);
      }
   }
   else
   {
      // boundary preserving smoothing doesn't have a wrapper yet.
      BoundaryPreservingOptimization( msq_mesh );
   }
}

}

#endif
