// Gmsh - Copyright (C) 1997-2021 C. Geuzaine, J.-F. Remacle
//
// See the LICENSE.txt file for license information. Please report all
// issues on https://gitlab.onelab.info/gmsh/gmsh/issues.

#include "Transform.h"

StringXNumber TransformOptions_Number[] = {
  {GMSH_FULLRC, "A11", nullptr, 1.},
  {GMSH_FULLRC, "A12", nullptr, 0.},
  {GMSH_FULLRC, "A13", nullptr, 0.},
  {GMSH_FULLRC, "A21", nullptr, 0.},
  {GMSH_FULLRC, "A22", nullptr, 1.},
  {GMSH_FULLRC, "A23", nullptr, 0.},
  {GMSH_FULLRC, "A31", nullptr, 0.},
  {GMSH_FULLRC, "A32", nullptr, 0.},
  {GMSH_FULLRC, "A33", nullptr, 1.},
  {GMSH_FULLRC, "Tx", nullptr, 0.},
  {GMSH_FULLRC, "Ty", nullptr, 0.}, // cannot use T2 (reserved token in parser)
  {GMSH_FULLRC, "Tz", nullptr, 0.}, // cannot use T3 (reserved token in parser)
  {GMSH_FULLRC, "SwapOrientation", nullptr, 0.},
  {GMSH_FULLRC, "View", nullptr, -1.}};

extern "C" {
GMSH_Plugin *GMSH_RegisterTransformPlugin()
{
  return new GMSH_TransformPlugin();
}
}

std::string GMSH_TransformPlugin::getHelp() const
{
  return "Plugin(Transform) transforms the homogeneous "
         "node coordinates (x,y,z,1) of the elements in "
         "the view `View' by the matrix\n\n"
         "[`A11' `A12' `A13' `Tx']\n"
         "[`A21' `A22' `A23' `Ty']\n"
         "[`A31' `A32' `A33' `Tz'].\n\n"
         "If `SwapOrientation' is set, the orientation of the "
         "elements is reversed.\n\n"
         "If `View' < 0, the plugin is run on the current view.\n\n"
         "Plugin(Transform) is executed in-place.";
}

int GMSH_TransformPlugin::getNbOptions() const
{
  return sizeof(TransformOptions_Number) / sizeof(StringXNumber);
}

StringXNumber *GMSH_TransformPlugin::getOption(int iopt)
{
  return &TransformOptions_Number[iopt];
}

PView *GMSH_TransformPlugin::execute(PView *v)
{
  double mat[3][4];

  mat[0][0] = TransformOptions_Number[0].def;
  mat[0][1] = TransformOptions_Number[1].def;
  mat[0][2] = TransformOptions_Number[2].def;
  mat[1][0] = TransformOptions_Number[3].def;
  mat[1][1] = TransformOptions_Number[4].def;
  mat[1][2] = TransformOptions_Number[5].def;
  mat[2][0] = TransformOptions_Number[6].def;
  mat[2][1] = TransformOptions_Number[7].def;
  mat[2][2] = TransformOptions_Number[8].def;

  mat[0][3] = TransformOptions_Number[9].def;
  mat[1][3] = TransformOptions_Number[10].def;
  mat[2][3] = TransformOptions_Number[11].def;

  int swap = (int)TransformOptions_Number[12].def;
  int iView = (int)TransformOptions_Number[13].def;

  PView *v1 = getView(iView, v);
  if(!v1) return v;

  PViewData *data1 = v1->getData();

  if(data1->isNodeData()) {
    // tag all the nodes with "0" (the default tag)
    for(int step = 0; step < data1->getNumTimeSteps(); step++) {
      for(int ent = 0; ent < data1->getNumEntities(step); ent++) {
        for(int ele = 0; ele < data1->getNumElements(step, ent); ele++) {
          if(data1->skipElement(step, ent, ele)) continue;
          if(swap) data1->reverseElement(step, ent, ele);
          for(int nod = 0; nod < data1->getNumNodes(step, ent, ele); nod++)
            data1->tagNode(step, ent, ele, nod, 0);
        }
      }
    }
  }

  // transform all "0" nodes
  for(int step = 0; step < data1->getNumTimeSteps(); step++) {
    for(int ent = 0; ent < data1->getNumEntities(step); ent++) {
      for(int ele = 0; ele < data1->getNumElements(step, ent); ele++) {
        if(data1->skipElement(step, ent, ele)) continue;
        for(int nod = 0; nod < data1->getNumNodes(step, ent, ele); nod++) {
          double x, y, z;
          int tag = data1->getNode(step, ent, ele, nod, x, y, z);
          if(data1->isNodeData() && tag) continue;
          double x2, y2, z2;
          x2 = mat[0][0] * x + mat[0][1] * y + mat[0][2] * z + mat[0][3];
          y2 = mat[1][0] * x + mat[1][1] * y + mat[1][2] * z + mat[1][3];
          z2 = mat[2][0] * x + mat[2][1] * y + mat[2][2] * z + mat[2][3];
          data1->setNode(step, ent, ele, nod, x2, y2, z2);
          if(data1->isNodeData()) data1->tagNode(step, ent, ele, nod, 1);
        }
      }
    }
  }

  data1->finalize();

  v1->setChanged(true);
  return v1;
}
