// 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 <math.h>
#include "ProData.h"
#include "ProDefine.h"
#include "GeoData.h"
#include "Get_DofOfElement.h"
#include "Cal_Quantity.h"
#include "Cal_Value.h"
#include "Get_Geometry.h"
#include "Get_FunctionValue.h"
#include "ExtendedGroup.h"
#include "Pos_Formulation.h"
#include "MallocUtils.h"
#include "Message.h"

extern struct Problem Problem_S;
extern struct CurrentData Current;

/* ------------------------------------------------------------------------ */
/*  P o s _ L o c a l O r I n t e g r a l Q u a n t i t y                   */
/* ------------------------------------------------------------------------ */

static int Warning_NoJacobian = 0;

void Pos_LocalOrIntegralQuantity(struct PostQuantity *PostQuantity_P,
                                 struct DefineQuantity *DefineQuantity_P0,
                                 struct QuantityStorage *QuantityStorage_P0,
                                 struct PostQuantityTerm *PostQuantityTerm_P,
                                 struct Element *Element, int Type_Quantity,
                                 double u, double v, double w,
                                 struct Value *Value)
{
  struct FunctionSpace *FunctionSpace_P;
  struct DefineQuantity *DefineQuantity_P;
  struct QuantityStorage *QuantityStorage_P;
  struct Value TermValue, tmpValue;
  struct JacobianCase *JacobianCase_P0;
  struct IntegrationCase *IntegrationCase_P;
  struct Quadrature *Quadrature_P;

  List_T *IntegrationCase_L, *JacobianCase_L;
  double ui, vi, wi, weight, Factor;
  int Index_DefineQuantity;
  int i, j, Type_Dimension;
  int CriterionIndex, Nbr_IntPoints, i_IntPoint;

  double (*Get_Jacobian)(struct Element * Element, MATRIX3x3 * Jac) = 0;
  void (*Get_IntPoint)(int Nbr_Points, int Num, double *u, double *v, double *w,
                       double *wght);

  /* Get the functionspaces
     Get the DoF for local quantities */

  for(i = 0; i < PostQuantityTerm_P->NbrQuantityIndex; i++) {
    Index_DefineQuantity = PostQuantityTerm_P->QuantityIndexTable[i];
    DefineQuantity_P = DefineQuantity_P0 + Index_DefineQuantity;
    QuantityStorage_P = QuantityStorage_P0 + Index_DefineQuantity;

    if(QuantityStorage_P->NumLastElementForFunctionSpace != Element->Num) {
      QuantityStorage_P->NumLastElementForFunctionSpace = Element->Num;

      if(Type_Quantity != INTEGRALQUANTITY) {
        QuantityStorage_P->FunctionSpace = FunctionSpace_P =
          (struct FunctionSpace *)List_Pointer(
            Problem_S.FunctionSpace, DefineQuantity_P->FunctionSpaceIndex);
        if(DefineQuantity_P->Type == LOCALQUANTITY)
          Get_DofOfElement(Element, FunctionSpace_P, QuantityStorage_P,
                           DefineQuantity_P->IndexInFunctionSpace);
      }
      else { /* INTEGRALQUANTITY */
        if(DefineQuantity_P->IntegralQuantity.DefineQuantityIndexDof >= 0)
          QuantityStorage_P->FunctionSpace =
            (struct FunctionSpace *)List_Pointer(
              Problem_S.FunctionSpace, DefineQuantity_P->FunctionSpaceIndex);
        /* Get the function space for the associated local quantities */
        for(j = 0; j < DefineQuantity_P->IntegralQuantity.NbrQuantityIndex;
            j++) {
          Index_DefineQuantity =
            DefineQuantity_P->IntegralQuantity.QuantityIndexTable[j];
          DefineQuantity_P = DefineQuantity_P0 + Index_DefineQuantity;
          QuantityStorage_P = QuantityStorage_P0 + Index_DefineQuantity;
          QuantityStorage_P->FunctionSpace =
            (struct FunctionSpace *)List_Pointer(
              Problem_S.FunctionSpace, DefineQuantity_P->FunctionSpaceIndex);
        }
      }
    }
  }

  /* get the jacobian */

  if(Element->Num != NO_ELEMENT) {
    if(PostQuantityTerm_P->JacobianMethodIndex >= 0) {
      JacobianCase_L =
        ((struct JacobianMethod *)List_Pointer(
           Problem_S.JacobianMethod, PostQuantityTerm_P->JacobianMethodIndex))
          ->JacobianCase;
      JacobianCase_P0 = (struct JacobianCase *)List_Pointer(JacobianCase_L, 0);

      i = 0;
      while((i < List_Nbr(JacobianCase_L)) &&
            ((j = (JacobianCase_P0 + i)->RegionIndex) >= 0) &&
            !List_Search(
              ((struct Group *)List_Pointer(Problem_S.Group, j))->InitialList,
              &Element->Region, fcmp_int))
        i++;

      if(i == List_Nbr(JacobianCase_L)) {
        Message::Error("Undefined Jacobian in Region %d", Element->Region);
        return;
      }

      Element->JacobianCase = JacobianCase_P0 + i;
      Get_Jacobian =
        (double (*)(struct Element *, MATRIX3x3 *))Get_JacobianFunction(
          Element->JacobianCase->TypeJacobian, Element->Type, &Type_Dimension);
    }
    else {
      if(!Warning_NoJacobian) {
        Message::Warning(
          "No Jacobian method specification in PostProcessing quantity: "
          "using default Jacobian (Vol)");
        Warning_NoJacobian = 1;
      }
      Get_Jacobian =
        (double (*)(struct Element *, MATRIX3x3 *))Get_JacobianFunction(
          JACOBIAN_VOL, Element->Type, &Type_Dimension);
    }

    Get_NodesCoordinatesOfElement(Element);
  }

  /* local evaluation at one point */

  if(PostQuantityTerm_P->EvaluationType == LOCAL) {
    if(Element->Num != NO_ELEMENT) {
      Get_BFGeoElement(Element, u, v, w);
      Element->DetJac = Get_Jacobian(Element, &Element->Jac);
      if(Element->DetJac != 0.)
        Get_InverseMatrix(Type_Dimension, Element->Type, Element->DetJac,
                          &Element->Jac, &Element->InvJac);
    }

    Cal_WholeQuantity(Current.Element = Element, QuantityStorage_P0,
                      PostQuantityTerm_P->WholeQuantity, Current.u = u,
                      Current.v = v, Current.w = w, -1, -1, &TermValue);
  }

  /* integral evaluation over the element */

  else if(PostQuantityTerm_P->EvaluationType == INTEGRAL) {
    if(Element->Num == NO_ELEMENT) {
      Message::Error("No element in which to integrate");
      return;
    }

    if(PostQuantityTerm_P->IntegrationMethodIndex < 0) {
      Message::Error(
        "Missing Integration method in PostProcesssing Quantity '%s'",
        PostQuantity_P->Name);
      return;
    }

    IntegrationCase_L = ((struct IntegrationMethod *)List_Pointer(
                           Problem_S.IntegrationMethod,
                           PostQuantityTerm_P->IntegrationMethodIndex))
                          ->IntegrationCase;

    CriterionIndex = ((struct IntegrationMethod *)List_Pointer(
                        Problem_S.IntegrationMethod,
                        PostQuantityTerm_P->IntegrationMethodIndex))
                       ->CriterionIndex;

    IntegrationCase_P =
      Get_IntegrationCase(Element, IntegrationCase_L, CriterionIndex);

    if(IntegrationCase_P->Type != GAUSS) {
      Message::Error("Only numerical integration is available "
                     "in Integral PostQuantities");
      return;
    }

    Quadrature_P = (struct Quadrature *)List_PQuery(IntegrationCase_P->Case,
                                                    &Element->Type, fcmp_int);

    if(!Quadrature_P) {
      Message::Error("Unknown type of Element (%s) for Integration method (%s) "
                     " in PostProcessing Quantity (%s)",
                     Get_StringForDefine(Element_Type, Element->Type),
                     ((struct IntegrationMethod *)List_Pointer(
                        Problem_S.IntegrationMethod,
                        PostQuantityTerm_P->IntegrationMethodIndex))
                       ->Name,
                     PostQuantity_P->Name);
      return;
    }

    Cal_ZeroValue(&TermValue);

    Nbr_IntPoints = Quadrature_P->NumberOfPoints;
    Get_IntPoint = (void (*)(int, int, double *, double *, double *,
                             double *))Quadrature_P->Function;

    for(i_IntPoint = 0; i_IntPoint < Nbr_IntPoints; i_IntPoint++) {
      Current.QuadraturePointIndex = i_IntPoint;

      Get_IntPoint(Nbr_IntPoints, i_IntPoint, &ui, &vi, &wi, &weight);

      Get_BFGeoElement(Element, ui, vi, wi);
      Element->DetJac = Get_Jacobian(Element, &Element->Jac);
      if(Element->DetJac != 0.) {
        Get_InverseMatrix(Type_Dimension, Element->Type, Element->DetJac,
                          &Element->Jac, &Element->InvJac);
      }
      else {
        Message::Warning("Zero determinant in 'Cal_PostQuantity'");
      }
      Current.x = Current.y = Current.z = 0.;
      if(Type_Quantity == INTEGRALQUANTITY) {
        for(i = 0; i < Element->GeoElement->NbrNodes; i++) {
          Current.x += Element->x[i] * Element->n[i];
          Current.y += Element->y[i] * Element->n[i];
          Current.z += Element->z[i] * Element->n[i];
        }
      }

      Cal_WholeQuantity(Current.Element = Element, QuantityStorage_P0,
                        PostQuantityTerm_P->WholeQuantity, Current.u = ui,
                        Current.v = vi, Current.w = wi, -1, -1, &tmpValue);

      Factor = weight * fabs(Element->DetJac);

      TermValue.Type = tmpValue.Type;
      Cal_AddMultValue(&TermValue, &tmpValue, Factor, &TermValue);
    }
  }

  Value->Type = TermValue.Type;
  Cal_AddValue(Value, &TermValue, Value);
}

/* ------------------------------------------------------------------------ */
/*  P o s _ G l o b a l Q u a n t i t y                                     */
/* ------------------------------------------------------------------------ */

void Pos_GlobalQuantity(struct PostQuantity *PostQuantity_P,
                        struct DefineQuantity *DefineQuantity_P0,
                        struct QuantityStorage *QuantityStorage_P0,
                        struct PostQuantityTerm *PostQuantityTerm_P,
                        struct Element *ElementEmpty, List_T *InRegion_L,
                        List_T *Support_L, struct Value *Value,
                        int Type_InRegion)
{
  struct DefineQuantity *DefineQuantity_P;
  struct QuantityStorage *QuantityStorage_P;
  struct FunctionSpace *FunctionSpace_P;
  struct GlobalQuantity *GlobalQuantity_P;
  struct Value TermValue;

  int k, Index_DefineQuantity;

  int Nbr_Element, i_Element;
  struct Element Element;
  int Type_Quantity;

  if(PostQuantityTerm_P->EvaluationType == LOCAL &&
     List_Search(InRegion_L, &Current.Region, fcmp_int)) {
    for(k = 0; k < PostQuantityTerm_P->NbrQuantityIndex; k++) {
      Index_DefineQuantity = PostQuantityTerm_P->QuantityIndexTable[k];
      DefineQuantity_P = DefineQuantity_P0 + Index_DefineQuantity;
      QuantityStorage_P = QuantityStorage_P0 + Index_DefineQuantity;

      if(QuantityStorage_P->NumLastElementForFunctionSpace != Current.Region) {
        QuantityStorage_P->NumLastElementForFunctionSpace = Current.Region;
        QuantityStorage_P->FunctionSpace = FunctionSpace_P =
          (struct FunctionSpace *)List_Pointer(
            Problem_S.FunctionSpace, DefineQuantity_P->FunctionSpaceIndex);
        GlobalQuantity_P = (struct GlobalQuantity *)List_Pointer(
          QuantityStorage_P->FunctionSpace->GlobalQuantity,
          *(int *)List_Pointer(DefineQuantity_P->IndexInFunctionSpace, 0));

        if(DefineQuantity_P->Type == GLOBALQUANTITY)
          Get_DofOfRegion(Current.Region, GlobalQuantity_P, FunctionSpace_P,
                          QuantityStorage_P);
      }
    }

    Cal_WholeQuantity(Current.Element = ElementEmpty, QuantityStorage_P0,
                      PostQuantityTerm_P->WholeQuantity, Current.u = 0.,
                      Current.v = 0., Current.w = 0., -1, -1, &TermValue);

    Value->Type = TermValue.Type;
    Cal_AddValue(Value, &TermValue, Value);

  } /* if LOCAL && ... */

  else if(PostQuantityTerm_P->EvaluationType == INTEGRAL) {
    Nbr_Element = Geo_GetNbrGeoElements();
    Get_InitDofOfElement(&Element);

    Type_Quantity = LOCALQUANTITY; /* Attention... il faut se comprendre: */
    /* il s'agit de grandeurs locales qui seront integrees */
    for(i_Element = 0; i_Element < Nbr_Element; i_Element++) {
      Element.GeoElement = Geo_GetGeoElement(i_Element);
      Element.Num = Element.GeoElement->Num;
      Element.Type = Element.GeoElement->Type;
      Current.Region = Element.Region = Element.GeoElement->Region;

      /* Filter: only elements in both InRegion_L and Support_L are considered
       */
      if((!InRegion_L ||
          (List_Search(
            InRegion_L,
            (Type_InRegion == ELEMENTSOF ? &Element.Num : &Element.Region),
            fcmp_int))) &&
         (!Support_L || List_Search(Support_L, &Element.Region, fcmp_int))) {
        Get_NodesCoordinatesOfElement(&Element);
        Current.x = Element.x[0];
        Current.y = Element.y[0];
        Current.z = Element.z[0];
        Pos_LocalOrIntegralQuantity(PostQuantity_P, DefineQuantity_P0,
                                    QuantityStorage_P0, PostQuantityTerm_P,
                                    &Element, Type_Quantity, 0., 0., 0., Value);
      }
    } /* for i_Element ... */

  } /* if INTEGRAL ... */
}

/* ------------------------------------------------------------------------ */
/*  C a l _ P o s t Q u a n t i t y                                         */
/* ------------------------------------------------------------------------ */

void Cal_PostQuantity(struct PostQuantity *PostQuantity_P,
                      struct DefineQuantity *DefineQuantity_P0,
                      struct QuantityStorage *QuantityStorage_P0,
                      List_T *Support_L, struct Element *Element, double u,
                      double v, double w, struct Value *Value)
{
  struct PostQuantityTerm PostQuantityTerm;

  // InRegion_L is the region (group) on which the quantity in the
  // PostProcessing section is defined (so it's actually a "support"...)

  List_T *InRegion_L;
  int i_PQT, Type_Quantity, Type_InRegion;
  struct Group *Group_P; /* For generating extended group */

  /* mettre tout a zero: on ne connait pas a priori le type de retour */
  /* (default type and value returned if Type_Quantity == -1) */

  Cal_ZeroValue(Value);
  Value->Type = SCALAR;

  /* Loop on PostQuantity Terms */
  /* ... with sum of results if common supports (In ...) */

  for(i_PQT = 0; i_PQT < List_Nbr(PostQuantity_P->PostQuantityTerm); i_PQT++) {
    List_Read(PostQuantity_P->PostQuantityTerm, i_PQT, &PostQuantityTerm);

    /*
    InRegion_L = (PostQuantityTerm.InIndex < 0)?  NULL :
      ((struct Group *)List_Pointer(Problem_S.Group,
                    PostQuantityTerm.InIndex))->InitialList ;
    */

    Group_P =
      (PostQuantityTerm.InIndex < 0) ?
        NULL :
        (struct Group *)List_Pointer(Problem_S.Group, PostQuantityTerm.InIndex);
    InRegion_L = Group_P ? Group_P->InitialList : NULL;

    if(PostQuantityTerm.SubRegion >= 0) {
      struct Group *GroupSubRegion_P = (struct Group *)List_Pointer(
        Problem_S.Group, PostQuantityTerm.SubRegion);
      if(List_Nbr(GroupSubRegion_P->InitialList) == 1) {
        List_Read(GroupSubRegion_P->InitialList, 0, &Current.SubRegion);
      }
      else {
        Message::Error("One region allowed in SubRegion");
        Current.SubRegion = -1;
      }
    }
    //+++ Not in pos!!!    else
    //      Current.SubRegion = -1;

    Type_InRegion = Group_P ? Group_P->FunctionType : REGION;

    /* Generating Extended Group if necessary */
    if(Group_P && Group_P->FunctionType == ELEMENTSOF) {
      if(!Group_P->ExtendedList) Generate_ExtendedGroup(Group_P);
      InRegion_L = Group_P->ExtendedList;
    }

    if(!Support_L)
      Type_Quantity = PostQuantityTerm.Type;
    else
      Type_Quantity = GLOBALQUANTITY; /* Always if Support */

    if(InRegion_L) {
      if(Element->Num != NO_ELEMENT) {
        /* not correct for ElementsOf (i.e. ELEMENTLIST)
        if (!List_Search(InRegion_L, &Element->Region, fcmp_int)) {
          Type_Quantity = -1 ;
        }
        */
        if(!((Group_P->Type != ELEMENTLIST &&
              List_Search(Group_P->InitialList, &Element->Region, fcmp_int)) ||
             (Group_P->Type == ELEMENTLIST &&
              Check_IsEntityInExtendedGroup(Group_P, Element->Num, 0)))) {
          Type_Quantity = -1;
        }
      }
      else {
        if(Type_Quantity == GLOBALQUANTITY) {
          /* Plus de test ici... vu que le OnRegion de la PostOperation n'a rien
             a voir avec le support d'une integration ...
          if (!List_Search(InRegion_L, &Current.Region, fcmp_int)) {
            Type_Quantity = -1 ;
          }
          */
          /* Il faut plutot voir si il existe au moins une region de InRegion_L
             qui soit dans Support_L ... cela est fait apres, pour chaque
             element */
        }
        else if(Type_Quantity != INTEGRALQUANTITY) {
          Type_Quantity = -1;
        }
      }
    }
    /* else if !InRegion_L -> No filter, i.e. globally defined quantity */

    /* ---------------------------- */
    /* Local or Integral quantities */
    /* ---------------------------- */

    if(Type_Quantity == LOCALQUANTITY || Type_Quantity == INTEGRALQUANTITY) {
      Pos_LocalOrIntegralQuantity(PostQuantity_P, DefineQuantity_P0,
                                  QuantityStorage_P0, &PostQuantityTerm,
                                  Element, Type_Quantity, u, v, w, Value);
    }

    /* ----------------- */
    /* Global quantities */
    /* ----------------- */

    else if(Type_Quantity == GLOBALQUANTITY) {
      Pos_GlobalQuantity(PostQuantity_P, DefineQuantity_P0, QuantityStorage_P0,
                         &PostQuantityTerm, Element, InRegion_L, Support_L,
                         Value, Type_InRegion);
    }

  } /* for i_PQT ... */
}

/* ------------------------------------------------------------------------ */
/*  C a l _ P o s t C u m u l a t i v e Q u a n t i t y                     */
/* ------------------------------------------------------------------------ */

void Cal_PostCumulativeQuantity(List_T *Region_L, int SupportIndex,
                                List_T *TimeStep_L,
                                struct PostQuantity *PostQuantity_P,
                                struct DefineQuantity *DefineQuantity_P0,
                                struct QuantityStorage *QuantityStorage_P0,
                                struct Value **Values)
{
  struct Element Element;
  List_T *Support_L;
  int i, NbrTimeStep;

  Support_L =
    ((struct Group *)List_Pointer(Problem_S.Group, SupportIndex))->InitialList;

  NbrTimeStep = List_Nbr(TimeStep_L);
  *Values = (struct Value *)Malloc(NbrTimeStep * sizeof(struct Value));

  Element.Num = NO_ELEMENT;

  for(i = 0; i < NbrTimeStep; i++) {
    Pos_InitAllSolutions(TimeStep_L, i);

    Cal_PostQuantity(PostQuantity_P, DefineQuantity_P0, QuantityStorage_P0,
                     Support_L, &Element, 0, 0, 0, &(*Values)[i]);
  }
}

/* ------------------------------------------------------------------------ */
/*  C o m b i n e _ P o s t Q u a n t i t y                                 */
/* ------------------------------------------------------------------------ */

void Combine_PostQuantity(int Type, int Order, struct Value *V1,
                          struct Value *V2)
{
  switch(Type) {
  case MULTIPLICATION: Cal_ProductValue(V1, V2, V1); break;
  case ADDITION: Cal_AddValue(V1, V2, V1); break;
  case DIVISION: Cal_DivideValue(Order ? V1 : V2, Order ? V2 : V1, V1); break;
  case SOUSTRACTION:
    Cal_SubstractValue(Order ? V1 : V2, Order ? V2 : V1, V1);
    break;
  }
}
