// 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 "Cal_Quantity.h"
#include "Cal_Value.h"
#include "Cal_AssembleTerm.h"
#include "Get_DofOfElement.h"
#include "Get_Geometry.h"
#include "Message.h"

extern struct Problem Problem_S;
extern struct CurrentData Current;

/* ------------------------------------------------------------------------ */
/*  C a l _ G l o b a l T e r m O f F e m F o r m u l a t i o n             */
/* ------------------------------------------------------------------------ */

#define OFFSET                                                                 \
  (iHar < NbrHar - OffSet) ? 0 : iHar - NbrHar + OffSet + 2 - iHar % 2

void MH_Get_InitData(int Case, int NbrPoints, int *NbrPointsX_P, double ***H_P,
                     double ****HH_P, double **t_P, double **w_P);

void Cal_GlobalTermOfFemEquation(int Num_Region,
                                 struct EquationTerm *EquationTerm_P,
                                 struct QuantityStorage *QuantityStorage_P0,
                                 struct QuantityStorage *QuantityStorageNoDof,
                                 struct Dof *DofForNoDof_P)
{
  struct FemGlobalTermActive *FI;
  struct QuantityStorage *QuantityStorageEqu_P, *QuantityStorageDof_P;
  struct Value vBFxDof[1];
  struct Element Element;

  int k;
  double Coefficient[NBR_MAX_HARMONIC];

  void (*Function_AssembleTerm)(struct Dof * Equ, struct Dof * Dof,
                                double Val[]) = 0;

  List_T *WholeQuantity_L;
  struct WholeQuantity *WholeQuantity_P0;
  int i_WQ;
  struct Expression *Expression_P;
  int NbrPointsX;
  double **H, ***HH, *time, *weight, Factor = 1., plus, plus0;

  double one = 1.0;
  int j = 0, iPul, ZeroHarmonic, DcHarmonic;
  int NbrHar, iTime, iHar, jHar, OffSet = 0;
  double Val_Dof[NBR_MAX_HARMONIC];

  double E_D[NBR_MAX_HARMONIC][NBR_MAX_HARMONIC];
  struct Dof *Dof;
  struct Value t_Value;
  gMatrix *Jac;
  struct QuantityStorage *QuantityStorage_P;

  FI = EquationTerm_P->Case.GlobalTerm.Active;

  Element.Num = NO_ELEMENT;

  switch(EquationTerm_P->Case.GlobalTerm.Term.TypeTimeDerivative) {
  case NODT_: Function_AssembleTerm = Cal_AssembleTerm_NoDt; break;
  case DTDOF_: Function_AssembleTerm = Cal_AssembleTerm_DtDof; break;
  case DT_: Function_AssembleTerm = Cal_AssembleTerm_Dt; break;
  case DTDTDOF_: Function_AssembleTerm = Cal_AssembleTerm_DtDtDof; break;
  case DTDT_: Function_AssembleTerm = Cal_AssembleTerm_DtDt; break;
  case DTDTDTDOF_: Function_AssembleTerm = Cal_AssembleTerm_DtDtDtDof; break;
  case DTDTDTDTDOF_:
    Function_AssembleTerm = Cal_AssembleTerm_DtDtDtDtDof;
    break;
  case DTDTDTDTDTDOF_:
    Function_AssembleTerm = Cal_AssembleTerm_DtDtDtDtDtDof;
    break;
  case NEVERDT_: Function_AssembleTerm = Cal_AssembleTerm_NeverDt; break;
  case JACNL_: Function_AssembleTerm = Cal_AssembleTerm_JacNL; break;
  case DTDOFJACNL_: Function_AssembleTerm = Cal_AssembleTerm_DtDofJacNL; break;
  // nleigchange
  case NLEIG1DOF_: Function_AssembleTerm = Cal_AssembleTerm_NLEig1Dof; break;
  case NLEIG2DOF_: Function_AssembleTerm = Cal_AssembleTerm_NLEig2Dof; break;
  case NLEIG3DOF_: Function_AssembleTerm = Cal_AssembleTerm_NLEig3Dof; break;
  case NLEIG4DOF_: Function_AssembleTerm = Cal_AssembleTerm_NLEig4Dof; break;
  case NLEIG5DOF_: Function_AssembleTerm = Cal_AssembleTerm_NLEig5Dof; break;
  case NLEIG6DOF_: Function_AssembleTerm = Cal_AssembleTerm_NLEig6Dof; break;
  default: Message::Error("Unknown type of operator for Global term"); return;
  }

  //+++ Num_Region, QuantityStorage_P0: not used any more
  QuantityStorageEqu_P = FI->QuantityStorageEqu_P;
  QuantityStorageDof_P = FI->QuantityStorageDof_P;
  if(!QuantityStorageDof_P) {
    QuantityStorageDof_P = QuantityStorageNoDof;
    Dof_InitDofForNoDof(DofForNoDof_P, Current.NbrHar);
    QuantityStorageDof_P->BasisFunction[0].Dof = DofForNoDof_P;
  }

  // search for MHBilinear-term(s)
  WholeQuantity_L = EquationTerm_P->Case.GlobalTerm.Term.WholeQuantity;
  WholeQuantity_P0 = (struct WholeQuantity *)List_Pointer(WholeQuantity_L, 0);
  i_WQ = 0;
  while(i_WQ < List_Nbr(WholeQuantity_L) &&
        (WholeQuantity_P0 + i_WQ)->Type != WQ_MHBILINEAR)
    i_WQ++;

  if(i_WQ < List_Nbr(WholeQuantity_L)) { // Multi-harmonic case
    if(Message::GetVerbosity() == 10)
      Message::Info("MHBilinear in Global term");
    if(QuantityStorageEqu_P != QuantityStorageDof_P) {
      Message::Error("Global term with MHBilinear is not symmetric ?!");
      return;
    }

    QuantityStorage_P = QuantityStorageEqu_P;

    if(List_Nbr(WholeQuantity_L) == 3) {
      if(i_WQ != 0 ||
         EquationTerm_P->Case.GlobalTerm.Term.DofIndexInWholeQuantity != 1 ||
         (WholeQuantity_P0 + 2)->Type != WQ_BINARYOPERATOR ||
         (WholeQuantity_P0 + 2)->Case.Operator.TypeOperator != OP_TIME) {
        Message::Error(
          "Not allowed expression in Global term with MHBilinear (case 1)");
        return;
      }
      Factor = 1.;
    }
    else {
      Message::Error(
        "Not allowed expression in Global term with MHBilinear (%d terms) ",
        List_Nbr(WholeQuantity_L));
      return;
    }

    if(EquationTerm_P->Case.GlobalTerm.Term.TypeTimeDerivative != JACNL_) {
      Message::Error("MHBilinear can only be used with JACNL");
      return;
    }

    Expression_P = (struct Expression *)List_Pointer(
      Problem_S.Expression, (WholeQuantity_P0 + i_WQ)->Case.MHBilinear.Index);

    MH_Get_InitData(2, (WholeQuantity_P0 + i_WQ)->Case.MHBilinear.NbrPoints,
                    &NbrPointsX, &H, &HH, &time, &weight);

    NbrHar = Current.NbrHar;

    /* special treatment of DC-term and associated dummy sinus-term */
    DcHarmonic = NbrHar;
    ZeroHarmonic = 0;
    for(iPul = 0; iPul < NbrHar / 2; iPul++)
      if(!Current.DofData->Val_Pulsation[iPul]) {
        DcHarmonic = 2 * iPul;
        ZeroHarmonic = 2 * iPul + 1;
        break;
      }

    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[k], &Val_Dof[k + 1]);

    /* time integration over fundamental period */
    for(iHar = 0; iHar < NbrHar; iHar++)
      for(jHar = OFFSET; jHar <= iHar; jHar++) E_D[iHar][jHar] = 0.;

    Current.NbrHar = 1; /* evaluation in time domain */

    for(iTime = 0; iTime < NbrPointsX; iTime++) {
      t_Value.Type = SCALAR;
      t_Value.Val[0] = 0;
      for(iHar = 0; iHar < NbrHar; iHar++)
        t_Value.Val[0] += H[iTime][iHar] * Val_Dof[iHar];

      // To generalize: Function in MHBilinear has 1 argument (e.g.
      // Resistance[{Iz}])
      Get_ValueOfExpression(Expression_P, QuantityStorage_P0, Current.u,
                            Current.v, Current.w, &t_Value, 1);

      for(iHar = 0; iHar < NbrHar; iHar++)
        for(jHar = OFFSET; jHar <= iHar; jHar++)
          E_D[iHar][jHar] += HH[iTime][iHar][jHar] * t_Value.Val[0];

    } // for i_IntPoint...

    Current.NbrHar = NbrHar;

    Jac = &Current.DofData->Jac;

    Dof = QuantityStorage_P->BasisFunction[0].Dof;

    for(iHar = 0; iHar < NbrHar; iHar++)
      for(jHar = OFFSET; jHar <= iHar; jHar++) {
        plus = plus0 = Factor * E_D[iHar][jHar];
        if(jHar == DcHarmonic && iHar != DcHarmonic) {
          plus0 *= 1.;
          plus *= 2.;
        }
        Dof_AssembleInMat(Dof + iHar, Dof + jHar, 1, &plus, Jac, NULL);
        if(iHar != jHar)
          Dof_AssembleInMat(Dof + jHar, Dof + iHar, 1, &plus0, Jac, NULL);
      }

    /* dummy 1's on the diagonal for sinus-term of dc-component */

    if(ZeroHarmonic) {
      Dof = QuantityStorage_P->BasisFunction[0].Dof + ZeroHarmonic;
      Dof_AssembleInMat(Dof, Dof, 1, &one, Jac, NULL);
    }
  }
  else { // standard (non multi-harmonic) bilinear term

    vBFxDof[0].Type = SCALAR;
    vBFxDof[0].Val[0] = 1.;
    if(Current.NbrHar > 1) Cal_SetHarmonicValue(&vBFxDof[0]);

    Cal_WholeQuantity(
      Current.Element = &Element, QuantityStorage_P0,
      EquationTerm_P->Case.GlobalTerm.Term.WholeQuantity, Current.u = 0.,
      Current.v = 0., Current.w = 0.,
      EquationTerm_P->Case.GlobalTerm.Term.DofIndexInWholeQuantity, 1, vBFxDof);

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

    Function_AssembleTerm(QuantityStorageEqu_P->BasisFunction[0].Dof,
                          QuantityStorageDof_P->BasisFunction[0].Dof,
                          Coefficient);
  }
}

#undef OFFSET

void Cal_GlobalTermOfFemEquation_old(
  int Num_Region, struct EquationTerm *EquationTerm_P,
  struct QuantityStorage *QuantityStorage_P0,
  struct QuantityStorage *QuantityStorageNoDof, struct Dof *DofForNoDof_P)
{
  struct QuantityStorage *QuantityStorageEqu_P, *QuantityStorageDof_P;
  struct Value vBFxDof[1];
  struct Element Element;

  int k;
  double Coefficient[NBR_MAX_HARMONIC];

  void (*Function_AssembleTerm)(struct Dof * Equ, struct Dof * Dof,
                                double Val[]) = 0;

  Element.Num = NO_ELEMENT;

  switch(EquationTerm_P->Case.GlobalTerm.Term.TypeTimeDerivative) {
  case NODT_: Function_AssembleTerm = Cal_AssembleTerm_NoDt; break;
  case DTDOF_: Function_AssembleTerm = Cal_AssembleTerm_DtDof; break;
  case DT_: Function_AssembleTerm = Cal_AssembleTerm_Dt; break;
  case DTDTDOF_: Function_AssembleTerm = Cal_AssembleTerm_DtDtDof; break;
  case DTDT_: Function_AssembleTerm = Cal_AssembleTerm_DtDt; break;
  case DTDTDTDOF_: Function_AssembleTerm = Cal_AssembleTerm_DtDtDtDof; break;
  case DTDTDTDTDOF_:
    Function_AssembleTerm = Cal_AssembleTerm_DtDtDtDtDof;
    break;
  case DTDTDTDTDTDOF_:
    Function_AssembleTerm = Cal_AssembleTerm_DtDtDtDtDtDof;
    break;
  case NEVERDT_: Function_AssembleTerm = Cal_AssembleTerm_NeverDt; break;
  case JACNL_: Function_AssembleTerm = Cal_AssembleTerm_JacNL; break;
  case DTDOFJACNL_: Function_AssembleTerm = Cal_AssembleTerm_DtDofJacNL; break;
  // nleigchange
  case NLEIG1DOF_: Function_AssembleTerm = Cal_AssembleTerm_NLEig1Dof; break;
  case NLEIG2DOF_: Function_AssembleTerm = Cal_AssembleTerm_NLEig2Dof; break;
  case NLEIG3DOF_: Function_AssembleTerm = Cal_AssembleTerm_NLEig3Dof; break;
  case NLEIG4DOF_: Function_AssembleTerm = Cal_AssembleTerm_NLEig4Dof; break;
  case NLEIG5DOF_: Function_AssembleTerm = Cal_AssembleTerm_NLEig5Dof; break;
  case NLEIG6DOF_: Function_AssembleTerm = Cal_AssembleTerm_NLEig6Dof; break;
  default: Message::Error("Unknown type of operator for Global term"); return;
  }

  QuantityStorageEqu_P =
    QuantityStorage_P0 +
    EquationTerm_P->Case.GlobalTerm.Term.DefineQuantityIndexEqu;

  if(EquationTerm_P->Case.GlobalTerm.Term.DefineQuantityIndexDof >= 0) {
    QuantityStorageDof_P =
      QuantityStorage_P0 +
      EquationTerm_P->Case.GlobalTerm.Term.DefineQuantityIndexDof;
  }
  else {
    QuantityStorageDof_P = QuantityStorageNoDof;
    Dof_InitDofForNoDof(DofForNoDof_P, Current.NbrHar);
    QuantityStorageDof_P->BasisFunction[0].Dof = DofForNoDof_P;
  }

  vBFxDof[0].Type = SCALAR;
  vBFxDof[0].Val[0] = 1.;
  if(Current.NbrHar > 1) Cal_SetHarmonicValue(&vBFxDof[0]);

  Cal_WholeQuantity(
    Current.Element = &Element, QuantityStorage_P0,
    EquationTerm_P->Case.GlobalTerm.Term.WholeQuantity, Current.u = 0.,
    Current.v = 0., Current.w = 0.,
    EquationTerm_P->Case.GlobalTerm.Term.DofIndexInWholeQuantity, 1, vBFxDof);

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

  Function_AssembleTerm(QuantityStorageEqu_P->BasisFunction[0].Dof,
                        QuantityStorageDof_P->BasisFunction[0].Dof,
                        Coefficient);
}
