/*=========================================================================

  Module:    V_GaussIntegration.cpp

  Copyright 2003,2006,2019 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
  Under the terms of Contract DE-NA0003525 with NTESS,
  the U.S. Government retains certain rights in this software.

  See LICENSE for details.

=========================================================================*/

/*
 *
 * GaussIntegration.cpp performs Gauss Integrations
 *
 * This file is part of VERDICT
 *
 */

#include "V_GaussIntegration.hpp"
#include "verdict.h"

namespace VERDICT_NAMESPACE
{
void GaussIntegration::initialize(int n, int m, int dim, int tri)
{
  numberGaussPoints = n;
  numberNodes = m;
  numberDims = dim;

  if (tri == 1)
  // triangular element
  {
    if (numberDims == 2)
    {
      totalNumberGaussPts = numberGaussPoints;
    }
    else if (numberDims == 3)
    {
      totalNumberGaussPts = numberGaussPoints;
    }
  }
  else if (tri == 0)
  {
    if (numberDims == 2)
    {
      totalNumberGaussPts = numberGaussPoints * numberGaussPoints;
    }
    else if (numberDims == 3)
    {
      totalNumberGaussPts = numberGaussPoints * numberGaussPoints * numberGaussPoints;
    }
  }
}

void GaussIntegration::get_shape_func(double shape_function[], double dndy1_at_gauss_pts[],
  double dndy2_at_gauss_pts[], double gauss_weight[])
{
  int i, j;
  for (i = 0; i < totalNumberGaussPts; i++)
  {
    for (j = 0; j < numberNodes; j++)
    {
      shape_function[i * maxNumberNodes + j] = shapeFunction[i][j];
      dndy1_at_gauss_pts[i * maxNumberNodes + j] = dndy1GaussPts[i][j];
      dndy2_at_gauss_pts[i * maxNumberNodes + j] = dndy2GaussPts[i][j];
    }
  }

  for (i = 0; i < totalNumberGaussPts; i++)
  {
    gauss_weight[i] = totalGaussWeight[i];
  }
}

void GaussIntegration::get_shape_func(double shape_function[], double dndy1_at_gauss_pts[],
  double dndy2_at_gauss_pts[], double dndy3_at_gauss_pts[], double gauss_weight[])
{
  int i, j;
  for (i = 0; i < totalNumberGaussPts; i++)
  {
    for (j = 0; j < numberNodes; j++)
    {
      shape_function[i * maxNumberNodes + j] = shapeFunction[i][j];
      dndy1_at_gauss_pts[i * maxNumberNodes + j] = dndy1GaussPts[i][j];
      dndy2_at_gauss_pts[i * maxNumberNodes + j] = dndy2GaussPts[i][j];
      dndy3_at_gauss_pts[i * maxNumberNodes + j] = dndy3GaussPts[i][j];
    }
  }

  for (i = 0; i < totalNumberGaussPts; i++)
  {
    gauss_weight[i] = totalGaussWeight[i];
  }
}

void GaussIntegration::get_gauss_pts_and_weight()
{

  switch (numberGaussPoints)
  {
    case 1:
      gaussPointY[0] = 0.0;
      gaussWeight[0] = 2.0;
      break;
    case 2:
      gaussPointY[0] = -0.577350269189626;
      gaussPointY[1] = 0.577350269189626;
      gaussWeight[0] = 1.0;
      gaussWeight[1] = 1.0;
      break;
    case 3:
      gaussPointY[0] = -0.774596669241483;
      gaussPointY[1] = 0.0;
      gaussPointY[2] = 0.774596669241483;
      gaussWeight[0] = 0.555555555555555;
      gaussWeight[1] = 0.888888888888889;
      gaussWeight[2] = 0.555555555555555;
      break;
  }
}

void GaussIntegration::calculate_shape_function_2d_quad()
{
  int ife = 0, i, j;
  double y1, y2;
  get_gauss_pts_and_weight();

  switch (numberNodes)
  {
    case 4:
      for (i = 0; i < numberGaussPoints; i++)
      {
        for (j = 0; j < numberGaussPoints; j++)
        {
          y1 = gaussPointY[i];
          y2 = gaussPointY[j];
          shapeFunction[ife][0] = 0.25 * (1 - y1) * (1 - y2);
          shapeFunction[ife][1] = 0.25 * (1 + y1) * (1 - y2);
          shapeFunction[ife][2] = 0.25 * (1 + y1) * (1 + y2);
          shapeFunction[ife][3] = 0.25 * (1 - y1) * (1 + y2);

          dndy1GaussPts[ife][0] = -0.25 * (1 - y2);
          dndy1GaussPts[ife][1] = 0.25 * (1 - y2);
          dndy1GaussPts[ife][2] = 0.25 * (1 + y2);
          dndy1GaussPts[ife][3] = -0.25 * (1 + y2);

          dndy2GaussPts[ife][0] = -0.25 * (1 - y1);
          dndy2GaussPts[ife][1] = -0.25 * (1 + y1);
          dndy2GaussPts[ife][2] = 0.25 * (1 + y1);
          dndy2GaussPts[ife][3] = 0.25 * (1 - y1);

          totalGaussWeight[ife] = gaussWeight[i] * gaussWeight[j];
          ife++;
        }
      }
      break;
    case 8:
      for (i = 0; i < numberGaussPoints; i++)
      {
        for (j = 0; j < numberGaussPoints; j++)
        {
          y1 = gaussPointY[i];
          y2 = gaussPointY[j];
          shapeFunction[ife][0] = 0.25 * (1 - y1) * (1 - y2) * (-y1 - y2 - 1);
          shapeFunction[ife][1] = 0.25 * (1 + y1) * (1 - y2) * (y1 - y2 - 1);
          shapeFunction[ife][2] = 0.25 * (1 + y1) * (1 + y2) * (y1 + y2 - 1);
          shapeFunction[ife][3] = 0.25 * (1 - y1) * (1 + y2) * (-y1 + y2 - 1);
          shapeFunction[ife][4] = 0.5 * (1 - y1 * y1) * (1 - y2);
          shapeFunction[ife][5] = 0.5 * (1 - y2 * y2) * (1 + y1);
          shapeFunction[ife][6] = 0.5 * (1 - y1 * y1) * (1 + y2);
          shapeFunction[ife][7] = 0.5 * (1 - y2 * y2) * (1 - y1);

          dndy1GaussPts[ife][0] = 0.25 * (1 - y2) * (2.0 * y1 + y2);
          dndy1GaussPts[ife][1] = 0.25 * (1 - y2) * (2.0 * y1 - y2);
          dndy1GaussPts[ife][2] = 0.25 * (1 + y2) * (2.0 * y1 + y2);
          dndy1GaussPts[ife][3] = 0.25 * (1 + y2) * (2.0 * y1 - y2);

          dndy1GaussPts[ife][4] = -y1 * (1 - y2);
          dndy1GaussPts[ife][5] = 0.5 * (1 - y2 * y2);
          dndy1GaussPts[ife][6] = -y1 * (1 + y2);
          dndy1GaussPts[ife][7] = -0.5 * (1 - y2 * y2);

          dndy2GaussPts[ife][0] = 0.25 * (1 - y1) * (2.0 * y2 + y1);
          dndy2GaussPts[ife][1] = 0.25 * (1 + y1) * (2.0 * y2 - y1);
          dndy2GaussPts[ife][2] = 0.25 * (1 + y1) * (2.0 * y2 + y1);
          dndy2GaussPts[ife][3] = 0.25 * (1 - y1) * (2.0 * y2 - y1);

          dndy2GaussPts[ife][4] = -0.5 * (1 - y1 * y1);
          dndy2GaussPts[ife][5] = -y2 * (1 + y1);
          dndy2GaussPts[ife][6] = 0.5 * (1 - y1 * y1);
          dndy2GaussPts[ife][7] = -y2 * (1 - y1);

          totalGaussWeight[ife] = gaussWeight[i] * gaussWeight[j];
          ife++;
        }
      }
      break;
  }
}

void GaussIntegration::calculate_shape_function_3d_hex()
{
  int ife = 0, i, j, k, node_id;
  double y1, y2, y3, sign_node_y1, sign_node_y2, sign_node_y3;
  double y1_term, y2_term, y3_term, y123_temp;

  get_gauss_pts_and_weight();

  switch (numberNodes)
  {
    case 8:
      for (i = 0; i < numberGaussPoints; i++)
      {
        for (j = 0; j < numberGaussPoints; j++)
        {
          for (k = 0; k < numberGaussPoints; k++)
          {
            y1 = gaussPointY[i];
            y2 = gaussPointY[j];
            y3 = gaussPointY[k];

            for (node_id = 0; node_id < numberNodes; node_id++)
            {
              get_signs_for_node_local_coord_hex(node_id, sign_node_y1, sign_node_y2, sign_node_y3);

              y1_term = 1 + sign_node_y1 * y1;
              y2_term = 1 + sign_node_y2 * y2;
              y3_term = 1 + sign_node_y3 * y3;

              shapeFunction[ife][node_id] = 0.125 * y1_term * y2_term * y3_term;
              dndy1GaussPts[ife][node_id] = 0.125 * sign_node_y1 * y2_term * y3_term;
              dndy2GaussPts[ife][node_id] = 0.125 * sign_node_y2 * y1_term * y3_term;
              dndy3GaussPts[ife][node_id] = 0.125 * sign_node_y3 * y1_term * y2_term;
            }
            totalGaussWeight[ife] = gaussWeight[i] * gaussWeight[j] * gaussWeight[k];
            ife++;
          }
        }
      }
      break;
    case 20:
      for (i = 0; i < numberGaussPoints; i++)
      {
        for (j = 0; j < numberGaussPoints; j++)
        {
          for (k = 0; k < numberGaussPoints; k++)
          {
            y1 = gaussPointY[i];
            y2 = gaussPointY[j];
            y3 = gaussPointY[k];

            for (node_id = 0; node_id < numberNodes; node_id++)
            {
              get_signs_for_node_local_coord_hex(node_id, sign_node_y1, sign_node_y2, sign_node_y3);

              y1_term = 1 + sign_node_y1 * y1;
              y2_term = 1 + sign_node_y2 * y2;
              y3_term = 1 + sign_node_y3 * y3;
              y123_temp = sign_node_y1 * y1 + sign_node_y2 * y2 + sign_node_y3 * y3 - 2.;

              switch (node_id)
              {
                case 0:
                case 1:
                case 2:
                case 3:
                case 4:
                case 5:
                case 6:
                case 7:
                {
                  shapeFunction[ife][node_id] = 0.125 * y1_term * y2_term * y3_term * y123_temp;
                  dndy1GaussPts[ife][node_id] =
                    0.125 * sign_node_y1 * y123_temp * y2_term * y3_term +
                    0.125 * y1_term * y2_term * y3_term * sign_node_y1;
                  dndy2GaussPts[ife][node_id] =
                    0.125 * sign_node_y2 * y1_term * y3_term * y123_temp +
                    0.125 * y1_term * y2_term * y3_term * sign_node_y2;
                  dndy3GaussPts[ife][node_id] =
                    0.125 * sign_node_y3 * y1_term * y2_term * y123_temp +
                    0.125 * y1_term * y2_term * y3_term * sign_node_y3;
                  break;
                }
                case 8:
                case 10:
                case 16:
                case 18:
                {
                  shapeFunction[ife][node_id] = 0.25 * (1 - y1 * y1) * y2_term * y3_term;
                  dndy1GaussPts[ife][node_id] = -0.5 * y1 * y2_term * y3_term;
                  dndy2GaussPts[ife][node_id] = 0.25 * (1 - y1 * y1) * sign_node_y2 * y3_term;
                  dndy3GaussPts[ife][node_id] = 0.25 * (1 - y1 * y1) * y2_term * sign_node_y3;
                  break;
                }
                case 9:
                case 11:
                case 17:
                case 19:
                {
                  shapeFunction[ife][node_id] = 0.25 * (1 - y2 * y2) * y1_term * y3_term;
                  dndy1GaussPts[ife][node_id] = 0.25 * (1 - y2 * y2) * sign_node_y1 * y3_term;
                  dndy2GaussPts[ife][node_id] = -0.5 * y2 * y1_term * y3_term;
                  dndy3GaussPts[ife][node_id] = 0.25 * (1 - y2 * y2) * y1_term * sign_node_y3;
                  break;
                }
                case 12:
                case 13:
                case 14:
                case 15:
                {
                  shapeFunction[ife][node_id] = 0.25 * (1 - y3 * y3) * y1_term * y2_term;
                  dndy1GaussPts[ife][node_id] = 0.25 * (1 - y3 * y3) * sign_node_y1 * y2_term;
                  dndy2GaussPts[ife][node_id] = 0.25 * (1 - y3 * y3) * y1_term * sign_node_y2;
                  dndy3GaussPts[ife][node_id] = -0.5 * y3 * y1_term * y2_term;
                  break;
                }
              }
            }
            totalGaussWeight[ife] = gaussWeight[i] * gaussWeight[j] * gaussWeight[k];
            ife++;
          }
        }
      }
      break;
  }
}

void GaussIntegration::calculate_derivative_at_nodes(
  double dndy1_at_nodes[][maxNumberNodes], double dndy2_at_nodes[][maxNumberNodes])
{
  double y1 = 0., y2 = 0.;
  int i;
  for (i = 0; i < numberNodes; i++)
  {
    switch (i)
    {
      case 0:
        y1 = -1.;
        y2 = -1.;
        break;

      case 1:
        y1 = 1.;
        y2 = -1.;
        break;

      case 2:
        y1 = 1.;
        y2 = 1.;
        break;

      case 3:
        y1 = -1.;
        y2 = 1.;
        break;

      // mid-side nodes if there is any
      case 4:
        y1 = 0.;
        y2 = -1.;
        break;

      case 5:
        y1 = 1.;
        y2 = 0.;
        break;

      case 6:
        y1 = 0.;
        y2 = 1.;
        break;

      case 7:
        y1 = -1.;
        y2 = 0.;
        break;
    }

    switch (numberNodes)
    {
      case 4:
        // dn_i/dy1 evaluated at node i
        dndy1_at_nodes[i][0] = -0.25 * (1 - y2);
        dndy1_at_nodes[i][1] = 0.25 * (1 - y2);
        dndy1_at_nodes[i][2] = 0.25 * (1 + y2);
        dndy1_at_nodes[i][3] = -0.25 * (1 + y2);

        // dn_i/dy2 evaluated at node i
        dndy2_at_nodes[i][0] = -0.25 * (1 - y1);
        dndy2_at_nodes[i][1] = -0.25 * (1 + y1);
        dndy2_at_nodes[i][2] = 0.25 * (1 + y1);
        dndy2_at_nodes[i][3] = 0.25 * (1 - y1);
        break;

      case 8:
        dndy1_at_nodes[i][0] = 0.25 * (1 - y2) * (2.0 * y1 + y2);
        dndy1_at_nodes[i][1] = 0.25 * (1 - y2) * (2.0 * y1 - y2);
        dndy1_at_nodes[i][2] = 0.25 * (1 + y2) * (2.0 * y1 + y2);
        dndy1_at_nodes[i][3] = 0.25 * (1 + y2) * (2.0 * y1 - y2);

        dndy1_at_nodes[i][4] = -y1 * (1 - y2);
        dndy1_at_nodes[i][5] = 0.5 * (1 - y2 * y2);
        dndy1_at_nodes[i][6] = -y1 * (1 + y2);
        dndy1_at_nodes[i][7] = -0.5 * (1 - y2 * y2);

        dndy2_at_nodes[i][0] = 0.25 * (1 - y1) * (2.0 * y2 + y1);
        dndy2_at_nodes[i][1] = 0.25 * (1 + y1) * (2.0 * y2 - y1);
        dndy2_at_nodes[i][2] = 0.25 * (1 + y1) * (2.0 * y2 + y1);
        dndy2_at_nodes[i][3] = 0.25 * (1 - y1) * (2.0 * y2 - y1);

        dndy2_at_nodes[i][4] = -0.5 * (1 - y1 * y1);
        dndy2_at_nodes[i][5] = -y2 * (1 + y1);
        dndy2_at_nodes[i][6] = 0.5 * (1 - y1 * y1);
        dndy2_at_nodes[i][7] = -y2 * (1 - y1);
        break;
    }
  }
}

void GaussIntegration::calculate_derivative_at_nodes_3d(double dndy1_at_nodes[][maxNumberNodes],
  double dndy2_at_nodes[][maxNumberNodes], double dndy3_at_nodes[][maxNumberNodes])
{
  double y1, y2, y3, sign_node_y1, sign_node_y2, sign_node_y3;
  double y1_term, y2_term, y3_term, y123_temp;
  int node_id, node_id_2;
  for (node_id = 0; node_id < numberNodes; node_id++)
  {
    get_signs_for_node_local_coord_hex(node_id, y1, y2, y3);

    switch (numberNodes)
    {
      case 8:
        for (node_id_2 = 0; node_id_2 < numberNodes; node_id_2++)
        {
          get_signs_for_node_local_coord_hex(node_id_2, sign_node_y1, sign_node_y2, sign_node_y3);
          y1_term = 1 + sign_node_y1 * y1;
          y2_term = 1 + sign_node_y2 * y2;
          y3_term = 1 + sign_node_y3 * y3;

          dndy1_at_nodes[node_id][node_id_2] = 0.125 * sign_node_y1 * y2_term * y3_term;

          dndy2_at_nodes[node_id][node_id_2] = 0.125 * sign_node_y2 * y1_term * y3_term;

          dndy3_at_nodes[node_id][node_id_2] = 0.125 * sign_node_y3 * y1_term * y2_term;
        }
        break;
      case 20:
        for (node_id_2 = 0; node_id_2 < numberNodes; node_id_2++)
        {
          get_signs_for_node_local_coord_hex(node_id_2, sign_node_y1, sign_node_y2, sign_node_y3);

          y1_term = 1 + sign_node_y1 * y1;
          y2_term = 1 + sign_node_y2 * y2;
          y3_term = 1 + sign_node_y3 * y3;
          y123_temp = sign_node_y1 * y1 + sign_node_y2 * y2 + sign_node_y3 * y3 - 2.;
          switch (node_id_2)
          {
            case 0:
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
            case 6:
            case 7:
            {
              dndy1_at_nodes[node_id][node_id_2] =
                0.125 * sign_node_y1 * y2_term * y3_term * y123_temp +
                0.125 * y1_term * y2_term * y3_term * sign_node_y1;
              dndy2_at_nodes[node_id][node_id_2] =
                0.125 * sign_node_y2 * y1_term * y3_term * y123_temp +
                0.125 * y1_term * y2_term * y3_term * sign_node_y2;
              dndy3_at_nodes[node_id][node_id_2] =
                0.125 * sign_node_y3 * y1_term * y2_term * y123_temp +
                0.125 * y1_term * y2_term * y3_term * sign_node_y3;
              break;
            }
            case 8:
            case 10:
            case 16:
            case 18:
            {
              dndy1_at_nodes[node_id][node_id_2] = -0.5 * y1 * y2_term * y3_term;
              dndy2_at_nodes[node_id][node_id_2] = 0.25 * (1 - y1 * y1) * sign_node_y2 * y3_term;
              dndy3_at_nodes[node_id][node_id_2] = 0.25 * (1 - y1 * y1) * y2_term * sign_node_y3;
              break;
            }
            case 9:
            case 11:
            case 17:
            case 19:
            {
              dndy1_at_nodes[node_id][node_id_2] = 0.25 * (1 - y2 * y2) * sign_node_y1 * y3_term;
              dndy2_at_nodes[node_id][node_id_2] = -0.5 * y2 * y1_term * y3_term;
              dndy3_at_nodes[node_id][node_id_2] = 0.25 * (1 - y2 * y2) * y1_term * sign_node_y3;
              break;
            }
            case 12:
            case 13:
            case 14:
            case 15:
            {
              dndy1_at_nodes[node_id][node_id_2] = 0.25 * (1 - y3 * y3) * sign_node_y1 * y2_term;
              dndy2_at_nodes[node_id][node_id_2] = 0.25 * (1 - y3 * y3) * y1_term * sign_node_y2;
              dndy3_at_nodes[node_id][node_id_2] = -0.5 * y3 * y1_term * y2_term;
              break;
            }
          }
        }
        break;
    }
  }
}

void GaussIntegration::get_signs_for_node_local_coord_hex(
  int node_id, double& sign_node_y1, double& sign_node_y2, double& sign_node_y3)
{
  switch (node_id)
  {
    case 0:
      sign_node_y1 = -1.;
      sign_node_y2 = -1.;
      sign_node_y3 = -1.;
      break;
    case 1:
      sign_node_y1 = 1.;
      sign_node_y2 = -1.;
      sign_node_y3 = -1.;
      break;
    case 2:
      sign_node_y1 = 1.;
      sign_node_y2 = 1.;
      sign_node_y3 = -1.;
      break;
    case 3:
      sign_node_y1 = -1.;
      sign_node_y2 = 1.;
      sign_node_y3 = -1.;
      break;
    case 4:
      sign_node_y1 = -1.;
      sign_node_y2 = -1.;
      sign_node_y3 = 1.;
      break;
    case 5:
      sign_node_y1 = 1.;
      sign_node_y2 = -1.;
      sign_node_y3 = 1.;
      break;
    case 6:
      sign_node_y1 = 1.;
      sign_node_y2 = 1.;
      sign_node_y3 = 1.;
      break;
    case 7:
      sign_node_y1 = -1.;
      sign_node_y2 = 1.;
      sign_node_y3 = 1.;
      break;
    case 8:
      sign_node_y1 = 0;
      sign_node_y2 = -1.;
      sign_node_y3 = -1.;
      break;
    case 9:
      sign_node_y1 = 1.;
      sign_node_y2 = 0;
      sign_node_y3 = -1.;
      break;
    case 10:
      sign_node_y1 = 0;
      sign_node_y2 = 1.;
      sign_node_y3 = -1.;
      break;
    case 11:
      sign_node_y1 = -1.;
      sign_node_y2 = 0.;
      sign_node_y3 = -1.;
      break;
    case 12:
      sign_node_y1 = -1.;
      sign_node_y2 = -1.;
      sign_node_y3 = 0.;
      break;
    case 13:
      sign_node_y1 = 1.;
      sign_node_y2 = -1.;
      sign_node_y3 = 0.;
      break;
    case 14:
      sign_node_y1 = 1.;
      sign_node_y2 = 1.;
      sign_node_y3 = 0.;
      break;
    case 15:
      sign_node_y1 = -1.;
      sign_node_y2 = 1.;
      sign_node_y3 = 0.;
      break;
    case 16:
      sign_node_y1 = 0;
      sign_node_y2 = -1.;
      sign_node_y3 = 1.;
      break;
    case 17:
      sign_node_y1 = 1.;
      sign_node_y2 = 0;
      sign_node_y3 = 1.;
      break;
    case 18:
      sign_node_y1 = 0;
      sign_node_y2 = 1.;
      sign_node_y3 = 1.;
      break;
    case 19:
      sign_node_y1 = -1.;
      sign_node_y2 = 0.;
      sign_node_y3 = 1.;
      break;
    default:
      // Should not be possible to get here, but if we do, at least results will be consistent, not
      // random
      sign_node_y1 = 0.;
      sign_node_y2 = 0.;
      sign_node_y3 = 0.;
      break;
  }
}

void GaussIntegration::get_tri_rule_pts_and_weight()
{
  // get triangular rule integration points and weight
  switch (numberGaussPoints)
  {
    case 6:
      y1Area[0] = 0.09157621;
      y2Area[0] = 0.09157621;

      y1Area[1] = 0.09157621;
      y2Area[1] = 0.8168476;

      y1Area[2] = 0.8168476;
      y2Area[2] = 0.09157621;

      y1Area[3] = 0.4459485;
      y2Area[3] = 0.4459485;

      y1Area[4] = 0.4459485;
      y2Area[4] = 0.1081030;

      y1Area[5] = 0.1081030;
      y2Area[5] = 0.4459485;

      int i;
      for (i = 0; i < 3; i++)
      {
        totalGaussWeight[i] = 0.06348067;
      }
      for (i = 3; i < 6; i++)
      {
        totalGaussWeight[i] = 0.1289694;
      }
      break;
  }
}

void GaussIntegration::calculate_shape_function_2d_tri()
{
  int ife;
  double y1, y2, y3;
  get_tri_rule_pts_and_weight();

  for (ife = 0; ife < totalNumberGaussPts; ife++)
  {
    y1 = y1Area[ife];
    y2 = y2Area[ife];
    y3 = 1.0 - y1 - y2;

    shapeFunction[ife][0] = y1 * (2. * y1 - 1.);
    shapeFunction[ife][1] = y2 * (2. * y2 - 1.);
    shapeFunction[ife][2] = y3 * (2. * y3 - 1.);

    shapeFunction[ife][3] = 4. * y1 * y2;
    shapeFunction[ife][4] = 4. * y2 * y3;
    shapeFunction[ife][5] = 4. * y1 * y3;

    dndy1GaussPts[ife][0] = 4 * y1 - 1.;
    dndy1GaussPts[ife][1] = 0;
    dndy1GaussPts[ife][2] = 1 - 4. * y3;

    dndy1GaussPts[ife][3] = 4. * y2;
    dndy1GaussPts[ife][4] = -4. * y2;
    dndy1GaussPts[ife][5] = 4. * (1 - 2 * y1 - y2);

    dndy2GaussPts[ife][0] = 0.0;
    dndy2GaussPts[ife][1] = 4. * y2 - 1.;
    dndy2GaussPts[ife][2] = 1 - 4. * y3;

    dndy2GaussPts[ife][3] = 4. * y1;
    dndy2GaussPts[ife][4] = 4. * (1 - y1 - 2. * y2);
    dndy2GaussPts[ife][5] = -4. * y1;
  }
}

void GaussIntegration::calculate_derivative_at_nodes_2d_tri(
  double dndy1_at_nodes[][maxNumberNodes], double dndy2_at_nodes[][maxNumberNodes])
{
  double y1 = 0., y2 = 0., y3;
  int i;
  for (i = 0; i < numberNodes; i++)
  {
    switch (i)
    {
      case 0:
        y1 = 1.;
        y2 = 0.;
        break;
      case 1:
        y1 = 0.;
        y2 = 1.;
        break;
      case 2:
        y1 = 0.;
        y2 = 0.;
        break;
      case 3:
        y1 = 0.5;
        y2 = 0.5;
        break;
      case 4:
        y1 = 0.;
        y2 = 0.5;
        break;
      case 5:
        y1 = 0.5;
        y2 = 0.0;
        break;
    }

    y3 = 1. - y1 - y2;

    dndy1_at_nodes[i][0] = 4 * y1 - 1.;
    dndy1_at_nodes[i][1] = 0;
    dndy1_at_nodes[i][2] = 1 - 4. * y3;

    dndy1_at_nodes[i][3] = 4. * y2;
    dndy1_at_nodes[i][4] = -4. * y2;
    dndy1_at_nodes[i][5] = 4. * (1 - 2 * y1 - y2);

    dndy2_at_nodes[i][0] = 0.0;
    dndy2_at_nodes[i][1] = 4. * y2 - 1.;
    dndy2_at_nodes[i][2] = 1 - 4. * y3;

    dndy2_at_nodes[i][3] = 4. * y1;
    dndy2_at_nodes[i][4] = 4. * (1 - y1 - 2. * y2);
    dndy2_at_nodes[i][5] = -4. * y1;
  }
}

void GaussIntegration::get_tet_rule_pts_and_weight()
{
  // get tetrahedron rule integration points and weight
  double a, b;
  switch (numberGaussPoints)
  {
    case 1:
      // 1 integration point formula, degree of precision 1
      y1Volume[0] = 0.25;
      y2Volume[0] = 0.25;
      y3Volume[0] = 0.25;
      y4Volume[0] = 0.25;
      totalGaussWeight[0] = 1.;
      break;
    case 4:
      // 4 integration points formula, degree of precision 2
      a = 0.58541020;
      b = 0.13819660;

      y1Volume[0] = a;
      y2Volume[0] = b;
      y3Volume[0] = b;
      y4Volume[0] = b;

      y1Volume[1] = b;
      y2Volume[1] = a;
      y3Volume[1] = b;
      y4Volume[1] = b;

      y1Volume[2] = b;
      y2Volume[2] = b;
      y3Volume[2] = a;
      y4Volume[2] = b;

      y1Volume[3] = b;
      y2Volume[3] = b;
      y3Volume[3] = b;
      y4Volume[3] = a;

      int i;
      for (i = 0; i < 4; i++)
      {
        totalGaussWeight[i] = 0.25;
      }
      break;
  }
}

void GaussIntegration::calculate_shape_function_3d_tet()
{
  int ife;
  double y1, y2, y3, y4;
  get_tet_rule_pts_and_weight();

  switch (numberNodes)
  {
    case 10: // 10 nodes quadratic tet
    {
      for (ife = 0; ife < totalNumberGaussPts; ife++)
      {
        // y1,y2,y3,y4 are the volume coordinates
        y1 = y1Volume[ife];
        y2 = y2Volume[ife];
        y3 = y3Volume[ife];
        y4 = y4Volume[ife];

        // shape function is the same as in ABAQUS
        // it is different from that in all the FEA book
        // in which node is the first node
        // here at node 1 y4=1
        shapeFunction[ife][0] = y4 * (2. * y4 - 1.);
        shapeFunction[ife][1] = y1 * (2. * y1 - 1.);
        shapeFunction[ife][2] = y2 * (2. * y2 - 1.);
        shapeFunction[ife][3] = y3 * (2. * y3 - 1.);

        shapeFunction[ife][4] = 4. * y1 * y4;
        shapeFunction[ife][5] = 4. * y1 * y2;
        shapeFunction[ife][6] = 4. * y2 * y4;
        shapeFunction[ife][7] = 4. * y3 * y4;
        shapeFunction[ife][8] = 4. * y1 * y3;
        shapeFunction[ife][9] = 4. * y2 * y3;

        dndy1GaussPts[ife][0] = 1 - 4 * y4;
        dndy1GaussPts[ife][1] = 4 * y1 - 1.;
        dndy1GaussPts[ife][2] = 0;
        dndy1GaussPts[ife][3] = 0;

        dndy1GaussPts[ife][4] = 4. * (y4 - y1);
        dndy1GaussPts[ife][5] = 4. * y2;
        dndy1GaussPts[ife][6] = -4. * y2;
        dndy1GaussPts[ife][7] = -4. * y3;
        dndy1GaussPts[ife][8] = 4. * y3;
        dndy1GaussPts[ife][9] = 0;

        dndy2GaussPts[ife][0] = 1 - 4 * y4;
        dndy2GaussPts[ife][1] = 0;
        dndy2GaussPts[ife][2] = 4. * y2 - 1.;
        dndy2GaussPts[ife][3] = 0;

        dndy2GaussPts[ife][4] = -4. * y1;
        dndy2GaussPts[ife][5] = 4. * y1;
        dndy2GaussPts[ife][6] = 4. * (y4 - y2);
        dndy2GaussPts[ife][7] = -4. * y3;
        dndy2GaussPts[ife][8] = 0.;
        dndy2GaussPts[ife][9] = 4. * y3;

        dndy3GaussPts[ife][0] = 1 - 4 * y4;
        dndy3GaussPts[ife][1] = 0;
        dndy3GaussPts[ife][2] = 0;
        dndy3GaussPts[ife][3] = 4. * y3 - 1.;

        dndy3GaussPts[ife][4] = -4. * y1;
        dndy3GaussPts[ife][5] = 0;
        dndy3GaussPts[ife][6] = -4. * y2;
        dndy3GaussPts[ife][7] = 4. * (y4 - y3);
        dndy3GaussPts[ife][8] = 4. * y1;
        dndy3GaussPts[ife][9] = 4. * y2;
      }
      break;
    }
    case 4: // four node linear tet for debug purpose
    {
      for (ife = 0; ife < totalNumberGaussPts; ife++)
      {
        y1 = y1Volume[ife];
        y2 = y2Volume[ife];
        y3 = y3Volume[ife];
        y4 = y4Volume[ife];

        shapeFunction[ife][0] = y4;
        shapeFunction[ife][1] = y1;
        shapeFunction[ife][2] = y2;
        shapeFunction[ife][3] = y3;

        dndy1GaussPts[ife][0] = -1.;
        dndy1GaussPts[ife][1] = 1;
        dndy1GaussPts[ife][2] = 0;
        dndy1GaussPts[ife][3] = 0;

        dndy2GaussPts[ife][0] = -1.;
        dndy2GaussPts[ife][1] = 0;
        dndy2GaussPts[ife][2] = 1;
        dndy2GaussPts[ife][3] = 0;

        dndy3GaussPts[ife][0] = -1.;
        dndy3GaussPts[ife][1] = 0;
        dndy3GaussPts[ife][2] = 0;
        dndy3GaussPts[ife][3] = 1;
      }
      break;
    }
  }
}

void GaussIntegration::calculate_derivative_at_nodes_3d_tet(double dndy1_at_nodes[][maxNumberNodes],
  double dndy2_at_nodes[][maxNumberNodes], double dndy3_at_nodes[][maxNumberNodes])
{
  double y1, y2, y3, y4;
  int i;

  switch (numberNodes)
  {
    case 10:
    {
      for (i = 0; i < numberNodes; i++)
      {
        get_node_local_coord_tet(i, y1, y2, y3, y4);

        dndy1_at_nodes[i][0] = 1 - 4 * y4;
        dndy1_at_nodes[i][1] = 4 * y1 - 1.;
        dndy1_at_nodes[i][2] = 0;
        dndy1_at_nodes[i][3] = 0;

        dndy1_at_nodes[i][4] = 4. * (y4 - y1);
        dndy1_at_nodes[i][5] = 4. * y2;
        dndy1_at_nodes[i][6] = -4. * y2;
        dndy1_at_nodes[i][7] = -4. * y3;
        dndy1_at_nodes[i][8] = 4. * y3;
        dndy1_at_nodes[i][9] = 0;

        dndy2_at_nodes[i][0] = 1 - 4 * y4;
        dndy2_at_nodes[i][1] = 0;
        dndy2_at_nodes[i][2] = 4. * y2 - 1.;
        dndy2_at_nodes[i][3] = 0;
        dndy2_at_nodes[i][4] = -4. * y1;
        dndy2_at_nodes[i][5] = 4. * y1;
        dndy2_at_nodes[i][6] = 4. * (y4 - y2);
        dndy2_at_nodes[i][7] = -4. * y3;
        dndy2_at_nodes[i][8] = 0.;
        dndy2_at_nodes[i][9] = 4. * y3;

        dndy3_at_nodes[i][0] = 1 - 4 * y4;
        dndy3_at_nodes[i][1] = 0;
        dndy3_at_nodes[i][2] = 0;
        dndy3_at_nodes[i][3] = 4. * y3 - 1.;

        dndy3_at_nodes[i][4] = -4. * y1;
        dndy3_at_nodes[i][5] = 0;
        dndy3_at_nodes[i][6] = -4. * y2;
        dndy3_at_nodes[i][7] = 4. * (y4 - y3);
        dndy3_at_nodes[i][8] = 4. * y1;
        dndy3_at_nodes[i][9] = 4. * y2;
      }
      break;
    }
    case 4:
    {
      for (i = 0; i < numberNodes; i++)
      {
        get_node_local_coord_tet(i, y1, y2, y3, y4);
        dndy1_at_nodes[i][0] = -1.;
        dndy1_at_nodes[i][1] = 1;
        dndy1_at_nodes[i][2] = 0;
        dndy1_at_nodes[i][3] = 0;

        dndy2_at_nodes[i][0] = -1.;
        dndy2_at_nodes[i][1] = 0;
        dndy2_at_nodes[i][2] = 1;
        dndy2_at_nodes[i][3] = 0;

        dndy3_at_nodes[i][0] = -1.;
        dndy3_at_nodes[i][1] = 0;
        dndy3_at_nodes[i][2] = 0;
        dndy3_at_nodes[i][3] = 1;
      }
      break;
    }
  }
}

void GaussIntegration::get_node_local_coord_tet(
  int node_id, double& y1, double& y2, double& y3, double& y4)
{
  switch (node_id)
  {
    case 0:
      y1 = 0.;
      y2 = 0.;
      y3 = 0.;
      y4 = 1.;
      break;
    case 1:
      y1 = 1.;
      y2 = 0.;
      y3 = 0.;
      y4 = 0.;
      break;
    case 2:
      y1 = 0.;
      y2 = 1.;
      y3 = 0.;
      y4 = 0.;
      break;
    case 3:
      y1 = 0.;
      y2 = 0.;
      y3 = 1.;
      y4 = 0.;
      break;
    case 4:
      y1 = 0.5;
      y2 = 0.;
      y3 = 0.;
      y4 = 0.5;
      break;
    case 5:
      y1 = 0.5;
      y2 = 0.5;
      y3 = 0.;
      y4 = 0.;
      break;
    case 6:
      y1 = 0.;
      y2 = 0.5;
      y3 = 0.;
      y4 = 0.5;
      break;
    case 7:
      y1 = 0.;
      y2 = 0.0;
      y3 = 0.5;
      y4 = 0.5;
      break;
    case 8:
      y1 = 0.5;
      y2 = 0.;
      y3 = 0.5;
      y4 = 0.0;
      break;
    case 9:
      y1 = 0.;
      y2 = 0.5;
      y3 = 0.5;
      y4 = 0.;
      break;
    default:
      // Should not be possible to get here, but if we do, at least results will be consistent, not
      // random
      y1 = 0.;
      y2 = 0.;
      y3 = 0.;
      y4 = 0.;
      break;
  }
}
} // namespace verdict
