// GetDP - Copyright (C) 1997-2022 P. Dular and C. Geuzaine, University of Liege
//
// See the LICENSE.txt file for license information. Please report all
// issues on https://gitlab.onelab.info/getdp/getdp/issues.

#include "ProData.h"
#include "GeoData.h"
#include "DofData.h"
#include "Get_DofOfElement.h"
#include "Get_ElementSource.h"
#include "Get_Geometry.h"
#include "Get_FunctionValue.h"
#include "Cal_IntegralQuantity.h"
#include "Pos_Search.h"
#include "Message.h"

extern struct Problem Problem_S;
extern struct CurrentData Current;

extern List_T *GeoData_L;

/* ------------------------------------------------------------------------ */
/*  P o s _ F e m I n t e r p o l a t i o n                                 */
/* ------------------------------------------------------------------------ */

void Pos_FemInterpolation(struct Element *Element,
                          struct QuantityStorage *QuantityStorage_P0,
                          struct QuantityStorage *QuantityStorage_P,
                          int Type_Quantity, int Type_Operator,
                          int Type_Dimension, int UseXYZ, double u, double v,
                          double w, double x, double y, double z, double Val[],
                          int *Type_Value, int Flag_ChangeOfCoordinates)
{
  void (*xFunctionBF[NBR_MAX_BASISFUNCTIONS])(struct Element *, int, double,
                                              double, double, double[]);
  void (*xChangeOfCoordinates)() = 0;

  struct IntegralQuantityActive IQA;
  struct Value vBFxDof[NBR_MAX_BASISFUNCTIONS];
  struct GeoData *GeoData_P;
  struct Element TheElement, *TheElement_P;
  struct QuantityStorage *QS_P;

  double vBFu[NBR_MAX_BASISFUNCTIONS][MAX_DIM];
  double Val_Dof, Val_Dof_r, Val_Dof_i;

  int Type_DefineQuantity, SubType_DefineQuantity, Type_Form;
  int i, j, k, Nbr_Dof = 0;
  int GeoDataNum = 0, UseNewGeo = 0;

  /* -------------
     Quantity Type
     ------------- */

  Type_DefineQuantity = QuantityStorage_P->DefineQuantity->Type;

  if(Type_DefineQuantity == INTEGRALQUANTITY) {
    if(QuantityStorage_P->DefineQuantity->IntegralQuantity
         .DefineQuantityIndexDof < 0) {
      SubType_DefineQuantity = NODOF;
    }
    else {
      SubType_DefineQuantity = INTEGRALQUANTITY;
    }
  }
  else {
    SubType_DefineQuantity = Type_DefineQuantity;
  }

  /* ---------------
     Get The Element
     --------------- */

  if(SubType_DefineQuantity != NODOF) {
    if(!QuantityStorage_P->FunctionSpace) {
      Message::Error("No available function space for quantity");
      return;
    }

    if(!QuantityStorage_P->FunctionSpace->DofData) {
      Message::Error("No available data to interpolate quantity");
      return;
    }

    GeoDataNum = QuantityStorage_P->FunctionSpace->DofData->GeoDataIndex;
    UseNewGeo = (GeoDataNum != Current.GeoData->Num);

    if(UseXYZ || UseNewGeo) {
      if(UseNewGeo) {
        GeoData_P = (struct GeoData *)List_Pointer(GeoData_L, GeoDataNum);
        GeoDataNum = Current.GeoData->Num;
        Geo_SetCurrentGeoData(Current.GeoData = GeoData_P);
      }
      if(!UseXYZ) {
        x = y = z = 0.;
        for(i = 0; i < Element->GeoElement->NbrNodes; i++) {
          x += Element->x[i] * Element->n[i];
          y += Element->y[i] * Element->n[i];
          z += Element->z[i] * Element->n[i];
        }
      }
      InWhichElement(&Current.GeoData->Grid, NULL, &TheElement,
                     (Type_Dimension >= 0) ? Type_Dimension : DIM_ALL, x, y, z,
                     &u, &v, &w);
      TheElement_P = &TheElement;
      Get_InitDofOfElement(&TheElement);
      Get_DofOfElement(&TheElement, QuantityStorage_P->FunctionSpace,
                       QuantityStorage_P,
                       QuantityStorage_P->DefineQuantity->IndexInFunctionSpace);
    }
    else {
      TheElement_P = Element;
    }
  }
  else {
    TheElement_P = Element;
  }

  /* ------------------
     Init LocalQuantity
     ------------------ */

  if(Type_DefineQuantity == LOCALQUANTITY) {
    if(TheElement_P->Num != NO_ELEMENT) {
      Nbr_Dof = QuantityStorage_P->NbrElementaryBasisFunction;
      Get_FunctionValue(Nbr_Dof, (void (**)())xFunctionBF, Type_Operator,
                        QuantityStorage_P, &Type_Form);
      xChangeOfCoordinates = (void (*)())Get_ChangeOfCoordinates(
        (Flag_ChangeOfCoordinates && TheElement_P->Num != NO_ELEMENT),
        Type_Form);
    }
    else {
      Message::Warning(
        "No element found in mesh for LocalQuantity interpolation");
      Nbr_Dof = 0;
      Type_Form = VECTOR;
    }
  }

  /* ---------------------
     Init IntegralQuantity
     --------------------- */

  else if(Type_DefineQuantity == INTEGRALQUANTITY) {
    if(Type_Operator != NOOP) {
      Message::Error("Operator acting on Integral Quantity");
    }
    Type_Form = VECTOR;
    Get_InitElementSource(
      TheElement_P,
      QuantityStorage_P->DefineQuantity->IntegralQuantity.InIndex);

    IQA.IntegrationCase_L = ((struct IntegrationMethod *)List_Pointer(
                               Problem_S.IntegrationMethod,
                               QuantityStorage_P->DefineQuantity
                                 ->IntegralQuantity.IntegrationMethodIndex))
                              ->IntegrationCase;
    IQA.CriterionIndex = ((struct IntegrationMethod *)List_Pointer(
                            Problem_S.IntegrationMethod,
                            QuantityStorage_P->DefineQuantity->IntegralQuantity
                              .IntegrationMethodIndex))
                           ->CriterionIndex;
    IQA.JacobianCase_L =
      ((struct JacobianMethod *)List_Pointer(
         Problem_S.JacobianMethod, QuantityStorage_P->DefineQuantity
                                     ->IntegralQuantity.JacobianMethodIndex))
        ->JacobianCase;

    xChangeOfCoordinates = (void (*)())Get_ChangeOfCoordinates(0, Type_Form);
  }

  /* ----------------------
     Compute GlobalQuantity
     ---------------------- */

  if(Type_DefineQuantity == GLOBALQUANTITY) {
    if(Current.NbrHar == 1) {
      if(QuantityStorage_P->NbrElementaryBasisFunction) {
        if(Type_Quantity == QUANTITY_BF)
          Val[0] = (QuantityStorage_P->BasisFunction[0].Dof->Entity ==
                    Current.SubRegion) ?
                     1. :
                     0.;
        else
          Dof_GetRealDofValue(QuantityStorage_P->FunctionSpace->DofData,
                              QuantityStorage_P->BasisFunction[0].Dof, &Val[0]);
      }
      else
        Val[0] = 0.;
    }
    else {
      for(k = 0; k < Current.NbrHar; k += 2) {
        if(QuantityStorage_P->NbrElementaryBasisFunction) {
          if(Type_Quantity == QUANTITY_BF) {
            Val[MAX_DIM * k] =
              (QuantityStorage_P->BasisFunction[0].Dof->Entity ==
               Current.SubRegion) ?
                1. :
                0.;
            Val[MAX_DIM * (k + 1)] = 0.;
          }
          else {
            Dof_GetComplexDofValue(QuantityStorage_P->FunctionSpace->DofData,
                                   QuantityStorage_P->BasisFunction[0].Dof +
                                     k / 2 * gCOMPLEX_INCREMENT,
                                   &Val[MAX_DIM * k], &Val[MAX_DIM * (k + 1)]);
          }
        }
        else {
          Val[MAX_DIM * k] = 0.;
          Val[MAX_DIM * (k + 1)] = 0.;
        }
      }
    }
    *Type_Value = SCALAR;
    return;
  }

  /* -----------------------------------
     Compute Local / Integral Quantities
     ----------------------------------- */

  i = Current.NbrHar * MAX_DIM;
  for(k = 0; k < i; k++) Val[k] = 0.;

  while(1) {
    if(Type_DefineQuantity == INTEGRALQUANTITY) {
      if(Get_NextElementSource(TheElement_P->ElementSource)) {
        Get_NodesCoordinatesOfElement(TheElement_P->ElementSource);

        if(SubType_DefineQuantity != NODOF) {
          Get_DofOfElement(
            TheElement_P->ElementSource, QuantityStorage_P->FunctionSpace,
            QuantityStorage_P,
            QuantityStorage_P->DefineQuantity->IndexInFunctionSpace);
          Nbr_Dof = QuantityStorage_P->NbrElementaryBasisFunction;
          Get_FunctionValue(
            Nbr_Dof, (void (**)())xFunctionBF,
            QuantityStorage_P->DefineQuantity->IntegralQuantity.TypeOperatorDof,
            QuantityStorage_P, &IQA.Type_FormDof);
          Type_Form = IQA.Type_FormDof; /* good form */
        }
        else {
          Nbr_Dof = 1;
          xFunctionBF[0] = NULL; /* for analytic integration tests */
          Type_Form = IQA.Type_FormDof = VECTOR; /* form type unknown */

          for(j = 0; j < QuantityStorage_P->DefineQuantity->IntegralQuantity
                           .NbrQuantityIndex;
              j++) {
            QS_P =
              QuantityStorage_P0 + QuantityStorage_P->DefineQuantity
                                     ->IntegralQuantity.QuantityIndexTable[j];
            Get_DofOfElement(TheElement_P->ElementSource, QS_P->FunctionSpace,
                             QS_P, QS_P->DefineQuantity->IndexInFunctionSpace);
          }
        }

        Cal_InitIntegralQuantity(TheElement_P, &IQA, QuantityStorage_P);
      }
      else
        break;
    }

    /* -----
       Local
       ----- */

    if(Type_DefineQuantity == LOCALQUANTITY) {
      if(TheElement_P->Num != NO_ELEMENT) {
        for(j = 0; j < Nbr_Dof; j++) {
          xFunctionBF[j](
            TheElement_P,
            QuantityStorage_P->BasisFunction[j].NumEntityInElement + 1, u, v, w,
            vBFu[j]);
          ((void (*)(struct Element *, double *,
                     double *))xChangeOfCoordinates)(TheElement_P, vBFu[j],
                                                     vBFxDof[j].Val);
        }
      }

      /* interpolate (vBFxDof is real-valued) */

      switch(Type_Form) {
      case FORM0:
      case FORM3:
      case FORM3P:
      case SCALAR:
        if(Current.NbrHar == 1) {
          for(j = 0; j < Nbr_Dof; j++) {
            if(Type_Quantity == QUANTITY_BF)
              Val_Dof = (QuantityStorage_P->BasisFunction[j].Dof->Entity ==
                         Current.SubRegion) ?
                          1. :
                          0.;
            else
              Dof_GetRealDofValue(QuantityStorage_P->FunctionSpace->DofData,
                                  QuantityStorage_P->BasisFunction[j].Dof,
                                  &Val_Dof);
            Val[0] += vBFxDof[j].Val[0] * Val_Dof;
          }
        }
        else {
          for(j = 0; j < Nbr_Dof; j++) {
            for(k = 0; k < Current.NbrHar; k += 2) {
              if(Type_Quantity == QUANTITY_BF) {
                Val_Dof_r = (QuantityStorage_P->BasisFunction[j].Dof->Entity ==
                             Current.SubRegion) ?
                              1. :
                              0.;
                Val_Dof_i = 0.;
              }
              else {
                Dof_GetComplexDofValue(
                  QuantityStorage_P->FunctionSpace->DofData,
                  QuantityStorage_P->BasisFunction[j].Dof +
                    k / 2 * gCOMPLEX_INCREMENT,
                  &Val_Dof_r, &Val_Dof_i);
              }
              Val[MAX_DIM * k] += vBFxDof[j].Val[0] * Val_Dof_r;
              Val[MAX_DIM * (k + 1)] += vBFxDof[j].Val[0] * Val_Dof_i;
            }
          }
        }
        *Type_Value = SCALAR;
        break;

      case FORM1:
      case FORM1P:
      case FORM2:
      case FORM2P:
      case FORM1S:
      case FORM2S:
      case VECTOR:
      case VECTORP:
        if(Current.NbrHar == 1) {
          for(j = 0; j < Nbr_Dof; j++) {
            if(Type_Quantity == QUANTITY_BF)
              Val_Dof = (QuantityStorage_P->BasisFunction[j].Dof->Entity ==
                         Current.SubRegion) ?
                          1. :
                          0.;
            else
              Dof_GetRealDofValue(QuantityStorage_P->FunctionSpace->DofData,
                                  QuantityStorage_P->BasisFunction[j].Dof,
                                  &Val_Dof);
            Val[0] += vBFxDof[j].Val[0] * Val_Dof;
            Val[1] += vBFxDof[j].Val[1] * Val_Dof;
            Val[2] += vBFxDof[j].Val[2] * Val_Dof;
          }
        }
        else {
          for(j = 0; j < Nbr_Dof; j++) {
            for(k = 0; k < Current.NbrHar; k += 2) {
              if(Type_Quantity == QUANTITY_BF) {
                Val_Dof_r = (QuantityStorage_P->BasisFunction[j].Dof->Entity ==
                             Current.SubRegion) ?
                              1. :
                              0.;
                Val_Dof_i = 0.;
              }
              else {
                Dof_GetComplexDofValue(
                  QuantityStorage_P->FunctionSpace->DofData,
                  QuantityStorage_P->BasisFunction[j].Dof +
                    k / 2 * gCOMPLEX_INCREMENT,
                  &Val_Dof_r, &Val_Dof_i);
              }
              Val[MAX_DIM * k] += vBFxDof[j].Val[0] * Val_Dof_r;
              Val[MAX_DIM * k + 1] += vBFxDof[j].Val[1] * Val_Dof_r;
              Val[MAX_DIM * k + 2] += vBFxDof[j].Val[2] * Val_Dof_r;
              Val[MAX_DIM * (k + 1)] += vBFxDof[j].Val[0] * Val_Dof_i;
              Val[MAX_DIM * (k + 1) + 1] += vBFxDof[j].Val[1] * Val_Dof_i;
              Val[MAX_DIM * (k + 1) + 2] += vBFxDof[j].Val[2] * Val_Dof_i;
            }
          }
        }
        *Type_Value = VECTOR;
        break;

      default:
        Message::Error("Unknown Form type in 'Pos_FemInterpolation'");
        break;
      }
    }

    /* --------
       Integral
       -------- */

    /*
      FIXME: Ce qu'il faut faire, c'est ne pas reinterpoler ici, mais
      laisser au Cal_Quantity dans Cal_IntegralQuantity le soin de
      reinterpoler directment la quantity local intervenant ds la qte
      integrale s'il y a lieu (mais, comment faire avec l'integration
      analytique ?)
    */

    else {
      if(IQA.IntegrationCase_P->Type == ANALYTIC)
        Cal_AnalyticIntegralQuantity(Current.Element = TheElement_P,
                                     QuantityStorage_P, Nbr_Dof,
                                     (void (**)())xFunctionBF, vBFxDof);
      else
        Cal_NumericalIntegralQuantity(Current.Element = TheElement_P, &IQA,
                                      QuantityStorage_P0, QuantityStorage_P,
                                      SubType_DefineQuantity, Nbr_Dof,
                                      (void (**)())xFunctionBF, vBFxDof);

      Type_Form = vBFxDof[0].Type;

      /* interpolate (vBFxDof can be complex-valued) */

      if(SubType_DefineQuantity == NODOF) {
        switch(Type_Form) {
        case FORM0:
        case FORM3:
        case FORM3P:
        case SCALAR:
          for(k = 0; k < Current.NbrHar; k++)
            Val[MAX_DIM * k] += vBFxDof[0].Val[MAX_DIM * k];
          *Type_Value = SCALAR;
          break;

        case FORM1:
        case FORM1P:
        case FORM2:
        case FORM2P:
        case FORM1S:
        case FORM2S:
        case VECTOR:
        case VECTORP:
          for(k = 0; k < Current.NbrHar; k++) {
            Val[MAX_DIM * k] += vBFxDof[0].Val[MAX_DIM * k];
            Val[MAX_DIM * k + 1] += vBFxDof[0].Val[MAX_DIM * k + 1];
            Val[MAX_DIM * k + 2] += vBFxDof[0].Val[MAX_DIM * k + 2];
          }
          *Type_Value = VECTOR;
          break;

        default:
          Message::Error("Unknown Form type in 'Pos_FemInterpolation'");
          break;
        }
      }
      else {
        switch(Type_Form) {
        case FORM0:
        case FORM3:
        case FORM3P:
        case SCALAR:
          if(Current.NbrHar == 1) {
            for(j = 0; j < Nbr_Dof; j++) {
              Dof_GetRealDofValue(QuantityStorage_P->FunctionSpace->DofData,
                                  QuantityStorage_P->BasisFunction[j].Dof,
                                  &Val_Dof);
              Val[0] += vBFxDof[j].Val[0] * Val_Dof;
            }
          }
          else {
            for(j = 0; j < Nbr_Dof; j++) {
              for(k = 0; k < Current.NbrHar; k += 2) {
                Dof_GetComplexDofValue(
                  QuantityStorage_P->FunctionSpace->DofData,
                  QuantityStorage_P->BasisFunction[j].Dof +
                    k / 2 * gCOMPLEX_INCREMENT,
                  &Val_Dof_r, &Val_Dof_i);
                Val[MAX_DIM * k] +=
                  vBFxDof[j].Val[MAX_DIM * k] * Val_Dof_r -
                  vBFxDof[j].Val[MAX_DIM * (k + 1)] * Val_Dof_i;
                Val[MAX_DIM * (k + 1)] +=
                  vBFxDof[j].Val[MAX_DIM * k] * Val_Dof_i +
                  vBFxDof[j].Val[MAX_DIM * (k + 1)] * Val_Dof_r;
              }
            }
          }
          *Type_Value = SCALAR;
          break;

        case FORM1:
        case FORM1P:
        case FORM2:
        case FORM2P:
        case FORM1S:
        case FORM2S:
        case VECTOR:
        case VECTORP:
          if(Current.NbrHar == 1) {
            for(j = 0; j < Nbr_Dof; j++) {
              Dof_GetRealDofValue(QuantityStorage_P->FunctionSpace->DofData,
                                  QuantityStorage_P->BasisFunction[j].Dof,
                                  &Val_Dof);
              Val[0] += vBFxDof[j].Val[0] * Val_Dof;
              Val[1] += vBFxDof[j].Val[1] * Val_Dof;
              Val[2] += vBFxDof[j].Val[2] * Val_Dof;
            }
          }
          else {
            for(j = 0; j < Nbr_Dof; j++) {
              for(k = 0; k < Current.NbrHar; k += 2) {
                Dof_GetComplexDofValue(
                  QuantityStorage_P->FunctionSpace->DofData,
                  QuantityStorage_P->BasisFunction[j].Dof +
                    k / 2 * gCOMPLEX_INCREMENT,
                  &Val_Dof_r, &Val_Dof_i);
                Val[MAX_DIM * k] +=
                  vBFxDof[j].Val[MAX_DIM * k] * Val_Dof_r -
                  vBFxDof[j].Val[MAX_DIM * (k + 1)] * Val_Dof_i;
                Val[MAX_DIM * (k + 1)] +=
                  vBFxDof[j].Val[MAX_DIM * k] * Val_Dof_i +
                  vBFxDof[j].Val[MAX_DIM * (k + 1)] * Val_Dof_r;
                Val[MAX_DIM * k + 1] +=
                  vBFxDof[j].Val[MAX_DIM * k + 1] * Val_Dof_r -
                  vBFxDof[j].Val[MAX_DIM * (k + 1) + 1] * Val_Dof_i;
                Val[MAX_DIM * (k + 1) + 1] +=
                  vBFxDof[j].Val[MAX_DIM * k + 1] * Val_Dof_i +
                  vBFxDof[j].Val[MAX_DIM * (k + 1) + 1] * Val_Dof_r;
                Val[MAX_DIM * k + 2] +=
                  vBFxDof[j].Val[MAX_DIM * k + 2] * Val_Dof_r -
                  vBFxDof[j].Val[MAX_DIM * (k + 1) + 2] * Val_Dof_i;
                Val[MAX_DIM * (k + 1) + 2] +=
                  vBFxDof[j].Val[MAX_DIM * k + 2] * Val_Dof_i +
                  vBFxDof[j].Val[MAX_DIM * (k + 1) + 2] * Val_Dof_r;
              }
            }
          }
          *Type_Value = VECTOR;
          break;

        default:
          Message::Error("Unknown Form type in 'Pos_FemInterpolation'");
          break;
        }
      }
    }

    if(Type_DefineQuantity != INTEGRALQUANTITY) break;

  } /* while (1) ... */

  if(UseNewGeo) {
    GeoData_P = (struct GeoData *)List_Pointer(GeoData_L, GeoDataNum);
    Geo_SetCurrentGeoData(Current.GeoData = GeoData_P);
  }
}
