/*
   This file is part of the RELXILL model code.

   RELXILL is free software: you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   any later version.

   RELXILL is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   GNU General Public License for more details.
   For a copy of the GNU General Public License see
   <http://www.gnu.org/licenses/>.

    Copyright 2022 Thomas Dauser, Remeis Observatory & ECAP
*/

#include "LocalModel.h"
#include "Rebin.h"
#include "XspecSpectrum.h"
#include "JedSad.h"

#include <stdexcept>
#include <iostream>



/*
 * @brief: calculate line model
 * @description: simply shift the energy grid by the line energy and call to the relbase function, which calculates
 *  the line for 1 keV
 */
void LocalModel::line_model(const XspecSpectrum &spectrum) {
  auto rel_param = get_rel_params_struct();

  // relbase calculates the line for 1keV, i.e., shift the energy grid accordingly
  spectrum.shift_energy_grid_1keV(rel_param->lineE);

  int status = EXIT_SUCCESS;
  relline_spec_multizone *spec = relbase(spectrum.energy, spectrum.num_flux_bins(), rel_param, &status);
  delete_rel_params(rel_param);

  for (int ii = 0; ii < spectrum.num_flux_bins(); ii++) {
    spectrum.flux[ii] = spec->flux[0][ii];
  }

  if (status != EXIT_SUCCESS) {
    throw std::exception();
  }

}

void LocalModel::add_jedsad_parameters_to_model_params()
{
  // convert jedsad parameters to Gamma/Ecut
  auto primespec_params = convert_jedsad_to_primaryspec_params(m_model_params);

  // add these parameters to the input list
  m_model_params.insert_par(XPar::gamma, primespec_params.gamma);
  m_model_params.insert_par(XPar::ecut, primespec_params.ecut);

  if (is_debug_run())
  {
    printf(" *** debug: jedsad parameters: gamma=%f, ecut=%f\n", primespec_params.gamma, primespec_params.ecut);
  }
}

bool LocalModel::handle_zero_jedsad_parameters(const XspecSpectrum& spectrum)
{
  if (m_model_params.get_par(XPar::gamma) < 1e-8 || m_model_params.get_par(XPar::ecut) < 1e-8)
  {
    printf(" *** warning: jedsad parameters are 0, no reflection calculated\n");
    setArrayToZero(spectrum.flux, spectrum.num_flux_bins());
    return true;
  }
  return false;
}

void LocalModel::check_relxill_error_default(int status)
{
  if (status != EXIT_SUCCESS)
  {
    throw std::exception();
  }
}


/**
 * @brief Calculates the RELXILL model spectrum combining relativistic reflection and X-ray illumination
 *
 * Handles different RELXILL variants:
 * - RelxillBB: Uses relxill_bb_kernel() with separate rel/xill parameter structures
 * - JedSad variant: Converts JedSad parameters to gamma/Ecut, validates non-zero values
 * - Standard: Uses relxill_kernel() with combined model parameters
 *
 * @param spectrum Input/output spectrum object (energy grid, flux array, bin count)
 * @throws std::exception on calculation failures
 * @note Modifies spectrum.flux array; may add parameters for JedSad models
 */
void LocalModel::relxill_model(const XspecSpectrum &spectrum) {

  int status = EXIT_SUCCESS;

  if (m_model_params.get_model_name() == ModelName::relxillBB || m_model_params.get_model_name() ==
    ModelName::relxillBBxill)
  {
    relxill_bb_kernel(spectrum, m_model_params, &status);
  }
  else
  {
    if (m_model_params.get_model_name() == ModelName::relxill_jedsad)
    {
      add_jedsad_parameters_to_model_params();
      if (handle_zero_jedsad_parameters(spectrum)) return;
    }
    relxill_kernel(spectrum, m_model_params, &status);
  }

  check_relxill_error_default(status);
}


/**
 * @brief calculate convolution of a given spectrum
 * @description note the model is only defined in the 0.01-1000 keV energy range and will
 * return 0 outside this range (see function relconv_kernel for more details)
 */
void LocalModel::conv_model(const XspecSpectrum& spectrum) const
{
  if (calcSum(spectrum.flux, spectrum.num_flux_bins()) <= 0.0) {
    throw ModelEvalFailed("input flux for convolution model needs to be >0");
  }

  int status = EXIT_SUCCESS;
  auto rel_params = get_rel_params(m_model_params);
  relconv_kernel(spectrum.energy, spectrum.flux, spectrum.num_flux_bins(), rel_params, &status);
  delete_rel_params(rel_params);

  if (status != EXIT_SUCCESS) {
    throw ModelEvalFailed("executing convolution failed");
  }
}


/**
 * @brief calculate xillver model
 */
void LocalModel::xillver_model(const XspecSpectrum& spectrum) const
{
  int status = EXIT_SUCCESS;

  xillParam* xill_param = get_xill_params(m_model_params);
  xillSpec *spec = get_xillver_spectra(xill_param, &status);

  // add the dependence on incl, assuming a semi-infinite slab
  norm_xillver_spec(spec, xill_param->incl);

  _rebin_spectrum(spectrum.energy, spectrum.flux, spectrum.num_flux_bins(), spec->ener, spec->flu[0], spec->n_ener);
  free_xill_spec(spec);

  add_primary_component(spectrum.energy,
                        spectrum.num_flux_bins(),
                        spectrum.flux,
                        nullptr,
                        xill_param,
                        nullptr,
                        &status);

  delete xill_param;

  if (status != EXIT_SUCCESS) {
    throw std::exception();
  }
}


/**
 * Wrapper function, which can be called from any C-type local model function as
 * required by Xspec
 * @param model_name: unique name of the model
 * @param parameter_values: input parameter_values array (size can be determined from the model definition for the model_name)
 * @param xspec_energy[num_flux_bins]: input energy grid
 * @param xspec_flux[num_flux_bins]: - flux array (already allocated), used to return the calculated values
 *                    - for convolution models this is also the input flux
 * @param num_flux_bins
 */
void xspec_C_wrapper_eval_model(ModelName model_name,
                                const double *parameter_values,
                                double *xspec_flux,
                                int num_flux_bins,
                                const double *xspec_energy) {

  try {
    LocalModel local_model{parameter_values, model_name};

    XspecSpectrum spectrum{xspec_energy, xspec_flux, static_cast<size_t>(num_flux_bins)};
    local_model.eval_model(spectrum);

  } catch (ModelNotFound &e) {
    std::cout << e.what();
  }
}
