/*! \file Configuration.h
 */

#ifndef INCLUDE_CONFIGURATION_H_
#define INCLUDE_CONFIGURATION_H_

/**
 * \brief Exception for open file error handlers.
 */
class OpenConfigurationFileException: public std::exception {
protected:
  //! \brief Name of the file that was accessed.
  std::string file_name;

public:
  /**
   * \brief Exception instance constructor.
   *
   * \param name: `string` Name of the file that was accessed.
   */
  OpenConfigurationFileException(std::string name) { file_name = name; }

  /**
   * \brief Exception message.
   */
  virtual const char* what() const throw() {
    return file_name.c_str();
  }
};

/**
 * \brief Exception for unrecognized configuration data sets.
 */
class UnrecognizedConfigurationException: public std::exception {
protected:
  //! Description of the problem.
  std::string message;
public:
  /**
   * \brief Exception instance constructor.
   *
   * \param problem: `string` Description of the problem that occurred.
   */
  UnrecognizedConfigurationException(std::string problem) { message = problem; }
  /**
   * \brief Exception message.
   */
  virtual const char* what() const throw() {
    return message.c_str();
  }
};

/**
 * \brief A class to represent the configuration of the scattering geometry.
 *
 * GeometryConfiguration is a class designed to store the necessary configuration
 * data to describe the scattering geometry, including the distribution of the
 * particle components, the orientation of the incident and scattered radiation
 * fields and their polarization properties.
 */
class GeometryConfiguration {
  //! Temporary work-around to allow cluster() and sphere() peeking in.
  friend void cluster(std::string, std::string, std::string);
  friend void sphere(std::string, std::string, std::string);
protected:
  //! \brief Number of spherical components.
  int number_of_spheres;
  //! \brief Maximum expansion order of angular momentum.
  int l_max;
  //! \brief QUESTION: definition?
  int li;
  //! \brief QUESTION: definition?
  int le;
  //! \brief QUESTION: definition?
  int mxndm;
  //! \brief QUESTION: definition?
  int iavm;
  //! \brief Incident field polarization status (0 - linear, 1 - circular).
  int in_pol;
  //! \brief Number of transition points. QUESTION: correct?
  int npnt;
  //! \brief Transition smoothness. QUESTION: correct?
  int npntts;
  //! \brief Type of meridional plane definition.
  int meridional_type;
  //! \brief Transition matrix layer ID. QUESTION: correct?
  int jwtm;
  //! \brief Incident field initial azimuth.
  double in_theta_start;
  //! \brief Incident field azimuth step.
  double in_theta_step;
  //! \brief Incident field final azimuth.
  double in_theta_end;
  //! \brief Scattered field initial azimuth.
  double sc_theta_start;
  //! \brief Scattered field azimuth step.
  double sc_theta_step;
  //! \brief Scattered field final azimuth.
  double sc_theta_end;
  //! \brief Incident field initial elevation.
  double in_phi_start;
  //! \brief Incident field elevation step.
  double in_phi_step;
  //! \brief Incident field final elevation.
  double in_phi_end;
  //! \brief Scattered field initial elevation.
  double sc_phi_start;
  //! \brief Scattered field elevation step.
  double sc_phi_step;
  //! \brief Scattered field final elevation.
  double sc_phi_end;
  //! \brief Vector of spherical components X coordinates.
  double *sph_x;
  //! \brief Vector of spherical components Y coordinates.
  double *sph_y;
  //! \brief Vector of spherical components Z coordinates.
  double *sph_z;

public:
  /*! \brief Build a scattering geometry configuration structure.
   *
   * \param nsph: `int` Number of spheres to be used in calculation.
   * \param lm: `int` Maximum field angular momentum expansion order.
   * \param in_pol: `int` Incident field polarization status
   * \param npnt: `int` Number of transition points. QUESTION: correct?
   * \param npntts: `int` Transition smoothness. QUESTION: correct?
   * \param meridional_type: `int` Type of meridional plane definition (<0
   * for incident angles, 0 if determined by incidence and observation, =1
   * accross z-axis for incidence and observation, >1 across z-axis as a
   * function of incidence angles for fixed scattering).
   * \param li: `int`
   * \param le: `int`
   * \param mxndm: `int`
   * \param iavm: `int`
   * \param x: `double*` Vector of spherical components X coordinates.
   * \param y: `double*` Vector of spherical components Y coordinates.
   * \param z: `double*` Vector of spherical components Z coordinates.
   * \param in_th_start: `double` Incident field starting azimuth angle.
   * \param in_th_step: `double` Incident field azimuth angle step.
   * \param in_th_end: `double` Incident field final azimuth angle.
   * \param sc_th_start: `double` Scattered field starting azimuth angle.
   * \param sc_th_step: `double` Scattered field azimuth angle step.
   * \param sc_th_end: `double` Scattered field final azimuth angle.
   * \param in_ph_start: `double` Incident field starting elevation angle.
   * \param in_ph_step: `double` Incident field elevation angle step.
   * \param in_ph_end: `double` Incident field final elevation angle.
   * \param sc_ph_start: `double` Scattered field starting elevation angle.
   * \param sc_ph_step: `double` Scattered field elevation angle step.
   * \param sc_ph_end: `double` Scattered field final elevation angle.
   * \param jwtm: `int` Transition Matrix layer ID. QUESTION: correct?
   */
  GeometryConfiguration(
			int nsph, int lm, int in_pol, int npnt, int npntts, int meridional_type,
			int li, int le, int mxndm, int iavm,
			double *x, double *y, double *z,
			double in_th_start, double in_th_step, double in_th_end,
			double sc_th_start, double sc_th_step, double sc_th_end,
			double in_ph_start, double in_ph_step, double in_ph_end,
			double sc_ph_start, double sc_ph_step, double sc_ph_end,
			int jwtm
			);

  /*! \brief Destroy a GeometryConfiguration instance.
   */
  ~GeometryConfiguration();

  /*! \brief Build geometry configuration from legacy configuration input file.
   *
   * To allow for consistency tests and backward compatibility, geometry
   * configurations can be built from legacy configuration files. This function
   * replicates the approach implemented by the FORTRAN SPH and CLU codes, but
   * using a C++ oriented work-flow.
   *
   * \param file_name: `string` Name of the legacy configuration data file.
   * \return config: `GeometryConfiguration*` Pointer to object containing the
   * configuration data.
   */
  static GeometryConfiguration *from_legacy(std::string file_name);
};

/**
 * \brief A class to represent scatterer configuration objects.
 *
 * ScattererConfiguration is a class designed to store the necessary configuration
 * data to describe the scatterer properties.
 */
class ScattererConfiguration {
  //! Temporary work-around to allow cluster() and sphere() peeking in.
  friend void cluster(std::string, std::string, std::string);
  friend void sphere(std::string, std::string, std::string);
protected:
  //! \brief Matrix of dielectric parameters with size [NON_TRANS_LAYERS x N_SPHERES x LAYERS].
  std::complex<double> ***dc0_matrix;
  //! \brief Vector of sphere radii expressed in m, with size [N_SPHERES].
  double *radii_of_spheres;
  //! \brief Matrix of fractional transition radii with size [N_SPHERES x LAYERS].
  double **rcf;
  //! \brief Vector of sphere ID numbers, with size [N_SPHERES].
  int *iog_vec;
  //! \brief Vector of layer numbers for every sphere, with size [N_SPHERES].
  int *nshl_vec;
  //! \brief Vector of scale parameters, with size [N_SCALES].
  double *scale_vec;
  //! \brief Name of the reference variable type (one of XIV, WNS, WLS, PUS, EVS).
  std::string reference_variable_name;
  //! \brief Number of spherical components.
  int number_of_spheres;
  //! \brief Number of scales to use in calculation.
  int number_of_scales;
  //! \brief Type of dielectric functions (<0 at XIP, =0 as function of XI, >0 contants).
  int idfc;
  //! \brief External medium dielectric constant. QUESTION: correct?
  double exdc;
  //! \brief WP. QUESTION: better definition?
  double wp;
  //! \brief Peak XI. QUESTION: correct?
  double xip;
  //! \brief Flag to control whether to add an external layer.
  bool use_external_sphere;

  /*! \brief Build configuration from a HDF5 binary input file.
   *
   * This is the function called by the public method `from_binary()` in case of
   * HDF5 mode selection. This function creates a configuration structure from
   * a binary file written according to the HDF5 format standard.
   *
   * \param file_name: `string` Name of the binary configuration data file.
   * \return config: `ScattererConfiguration*` Pointer to object containing the
   * scatterer configuration data.
   */
  static ScattererConfiguration *from_hdf5(std::string file_name);
  
  /*! \brief Build configuration from legacy binary input file.
   *
   * This is the function called by the public method `from_binary()` in case of
   * legacy mode selection. This function creates a configuration structure from
   * a binary file written according to the proprietary mode used by the original
   * FORTRAN code.
   *
   * \param file_name: `string` Name of the binary configuration data file.
   * \return config: `ScattererConfiguration*` Pointer to object containing the
   * scatterer configuration data.
   */
  static ScattererConfiguration *from_legacy(std::string file_name);

  /*! \brief Write the scatterer configuration data to HDF5 binary output.
   *
   * This function is invoked by the public method `write_binary()` with the
   * "HDF5" format mode. It undertakes the task of writing the configuration
   * information to a binary file using the standard HDF5 format.
   *
   * \param file_name: `string` Name of the binary configuration data file.
   */
  void write_hdf5(std::string file_name);
  
  /*! \brief Write the scatterer configuration data to legacy binary output.
   *
   * This function is invoked by the public method `write_binary()` with the
   * "LEGACY" format mode. It undertakes the task of writing the configuration
   * information to a binary file using a proprietary format, as it was done
   * originally in the FORTRAN code.
   *
   * \param file_name: `string` Name of the binary configuration data file.
   */
  void write_legacy(std::string file_name);
public:
  /*! \brief Build a scatterer configuration structure.
   *
   * Prepare a default configuration structure by allocating the necessary
   * memory structures.
   *
   * \param nsph: `int` The number of spheres in the simulation.
   * \param scale_vector: `double*` The radiation-particle scale vector.
   * \param nxi: `int` The number of radiation-particle scalings.
   * \param variable_name: `string` The name of the radiation-particle scaling type.
   * \param iog_vector: `int*` Array of sphere identification numbers. QUESTION: correct?
   * \param ros_vector: `double*` Sphere radius array.
   * \param nshl_vector: `int*` Array of layer numbers.
   * \param rcf_vector: `double**` Array of fractional break radii. QUESTION: correct?
   * \param dielectric_func_type: `int` Type of dielectric function definition (=0 for constant,
   * \>0 as function of scale parameter, <0 for functions at XIP value and XI is scale factor
   * for dimensions).
   * \param dc_matrix: Matrix of reference dielectric constants.
   * \param has_external: `bool` Flag to set whether to add an external spherical layer.
   * \param exdc: `double` EXDC
   * \param wp: `double` wp
   * \param xip: `double` xip
   */
  ScattererConfiguration(
			 int nsph,
			 double *scale_vector,
			 int nxi,
			 std::string variable_name,
			 int *iog_vector,
			 double *ros_vector,
			 int *nshl_vector,
			 double **rcf_vector,
			 int dielectric_func_type,
			 std::complex<double> ***dc_matrix,
			 bool has_external,
			 double exdc,
			 double wp,
			 double xip
			 );

  /*! \brief Destroy a scatterer configuration instance.
   */
  ~ScattererConfiguration();

  /*! \brief Build configuration from binary configuration input file.
   *
   * The configuration step can save configuration data as a binary file. The original
   * FORTRAN code used this possibility to manage communication between the configuring
   * code and the calculation program. This possibility is maintained, in case the
   * configuration step needs to be separated from the calculation execution. In this
   * case, `from_binary()` is the class method that restores a ScattererConfiguration
   * object from a previously saved binary file.
   *
   * \param file_name: `string` Name of the binary configuration data file.
   * \param mode: `string` Binary encoding. Can be one of "LEGACY", ... . Optional
   * (default is "LEGACY").
   * \return config: `ScattererConfiguration*` Pointer to object containing the
   * scatterer configuration data.
   */
  static ScattererConfiguration* from_binary(std::string file_name, std::string mode = "LEGACY");

  /*! \brief Build scatterer configuration from legacy configuration input file.
   *
   * To allow for consistency tests and backward compatibility, ScattererConfiguration
   * objects can be built from legacy configuration files. This function replicates
   * the approach implemented by the FORTRAN EDFB code, but using a C++ oriented
   * work-flow.
   *
   * \param file_name: `string` Name of the legacy configuration data file.
   * \return config: `ScattererConfiguration*` Pointer to object containing the
   * scatterer configuration data.
   */
  static ScattererConfiguration* from_dedfb(std::string file_name);

  /*! \brief Print the contents of the configuration object to terminal.
   *
   * In case of quick debug testing, `ScattererConfiguration.print()` allows printing
   * a formatted summary of the configuration data to terminal.
   */
  void print();

  /*! \brief Write the scatterer configuration data to binary output.
   *
   * The execution work-flow may be split in a configuration step and one or more
   * calculation steps. In case the calculation is not being run all-in-one, it can
   * be useful to save the configuration data. `ScattererConfiguration.write_binary()`
   * performs the operation of saving the configuration in binary format. This function
   * can work in legacy mode, to write backward compatible configuration files, as well
   * as by wrapping the data into common scientific formats.
   *
   * \param file_name: `string` Name of the file to be written.
   * \param mode: `string` Binary encoding. Can be one of ["LEGACY", "HDF5"] . Optional
   * (default is "LEGACY").
   */
  void write_binary(std::string file_name, std::string mode="LEGACY");

  /*! \brief Write the scatterer configuration data to formatted text output.
   *
   * Writing configuration to formatted text is an optional operation, which may turn
   * out to be useful for consistency checks. As a matter of fact, formatted configuration
   * output is not read back by the FORTRAN code work-flow and it can be safely omitted,
   * unless there is a specific interest in assessing that the legacy code and the
   * updated one are doing the same thing.
   *
   * \param file_name: `string` Name of the file to be written.
   */
  void write_formatted(std::string file_name);
};

#endif
