#ifndef TTR_SUPPORT
#define TTR_SUPPORT

#include <ttr_environment.hpp>
#include <cmath>
#include <limits>
#include <algorithm>

namespace ttr{
// TTR functions
inline double F_dMs_dt(double Gs,double Kl,double KM,double Ms){
  return Gs - ( Kl*Ms ) / ( 1 + KM / Ms );
}
inline double F_dMr_dt(double Gr,double Kl,double KM,double Mr){
  return Gr - ( Kl*Mr ) / ( 1 + KM / Mr );
}
inline double F_dCs_dt(double Uc,double Fc,double Gs,double TAUc){
  return Uc - Fc * Gs - TAUc;
}
inline double F_dCr_dt(double Fc,double Gr,double TAUc){
  return TAUc - Fc * Gr;
}
inline double F_dNs_dt(double Fn,double Gs,double TAUn) {
  return TAUn - Fn * Gs;
}
inline double F_dNr_dt(double Un,double Fn,double Gr,double TAUn) {
  return Un - Fn * Gr - TAUn;
}
inline double F_Gs(double gs,double Ms,double Cs,double Ns) {
  return gs * Cs * Ns / Ms;
}
inline double F_Gr(double gr,double Mr,double Cr,double Nr) {
  return gr * Cr * Nr / Mr;
}
inline double F_Uc(double A0,double Ms,double KA,double Cs,double Jc) {
  return ( A0*Ms ) / ( (1 + Ms / KA) * (1 + Cs / (Jc*Ms))  );
}
inline double F_Un(double N0,double Mr,double KA,double Nr,double Jn) {
  return ( N0*Mr ) / ( (1 + Mr / KA) * (1 + Nr / (Jn*Mr))  );
}
inline double F_TAUc(double Cs,double Cr,double Ms,double Mr,double RC) {
  return RC * ( Cs/Ms - Cr/Mr );
}
inline double F_TAUn(double Ns,double Nr,double Ms,double Mr, double RN) {
  return RN * (Nr / Mr - Ns / Ms);
}

static inline double reflect(double x, double lo, double up){
  if(lo > up){
    std::swap(lo,up);
  }
  double inf = std::numeric_limits<double>::infinity();
  if(x == inf)
    return up;
  if(x == -1.0*inf)
    return lo;
  if(x >= lo && x <= up)
    return x;
  if(lo == up){
    return lo;
  }
  double interval = up - lo;
  double distance;
  int ft;
  if(x < lo){
    distance = lo - x;
    if(up == inf)
      return lo + distance;
    ft = 0;
  }
  else{
    distance = x - up;
    if(lo == -1* inf)
      return up - distance;
    ft = 1;
  }
  double ping_pong = distance / interval;
  int reflect_times = (int)(ping_pong);
  reflect_times -= (reflect_times > ping_pong);
  double rest = distance - (reflect_times * interval);
  int from_top = (reflect_times + ft) % 2;
  if(from_top == 1){
    return up - rest;
  }
  else{
    return lo + rest;
  }
}

//WATCH:
// for performance reasons, the q parameter is unused. If non-linear resistances are 
// desired, use the commented lines
inline double F_RsC(double RHOc,double Ms){ //,double q) {
  //return RHOc / std::pow(Ms,q);
  return RHOc / Ms;
}
inline double F_RrC(double RHOc,double Mr){ //,double q) {
  //return RHOc / std::pow(Mr,q);
  return RHOc / Mr;
}
inline double F_RrN(double RHOn,double Mr){ //,double q) {
  //return RHOn / std::pow(Mr,q);
  return RHOn / Mr;
}
inline double F_RsN(double RHOn,double Ms){ //,double q) {
  //return RHOn / std::pow(Ms,q);
  return RHOn / Ms;
}

//helper functions
inline double trap2(double  x, double a, double b, double c, double d) {
  double trp2 = std::fmin((x-a)/(b-a), (d-x)/(d-c));
  return std::clamp(trp2, 0.0, 1.0);
}
inline double trap1( double x, double a,  double b){
  return std::clamp((x-a)/(b-a), 0.0, 1.0);
}
inline double my0100(double X,double A,double B,double C=0,double D=100){
  return (X-A)/(B-A) * (D-C) + C;
}
inline double invcloglog(double x){
  return 1.0 - exp(-exp(x));
}

//' calculates the realised carbon uptake rate
inline double get_CUR_STD(double tcur, double ppfd, double swc, double Ns, double Ms, ParameterVector<Global> const& globals, ParameterVector<BPar> const& beta){
  double min_el =
    std::min({
      trap2(tcur,beta[BPar::CU_tcur_1],beta[BPar::CU_tcur_2],beta[BPar::CU_tcur_3],beta[BPar::CU_tcur_4]),
      trap1(ppfd,beta[BPar::CU_ppfd_1],beta[BPar::CU_ppfd_2]),
      trap1(swc,beta[BPar::CU_swc_1],beta[BPar::CU_swc_2]),
      trap1(Ns/Ms,beta[BPar::CU_Ns_1],beta[BPar::CU_Ns_2])});
  return globals[Global::CUmax] * min_el;
}

//' calculates the realised carbon uptake rate
inline double get_CUR_FQR(double swc, double Ns, double Ms, double A, ParameterVector<Global> const& globals, ParameterVector<BPar> const& beta){
  return globals[Global::CUmax] / 30.0 * A *
    std::fmin(
      trap1(swc,beta[BPar::CU_swc_1],beta[BPar::CU_swc_2]),
      trap1(Ns/Ms,beta[BPar::CU_Ns_1],beta[BPar::CU_Ns_2]));
}

//' calculates the realised carbon uptake rate
inline double get_CUR_RED(double swc, double Ns, double Ms, double A, ParameterVector<Global> const& globals, ParameterVector<BPar> const& beta){
  return globals[Global::CUmax]/30.0 * A *
    trap1(swc,beta[BPar::CU_swc_1],beta[BPar::CU_swc_2]) *
    trap1(Ns/Ms,beta[BPar::CU_Ns_1],beta[BPar::CU_Ns_2]);
  //Global["CUmax"]/30 normalises so that photo = 30 = CUmax
}

//' calculates the realised nitrogen uptake rate
inline double get_NUR_STD(double tnur, double Nsoil, double swc, ParameterVector<Global> const& globals, ParameterVector<BPar> const& beta){
  double min_el =
    std::min({
      trap1(tnur,beta[BPar::NU_tnur_1],beta[BPar::NU_tnur_2]),
      trap1(Nsoil,beta[BPar::NU_Nsoil_1],beta[BPar::NU_Nsoil_2]),
      trap2(swc,beta[BPar::NU_swc_1],beta[BPar::NU_swc_2],beta[BPar::NU_swc_3],beta[BPar::NU_swc_4])});
  return globals[Global::NUmax] * min_el;
}

//' calculates the realised nitrogen uptake rate
inline double get_NUR_RED(double tnur, double Nsoil, double swc, ParameterVector<Global> const& globals, ParameterVector<BPar> const& beta){
  // Rcout << "tnur: " << trap1(tnur,beta[BPar::NU_tnur_1],beta[BPar::NU_tnur_2]) << "\n";
  // Rcout << "Nsoil: " << trap1(Nsoil,beta[BPar::NU_Nsoil_1],beta[BPar::NU_Nsoil_2]) << "\n";
  // Rcout << "swc: " << trap2(swc,beta[BPar::NU_swc_1],beta[BPar::NU_swc_2],beta[BPar::NU_swc_3],beta[BPar::NU_swc_4]) << "\n";
  // Rcout << "NUmax: " << globals[Global::NUmax] << "\n";

  return globals[Global::NUmax] *
    trap1(tnur,beta[BPar::NU_tnur_1],beta[BPar::NU_tnur_2]) *
    trap1(Nsoil,beta[BPar::NU_Nsoil_1],beta[BPar::NU_Nsoil_2]) *
    trap2(swc,beta[BPar::NU_swc_1],beta[BPar::NU_swc_2],beta[BPar::NU_swc_3],beta[BPar::NU_swc_4]);
}

//' calculates the realised growth rate
inline double get_g_STD(double tgrowth, double swc, ParameterVector<Global> const& globals, ParameterVector<BPar> const& beta){
  return globals[Global::gmax] * trap2(tgrowth,beta[BPar::g_tgrowth_1],beta[BPar::g_tgrowth_2],beta[BPar::g_tgrowth_3],beta[BPar::g_tgrowth_4]);
}

//' calculates the realised growth rate
inline double get_g_RED(double tgrowth, double swc, ParameterVector<Global> const& globals, ParameterVector<BPar> const& beta){
  return globals[Global::gmax] *
    trap2(tgrowth,beta[BPar::g_tgrowth_1],beta[BPar::g_tgrowth_2],beta[BPar::g_tgrowth_3],beta[BPar::g_tgrowth_4]) *
    trap1(swc,beta[BPar::g_swc_1],beta[BPar::g_swc_2]);
}

//' Calculates the realised loss rate
inline double get_m_std(double tloss, ParameterVector<Global> const& globals, ParameterVector<BPar> const& beta){
  double   p_1 = beta[BPar::L_mix_1]/100.0;
  double   p_2 = 1.0 - p_1;
  double temp_loss = trap1(tloss, beta[BPar::r_tloss_1],  beta[BPar::r_tloss_2]) ;
  double loss =  p_1 * globals[Global::mmax] + 
                 p_2 * globals[Global::mmax] * temp_loss ;
  return loss;           
}

//' Calculates the realised loss rate
inline double get_m_oak(double tloss, double swc, ParameterVector<Global> const& globals, ParameterVector<BPar> const& beta){
  double sum_m = beta[BPar::L_mix_1] + beta[BPar::L_mix_2] + beta[BPar::L_mix_3];
  double p_1 = beta[BPar::L_mix_1] / sum_m;
  double p_2 = beta[BPar::L_mix_2] / sum_m;
  double p_3 = beta[BPar::L_mix_3] / sum_m;
  
  double temp_loss =  (1.0 - trap1(tloss, beta[BPar::L_tloss_1], beta[BPar::L_tloss_2]));
  double moist_loss =  (1.0 - trap1(swc, beta[BPar::L_swc_1], beta[BPar::L_swc_2])) ;
  double loss = p_1 * globals[Global::mmax] + 
                p_2 * globals[Global::mmax] * temp_loss +
                p_3 * globals[Global::mmax] * moist_loss;
  return loss;
}



inline double f_pe(double pe, double pe_scale){
  if(pe <= 0) return 0;
  return rnorm(1,0,pe * pe_scale)[0];
}

struct NONE{};
template <typename V>
void not_implemented(){
  static_assert(std::is_same_v<V,NONE>, "Function not implemented for new variant. check site function.");
}

}


#endif
