/*! \file writegmsh2.cpp
*  \brief Export Netgen Mesh in the GMSH v2.xx File format
*  \author Philippose Rajan
*  \date 02 November 2008
*
*  This function extends the export capabilities of
*  Netgen to include the GMSH v2.xx File Format.
*
*  Current features of this function include:
*
*  1. Exports Triangles, Quadrangles and Tetrahedra \n
*  2. Supports upto second order elements of each type
*
*/


#include <mystdlib.h>

#include <myadt.hpp>
#include <linalg.hpp>
#include <csg.hpp>
#include <meshing.hpp>
#include "writeuser.hpp"

namespace netgen
{

  extern MeshingParameters mparam;

   // Mapping of entities from Netgen definitions to GMSH definitions
   enum GMSH_ELEMENTS {GMSH_TRIG = 2, GMSH_TRIG6 = 9,
      GMSH_QUAD = 3, GMSH_QUAD8 = 16,
      GMSH_TET = 4, GMSH_TET10 = 11};
   const int triGmsh[7] = {0,1,2,3,6,4,5};
   const int quadGmsh[9] = {0,1,2,3,4,5,8,6,7};
   const int tetGmsh[11] = {0,1,2,3,4,5,8,6,7,10,9};


   /*! GMSH v2.xx mesh format export function
   *
   *  This function extends the export capabilities of
   *  Netgen to include the GMSH v2.xx File Format.
   *
   *  Current features of this function include:
   *
   *  1. Exports Triangles, Quadrangles and Tetrahedra \n
   *  2. Supports upto second order elements of each type
   *
   */
   void WriteGmsh2Format (const Mesh & mesh,
      const filesystem::path & filename)
   {
      ofstream outfile (filename);
      outfile.precision(6);
      outfile.setf (ios::fixed, ios::floatfield);
      outfile.setf (ios::showpoint);

      int np = mesh.GetNP();  /// number of points in mesh
      int ne = mesh.GetNE();  /// number of 3D elements in mesh
      int nse = mesh.GetNSE();  /// number of surface elements (BC)
      // int i, j, k, l;


      /*
      * 3D section : Volume elements (currently only tetrahedra)
      */

      if ((ne > 0)
          && (mesh.VolumeElements().First().GetNP() <= 10)
          && (mesh.SurfaceElements().First().GetNP() <= 6))
      {
         cout << "Write GMSH v2.xx Format \n";
         cout << "The GMSH v2.xx export is currently available for elements upto 2nd Order\n" << endl;

         int inverttets = mparam.inverttets;
         int invertsurf = mparam.inverttrigs;

         /// Prepare GMSH 2.0 file (See GMSH 2.0 Documentation)
         outfile << "$MeshFormat\n";
         outfile << (float)2.0 << " "
            << (int)0 << " "
            << (int)sizeof(double) << "\n";
         outfile << "$EndMeshFormat\n";

         /// Write nodes
         outfile << "$Nodes\n";
         outfile << np << "\n";

         for (int i = 1; i <= np; i++)
         {
            const Point3d & p = mesh.Point(i);
            outfile << i << " "; /// node number
            outfile << p.X() << " ";
            outfile << p.Y() << " ";
            outfile << p.Z() << "\n";
         }

         outfile << "$EndNodes\n";

         /// write elements (both, surface elements and volume elements)
         outfile << "$Elements\n";
         outfile << ne + nse << "\n";  ////  number of elements + number of surfaces BC

         for (auto sei : Range(mesh.SurfaceElements()))
         {
            int elType = 0;

            Element2d el = mesh[sei]; // .SurfaceElement(i);
            if(invertsurf) el.Invert();
            
            if(el.GetNP() == 3) elType = GMSH_TRIG;	//// GMSH Type for a 3 node triangle
            if(el.GetNP() == 6) elType = GMSH_TRIG6;  //// GMSH Type for a 6 node triangle
            if(elType == 0)
            {
               cout << " Invalid surface element type for Gmsh 2.0 3D-Mesh Export Format !\n";
               return;
            }

            outfile << sei-IndexBASE(sei)+1;  
            outfile << " ";
            outfile << elType;
            outfile << " ";
            outfile << "2";                  //// Number of tags (2 => Physical and elementary entities)
            outfile << " ";
            outfile << mesh.GetFaceDescriptor (el.GetIndex()).BCProperty() << " ";
            /// that means that physical entity = elementary entity (arbitrary approach)
            outfile << mesh.GetFaceDescriptor (el.GetIndex()).BCProperty() << " ";
            for (int j = 1; j <= el.GetNP(); j++)
            {
               outfile << " ";
               outfile << el.PNum(triGmsh[j]);
            }
            outfile << "\n";
         }

         for (ElementIndex ei : Range(mesh.VolumeElements()))
         {
           int i = ei-IndexBASE(ei)+1;
            int elType = 0;

            Element el = mesh[ei];
            if (inverttets) el.Invert();

            if(el.GetNP() == 4) elType = GMSH_TET;    //// GMSH Element type for 4 node tetrahedron
            if(el.GetNP() == 10) elType = GMSH_TET10; //// GMSH Element type for 10 node tetrahedron
            if(elType == 0)
            {
               cout << " Invalid volume element type for Gmsh 2.0 3D-Mesh Export Format !\n";
               return;
            }

            outfile << nse + i;                       //// element number (Remember to add on surface elements)
            outfile << " ";
            outfile << elType;
            outfile << " ";
            outfile << "2";                   //// Number of tags (2 => Physical and elementary entities)
            outfile << " ";
            outfile << 100000 + el.GetIndex();
            /// that means that physical entity = elementary entity (arbitrary approach)
            outfile << " ";
            outfile << 100000 + el.GetIndex();   /// volume number
            outfile << " ";
            for (int j = 1; j <= el.GetNP(); j++)
            {
               outfile << " ";
               outfile << el.PNum(tetGmsh[j]);
            }
            outfile << "\n";
         }
         outfile << "$EndElements\n";
      }
      /*
      * End of 3D section
      */


      /*
      * 2D section : available for triangles and quadrangles
      *              upto 2nd Order
      */
      else if(ne == 0)   /// means that there's no 3D element
      {
         cout << "\n Write Gmsh v2.xx Surface Mesh (triangle and/or quadrangles upto 2nd Order)" << endl;

         /// Prepare GMSH 2.0 file (See GMSH 2.0 Documentation)
         outfile << "$MeshFormat\n";
         outfile << (float)2.0 << " "
            << (int)0 << " "
            << (int)sizeof(double) << "\n";
         outfile << "$EndMeshFormat\n";

         /// Write nodes
         outfile << "$Nodes\n";
         outfile << np << "\n";

         for (int i = 1; i <= np; i++)
         {
            const Point3d & p = mesh.Point(i);
            outfile << i << " "; /// node number
            outfile << p.X() << " ";
            outfile << p.Y() << " ";
            outfile << p.Z() << "\n";
         }
         outfile << "$EndNodes\n";

         /// write triangles & quadrangles
         outfile << "$Elements\n";
         outfile << nse << "\n";

         for (int k = 1; k <= nse; k++)
         {
            int elType = 0;

            const Element2d & el = mesh.SurfaceElement(k);

            if(el.GetNP() == 3) elType = GMSH_TRIG;   //// GMSH Type for a 3 node triangle
            if(el.GetNP() == 6) elType = GMSH_TRIG6;  //// GMSH Type for a 6 node triangle
            if(el.GetNP() == 4) elType = GMSH_QUAD;   //// GMSH Type for a 4 node quadrangle
            if(el.GetNP() == 8) elType = GMSH_QUAD8;  //// GMSH Type for an 8 node quadrangle
            if(elType == 0)
            {
               cout << " Invalid surface element type for Gmsh 2.0 2D-Mesh Export Format !\n";
               return;
            }

            outfile << k;
            outfile << " ";
            outfile << elType;
            outfile << " ";
            outfile << "2";
            outfile << " ";
            outfile << mesh.GetFaceDescriptor (el.GetIndex()).BCProperty() << " ";
            /// that means that physical entity = elementary entity (arbitrary approach)
            outfile << mesh.GetFaceDescriptor (el.GetIndex()).BCProperty() << " ";
            for (int l = 1; l <= el.GetNP(); l++)
            {
               outfile << " ";
               if((elType == GMSH_TRIG) || (elType == GMSH_TRIG6))
               {
                  outfile << el.PNum(triGmsh[l]);
               }
               else if((elType == GMSH_QUAD) || (elType == GMSH_QUAD8))
               {
                  outfile << el.PNum(quadGmsh[l]);
               }
            }
            outfile << "\n";
         }
         outfile << "$EndElements\n";
      }
      /*
      * End of 2D section
      */

      else
      {
         cout << " Invalid element type for Gmsh v2.xx Export Format !\n";
      }
   } // End: WriteGmsh2Format
static RegisterUserFormat reg_gmsh2 ("Gmsh2 Format", {".gmsh2"}, nullopt, WriteGmsh2Format);
} // End: namespace netgen


