// 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):
//   Koen Hillewaert
//   Jonathan Lambrechts
//

#include <stdlib.h>
#include <cmath>
#include "GmshDefines.h"
#include "GmshMessage.h"
#include "polynomialBasis.h"
#include "GaussIntegration.h"
#include "pointsGenerators.h"
#include <limits>

namespace {
  fullMatrix<double> generateLagrangeMonomialCoefficients
    (const fullMatrix<double>& monomial, const fullMatrix<double>& point)
  {
    if(monomial.size1() != point.size1() || monomial.size2() != point.size2()){
      Msg::Fatal("Wrong sizes for Lagrange coefficients generation %d %d -- %d %d",
           monomial.size1(), point.size1(),
           monomial.size2(), point.size2() );
      return fullMatrix<double>(1, 1);
    }

    int ndofs = monomial.size1();
    int dim = monomial.size2();

    fullMatrix<double> Vandermonde(ndofs, ndofs);
    for (int i = 0; i < ndofs; i++) {
      for (int j = 0; j < ndofs; j++) {
        double dd = 1.;
        for (int k = 0; k < dim; k++) dd *= pow_int(point(j, k), monomial(i, k));
        Vandermonde(i, j) = dd;
      }
    }

    fullMatrix<double> coefficient(ndofs, ndofs);
    Vandermonde.invert(coefficient);

    return coefficient;
  }

  /*
  void printClosure(polynomialBasis::clCont &fullClosure,
                           std::vector<int> &closureRef,
                           polynomialBasis::clCont &closures)
  {
    for (unsigned int i = 0; i < closures.size(); i++) {
      printf("%3i  (%2i): ", i, closureRef[i]);
      if(closureRef[i]==-1){
        printf("\n");
        continue;
      }
      for (unsigned int j = 0; j < fullClosure[i].size(); j++) {
        printf("%2i ", fullClosure[i][j]);
      }
      printf ("  --  ");
      for (unsigned int j = 0; j < closures[closureRef[i]].size(); j++) {
        std::string equalSign = "-";
        if (fullClosure[i][closures[closureRef[i]][j]] != closures[i][j])
          equalSign = "#";
        printf("%2i%s%2i ", fullClosure[i][closures[closureRef[i]][j]],equalSign.c_str(),
               closures[i][j]);
      }
      printf("\n");
    }
  }
  */
}

polynomialBasis::polynomialBasis(int tag) : nodalBasis(tag)
{
  switch (parentType) {
  case TYPE_PNT :
    monomials = gmshGenerateMonomialsLine(0);
    break;
  case TYPE_LIN :
    monomials = gmshGenerateMonomialsLine(order);
    break;
  case TYPE_TRI :
    monomials = gmshGenerateMonomialsTriangle(order, serendip);
    break;
  case TYPE_QUA :
    monomials = serendip ? gmshGenerateMonomialsQuadSerendipity(order) :
    gmshGenerateMonomialsQuadrangle(order);
    break;
  case TYPE_TET :
    monomials = gmshGenerateMonomialsTetrahedron(order, serendip);
    break;
  case TYPE_PRI :
    monomials = serendip ? gmshGenerateMonomialsPrismSerendipity(order) :
    gmshGenerateMonomialsPrism(order);
    break;
  case TYPE_HEX :
    monomials = serendip ? gmshGenerateMonomialsHexaSerendipity(order) :
    gmshGenerateMonomialsHexahedron(order);
    break;
  }

  coefficients = generateLagrangeMonomialCoefficients(monomials, points);
}

polynomialBasis::~polynomialBasis()
{
}

void polynomialBasis::evaluateMonomials(double u, double v, double w, double p[]) const
{
  for (int j = 0; j < monomials.size1(); ++j) {
    p[j] = 1.;
    switch (dimension) {
    case 3 : p[j] *= pow_int(w, monomials(j, 2));
    case 2 : p[j] *= pow_int(v, monomials(j, 1));
    case 1 : p[j] *= pow_int(u, monomials(j, 0));
    default :
      break;
    }
  }
}

void polynomialBasis::f(double u, double v, double w, double *sf) const
{
  double p[1256];
  evaluateMonomials(u, v, w, p);
  for (int i = 0; i < coefficients.size1(); i++) {
    sf[i] = 0.0;
    for (int j = 0; j < coefficients.size2(); j++) {
      sf[i] += coefficients(i, j) * p[j];
    }
  }
}

void polynomialBasis::f(const fullMatrix<double> &coord, fullMatrix<double> &sf) const
{
  double p[1256];
  sf.resize (coord.size1(), coefficients.size1());
  for (int iPoint = 0; iPoint < coord.size1(); iPoint++) {
    evaluateMonomials(coord(iPoint, 0), coord(iPoint, 1),
                      coord.size2() > 2 ? coord(iPoint, 2) : 0, p);
    for (int i = 0; i < coefficients.size1(); i++) {
      sf(iPoint,i) = 0.;
      for (int j = 0; j < coefficients.size2(); j++)
        sf(iPoint,i) += coefficients(i, j) * p[j];
    }
  }
}

void polynomialBasis::df(const fullMatrix<double> &coord, fullMatrix<double> &dfm) const
{
  double dfv[1256][3];
  dfm.resize (coefficients.size1(), coord.size1() * 3, false);
  int dimCoord = coord.size2();
  for (int iPoint=0; iPoint< coord.size1(); iPoint++) {
    df(coord(iPoint, 0), dimCoord > 1 ? coord(iPoint, 1) : 0.,
       dimCoord > 2 ? coord(iPoint, 2) : 0., dfv);
    for (int i = 0; i < coefficients.size1(); i++) {
      dfm(i, iPoint * 3 + 0) = dfv[i][0];
      dfm(i, iPoint * 3 + 1) = dfv[i][1];
      dfm(i, iPoint * 3 + 2) = dfv[i][2];
    }
  }
}

void polynomialBasis::df(double u, double v, double w, double grads[][3]) const
{
  switch (monomials.size2()) {
  case 1:
    for (int i = 0; i < coefficients.size1(); i++){
      grads[i][0] = 0;
      grads[i][1] = 0;
      grads[i][2] = 0;
      for(int j = 0; j < coefficients.size2(); j++){
        if (monomials(j, 0) > 0)
          grads[i][0] += coefficients(i, j) *
            pow_int(u, (monomials(j, 0) - 1)) * monomials(j, 0);
      }
    }
    break;
  case 2:
    for (int i = 0; i < coefficients.size1(); i++){
      grads[i][0] = 0;
      grads[i][1] = 0;
      grads[i][2] = 0;
      for(int j = 0; j < coefficients.size2(); j++){
        if (monomials(j, 0) > 0)
          grads[i][0] += coefficients(i, j) *
            pow_int(u, (monomials(j, 0) - 1)) * monomials(j, 0) *
            pow_int(v, monomials(j, 1));
        if (monomials(j, 1) > 0)
          grads[i][1] += coefficients(i, j) *
            pow_int(u, monomials(j, 0)) *
            pow_int(v, (monomials(j, 1) - 1)) * monomials(j, 1);
      }
    }
    break;
  case 3:
    for (int i = 0; i < coefficients.size1(); i++){
      grads[i][0] = 0;
      grads[i][1] = 0;
      grads[i][2] = 0;
      for(int j = 0; j < coefficients.size2(); j++){
        if (monomials(j, 0) > 0)
          grads[i][0] += coefficients(i, j) *
            pow_int(u, (monomials(j, 0) - 1)) * monomials(j, 0) *
            pow_int(v, monomials(j, 1)) *
            pow_int(w, monomials(j, 2));
        if (monomials(j, 1) > 0)
          grads[i][1] += coefficients(i, j) *
            pow_int(u, monomials(j, 0)) *
            pow_int(v, (monomials(j, 1) - 1)) * monomials(j, 1) *
            pow_int(w, monomials(j, 2));
        if (monomials(j, 2) > 0)
          grads[i][2] += coefficients(i, j) *
            pow_int(u, monomials(j, 0)) *
            pow_int(v, monomials(j, 1)) *
            pow_int(w, (monomials(j, 2) - 1)) * monomials(j, 2);
      }
    }
    break;
  }
}

void polynomialBasis::ddf(double u, double v, double w, double hess[][3][3]) const
{
  switch (monomials.size2()) {
  case 1:
    for (int i = 0; i < coefficients.size1(); i++){
      hess[i][0][0] = hess[i][0][1] = hess[i][0][2] = 0;
      hess[i][1][0] = hess[i][1][1] = hess[i][1][2] = 0;
      hess[i][2][0] = hess[i][2][1] = hess[i][2][2] = 0;

      for(int j = 0; j < coefficients.size2(); j++){
        if (monomials(j, 0) > 1) // second derivative !=0
          hess[i][0][0] += coefficients(i, j) * pow_int(u, (monomials(j, 0) - 2)) *
            monomials(j, 0) * (monomials(j, 0) - 1);
      }
    }
    break;
  case 2:
    for (int i = 0; i < coefficients.size1(); i++){
      hess[i][0][0] = hess[i][0][1] = hess[i][0][2] = 0;
      hess[i][1][0] = hess[i][1][1] = hess[i][1][2] = 0;
      hess[i][2][0] = hess[i][2][1] = hess[i][2][2] = 0;
      for(int j = 0; j < coefficients.size2(); j++){
        if (monomials(j, 0) > 1) // second derivative !=0
          hess[i][0][0] += coefficients(i, j) * pow_int(u, (monomials(j, 0) - 2)) *
            monomials(j, 0) * (monomials(j, 0) - 1) * pow_int(v, monomials(j, 1));
        if ((monomials(j, 1) > 0) && (monomials(j, 0) > 0))
          hess[i][0][1] += coefficients(i, j) *
            pow_int(u, monomials(j, 0) - 1) * monomials(j, 0) *
            pow_int(v, monomials(j, 1) - 1) * monomials(j, 1);
        if (monomials(j, 1) > 1)
          hess[i][1][1] += coefficients(i, j) *
            pow_int(u, monomials(j, 0)) *
            pow_int(v, monomials(j, 1) - 2) * monomials(j, 1) * (monomials(j, 1) - 1);
      }
      hess[i][1][0] = hess[i][0][1];
    }
    break;
  case 3:
    for (int i = 0; i < coefficients.size1(); i++){
      hess[i][0][0] = hess[i][0][1] = hess[i][0][2] = 0;
      hess[i][1][0] = hess[i][1][1] = hess[i][1][2] = 0;
      hess[i][2][0] = hess[i][2][1] = hess[i][2][2] = 0;
      for(int j = 0; j < coefficients.size2(); j++){
        if (monomials(j, 0) > 1)
          hess[i][0][0] += coefficients(i, j) *
            pow_int(u, monomials(j, 0) - 2) * monomials(j, 0) * (monomials(j, 0)-1) *
            pow_int(v, monomials(j, 1)) *
            pow_int(w, monomials(j, 2));

        if ((monomials(j, 0) > 0) && (monomials(j, 1) > 0))
          hess[i][0][1] += coefficients(i, j) *
            pow_int(u, monomials(j, 0) - 1) * monomials(j, 0) *
            pow_int(v, monomials(j, 1) - 1) * monomials(j, 1) *
            pow_int(w, monomials(j, 2));
        if ((monomials(j, 0) > 0) && (monomials(j, 2) > 0))
          hess[i][0][2] += coefficients(i, j) *
            pow_int(u, monomials(j, 0) - 1) * monomials(j, 0) *
            pow_int(v, monomials(j, 1)) *
            pow_int(w, monomials(j, 2) - 1) * monomials(j, 2);
        if (monomials(j, 1) > 1)
          hess[i][1][1] += coefficients(i, j) *
            pow_int(u, monomials(j, 0)) *
            pow_int(v, monomials(j, 1) - 2) * monomials(j, 1) * (monomials(j, 1)-1) *
            pow_int(w, monomials(j, 2));
        if ((monomials(j, 1) > 0) && (monomials(j, 2) > 0))
          hess[i][1][2] += coefficients(i, j) *
            pow_int(u, monomials(j, 0)) *
            pow_int(v, monomials(j, 1) - 1) * monomials(j, 1) *
            pow_int(w, monomials(j, 2) - 1) * monomials(j, 2);
        if (monomials(j, 2) > 1)
          hess[i][2][2] += coefficients(i, j) *
            pow_int(u, monomials(j, 0)) *
            pow_int(v, monomials(j, 1)) *
            pow_int(w, monomials(j, 2) - 2) * monomials(j, 2) * (monomials(j, 2) - 1);
      }
      hess[i][1][0] = hess[i][0][1];
      hess[i][2][0] = hess[i][0][2];
      hess[i][2][1] = hess[i][1][2];
    }
    break;
  }
}

void polynomialBasis::dddf(double u, double v, double w, double third[][3][3][3]) const
{
  switch (monomials.size2()) {
  case 1:
    for (int i = 0; i < coefficients.size1(); i++){
      for (int p=0; p<3; p++)
        for (int q=0; q<3; q++)
          for (int r=0; r<3; r++)
            third[i][p][q][r] = 0.;

      for(int j = 0; j < coefficients.size2(); j++){
        if (monomials(j, 0) > 2) // third derivative !=0
          third[i][0][0][0] += coefficients(i, j) * pow_int(u, (monomials(j, 0) - 3)) *
            monomials(j, 0) * (monomials(j, 0) - 1)*(monomials(j, 0) - 2);
      }
    }
    break;
  case 2:
    for (int i = 0; i < coefficients.size1(); i++){
      for (int p=0; p<3; p++)
        for (int q=0; q<3; q++)
          for (int r=0; r<3; r++)
            third[i][p][q][r] = 0.;
      for(int j = 0; j < coefficients.size2(); j++){
        if (monomials(j, 0) > 2) // second derivative !=0
          third[i][0][0][0] += coefficients(i, j) *
            pow_int(u, (monomials(j, 0) - 3))*monomials(j, 0) *
            (monomials(j, 0) - 1) *(monomials(j, 0) - 2) *
            pow_int(v, monomials(j, 1));
        if ((monomials(j, 0) > 1) && (monomials(j, 1) > 0))
          third[i][0][0][1] += coefficients(i, j) *
            pow_int(u, monomials(j, 0) - 2) * monomials(j, 0)*(monomials(j, 0) - 1) *
            pow_int(v, monomials(j, 1) - 1) * monomials(j, 1);
        if ((monomials(j, 0) > 0) && (monomials(j, 1) > 1))
          third[i][0][1][1] += coefficients(i, j) *
            pow_int(u, monomials(j, 0) - 1) *monomials(j, 0)*
            pow_int(v, monomials(j, 1) - 2) * monomials(j, 1) * (monomials(j, 1) - 1);
        if (monomials(j, 1) > 2)
          third[i][1][1][1] += coefficients(i, j) *
            pow_int(u, monomials(j, 0)) *
            pow_int(v, monomials(j, 1) - 3) * monomials(j, 1) *
            (monomials(j, 1) - 1) * (monomials(j, 1) - 2);
      }
      third[i][0][1][0] = third[i][0][0][1];
      third[i][1][0][0] = third[i][0][0][1];
      third[i][1][0][1] = third[i][0][1][1];
      third[i][1][1][0] = third[i][0][1][1];
    }
    break;
  case 3:
    for (int i = 0; i < coefficients.size1(); i++){
      for (int p=0; p<3; p++)
        for (int q=0; q<3; q++)
          for (int r=0; r<3; r++)
            third[i][p][q][r] = 0.;
      for(int j = 0; j < coefficients.size2(); j++){
        if (monomials(j, 0) > 2) // second derivative !=0
          third[i][0][0][0] += coefficients(i, j) *
            pow_int(u, (monomials(j, 0) - 3)) *monomials(j, 0) *
            (monomials(j, 0) - 1) * (monomials(j, 0) - 2) *
            pow_int(v, monomials(j, 1))*
            pow_int(w, monomials(j, 2));

        if ((monomials(j, 0) > 1) && (monomials(j, 1) > 0))
          third[i][0][0][1] += coefficients(i, j) *
            pow_int(u, monomials(j, 0) - 2) * monomials(j, 0)*(monomials(j, 0) - 1) *
            pow_int(v, monomials(j, 1) - 1) * monomials(j, 1)*
            pow_int(w, monomials(j, 2));

        if ((monomials(j, 0) > 1) && (monomials(j, 2) > 0))
          third[i][0][0][2] += coefficients(i, j) *
            pow_int(u, monomials(j, 0) - 2) * monomials(j, 0)*(monomials(j, 0) - 1) *
            pow_int(v, monomials(j, 1)) *
            pow_int(w, monomials(j, 2) - 1)* monomials(j, 2);

        if ((monomials(j, 0) > 0) && (monomials(j, 1) > 1))
          third[i][0][1][1] += coefficients(i, j) *
            pow_int(u, monomials(j, 0) - 1) *monomials(j, 0)*
            pow_int(v, monomials(j, 1) - 2) * monomials(j, 1) * (monomials(j, 1) - 1)*
            pow_int(w, monomials(j, 2));

        if ((monomials(j, 0) > 0) && (monomials(j, 1) > 0)&& (monomials(j, 2) > 0))
          third[i][0][1][2] += coefficients(i, j) *
            pow_int(u, monomials(j, 0) - 1) *monomials(j, 0)*
            pow_int(v, monomials(j, 1) - 1) *monomials(j, 1) *
            pow_int(w, monomials(j, 2) - 1) *monomials(j, 2);

        if ((monomials(j, 0) > 0) && (monomials(j, 2) > 1))
          third[i][0][2][2] += coefficients(i, j) *
            pow_int(u, monomials(j, 0) - 1) *monomials(j, 0)*
            pow_int(v, monomials(j, 1))*
            pow_int(w, monomials(j, 2) - 2) * monomials(j, 2) * (monomials(j, 2) - 1);
        if ((monomials(j, 1) > 2))
          third[i][1][1][1] += coefficients(i, j) *
            pow_int(u, monomials(j, 0)) *
            pow_int(v, monomials(j, 1)-3) * monomials(j, 1) *
            (monomials(j, 1) - 1) * (monomials(j, 1) - 2)*
            pow_int(w, monomials(j, 2));

        if ((monomials(j, 1) > 1) && (monomials(j, 2) > 0))
          third[i][1][1][2] += coefficients(i, j) *
            pow_int(u, monomials(j, 0)) *
            pow_int(v, monomials(j, 1)-2) * monomials(j, 1) * (monomials(j, 1) - 1)*
            pow_int(w, monomials(j, 2) - 1) *monomials(j, 2) ;

        if ((monomials(j, 1) > 0) && (monomials(j, 2) > 1))
          third[i][1][2][2] += coefficients(i, j) *
            pow_int(u, monomials(j, 0)) *
            pow_int(v, monomials(j, 1)-1) *monomials(j, 1)*
            pow_int(w, monomials(j, 2) - 2) * monomials(j, 2) * (monomials(j, 2) - 1) ;

        if ((monomials(j, 2) > 2))
          third[i][2][2][2] += coefficients(i, j) *
            pow_int(u, monomials(j, 0)) *
            pow_int(v, monomials(j, 1))*
            pow_int(w, monomials(j, 2) - 3) * monomials(j, 2) *
            (monomials(j, 2) - 1) * (monomials(j, 2) - 2);
      }
      third[i][0][1][0] = third[i][0][0][1];
      third[i][1][0][0] = third[i][0][0][1];

      third[i][2][0][0] = third[i][0][0][2];
      third[i][0][2][0] = third[i][0][0][2];

      third[i][1][0][1] = third[i][0][1][1];
      third[i][1][1][0] = third[i][0][1][1];

      third[i][0][2][1] = third[i][0][1][2];
      third[i][1][0][2] = third[i][0][1][2];
      third[i][1][2][0] = third[i][0][1][2];
      third[i][2][1][0] = third[i][0][1][2];
      third[i][2][0][1] = third[i][0][1][2];

      third[i][2][2][0] = third[i][0][2][2];
      third[i][2][0][2] = third[i][0][2][2];

      third[i][1][2][1] = third[i][1][1][2];
      third[i][2][1][1] = third[i][1][1][2];

      third[i][2][2][1] = third[i][1][2][2];
      third[i][2][1][2] = third[i][1][2][2];
    }
    break;
  }
}
