// Copyright (c) 2010-2023, 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.
//
//   ------------------------------------------------------------------------
//   Extruder Miniapp: Extrude a low-dimensional mesh into a higher dimension
//   ------------------------------------------------------------------------
//
// This miniapp creates higher-dimensional meshes from lower-dimensional meshes
// by extrusion. Simple coordinate transformations can also be applied if
// desired. The initial mesh can be 1D or 2D. 1D meshes can be extruded in the
// y-direction first and then in the z-direction. 2D meshes can be triangular,
// quadrilateral, or contain both element types. The initial mesh can also be
// curved although NURBS meshes are not supported.
//
// The resulting mesh is displayed with GLVis (unless explicitly disabled) and
// is also written to the file "extruder.mesh".
//
// Compile with: make extruder
//
// Sample runs:
//    extruder
//    extruder -m ../../data/inline-segment.mesh -ny 8 -wy 2
//    extruder -m ../../data/inline-segment.mesh -ny 8 -wy 2 -nz 12 -hz 3
//    extruder -m ../../data/star.mesh -nz 3
//    extruder -m ../../data/star-mixed.mesh -nz 3
//    extruder -m ../../data/square-disc.mesh -nz 3
//    extruder -m ../../data/inline-segment.mesh -ny 8 -wy 2 -trans
//    extruder -m ../../data/inline-segment.mesh -ny 8 -wy 2 -nz 12 -hz 3 -trans
//    extruder -m ../../data/square-disc-p2.mesh -nz 16 -hz 2 -trans

#include "mfem.hpp"
#include <fstream>
#include <iostream>

using namespace mfem;
using namespace std;

void trans2D(const Vector&, Vector&);
void trans3D(const Vector&, Vector&);

int main(int argc, char *argv[])
{
   const char *mesh_file = "../../data/inline-quad.mesh";
   int order = -1;
   int ny = -1, nz = -1; // < 0: autoselect based on the initial mesh dimension
   double wy = 1.0, hz = 1.0;
   bool trans = false;
   bool visualization = 1;

   // Parse command line
   OptionsParser args(argc, argv);
   args.AddOption(&mesh_file, "-m", "--mesh",
                  "Input mesh to extrude.");
   args.AddOption(&order, "-o", "--mesh-order",
                  "Order (polynomial degree) of the mesh elements.");
   args.AddOption(&ny, "-ny", "--num-elem-in-y",
                  "Extrude a 1D mesh into ny elements in the y-direction.");
   args.AddOption(&wy, "-wy", "--width-in-y",
                  "Extrude a 1D mesh to a width wy in the y-direction.");
   args.AddOption(&nz, "-nz", "--num-elem-in-z",
                  "Extrude a 2D mesh into nz elements in the z-direction.");
   args.AddOption(&hz, "-hz", "--height-in-z",
                  "Extrude a 2D mesh to a height hz in the z-direction.");
   args.AddOption(&trans, "-trans", "--transform", "-no-trans",
                  "--no-transform",
                  "Enable or disable mesh transformation after extrusion.");
   args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
                  "--no-visualization",
                  "Enable or disable GLVis visualization.");
   args.Parse();
   if (!args.Good()) { args.PrintUsage(cout); return 1; }
   args.PrintOptions(cout);

   // Read the initial mesh
   Mesh *mesh = new Mesh(mesh_file, 1, 1);
   int dim = mesh->Dimension();

   // Autoselect ny and nz if not set on the command line or set to < 0 values
   switch (dim)
   {
      case 1:
         ny = (ny < 0) ? 1 : ny;
         nz = (nz < 0) ? 0 : nz;
         break;
      case 2:
         // ny is not used
         nz = (nz < 0) ? 1 : nz;
         break;
      default:
         cout << "Extruding " << dim << "D meshes is not (yet) supported."
              << endl;
         delete mesh;
         return 1;
   }

   // Determine the order to use for a transformed mesh
   int meshOrder = 1;
   if (mesh->GetNodalFESpace() != NULL)
   {
      meshOrder = mesh->GetNodalFESpace()->GetElementOrder(0);
   }
   if (order < 0 && trans)
   {
      order = meshOrder;
   }

   bool newMesh = false;

   if (dim == 1 && ny > 0)
   {
      cout << "Extruding 1D mesh to a width of " << wy
           << " using " << ny << " elements." << endl;

      Mesh *mesh2d = Extrude1D(mesh, ny, wy);
      delete mesh;
      mesh = mesh2d;
      dim = 2;
      if (trans)
      {
         if (order != meshOrder)
         {
            mesh->SetCurvature(order, false, 2, Ordering::byVDIM);
         }
         mesh->Transform(trans2D);
      }
      newMesh = true;
   }
   if (dim == 2 && nz > 0)
   {
      cout << "Extruding 2D mesh to a height of " << hz
           << " using " << nz << " elements." << endl;

      Mesh *mesh3d = Extrude2D(mesh, nz, hz);
      delete mesh;
      mesh = mesh3d;
      dim = 3;
      if (trans)
      {
         if (order != meshOrder)
         {
            mesh->SetCurvature(order, false, 3, Ordering::byVDIM);
         }
         mesh->Transform(trans3D);
      }
      newMesh = true;
   }

   if (newMesh)
   {
      if (visualization)
      {
         // GLVis server to visualize to
         char vishost[] = "localhost";
         int  visport   = 19916;
         socketstream sol_sock(vishost, visport);
         sol_sock.precision(8);
         sol_sock << "mesh\n" << *mesh << flush;
      }

      // Save the final mesh
      ofstream mesh_ofs("extruder.mesh");
      mesh_ofs.precision(8);
      mesh->Print(mesh_ofs);
   }
   else
   {
      cout << "No mesh extrusion performed." << endl;
   }

   delete mesh;
}

void trans2D(const Vector&x, Vector&p)
{
   p[0] = x[0] + 0.25 * sin(M_PI * x[1]);
   p[1] = x[1];
}

void trans3D(const Vector&x, Vector&p)
{
   double r = sqrt(x[0] * x[0] + x[1] * x[1]);
   double theta = atan2(x[1], x[0]);
   p[0] = r * cos(theta + 0.25 * M_PI * x[2]);
   p[1] = r * sin(theta + 0.25 * M_PI * x[2]);
   p[2] = x[2];
}

