// Gmsh - Copyright (C) 1997-2015 C. Geuzaine, J.-F. Remacle
//
// See the LICENSE.txt file for license information. Please report all
// bugs and problems to the public mailing list <gmsh@geuz.org>.
//
// Contributor(s):
//   Brian Helenbrook
//

#include "HighOrder.h"
#include "MLine.h"
#include "MTriangle.h"
#include "MQuadrangle.h"
#include "MTetrahedron.h"
#include "MHexahedron.h"
#include "MPrism.h"
#include "MPyramid.h"
#include "GmshMessage.h"
#include "OS.h"
#include "Context.h"
#include "meshGFaceOptimize.h"

void subdivide_pyramid(MElement* element,
		       GRegion* gr, 
		       faceContainer &faceVertices,
		       std::vector<MHexahedron*> &dwarfs88);

class MVertexLessThanParam{
 public:
  bool operator()(const MVertex *v1, const MVertex *v2) const
  {
    double u1 = 0., u2 = 1.;
    v1->getParameter(0, u1);
    v2->getParameter(0, u2);
    return u1 < u2;
  }
};

// Set BM data on vertex
static void setBLData(MVertex *v)
{
  switch (v->onWhat()->dim()) {
    case 1: {
      MEdgeVertex *ve = dynamic_cast<MEdgeVertex*>(v);
      if (ve) ve->bl_data = new MVertexBoundaryLayerData();
      break;
    }
    case 2: {
      MFaceVertex *vf = dynamic_cast<MFaceVertex*>(v);
      if (vf) vf->bl_data = new MVertexBoundaryLayerData();
      break;
    }
  }
}

// If all low-order nodes in are marked as BL, then mark high-order nodes as BL (only works in 2D)
static bool setBLData(MElement *el)
{
  // Check whether all low-order nodes are marked as BL nodes (only works in 2D)
  for(int i=0; i<el->getNumPrimaryVertices(); i++) {
    MVertex *v = el->getVertex(i);
    bool isBL = false;
    switch (v->onWhat()->dim()) {
      case 0:
        isBL = true;
        break;
      case 1: {
        MEdgeVertex *ve = dynamic_cast<MEdgeVertex*>(v);
        if (ve && ve->bl_data) isBL = true;
        break;
      }
      case 2: {
        MFaceVertex *vf = dynamic_cast<MFaceVertex*>(v);
        if (vf && vf->bl_data) isBL = true;
        break;
      }
    }
    if (!isBL) return false;
  }
  // Mark high-order nodes as BL nodes (only works in 2D)
  for(int i=el->getNumPrimaryVertices(); i<el->getNumVertices(); i++)
    setBLData(el->getVertex(i));
  return true;
}

static void Subdivide(GEdge *ge)
{
  std::vector<MLine*> lines2;
  for(unsigned int i = 0; i < ge->lines.size(); i++){
    MLine *l = ge->lines[i];
    if(l->getNumVertices() == 3){
      lines2.push_back(new MLine(l->getVertex(0), l->getVertex(2)));
      lines2.push_back(new MLine(l->getVertex(2), l->getVertex(1)));
      setBLData(l);
    }
    delete l;
  }
  ge->lines = lines2;

  // 2nd order meshing destroyed the ordering of the vertices on the edge
  std::sort(ge->mesh_vertices.begin(), ge->mesh_vertices.end(),
            MVertexLessThanParam());
  for(unsigned int i = 0; i < ge->mesh_vertices.size(); i++)
    ge->mesh_vertices[i]->setPolynomialOrder(1);
  ge->deleteVertexArrays();
}

static void Subdivide(GFace *gf, bool splitIntoQuads, bool splitIntoHexas,
                      faceContainer &faceVertices)
{

  if(!splitIntoQuads && !splitIntoHexas){
    std::vector<MTriangle*> triangles2;
    for(unsigned int i = 0; i < gf->triangles.size(); i++){
      MTriangle *t = gf->triangles[i];
      if(t->getNumVertices() == 6){
        triangles2.push_back
          (new MTriangle(t->getVertex(0), t->getVertex(3), t->getVertex(5)));
        triangles2.push_back
          (new MTriangle(t->getVertex(3), t->getVertex(4), t->getVertex(5)));
        triangles2.push_back
          (new MTriangle(t->getVertex(3), t->getVertex(1), t->getVertex(4)));
        triangles2.push_back
          (new MTriangle(t->getVertex(5), t->getVertex(4), t->getVertex(2)));
        setBLData(t);
      }
      delete t;
    }
    gf->triangles = triangles2;
  }

  std::vector<MQuadrangle*> quadrangles2;
  for(unsigned int i = 0; i < gf->quadrangles.size(); i++){
    MQuadrangle *q = gf->quadrangles[i];
    if(q->getNumVertices() == 9){
      quadrangles2.push_back
        (new MQuadrangle(q->getVertex(0), q->getVertex(4), q->getVertex(8), q->getVertex(7)));
      quadrangles2.push_back
        (new MQuadrangle(q->getVertex(4), q->getVertex(1), q->getVertex(5), q->getVertex(8)));
      quadrangles2.push_back
        (new MQuadrangle(q->getVertex(8), q->getVertex(5), q->getVertex(2), q->getVertex(6)));
      quadrangles2.push_back
        (new MQuadrangle(q->getVertex(7), q->getVertex(8), q->getVertex(6), q->getVertex(3)));
      setBLData(q);
    }
    delete q;
  }
  if(splitIntoQuads || splitIntoHexas){
    for(unsigned int i = 0; i < gf->triangles.size(); i++){
      MTriangle *t = gf->triangles[i];
      if(t->getNumVertices() == 6){
        SPoint2 pt;
        SPoint3 ptx; t->pnt(0.5, 0.5, 0, ptx);
        bool reparamOK = true;
        for(int k = 0; k < 6; k++){
          SPoint2 temp;
          reparamOK &= reparamMeshVertexOnFace(t->getVertex(k), gf, temp);
          pt[0] += temp[0] / 6.;
          pt[1] += temp[1] / 6.;
        }
        MVertex *newv;
        if (reparamOK){
          GPoint gp = gf->point(pt);
          newv = new MFaceVertex(gp.x(), gp.y(), gp.z(), gf, pt[0], pt[1]);
        }
        else {
          newv = new MVertex(ptx.x(), ptx.y(), ptx.z(), gf);
        }
        gf->mesh_vertices.push_back(newv);
        if(splitIntoHexas) faceVertices[t->getFace(0)].push_back(newv);
        quadrangles2.push_back
          (new MQuadrangle(t->getVertex(0), t->getVertex(3), newv, t->getVertex(5)));
        quadrangles2.push_back
          (new MQuadrangle(t->getVertex(3), t->getVertex(1), t->getVertex(4), newv));
        quadrangles2.push_back
          (new MQuadrangle(t->getVertex(5), newv,t->getVertex(4), t->getVertex(2)));
        if (setBLData(t)) setBLData(newv);
        delete t;
      }
    }
    gf->triangles.clear();
  }
  gf->quadrangles = quadrangles2;

  for(unsigned int i = 0; i < gf->mesh_vertices.size(); i++)
    gf->mesh_vertices[i]->setPolynomialOrder(1);
  gf->deleteVertexArrays();
}

static void Subdivide(GRegion *gr, bool splitIntoHexas, faceContainer &faceVertices)
{
  if(!splitIntoHexas){
    // Split tets into other tets
    std::vector<MTetrahedron*> tetrahedra2;
    for(unsigned int i = 0; i < gr->tetrahedra.size(); i++){
      MTetrahedron *t = gr->tetrahedra[i];
      // FIXME: we should choose the template to maximize the quality
      if(t->getNumVertices() == 10){
        tetrahedra2.push_back
          (new MTetrahedron(t->getVertex(0), t->getVertex(4), t->getVertex(7), t->getVertex(6)));
        tetrahedra2.push_back
          (new MTetrahedron(t->getVertex(1), t->getVertex(4), t->getVertex(5), t->getVertex(9)));
        tetrahedra2.push_back
          (new MTetrahedron(t->getVertex(2), t->getVertex(5), t->getVertex(6), t->getVertex(8)));
        tetrahedra2.push_back
          (new MTetrahedron(t->getVertex(3), t->getVertex(7), t->getVertex(9), t->getVertex(8)));
        tetrahedra2.push_back
          (new MTetrahedron(t->getVertex(5), t->getVertex(8), t->getVertex(7), t->getVertex(9)));
        tetrahedra2.push_back
          (new MTetrahedron(t->getVertex(5), t->getVertex(7), t->getVertex(4), t->getVertex(9)));
        tetrahedra2.push_back
          (new MTetrahedron(t->getVertex(7), t->getVertex(8), t->getVertex(5), t->getVertex(6)));
        tetrahedra2.push_back
          (new MTetrahedron(t->getVertex(4), t->getVertex(7), t->getVertex(5), t->getVertex(6)));
        setBLData(t);
      }
      delete t;
    }
    gr->tetrahedra = tetrahedra2;
  }

  // Split hexes into other hexes.
  std::vector<MHexahedron*> hexahedra2;
  for(unsigned int i = 0; i < gr->hexahedra.size(); i++){
    MHexahedron *h = gr->hexahedra[i];
    if(h->getNumVertices() == 27){
      hexahedra2.push_back
        (new MHexahedron(h->getVertex(0), h->getVertex(8), h->getVertex(20), h->getVertex(9),
                         h->getVertex(10), h->getVertex(21), h->getVertex(26), h->getVertex(22)));
      hexahedra2.push_back
        (new MHexahedron(h->getVertex(10), h->getVertex(21), h->getVertex(26), h->getVertex(22),
                         h->getVertex(4), h->getVertex(16), h->getVertex(25), h->getVertex(17)));
      hexahedra2.push_back
        (new MHexahedron(h->getVertex(8), h->getVertex(1), h->getVertex(11), h->getVertex(20),
                         h->getVertex(21), h->getVertex(12), h->getVertex(23), h->getVertex(26)));
      hexahedra2.push_back
        (new MHexahedron(h->getVertex(21), h->getVertex(12), h->getVertex(23), h->getVertex(26),
                         h->getVertex(16), h->getVertex(5), h->getVertex(18), h->getVertex(25)));
      hexahedra2.push_back
        (new MHexahedron(h->getVertex(9), h->getVertex(20), h->getVertex(13), h->getVertex(3),
                         h->getVertex(22), h->getVertex(26), h->getVertex(24), h->getVertex(15)));
      hexahedra2.push_back
        (new MHexahedron(h->getVertex(22), h->getVertex(26), h->getVertex(24), h->getVertex(15),
                         h->getVertex(17), h->getVertex(25), h->getVertex(19), h->getVertex(7)));
      hexahedra2.push_back
        (new MHexahedron(h->getVertex(20), h->getVertex(11), h->getVertex(2), h->getVertex(13),
                         h->getVertex(26), h->getVertex(23), h->getVertex(14), h->getVertex(24)));
      hexahedra2.push_back
        (new MHexahedron(h->getVertex(26), h->getVertex(23), h->getVertex(14), h->getVertex(24),
                         h->getVertex(25), h->getVertex(18), h->getVertex(6), h->getVertex(19)));
      setBLData(h);
    }
    delete h;
  }

  // Split tets into other hexes.
  if(splitIntoHexas){
    for(unsigned int i = 0; i < gr->tetrahedra.size(); i++){
      MTetrahedron *t = gr->tetrahedra[i];
      if(t->getNumVertices() == 10){
        std::vector<MVertex*> newv;
        for(int j = 0; j < t->getNumFaces(); j++){
          MFace face = t->getFace(j);
          faceContainer::iterator fIter = faceVertices.find(face);
          if (fIter != faceVertices.end()){
            newv.push_back(fIter->second[0]);
          }
          else{
            SPoint3 pc = face.barycenter();
            newv.push_back(new MVertex(pc.x(), pc.y(), pc.z(), gr));
            faceVertices[face].push_back(newv.back());
            gr->mesh_vertices.push_back(newv.back());
          }
        }
        SPoint3 pc = t->barycenter();
        newv.push_back(new MVertex(pc.x(), pc.y(), pc.z(), gr));
        gr->mesh_vertices.push_back(newv.back());
        hexahedra2.push_back
          (new MHexahedron(t->getVertex(0), t->getVertex(4), newv[0], t->getVertex(6),
                           t->getVertex(7), newv[1], newv[4], newv[2]));
        hexahedra2.push_back
          (new MHexahedron(t->getVertex(4), t->getVertex(1), t->getVertex(5), newv[0],
                           newv[1], t->getVertex(9), newv[3], newv[4]));
        hexahedra2.push_back
          (new MHexahedron(t->getVertex(6),  newv[0], t->getVertex(5), t->getVertex(2),
                           newv[2], newv[4], newv[3], t->getVertex(8)));
        hexahedra2.push_back
          (new MHexahedron(t->getVertex(3),  t->getVertex(9), newv[1], t->getVertex(7),
                           t->getVertex(8), newv[3], newv[4], newv[2]));
        if (setBLData(t)) {
          setBLData(newv[0]); setBLData(newv[1]);
          setBLData(newv[2]); setBLData(newv[3]); setBLData(newv[4]);
        }
        delete t;
      }
    }
    gr->tetrahedra.clear();

    for(unsigned int i = 0; i < gr->prisms.size(); i++){
      MPrism *p = gr->prisms[i];
      if(p->getNumVertices() == 18){
        std::vector<MVertex*> newv;
        for(int j = 0; j < 2; j++){
          MFace face = p->getFace(j);
          faceContainer::iterator fIter = faceVertices.find(face);
          if (fIter != faceVertices.end()){
            newv.push_back(fIter->second[0]);
          }
          else{
            SPoint3 pc = face.barycenter();
            newv.push_back(new MVertex(pc.x(), pc.y(), pc.z(), gr));
            faceVertices[face].push_back(newv.back());
            gr->mesh_vertices.push_back(newv.back());
          }
        }
        SPoint3 pc = p->barycenter();
        newv.push_back(new MVertex(pc.x(), pc.y(), pc.z(), gr));
        gr->mesh_vertices.push_back(newv.back());
        hexahedra2.push_back
          (new MHexahedron(p->getVertex(0), p->getVertex(6), newv[0], p->getVertex(7),
                           p->getVertex(8), p->getVertex(15), newv[2], p->getVertex(16)));
        hexahedra2.push_back
          (new MHexahedron(p->getVertex(1), p->getVertex(9), newv[0], p->getVertex(6),
                           p->getVertex(10), p->getVertex(17), newv[2], p->getVertex(15)));
        hexahedra2.push_back
          (new MHexahedron(p->getVertex(2), p->getVertex(7), newv[0], p->getVertex(9),
                           p->getVertex(11), p->getVertex(16), newv[2], p->getVertex(17)));
        hexahedra2.push_back
          (new MHexahedron(p->getVertex(8), p->getVertex(15), newv[2], p->getVertex(16),
                           p->getVertex(3), p->getVertex(12), newv[1], p->getVertex(13)));
        hexahedra2.push_back
          (new MHexahedron(p->getVertex(10), p->getVertex(17), newv[2], p->getVertex(15),
                           p->getVertex(4), p->getVertex(14), newv[1], p->getVertex(12)));
        hexahedra2.push_back
          (new MHexahedron(p->getVertex(11), p->getVertex(16), newv[2], p->getVertex(17),
                           p->getVertex(5), p->getVertex(13), newv[1], p->getVertex(14)));
        if (setBLData(p)) {
          setBLData(newv[0]); setBLData(newv[1]); setBLData(newv[2]);
        }
      }
    }
    gr->prisms.clear();
    // --------------------------------------------------
    // YAMAKAWA DIVISION OF A PYRAMID INTO 88 HEXES !!!!!
    // --------------------------------------------------
    std::vector<MHexahedron*> dwarfs88;
    for(unsigned int i = 0; i < gr->pyramids.size(); i++){
      MPyramid *p = gr->pyramids[i];
      if(p->getNumVertices() == 14){
	subdivide_pyramid(p,gr,faceVertices,dwarfs88);
	for (int j=0;j<88;j++)hexahedra2.push_back(dwarfs88[j]);
      }
    }
    gr->pyramids.clear();
    // --------------------------------------------------
    // ---- THANKS TO TRISTAN CARRIER BAUDOUIN ----------
    // --------------------------------------------------
  }
  gr->hexahedra = hexahedra2;

  std::vector<MPrism*> prisms2;
  for(unsigned int i = 0; i < gr->prisms.size(); i++){
    MPrism *p = gr->prisms[i];
    if(p->getNumVertices() == 18){
      prisms2.push_back
        (new MPrism(p->getVertex(0), p->getVertex(6), p->getVertex(7),
                    p->getVertex(8), p->getVertex(15), p->getVertex(16)));
      prisms2.push_back
        (new MPrism(p->getVertex(8), p->getVertex(15), p->getVertex(16),
                    p->getVertex(3), p->getVertex(12), p->getVertex(13)));
      prisms2.push_back
        (new MPrism(p->getVertex(6), p->getVertex(1), p->getVertex(9),
                    p->getVertex(15), p->getVertex(10), p->getVertex(17)));
      prisms2.push_back
        (new MPrism(p->getVertex(15), p->getVertex(10), p->getVertex(17),
                    p->getVertex(12), p->getVertex(4), p->getVertex(14)));
      prisms2.push_back
        (new MPrism(p->getVertex(7), p->getVertex(9), p->getVertex(2),
                    p->getVertex(16), p->getVertex(17), p->getVertex(11)));
      prisms2.push_back
        (new MPrism(p->getVertex(16), p->getVertex(17), p->getVertex(11),
                    p->getVertex(13), p->getVertex(14), p->getVertex(5)));
      prisms2.push_back
        (new MPrism(p->getVertex(9), p->getVertex(7), p->getVertex(6),
                    p->getVertex(17), p->getVertex(16), p->getVertex(15)));
      prisms2.push_back
        (new MPrism(p->getVertex(17), p->getVertex(16), p->getVertex(15),
                    p->getVertex(14), p->getVertex(13), p->getVertex(12)));
      setBLData(p);
    }
    delete p;
  }
  gr->prisms = prisms2;

  std::vector<MPyramid*> pyramids2;
  for(unsigned int i = 0; i < gr->pyramids.size(); i++){
    if(splitIntoHexas){
      Msg::Error("Full hexahedron subdivision is not implemented for pyramids");
      return;
    }
    MPyramid *p = gr->pyramids[i];
    if(p->getNumVertices() == 14){
      // Base
      pyramids2.push_back
        (new MPyramid(p->getVertex(0), p->getVertex(5), p->getVertex(13),
                      p->getVertex(6), p->getVertex(7)));
      pyramids2.push_back
        (new MPyramid(p->getVertex(5), p->getVertex(1), p->getVertex(8),
                      p->getVertex(13), p->getVertex(9)));
      pyramids2.push_back
        (new MPyramid(p->getVertex(13), p->getVertex(8), p->getVertex(2),
                      p->getVertex(10), p->getVertex(11)));
      pyramids2.push_back
        (new MPyramid(p->getVertex(6), p->getVertex(13), p->getVertex(10),
                      p->getVertex(3), p->getVertex(12)));

      // Split remaining into tets
      // Top
      gr->tetrahedra.push_back
        ((new MTetrahedron(p->getVertex(7), p->getVertex(9), p->getVertex(12), p->getVertex(4))));
      gr->tetrahedra.push_back
        ((new MTetrahedron(p->getVertex(9), p->getVertex(11), p->getVertex(12), p->getVertex(4))));

      // Upside down one
      gr->tetrahedra.push_back
        ((new MTetrahedron(p->getVertex(9), p->getVertex(12), p->getVertex(11), p->getVertex(13))));
      gr->tetrahedra.push_back
        ((new MTetrahedron(p->getVertex(7), p->getVertex(12), p->getVertex(9), p->getVertex(13))));

      // Four tets around bottom perimeter
      gr->tetrahedra.push_back
        ((new MTetrahedron(p->getVertex(7), p->getVertex(9), p->getVertex(5), p->getVertex(13))));
      gr->tetrahedra.push_back
        ((new MTetrahedron(p->getVertex(9), p->getVertex(11), p->getVertex(8), p->getVertex(13))));
      gr->tetrahedra.push_back
        ((new MTetrahedron(p->getVertex(12), p->getVertex(10), p->getVertex(11), p->getVertex(13))));
      gr->tetrahedra.push_back
        ((new MTetrahedron(p->getVertex(7), p->getVertex(6), p->getVertex(12), p->getVertex(13))));
      setBLData(p);
    }
    delete p;
  }
  gr->pyramids = pyramids2;

  for(unsigned int i = 0; i < gr->mesh_vertices.size(); i++)
    gr->mesh_vertices[i]->setPolynomialOrder(1);
  gr->deleteVertexArrays();
}

void RefineMesh(GModel *m, bool linear, bool splitIntoQuads, bool splitIntoHexas)
{
  Msg::StatusBar(true, "Refining mesh...");
  double t1 = Cpu();

  // Create 2nd order mesh (using "2nd order complete" elements) to
  // generate vertex positions
  SetOrderN(m, 2, linear, false);

  // only used when splitting tets into hexes
  faceContainer faceVertices;

  // Subdivide the second order elements to create the refined linear
  // mesh
  for(GModel::eiter it = m->firstEdge(); it != m->lastEdge(); ++it)
    Subdivide(*it);
  for(GModel::fiter it = m->firstFace(); it != m->lastFace(); ++it){
    Subdivide(*it, splitIntoQuads, splitIntoHexas, faceVertices);
    if (splitIntoQuads){
      recombineIntoQuads(*it,true,true);
    }
  }
  for(GModel::riter it = m->firstRegion(); it != m->lastRegion(); ++it)
    Subdivide(*it, splitIntoHexas, faceVertices);

  // Check all 3D elements for negative volume and reverse if needed
  m->setAllVolumesPositive();

  double t2 = Cpu();
  Msg::StatusBar(true, "Done refining mesh (%g s)", t2 - t1);
}


///------ Tristan Carrier Baudouin's Contribution on Full Hex Meshing

static double schneiders_x(int i){
  double coord[105] = {
    0.500000,
    0.666667,
    0.500000,
    1.000000,
    1.000000,
    1.000000,
    0.289057,
    0.324970,
    0.276710,
    0.337200,
    0.364878,
    0.325197,
    0.000000,
    0.000000,
    0.000000,
    0.000000,
    0.000000,
    0.000000,
    -0.289057,
    -0.324970,
    -0.276710,
    -0.337200,
    -0.364878,
    -0.325197,
    -0.500000,
    -0.666667,
    -0.500000,
    -1.000000,
    -1.000000,
    -1.000000,
    0.084599,
    0.263953,
    0.442960,
    0.310954,
    0.000000,
    0.000000,
    0.118244,
    0.212082,
    0.244049,
    0.213940,
    0.040495,
    0.110306,
    0.000000,
    0.000000,
    0.000000,
    0.000000,
    0.000000,
    0.000000,
    -0.118244,
    -0.212082,
    -0.244049,
    -0.213940,
    -0.040495,
    -0.110306,
    -0.084599,
    -0.263953,
    -0.442960,
    -0.310954,
    0.650493,
    0.454537,
    0.000000,
    0.000000,
    0.320508,
    0.264129,
    0.063695,
    0.092212,
    0.000000,
    0.000000,
    0.000000,
    0.000000,
    -0.320508,
    -0.264129,
    -0.063695,
    -0.092212,
    -0.650493,
    -0.454537,
    0.619616,
    0.000000,
    0.277170,
    0.124682,
    0.000000,
    0.000000,
    -0.277170,
    -0.124682,
    -0.619616,
    0.128101,
    0.000000,
    0.176104,
    0.084236,
    0.000000,
    0.000000,
    -0.176104,
    -0.084236,
    -0.128101,
    0.000000,
    0.000000,
    0.000000,
    0.000000,
    0.000000,
    0.000000,
    0.000000,
    0.000000,
    0.000000,
    0.000000,
    0.000000
  };

  return coord[i];
}

static double schneiders_y(int i){
  double coord[105] = {
    0.707107,
    0.471405,
    0.707107,
    0.000000,
    0.000000,
    0.000000,
    0.474601,
    0.421483,
    0.463847,
    0.367090,
    0.344843,
    0.361229,
    0.429392,
    0.410339,
    0.435001,
    0.407674,
    0.401208,
    0.404164,
    0.474601,
    0.421483,
    0.463847,
    0.367090,
    0.344843,
    0.361229,
    0.707107,
    0.471405,
    0.707107,
    0.000000,
    0.000000,
    0.000000,
    0.536392,
    0.697790,
    0.569124,
    0.742881,
    0.558045,
    0.946690,
    0.497378,
    0.532513,
    0.500133,
    0.541479,
    0.530503,
    0.666252,
    0.463274,
    0.465454,
    0.430972,
    0.451135,
    0.515274,
    0.589713,
    0.497378,
    0.532513,
    0.500133,
    0.541479,
    0.530503,
    0.666252,
    0.536392,
    0.697790,
    0.569124,
    0.742881,
    0.080018,
    0.104977,
    0.216381,
    0.200920,
    0.326416,
    0.339933,
    0.280915,
    0.305725,
    0.396502,
    0.394423,
    0.310617,
    0.337499,
    0.326416,
    0.339933,
    0.280915,
    0.305725,
    0.080018,
    0.104977,
    0.081443,
    0.204690,
    0.354750,
    0.334964,
    0.403611,
    0.367496,
    0.354750,
    0.334964,
    0.081443,
    0.501199,
    0.538575,
    0.447454,
    0.486224,
    0.431723,
    0.470065,
    0.447454,
    0.486224,
    0.501199,
    0.488701,
    0.471405,
    0.017336,
    0.000000,
    0.452197,
    0.471405,
    0.057682,
    0.000000,
    1.414213,
    0.015731,
    0.000000
  };

  return coord[i];
}

static double schneiders_z(int i){
  double coord[105] = {
    0.500000,
    0.000000,
    -0.500000,
    1.000000,
    0.000000, 
    -1.000000,
    0.051666,
    -0.058015,
    -0.148140,
    0.071987,
    -0.057896,
    -0.181788,
    -0.016801,
    -0.054195,
    -0.104114,
    -0.015276,
    -0.054392,
    -0.110605,
    0.051666,
    -0.058015,
    -0.148140,
    0.071987,
    -0.057896,
    -0.181788,
    0.500000,
    0.000000,
    -0.500000,
    1.000000,
    0.000000,
    -1.000000,
    -0.208673,
    -0.162945,
    0.021476,
    0.389516,
    -0.157646,
    0.159885,
    -0.142645,
    -0.073557,
    -0.032793,
    0.060339,
    -0.136482,
    0.043449,
    -0.111103,
    -0.079664,
    -0.047879,
    -0.008734,
    -0.124554,
    0.008560,
    -0.142645,
    -0.073557,
    -0.032793,
    0.060339,
    -0.136482,
    0.043449,
    -0.208673,
    -0.162945,
    0.021476,
    0.389516,
    -0.041899,
    -0.680880,
    -0.103504,
    -0.392255,
    -0.065989,
    -0.212535,
    -0.093142,
    -0.227139,
    -0.056201,
    -0.124443,
    -0.087185,
    -0.182164,
    -0.065989,
    -0.212535,
    -0.093142,
    -0.227139,
    -0.041899,
    -0.680880,
    0.786284,
    0.443271,
    0.104202,
    0.144731,
    -0.005330,
    0.073926,
    0.104202,
    0.144731,
    0.786284,
    -0.364254,
    -0.282882,
    -0.189794,
    -0.182143,
    -0.127036,
    -0.148665,
    -0.189794,
    -0.182143,
    -0.364254,
    0.642222,
    0.666667,
    0.959658,
    1.000000,
    -0.455079,
    -0.666667,
    -0.844452,
    -1.000000,
    0.000000,
    -0.009020,
    0.000000
  };

  return coord[i];
}

static int schneiders_connect(int i,int j){
  int n0[88] = {
    0,1,6,7,12,13,18,19,41,39,37,
    41,36,40,47,45,43,47,42,46,53,51,
    49,53,48,52,35,57,55,35,54,34,34,
    35,65,63,64,62,69,67,68,66,73,71,
    72,70,61,75,60,74,60,61,79,78,81,
    80,83,82,77,84,77,88,87,90,89,92,
    91,86,93,86,57,24,95,85,2,99,84,
    74,97,104,104,97,26,35,35,24,35,24
  };

  int n1[88] = {
    1,2,7,8,13,14,19,20,39,6,38,
    37,37,36,45,12,44,43,43,42,51,18,
    50,49,49,48,57,24,56,55,55,54,40,
    41,63,11,62,10,67,17,66,16,71,23,
    70,22,75,29,74,28,64,65,78,9,80,
    15,82,21,84,27,79,87,8,89,14,91,
    20,93,26,88,35,57,94,86,85,98,77,
    60,96,103,28,27,99,55,102,25,31,102
  };

  int n2[88] = {
    4,5,10,11,16,17,22,23,38,7,7,
    36,8,87,44,13,13,42,14,89,50,19,
    19,48,20,91,56,25,25,54,26,93,46,
    47,62,10,78,9,66,16,80,15,70,22,
    82,21,74,28,84,27,68,69,39,6,45,
    12,51,18,57,24,81,63,11,67,17,71,
    23,75,29,90,33,94,33,93,98,93,76,
    58,76,58,74,84,2,26,2,26,2,0
  };

  int n3[88] = {
    3,4,9,10,15,16,21,22,37,38,8,
    40,87,88,43,44,14,46,89,90,49,50,
    20,52,91,92,55,56,26,34,93,86,52,
    53,64,62,79,78,68,66,81,80,72,70,
    83,82,60,74,77,84,72,73,41,39,47,
    45,53,51,35,57,83,65,63,69,67,73,
    71,61,75,92,94,95,0,98,99,26,96,
    103,3,4,103,96,102,102,31,102,102,95
  };

  int n4[88] = {
    6,7,12,13,18,19,24,25,35,33,31,
    35,30,34,41,39,37,41,36,40,47,45,
    43,47,42,46,53,51,49,53,48,52,86,
    34,61,59,60,58,65,63,64,62,69,67,
    68,66,73,71,72,70,77,60,77,76,79,
    78,81,80,83,82,35,86,85,88,87,90,
    89,92,91,61,84,27,97,59,5,101,74,
    75,104,101,101,104,93,34,34,57,33,57
  };

  int n5[88] = {
    7,8,13,14,19,20,25,26,33,0,32,
    31,31,30,39,6,38,37,37,36,45,12,
    44,43,43,42,51,18,50,49,49,48,88,
    40,59,5,58,4,63,11,62,10,67,17,
    66,16,71,23,70,22,79,64,76,3,78,
    9,80,15,82,21,41,85,2,87,8,89,
    14,91,20,65,77,84,96,61,59,100,60,
    61,103,100,29,28,98,54,86,56,32,35
  };

  int n6[88] = {
    10,11,16,17,22,23,28,29,32,1,1,
    30,2,85,38,7,7,36,8,87,44,13,
    13,42,14,89,50,19,19,48,20,91,90,
    46,58,4,76,3,62,10,78,9,66,16,
    80,15,70,22,82,21,81,68,33,0,39,
    6,45,12,51,18,47,59,5,63,11,67,
    17,71,23,69,76,96,76,75,100,75,58,
    59,58,59,75,74,85,93,85,55,1,33
  };

  int n7[88] = {
    9,10,15,16,21,22,27,28,31,32,2,
    34,85,86,37,38,8,40,87,88,43,44,
    14,46,89,90,49,50,20,52,91,92,92,
    52,60,58,77,76,64,62,79,78,68,66,
    81,80,72,70,83,82,83,72,35,33,41,
    39,47,45,53,51,53,61,59,65,63,69,
    67,73,71,73,96,97,3,100,101,29,103,
    100,4,5,100,103,86,86,30,35,0,94
  };

  if(i==0){
    return n0[j];
  }
  else if(i==1){
    return n1[j];
  }
  else if(i==2){
    return n2[j];
  }
  else if(i==3){
    return n3[j];
  }
  else if(i==4){
    return n4[j];
  }
  else if(i==5){
    return n5[j];
  }
  else if(i==6){
    return n6[j];
  }
  else{
    return n7[j];
  }
}


void subdivide_pyramid(MElement* element,
		       GRegion* gr, 
		       faceContainer &faceVertices,
		       std::vector<MHexahedron*> &dwarfs88)
{
  unsigned int i;
  int index1,index2,index3,index4;
  int index5,index6,index7,index8;
  SPoint3 point;
  std::vector<MVertex*> v;

  dwarfs88.resize(88);
  v.resize(105);
  
  for (int i=0;i<105;i++)v[i] = NULL;
  
  v[29] = element->getVertex(0);
  v[27] = element->getVertex(1);
  v[3] = element->getVertex(2);
  v[5] = element->getVertex(3);
  v[102] = element->getVertex(4);

  v[28] = element->getVertex(5);
  v[97] = element->getVertex(8);
  v[4] = element->getVertex(10);
  v[101] = element->getVertex(6);
  v[26] = element->getVertex(7);
  v[24] = element->getVertex(9);
  v[0] = element->getVertex(11);
  v[2] = element->getVertex(12);

  // the one in the middle of rect face
  v[104] = element->getVertex(13);

  faceContainer::iterator fIter;

  fIter = faceVertices.find(MFace(v[29],v[27],v[102]));
  if (fIter != faceVertices.end())
    v[25] = fIter->second[0];
  else {
    element->pnt(0.0,-0.666667,0.471405/1.414213,point);
    v[25] = new MVertex(point.x(),point.y(),point.z(),gr);
    gr->addMeshVertex(v[25]);
    faceVertices[MFace(v[29],v[27],v[102])].push_back(v[25]);
  }
  
  fIter = faceVertices.find(MFace(v[27],v[3],v[102]));
  if (fIter != faceVertices.end())
    v[95] = fIter->second[0];
  else {
    element->pnt(0.666667,0.0,0.471405/1.414213,point);
    v[95] = new MVertex(point.x(),point.y(),point.z(),gr);
    gr->addMeshVertex(v[95]);
    faceVertices[MFace(v[27],v[3],v[102])].push_back(v[95]);
  }

  fIter = faceVertices.find(MFace(v[3],v[5],v[102]));
  if (fIter != faceVertices.end())
    v[1] = fIter->second[0];
  else {
    element->pnt(0.0,0.666667,0.471405/1.414213,point);
    v[1] = new MVertex(point.x(),point.y(),point.z(),gr);
    gr->addMeshVertex(v[1]);
    faceVertices[MFace(v[3],v[5],v[102])].push_back(v[1]);
  }

  fIter = faceVertices.find(MFace(v[5],v[29],v[102]));
  if (fIter != faceVertices.end())
    v[99] = fIter->second[0];
  else {
    element->pnt(-0.666667,0.0,0.471405/1.414213,point);
    v[99] = new MVertex(point.x(),point.y(),point.z(),gr);
    gr->addMeshVertex(v[99]);
    faceVertices[MFace(v[5],v[29],v[102])].push_back(v[99]);
  }
  
  for(i=0;i<105;i++){
    if (!v[i]){
      element->pnt(schneiders_z(i),schneiders_x(i),schneiders_y(i)/1.414213,point);
      v[i] = new MVertex(point.x(),point.y(),point.z(),gr);
      gr->addMeshVertex(v[i]);
    }
  }

  for(i=0;i<88;i++){
    index1 = schneiders_connect(0,i);
    index2 = schneiders_connect(1,i);
    index3 = schneiders_connect(2,i);
    index4 = schneiders_connect(3,i);
    index5 = schneiders_connect(4,i);
    index6 = schneiders_connect(5,i);
    index7 = schneiders_connect(6,i);
    index8 = schneiders_connect(7,i);
    
    dwarfs88[i]=(new MHexahedron(v[index1],v[index2],
				v[index3],v[index4],
				v[index5],v[index6],
				v[index7],v[index8]));
  }
}

