/* Copyright 2024 INAF - Osservatorio Astronomico di Cagliari

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */

/*! \file Commons.h
 *
 * \brief C++ porting of common data structures.
 *
 * Many functions of the original FORTRAN code share complex data blocks in
 * form of COMMON blocks. This poses the limit of freezing the structure of
 * the data blocks in the code, therefore implying the necessity to modify
 * the code to adapt it to the input and to recompile before running. C++,
 * on the contrary, offers the possibility to represent the necessary data
 * structures as classes that can dynamically instantiate the shared information
 * in the most convenient format for the current configuration. This approach
 * adds an abstraction layer that lifts the need to modify and recompile the
 * code depending on the input.
 *
 */

#ifndef INCLUDE_COMMONS_H_
#define INCLUDE_COMMONS_H_

#ifdef USE_MPI
#include <mpi.h>
#endif

class mixMPI;

/*! \brief Representation of the FORTRAN C1 common blocks.
 *
 * C1 common blocks are used to store vector field expansions and geometric
 * properties, such as sphere sizes, positions and cross-sections. These are
 * used by functions such as `aps`, `diel`, `pwma`, `rabas`, `sscr0`, `sscr2`,
 * `wmamp`, `wmasp` and `dme`. QUESTION: correct?
 *
 * Due to the necessity to share the class contents with many functions that
 * need to read and update various fields, all shared members of C1 are declared
 * public (i.e., the class is just a structure with more advanced constructor
 * and destroyer). Further development may go in the direction of creating
 * a better encapsulation, either by unpacking the contents (recommended) or
 * by creating public methods to access protected fields (standard way, but not
 * recommended for HPC applications).
 */
class C1 {
protected:
  //! \brief Maximum order of field expansion.
  int lm;
  //! \brief Contiguous RMI vector.
  dcomplex *vec_rmi;
  //! \brief Contiguous REI vector.
  dcomplex *vec_rei;
  //! \brief Contiguous W vector.
  dcomplex *vec_w;
  //! \brief Contiguous VINTS vector
  dcomplex *vec_vints;
  
public:
  //! \brief Number of spheres.
  int nsph;
  //! \brief NLMMT = 2 * LM * (LM + 2).
  int nlmmt;
  //! \brief Number of configurations
  int configurations;
  //! \brief QUESTION: definition?
  dcomplex **rmi;
  //! \brief QUESTION: definition?
  dcomplex **rei;
  //! \brief QUESTION: definition?
  dcomplex **w;
  //! \brief QUESTION: definition?
  dcomplex *fsas;
  //! \brief QUESTION: definition?
  dcomplex *vint;
  //! \brief QUESTION: definition?
  dcomplex **vints;
  //! \brief QUESTION: definition?
  double *sscs;
  //! \brief QUESTION: definition?
  double *sexs;
  //! \brief QUESTION: definition?
  double *sabs;
  //! \brief QUESTION: definition?
  double *sqscs;
  //! \brief QUESTION: definition?
  double *sqexs;
  //! \brief QUESTION: definition?
  double *sqabs;
  //! \brief QUESTION: definition?
  double *gcsv;
  //! \brief Vector of sphere X coordinates.
  double *rxx;
  //! \brief Vector of sphere X coordinates.
  double *ryy;
  //! \brief Vector of sphere X coordinates.
  double *rzz;
  //! \brief Vector of sphere radii.
  double *ros;
  //! \brief Matrix of spherical layer break radii. QUESTION: correct?
  double **rc;
  //! \brief Vector of spherical component identifiers.
  int *iog;
  //! \brief Vector of numbers of spherical layers.
  int *nshl;
  //! \brief QUESTION: definition?
  dcomplex ***sas;

  /*! \brief C1 instance constructor.
   *
   * \param gconf: `GeometryConfiguration *` Pointer to a geometry configuration object.
   * \param sconf: `ScattererConfiguration *` Pointer to a scatterer configuration object.
   */
  C1(GeometryConfiguration *gconf, ScattererConfiguration *sconf);

  /*! \brief C1 instance constructor copying all contents from a preexisting template
   *
   * \param rhs: `C1` preexisting template.
   */
  C1(const C1& rhs);

#ifdef MPI_VERSION
  /*! \brief C1 instance constructor copying all contents off MPI broadcast from MPI process 0
   *
   * \param mpidata: `mixMPI *` pointer to MPI data structure.
   */
  C1(const mixMPI *mpidata);

  /*! \brief send C1 instance from MPI process 0 via MPI broadcasts to all other processes
   *
   * \param mpidata: `mixMPI *` pointer to MPI data structure.
   */
  void mpibcast(const mixMPI *mpidata);
#endif

  //! \brief C1 instance destroyer.
  ~C1();
};

/*! \brief Representation of the FORTRAN C2 blocks.
 *
 */
class C2 {
protected:
  //! \brief Number of spheres.
  int nsph;
  //! \brief Number of required orders.
  int nhspo;
  //! \brief QUESTION: what is nl?
  int nl;

public:
  //! \brief QUESTION: definition?
  dcomplex *ris;
  //! \brief QUESTION: definition?
  dcomplex *dlri;
  //! \brief QUESTION: definition?
  dcomplex *dc0;
  //! \brief QUESTION: definition?
  dcomplex *vkt;
  //! Vector of scaled sizes. QUESTION: correct?
  double *vsz;

  /*! \brief C2 instance constructor.
   *
   * \param gconf: `GeometryConfiguration*` Pointer to a GeometryConfiguration instance.
   * \param sconf: `ScattererConfiguration*` Pointer to a ScattererConfiguration instance.
   */
  C2(GeometryConfiguration *gconf, ScattererConfiguration *sconf);

  /*! \brief C2 instance constructor copying its contents from preexisting instance.
   *
   * \param rhs: `C2` object to copy contents from
   */
  C2(const C2& rhs);

  //! \brief C2 instance destroyer.
  ~C2();

#ifdef MPI_VERSION
  /*! \brief C2 instance constructor copying all contents off MPI broadcast from MPI process 0
   *
   * \param mpidata: `mixMPI *` pointer to MPI data structure.
   */
  C2(const mixMPI *mpidata);

  /*! \brief send C2 instance from MPI process 0 via MPI broadcasts to all other processes
   *
   * \param mpidata: `mixMPI *` pointer to MPI data structure.
   */
  void mpibcast(const mixMPI *mpidata);
#endif

};

/*! \brief Representation of the FORTRAN C3 blocks.
 */
class C3 {
public:
  //! \brief QUESTION: definition?
  dcomplex tfsas;
  //! \brief QUESTION: definition?
  dcomplex **tsas;
  //! \brief QUESTION: definition?
  double gcs;
  //! \brief QUESTION: definition?
  double scs;
  //! \brief QUESTION: definition?
  double ecs;
  //! \brief QUESTION: definition?
  double acs;

  /*! \brief C3 instance constructor.
   */
  C3();

  /*! \brief C3 instance constructor copying its contents from a preexisting object.
   */
  C3(const C3& rhs);

  /*! \brief C3 instance destroyer.
   */
  ~C3();

#ifdef MPI_VERSION
  /*! \brief C3 instance constructor copying all contents off MPI broadcast from MPI process 0
   *
   * \param mpidata: `mixMPI *` pointer to MPI data structure.
   */
  C3(const mixMPI *mpidata);

  /*! \brief send C3 instance from MPI process 0 via MPI broadcasts to all other processes
   *
   * \param mpidata: `mixMPI *` pointer to MPI data structure.
   */
  void mpibcast(const mixMPI *mpidata);
#endif

};

/*! \brief Representation of the FORTRAN C4 blocks.
 */
class C4 {
public:
  //! \brief LITPO = 2 * LI + 1.
  int litpo;
  //! \brief LITPOS = LITPO * LITPO
  int litpos;
  //! \brief LMPO = LM + 1.
  int lmpo;
  //! \brief LMTPO = 2 * LM + 1.
  int lmtpo;
  //! \brief LMTPOS = LMTPO * LMTPO.
  int lmtpos;
  //! \brief Internal field expansion order.
  int li;
  //! \brief QUESTION: definition?
  int nlim;
  //! \brief External field expansion order.
  int le;
  //! \brief QUESTION: definition?
  int nlem;
  //! \brief Maximum field expansion order.
  int lm;
  //! \brief Number of spheres.
  int nsph;
  //! \brief QUESTION: definition?
  int nv3j;

  /*! \brief C4 instance constructor.
   *
   * \param gconf: `GeometryConfiguration*` Pointer to a GeometryConfiguration instance.
   */
  C4(GeometryConfiguration *gconf);
  
  /*! \brief C4 instance constructor copying its contents from a preexisting object.
   *
   * \param rhs: `C4&` Reference of the object to be copied.
   */
  C4(const C4& rhs);

  /*! \brief C4 instance destroyer.
   */
  ~C4();

#ifdef MPI_VERSION
  /*! \brief C4 instance constructor copying all contents off MPI broadcast from MPI process 0
   *
   * \param mpidata: `mixMPI *` pointer to MPI data structure.
   */
  C4(const mixMPI *mpidata);

  /*! \brief send C4 instance from MPI process 0 via MPI broadcasts to all other processes
   *
   * \param mpidata: `mixMPI *` pointer to MPI data structure.
   */
  void mpibcast(const mixMPI *mpidata);
#endif

};

/*! \brief Vectors and matrices that are specific to cluster C1 blocks.
 *
 */
class C1_AddOns {
protected:
  //! \brief Number of spheres.
  int nsph;
  //! \brief QUESTION: definition?
  int nlemt;
  //! \brief Maximum expansion order plus one. QUESTION: correct?
  int lmpo;
  //! \brief QUESTION: definition?
  int litpo;
  //! \brief QUESTION: definition?
  int lmtpo;
  //! \brief QUESTION: definition?
  int litpos;
  //! \brief QUESTION: definition?
  int lmtpos;
  //! \brief QUESTION: definition?
  int nv3j;
  //! \brief QUESTION: definition?
  int lm;

public:
  //! \brief QUESTION: definition?
  dcomplex *vh;
  //! \brief QUESTION: definition?
  dcomplex *vj0;
  //! \brief QUESTION: definition?
  dcomplex *vj;
  //! \brief QUESTION: definition?
  dcomplex *vyhj;
  //! \brief QUESTION: definition?
  dcomplex *vyj0;
  //! \brief QUESTION: definition?
  dcomplex **am0m;
  //! \brief QUESTION: definition?
  dcomplex *am0v;
  //! \brief QUESTION: definition?
  dcomplex *vintm;
  //! \brief QUESTION: definition?
  dcomplex *vintt;
  //! \brief QUESTION: definition?
  dcomplex **fsac;
  //! \brief QUESTION: definition?
  dcomplex **sac;
  //! \brief QUESTION: definition?
  dcomplex **fsacm;
  //! \brief QUESTION: definition?
  double *scsc;
  //! \brief QUESTION: definition?
  dcomplex *scscp;
  //! \brief QUESTION: definition?
  double *ecsc;
  //! \brief QUESTION: definition?
  double *ecscm;
  //! \brief QUESTION: definition?
  double *scscm;
  //! \brief QUESTION: definition?
  dcomplex *ecscp;
  //! \brief QUESTION: definition?
  dcomplex *scscpm;
  //! \brief QUESTION: definition?
  dcomplex *ecscpm;
  //! \brief QUESTION: definition?
  double *v3j0;
  //! \brief QUESTION: definition?
  double *sscs;
  //! \brief QUESTION: definition?
  int **ind3j;

  /*! \brief C1_AddOns instance constructor.
   *
   * \param c4: `C4 *` Pointer to a C4 structure.
   */
  C1_AddOns(C4 *c4);

  /*! \brief C1_AddOns instance constructor copying contents from a preexisting object.
   *
   * \param rhs: `C1_AddOns` preexisting object to copy from.
   */
  C1_AddOns(const C1_AddOns& rhs);

  //! \brief C1_AddOns instance destroyer.
  ~C1_AddOns();

#ifdef MPI_VERSION
  /*! \brief C1_AddOns instance constructor copying all contents off MPI broadcast from MPI process 0
   *
   * \param mpidata: `mixMPI *` pointer to MPI data structure.
   */
  C1_AddOns(const mixMPI *mpidata);

  /*! \brief send C1_AddOns instance from MPI process 0 via MPI broadcasts to all other processes
   *
   * \param mpidata: `mixMPI *` pointer to MPI data structure.
   */
  void mpibcast(const mixMPI *mpidata);
#endif

};

/*! \brief Representation of the FORTRAN C6 blocks.
 */
class C6 {
public:
  //! \brief LMTPO = 2 * LM + 1.
  int lmtpo;
  //! \brief QUESTION: definition?
  double *rac3j;

  /*! \brief C6 instance constructor.
   *
   * \param lmtpo: `int` QUESTION: definition?
   */
  C6(int lmtpo);

  /*! \brief C6 instance constructor copying contents from preexisting object.
   *
   * \param lmtpo: `int` QUESTION: definition?
   */
  C6(const C6& rhs);

  /*! \brief C6 instance destroyer.
   */
  ~C6();

#ifdef MPI_VERSION
  /*! \brief C6 instance constructor copying all contents off MPI broadcast from MPI process 0
   *
   * \param mpidata: `mixMPI *` pointer to MPI data structure.
   */
  C6(const mixMPI *mpidata);

  /*! \brief send C6 instance from MPI process 0 via MPI broadcasts to all other processes
   *
   * \param mpidata: `mixMPI *` pointer to MPI data structure.
   */
  void mpibcast(const mixMPI *mpidata);
#endif

};


/*! \brief Representation of the FORTRAN C9 blocks.
 */
class C9 {
protected:
  //! \brief Number of rows in the GIS and GLS matrices
  int gis_size_0;
  //! \brief Number of rows in the SAM matrix
  int sam_size_0;

public:
  //! \brief QUESTION: definition?
  int nlem;
  //! \brief QUESTION: definition?
  int nlemt;
  //! \brief QUESTION: definition?
  dcomplex **gis;
  //! \brief QUESTION: definition?
  dcomplex **gls;
  //! \brief QUESTION: definition?
  dcomplex **sam;

  /*! \brief C9 instance constructor.
   *
   * \param ndi: `int` QUESTION: definition?
   * \param nlem: `int` QUESTION: definition?
   * \param ndit: `int` QUESTION: definition?
   * \param nlemt: `int` QUESTION: definition?
   */
  C9(int ndi, int nlem, int ndit, int nlemt);

  /*! \brief C9 instance constructor copying contents from preexisting object.
   *
   * \param rhs: `C9` preexisting object to copy from
   */
  C9(const C9& rhs);

  /*! \brief C9 instance destroyer.
   */
  ~C9();

#ifdef MPI_VERSION
  /*! \brief C9 instance constructor copying all contents off MPI broadcast from MPI process 0
   *
   * \param mpidata: `mixMPI *` pointer to MPI data structure.
   */
  C9(const mixMPI *mpidata);

  /*! \brief send C9 instance from MPI process 0 via MPI broadcasts to all other processes
   *
   * \param mpidata: `mixMPI *` pointer to MPI data structure.
   */
  void mpibcast(const mixMPI *mpidata);
#endif

};

/*! \brief structure with essential MPI data.
 */
class mixMPI {
public:
  //! \brief was MPI initialised?
  bool mpirunning;
  //! \brief MPI rank
  int rank;
  //! \brief MPI nprocs
  int nprocs;

  /*! \brief empty mixMPI instance constructor.
   */
  mixMPI();

  /*! \brief mixMPI instance constructor from an actual MPI communicator.
   */
#ifdef MPI_VERSION
  mixMPI(MPI_Comm comm);
#endif
  
  /*! \brief mixMPI instance constructor copying its contents from a preexisting object.
   */
  mixMPI(const mixMPI& rhs);

  /*! \brief mixMPI instance destroyer.
   */
  ~mixMPI();
};

/*! \brief A data structure representing the information used for a single scale
 * of the CLUSTER case.
 */
class ClusterIterationData {
public:
  //! \brief Pointer to a C1 structure.
  C1 *c1;
  //! \brief Pointer to a C1_AddOns structure.
  C1_AddOns *c1ao;
  //! \brief Pointer to a C2 structure.
  C2 *c2;
  //! \brief Pointer to a C3 structure.
  C3 *c3;
  //! brief Pointer to a C4 structure.
  C4 *c4;
  //! \brief Pointer to a C6 structure.
  C6 *c6;
  //! \brief Pointer to a C9 structure.
  C9 *c9;
  //! \brief Pointer to a formatted output file.
  double *gaps;
  double **tqse;
  dcomplex **tqspe;
  double **tqss;
  dcomplex **tqsps;
  double ****zpv;
  double **gapm;
  dcomplex **gappm;
  double *argi;
  double *args;
  double **gap;
  dcomplex **gapp;
  double **tqce;
  dcomplex **tqcpe;
  double **tqcs;
  dcomplex **tqcps;
  double *duk;
  double **cextlr;
  double **cext;
  double **cmullr;
  double **cmul;
  double *gapv;
  double *tqev;
  double *tqsv;
  double *u;
  double *us;
  double *un;
  double *uns;
  double *up;
  double *ups;
  double *unmp;
  double *unsmp;
  double *upmp;
  double *upsmp;
  double scan;
  double cfmp;
  double sfmp;
  double cfsp;
  double sfsp;
  double qsfi;
  double sqsfi;
  dcomplex *am_vector;
  dcomplex **am;
  dcomplex arg;
  //! \brief Wave vector.
  double vk;
  //! \brief Wave number.
  double wn;
  double xip;
  int number_of_scales;
  int xiblock;
  int firstxi;
  int lastxi;

  ClusterIterationData(GeometryConfiguration *gconf, ScattererConfiguration *sconf, const mixMPI *mpidata);
  
  ClusterIterationData(const ClusterIterationData& rhs);

#ifdef MPI_VERSION
  ClusterIterationData(const mixMPI *mpidata);

  /*! \brief Broadcast over MPI the ClusterIterationData instance from MPI process 0 to all others.
   *
   * When using MPI, the initial ClusterIterationData instance created by MPI process 0
   * needs to be replicated on all other processes. This function sends it using
   * MPI broadcast calls. The MPI broadcast calls in this function must match those
   * in the constructor using the mixMPI pointer.
   *
   * \param mpidata: `mixMPI *` Pointer to the mpi structure used to do the MPI broadcast.
   */
  void mpibcast(const mixMPI *mpidata);
#endif

  ~ClusterIterationData();

};

/*! \brief A data structure representing the angles to be evaluated in the problem.
 *
 */
class ScatteringAngles {
protected:
  //! \brief Number of incident field azimuth angles.
  int _nth;
  //! \brief Number of scattered field azimuth angles.
  int _nths;
  //! \brief Number of incident field elevation angles.
  int _nph;
  //! \brief Number of scattered field elevation angles.
  int _nphs;
  //! \brief Number of incident field propagation angles.
  int _nk;
  //! \brief Number of scattered field propagation angles.
  int _nks;
  //! \brief Total number of field propagation angles.
  int _nkks;
  //! \brief First incident field azimuth angle.
  double _th;
  //! \brief Incident field azimuth angle increment.
  double _thstp;
  //! \brief Last incident field azimuth angle.
  double _thlst;
  //! \brief First scattered field azimuth angle.
  double _ths;
  //! \brief Scattered field azimuth angle increment.
  double _thsstp;
  //! \brief Last scattered field azimuth angle.
  double _thslst;
  //! \brief First incident field elevation angle.
  double _ph;
  //! \brief Incident field elevation angle increment.
  double _phstp;
  //! \brief Last incident field elevation angle.
  double _phlst;
  //! \brief First scattered field elevation angle.
  double _phs;
  //! \brief Scattered field elevation angle increment.
  double _phsstp;
  //! \brief Last scattered field elevation angle.
  double _phslst;
  //! \brief Azimuth scattering deflection.
  double _thsca;

public:
  //! \brief Read only view of `_nth`.
  const int& nth = _nth;
  //! \brief Read only view of `_nths`.
  const int& nths = _nths;
  //! \brief Read only view of `_nph`.
  const int& nph = _nph;
  //! \brief Read only view of `_nphs`.
  const int& nphs = _nphs;
  //! \brief Read only view of `_nk`.
  const int& nk = _nk;
  //! \brief Read only view of `_nks`.
  const int& nks = _nks;
  //! \brief Read only view of `_nkks`.
  const int& nkks = _nkks;
  //! \brief Read only view of `_th`.
  const double& th = _th;
  //! \brief Read only view of `_thstp`.
  const double& thstp = _thstp;
  //! \brief Read only view of `_thlst`.
  const double& thlst = _thlst;
  //! \brief Read only view of `_ths`.
  const double& ths = _ths;
  //! \brief Read only view of `_thsstp`.
  const double& thsstp = _thsstp;
  //! \brief Read only view of `_thslst`.
  const double& thslst = _thslst;
  //! \brief Read only view of `_ph`.
  const double& ph = _ph;
  //! \brief Read only view of `_phstp`.
  const double& phstp = _phstp;
  //! \brief Read only view of `_phlst`.
  const double& phlst = _phlst;
  //! \brief Read only view of `_phs`.
  const double& phs = _phs;
  //! \brief Read only view of `_phsstp`.
  const double& phsstp = _phsstp;
  //! \brief Read only view of `_phslst`.
  const double& phslst = _phslst;
  //! \brief Read only view of `_thsca`.
  const double& thsca = _thsca;

  /*! \brief ScatteringAngles instance constructor.
   *
   * \param gconf: `GeometryConfiguration*` Pointer to a GeometryConfiguration object.
   */
  ScatteringAngles(GeometryConfiguration *gconf);
  
  /*! \brief ScatteringAngles copy constructor.
   *
   * \param rhs: `ScatteringAngles&` Reference to the ScatteringAngles object to be copied.
   */
  ScatteringAngles(const ScatteringAngles &rhs);

#ifdef MPI_VERSION
  /*! \brief ScatteringAngles copy from MPI broadcast.
   *
   * \param mpidata: `mixMPI *` Pointer to the mpidata instance used to copy the data.
   */
  ScatteringAngles(const mixMPI *mpidata);

    /*! \brief Broadcast over MPI the ScatteringAngles instance from MPI process 0 to all others.
   *
   * When using MPI, the initial ScatteringAngles instance created by MPI process 0
   * needs to be replicated on all other processes. This function sends it using
   * MPI broadcast calls. The MPI broadcast calls in this function must match those
   * in the constructor using the mixMPI pointer.
   *
   * \param mpidata: `mixMPI *` Pointer to the mpi structure used to do the MPI broadcast.
   */
  void mpibcast(const mixMPI *mpidata);
#endif

};


#endif
