#include <string.h>
#include "preact/impreact_adpak.h"
#include "preact/adpak.h"
#include "preact/periodic_table.h"
#include "preact/iutil_str.h"

using namespace TRANSP ;

// --- adset_param ---
void adset_param::adp_reset_param(int knucz) const {
  int ier = 0 ;
  ::F77NAME(adp_reset)(knucz, kladen, kladtip, kleci, kldrmlt, ier) ;
  if (ier) TRANSP_THROW("?preact::adset_param::adp_reset_param(): Error initializing ADPAK") ;
} ;

// -------------------- ImpReactADPAK ----------------------
ImpReactADPAK::~ImpReactADPAK() { } ;

ImpReactADPAK::ImpReactADPAK(int knucz, string name, size_t num, Shape fshape) : aReact(name,num,fshape),
 inucz(knucz), adpak_data() {
  // bad Z
  if (knucz>=58 && knucz<=60) {
    string s = "?preact::ImpReactADPAK -- Unable to construct an ADPAK based Impurity\n" ;
    s = s +    "     reaction for elements with nuclear charge 58, 59 or 60 due to strange\n" ;
    s = s +    "     results (Inf and Nan) seen in the neutral radiation rate." ;
    TRANSP_THROW(s) ;
  } ;

  // setup ranges
  Range& te = range(0) ;
  te.set_name(string("Te(keV) -- ") + name) ;
  te.set_scale(Range::LOG) ;
  te.set_min(.001) ;
  te.set_max(100.) ;
  te.set_min_policy(Range::IGNORE) ;
  te.set_max_policy(Range::HALT) ;

  Range& ne = range(1) ;
  ne.set_name(string("Ne(m-3) -- ") + name) ;
  ne.set_scale(Range::LOG) ;
  ne.set_min(1.e15) ;
  ne.set_max(1.e23) ;
  ne.set_min_policy(Range::IGNORE) ;
  ne.set_max_policy(Range::HALT) ;
} ;

void ImpReactADPAK::set_laden(int i)  { 
  if (i<0) TRANSP_THROW("?preact::ImpReactADPAK::set_laden(): Argument must be nonnegative") ;
  bool change = adpak_data.kladen != i ; 
  adpak_data.kladen = i ; 
  if (change) notify() ;
} ;

void ImpReactADPAK::set_ladtip(int i) {  
  if (i<0) TRANSP_THROW("?preact::ImpReactADPAK::set_ladtip(): Argument must be nonnegative") ;
  bool change = adpak_data.kladtip != i ; 
  adpak_data.kladtip = i ; 
  if (change) notify() ;
} ;

void ImpReactADPAK::set_leci(int i)   {  
  if (i<0) TRANSP_THROW("?preact::ImpReactADPAK::set_leci(): Argument must be nonnegative") ;
  bool change = adpak_data.kleci != i ; 
  adpak_data.kleci = i ; 
  if (change) notify() ;
} ;

void ImpReactADPAK::set_ldrmlt(int i) {  
  if (i<0) TRANSP_THROW("?preact::ImpReactADPAK::set_ldrmlt(): Argument must be nonnegative") ;
  bool change = adpak_data.kldrmlt != i ;
  adpak_data.kldrmlt = i ; 
  if (change) notify() ;
} ;

ImpReactADPAK* ImpReactADPAK::cast(const React& r) {
  return dynamic_cast<ImpReactADPAK*>(aReact::cast(r)) ;
} ;

void ImpReactADPAK::call(const ap_64& xarg, ap_64& f, Range::range_error& rerr) {
  adpak_data.adp_reset_param(inucz) ;  // redo ADPAK setup if parameters have changed

  fp_64 x = xarg(0) ;
  fp_64 y = xarg(1) ;

  fp_64 xlow  = xmin(0) ;
  fp_64 xhigh = xmax(0) ;
  fp_64 ylow  = xmin(1) ;
  fp_64 yhigh = xmax(1) ;

  rerr = Range::RANGE_OK ;

  range_check<fp_64>(x,xlow,xhigh,rerr) ;
  range_check<fp_64>(y,ylow,yhigh,rerr) ;

  adpak_func(x, y, f.fptr()) ;

  return ;
} ;

void ImpReactADPAK::vcall2(const ap_64& xarg, const ap_64& yarg, ap_64& f, Range::range_error& rerr) {
  adpak_data.adp_reset_param(inucz) ;  // redo ADPAK setup if parameters have changed

  fp_64 xlow  = xmin(0) ;
  fp_64 xhigh = xmax(0) ;
  fp_64 ylow  = xmin(1) ;
  fp_64 yhigh = xmax(1) ;

  rerr = Range::RANGE_OK ;
  size_t n = xarg.xsize(0) ;

  for (size_t i=0 ; i<n ; i++) { 
    fp_64 x = xarg(i) ;
    fp_64 y = yarg(i) ;
    range_check<fp_64>(x,xlow,xhigh,rerr) ;
    range_check<fp_64>(y,ylow,yhigh,rerr) ;

    adpak_func(x, y, &(f(0,i))) ;
  } ;
  return ;
} ;

state ImpReactADPAK::current() const {
  state s ;
  // -- super state --
  s.vresize(1) ;
  s.vstate(0) = aReact::current() ;

  // -- this data --
  s.vi.resize(5) ;
  s.vi[0] = inucz ;
  s.vi[1] = adpak_data.kladen ;
  s.vi[2] = adpak_data.kladtip ;
  s.vi[3] = adpak_data.kleci ;
  s.vi[4] = adpak_data.kldrmlt ;
  return s ;
} ;

string ImpReactADPAK::dprint() const {
  ostringstream c ;
  c << " Nuclear Z: " << inucz << endl ;
  c << " Element:   " << ImpReaction::table_name(inucz) << endl ;
  c << " ADPAK Parameters " << endl ;
  c << "    laden:  " << adpak_data.kladen  << endl ;
  c << "    ladtip: " << adpak_data.kladtip << endl ;
  c << "    leci:   " << adpak_data.kleci   << endl ;
  c << "    ldrmlt: " << adpak_data.kldrmlt << endl ;
  return ss2string(c) ;
} ;

// -------------------- ImpReactADPAK_Ioniz ----------------------
ImpReactADPAK_Ioniz::~ImpReactADPAK_Ioniz() { } ;

ImpReactADPAK_Ioniz::ImpReactADPAK_Ioniz(int knucz, string name, size_t num, Shape fshape) :
  ImpReactADPAK(knucz,name,num,fshape) {
} ;

React ImpReactADPAK_Ioniz::create(int knucz) {
  if (knucz<1 || knucz>=100) TRANSP_THROW("?preact::ImpReactADPAK_Ioniz::create(): nuclear Z is out of bounds") ;
  string sname = "ADPAK Impurity Ionization -- " + ImpReaction::table_name(knucz) ;
  Shape fshape(1) ;
  fshape[0] = knucz+1 ;  // number of ionization states including ground state

  ImpReactADPAK_Ioniz* a = new ImpReactADPAK_Ioniz(knucz, sname, 2, fshape) ;
  return React(a) ;
} ;

void ImpReactADPAK_Ioniz::adpak_func(const fp_64& x, const fp_64& y, fp_64* f) const {
  F77NAME(adp_ioniz_r8)(x,y,f) ;
} ;

state ImpReactADPAK_Ioniz::current() const {
  state s ;
  s.vresize(1) ;
  s.vstate(0) = ImpReactADPAK::current() ;
  return s ;
} ;

string ImpReactADPAK_Ioniz::dprint() const {
  string s = " Impurity Ionization using ADPAK\n" + ImpReactADPAK::dprint() ;
  return s ;
} ;

ImpReactADPAK_Ioniz* ImpReactADPAK_Ioniz::cast(const React& r) {
  return dynamic_cast<ImpReactADPAK_Ioniz*>(aReact::cast(r)) ;
} ;

// -------------------- ImpReactADPAK_Recom ----------------------
ImpReactADPAK_Recom::~ImpReactADPAK_Recom() { } ;

ImpReactADPAK_Recom::ImpReactADPAK_Recom(int knucz, string name, size_t num, Shape fshape) :
  ImpReactADPAK(knucz,name,num,fshape) {
} ;

React ImpReactADPAK_Recom::create(int knucz) {
  if (knucz<1 || knucz>=100) TRANSP_THROW("?preact::ImpReactADPAK_Recom::create(): nuclear Z is out of bounds") ;
  string sname = "ADPAK Impurity Recombination -- " + ImpReaction::table_name(knucz) ;
  Shape fshape(1) ;
  fshape[0] = knucz+1 ;  // number of recombination states including ground state

  ImpReactADPAK_Recom* a = new ImpReactADPAK_Recom(knucz, sname, 2, fshape) ;
  return React(a) ;
} ;

void ImpReactADPAK_Recom::adpak_func(const fp_64& x, const fp_64& y, fp_64* f) const {
  F77NAME(adp_recom_r8)(x,y,f) ;
} ;

state ImpReactADPAK_Recom::current() const {
  state s ;
  s.vresize(1) ;
  s.vstate(0) = ImpReactADPAK::current() ;
  return s ;
} ;

string ImpReactADPAK_Recom::dprint() const {
  string s = " Impurity Recombination using ADPAK\n" + ImpReactADPAK::dprint() ;
  return s ;
} ;

ImpReactADPAK_Recom* ImpReactADPAK_Recom::cast(const React& r) {
  return dynamic_cast<ImpReactADPAK_Recom*>(aReact::cast(r)) ;
} ;

// -------------------- ImpReactADPAK_Brem ----------------------
ImpReactADPAK_Brem::~ImpReactADPAK_Brem() { } ;

ImpReactADPAK_Brem::ImpReactADPAK_Brem(int knucz, string name, size_t num, Shape fshape) :
  ImpReactADPAK(knucz,name,num,fshape) {
} ;

React ImpReactADPAK_Brem::create(int knucz) {
  if (knucz<1 || knucz>=100) TRANSP_THROW("?preact::ImpReactADPAK_Brem::create(): nuclear Z is out of bounds") ;
  string sname = "ADPAK Impurity Bremsstrahlung -- " + ImpReaction::table_name(knucz) ;
  Shape fshape(1) ;
  fshape[0] = knucz+1 ;  // number of states including ground state

  ImpReactADPAK_Brem* a = new ImpReactADPAK_Brem(knucz, sname, 2, fshape) ;
  return React(a) ;
} ;

void ImpReactADPAK_Brem::adpak_func(const fp_64& x, const fp_64& y, fp_64* f) const {
  F77NAME(adp_brem_r8)(x,y,f) ;
} ;

state ImpReactADPAK_Brem::current() const {
  state s ;
  s.vresize(1) ;
  s.vstate(0) = ImpReactADPAK::current() ;
  return s ;
} ;

string ImpReactADPAK_Brem::dprint() const {
  string s = " Impurity Bremsstrahlung using ADPAK\n" + ImpReactADPAK::dprint() ;
  return s ;
} ;

ImpReactADPAK_Brem* ImpReactADPAK_Brem::cast(const React& r) {
  return dynamic_cast<ImpReactADPAK_Brem*>(aReact::cast(r)) ;
} ;
// -------------------- ImpReactADPAK_Rad ----------------------
ImpReactADPAK_Rad::~ImpReactADPAK_Rad() { } ;

ImpReactADPAK_Rad::ImpReactADPAK_Rad(int knucz, string name, size_t num, Shape fshape) :
  ImpReactADPAK(knucz,name,num,fshape), i_brem(0) {
} ;

React ImpReactADPAK_Rad::create(int knucz) {
  if (knucz<1 || knucz>=100) TRANSP_THROW("?preact::ImpReactADPAK_Rad::create(): nuclear Z is out of bounds") ;
  string sname = "ADPAK Impurity Radiation -- " + ImpReaction::table_name(knucz) ;
  Shape fshape(1) ;
  fshape[0] = knucz+1 ;  // number of ionization states including ground state

  ImpReactADPAK_Rad* a = new ImpReactADPAK_Rad(knucz, sname, 2, fshape) ;
  return React(a) ;
} ;

bool ImpReactADPAK_Rad::brem() const { 
  return i_brem!=0 ; 
} ; 

void ImpReactADPAK_Rad::set_brem(bool b) {
  int i = b ? 1 : 0 ;
  bool change = i_brem != i ;
  i_brem = i ;
  if (change) notify() ;
} ;

void ImpReactADPAK_Rad::adpak_func(const fp_64& x, const fp_64& y, fp_64* f) const {
  F77NAME(adp_rad_r8)(x,y,i_brem,f) ;
} ;

state ImpReactADPAK_Rad::current() const {
  state s ;
  s.vresize(1) ;
  s.vstate(0) = ImpReactADPAK::current() ;

  s.vi.resize(1) ;
  s.vi[0] = i_brem ;
  return s ;
} ;

string ImpReactADPAK_Rad::dprint() const {
  string s = " Impurity Radiation using ADPAK\n" ;
  if (i_brem != 0) {
    s += " Bremsstrahlung: Yes\n" ;
  }
  else {
    s +=  " Bremsstrahlung: No\n" ;
  } ;
  s+= ImpReactADPAK::dprint() ;
  return s ;
} ;

ImpReactADPAK_Rad* ImpReactADPAK_Rad::cast(const React& r) {
  return dynamic_cast<ImpReactADPAK_Rad*>(aReact::cast(r)) ;
} ;

