#ifndef TTR_PHOTO_FUNC
#define TTR_PHOTO_FUNC

#include "dependencies/optimise.hpp"
#include "enum_functions.hpp"
#include "definitions.hpp"
#include "ParameterVector.hpp"

namespace ttr_photo{

using namespace Rcpp;
using namespace arma;

// VPD needed by Medlyn equation
// rh as a proportion
// es and ea in Pa
// return VPS in kPa
// Jones. Plants and Microclimate. Second edition. page 110.
inline double VPDcalc(double t, double rh){
  //saturated vapour pressure Pa
  double es = 613.75*exp(17.502*t/(t+240.97));
  //actual vapour pressure Pa
  double ea = rh*es;
  return ((es-ea)/1000.0);
}

//// temperature dependency functions

/*
// peaked arrhenius function "Entropy" version
inline double pf(double KTemp,double E,double S, double H, double y25, double KTref=298, double R=0.008314) {
  double firstexp = exp(((KTemp-KTref)*E)/(KTref*R*KTemp));
  double topexp = (1+exp((KTref*S-H)/(KTref*R)));
  double bottomexp = (1+exp((KTemp*S-H)/(KTemp*R)));
  return y25*firstexp*(topexp/bottomexp);
}
*/

// peaked arrhenius function "Topt" version
//#pfo<-function(KTemp,E,Topt,H,y25=1,KTref=298,R=0.008314) {
inline double pfo(double KTemp,double E,double Topt, double H, double y25, double KTref=298, double R=0.008314) {
    double d  = 1/(Topt+273) - 1/KTemp;
    double r  = y25 * H * exp( E/R * d ) / ( H - E * (1 - exp(H/R * d ) ) );
	return(r);
}


//non-peaked Arrhenius function
inline double npf(double KTemp, double E, double y25, double KTref=298, double R=0.008314){
  return y25*exp((KTemp-KTref)*E/(KTref*R*KTemp));
}

//// C3 model equations

// Rubisco-limited photosynthetic rate (umol/(m^2s^1))
inline double acC3(double cm, double om, double tl, const ParameterVector<p3>& pp) {
  //double Vcmax =  npf(tl,pp[p3::EVcmax],pp[p3::Vcmax25]);
  double Vcmax =  pfo(tl,pp[p3::EVcmax],pp[p3::ToptVcmax],pp[p3::HVcmax],pp[p3::Vcmax25]);

  //Michaelis-menten coefficient for O2
  double Ko = npf(tl,pp[p3::EKo],pp[p3::Ko25]);
  //Michaelis-menten coefficient for CO2
  double Kc = npf(tl,pp[p3::EKc],pp[p3::Kc25]);
  double Gstar = npf(tl,pp[p3::EGstar],pp[p3::Gstar25]);
  double Rd = npf(tl,pp[p3::ERd],pp[p3::Rd25]);
  double gm = pfo(tl,pp[p3::Egm],pp[p3::Toptgm],pp[p3::Hgm],pp[p3::gm25]);

  double a1  =  (-1.0/gm);
  double b1  =  (Vcmax - Rd)/gm + cm + Kc*(1.0 + om/Ko);
  double c1  =  Rd*(cm + Kc*(1.0 + om/Ko)) - Vcmax*(cm - Gstar);
  double tosqrt  =  fmax(0.0, pow(b1, 2.0) - 4.0*a1*c1 );
  double Ac  =  (-b1+sqrt(tosqrt)) / (2.0*a1);
  return Ac;
}

// Electron transport - limited photosynthetic rate (umol/(m^2s^1))
inline double ajC3(double cm, double tl, double PAR, const ParameterVector<p3>& pp) {
  double Gstar = npf(tl,pp[p3::EGstar],pp[p3::Gstar25]);
  double Rd = npf(tl,pp[p3::ERd],pp[p3::Rd25]);
  double gm = pfo(tl,pp[p3::Egm],pp[p3::Toptgm],pp[p3::Hgm],pp[p3::gm25]);
  /* ps2 method */
  double ps2a =  npf(tl,pp[p3::Eps2a],pp[p3::ps2a25]);
  double ps2b =  npf(tl,pp[p3::Eps2a],pp[p3::ps2b25]);
  double ps2c =  npf(tl,pp[p3::Eps2c],pp[p3::ps2c25]);
  double ps2phi  =   ps2a + (ps2b - ps2a) * exp( - PAR / ps2c );
  double Jmax = pfo(tl,pp[p3::EJmax],pp[p3::ToptJmax],pp[p3::HJmax],pp[p3::Jmax25]);
  double J  =  (Jmax*PAR) / (ps2phi*pp[p3::Jscale] + PAR);
  /* ps2 method */

  double a2 = (-1.0/gm);
  double b2 = ((J/4.0) - Rd)/gm + cm + 2.0*Gstar;
  double c2 = Rd*(cm+2.0*Gstar) - (J/4.0)*(cm - Gstar);
  double tosqrt  =  fmax(0, pow(b2,2.0) - 4.0*a2*c2 );
  double Aj  =  (-b2+sqrt(tosqrt)) / (2.0*a2);
  return Aj;
}

////--------C4 model equations--------------------------------------//
inline double vpf(double cm, double tl, const ParameterVector<p4>& pp){
  // CO2 concentrating flux (umol/m2/s)
  // von Caemmerer eq 4.19
  double Kp = npf(tl,pp[p4::EKp], pp[p4::Kp25]);
  double vpmax = npf(tl,pp[p4::EVpmax],pp[p4::Vpmax25]);
  return( std::fmin((cm*vpmax)/(cm + Kp), pp[p4::Vpr]) );
}

inline double acC4(double cm, double om, double tl, const ParameterVector<p4>& pp) {
  // Rubisco-limited photosynthetic rate (umol/(m^2s^1))
  // Cm and Om are (mesophyll partial pressures)
  // gbs is bundle sheath conductance to CO2

  // Maximum carboxylation rate (umol/(m^2s))
  //double Vcmax = npf(tl,pp[p4::EVcmax],pp[p4::Vcmax25]);
  double Vcmax = pfo(tl,pp[p4::EVcmax],pp[p4::ToptVcmax],pp[p4::HVcmax],pp[p4::Vcmax25]);
  //Michaelis-menten coefficient for O2
  double Ko = npf(tl,pp[p4::EKo],pp[p4::Ko25]);
  //Michaelis-menten coefficient for CO2
  double Kc = npf(tl,pp[p4::EKc],pp[p4::Kc25]);
  double sco = npf(tl,pp[p4::ESco],pp[p4::Sco25]);
  double gammastar=0.5/sco;
  double Rd = npf(tl,pp[p4::ERd],pp[p4::Rd25]);
  double Rm = Rd*0.5;
  double gbs = pfo(tl,pp[p4::Egbs],pp[p4::Toptgbs],pp[p4::Hgbs],pp[p4::gbs25]);
  double a0 = npf(tl,pp[p4::Ea0],pp[p4::a025]);
  double Vp = vpf(cm, tl, pp);

  double a1 = 1 - pp[p4::alpha]/a0 * Kc/Ko;
  double b1 = -1 * ( (Vp - Rm + gbs * cm) + (Vcmax - Rd) +  gbs*(Kc*(1+om/Ko)) + (pp[p4::alpha]/a0 * (gammastar*Vcmax + Rd * Kc/Ko ) ) );
  double c1 = (Vcmax - Rd) * (Vp - Rm + gbs*cm) -  (Vcmax*gbs*gammastar*om + Rd*gbs*Kc*(1+om/Ko));
  double tosqrt = std::max(0.0, std::pow(b1,2.0) - 4.0*a1*c1 );
  double Ac = (-b1 - sqrt( tosqrt ) ) / (2.0 * a1);

  return Ac;
}

double ajC4(double cm, double om, double tl, double PAR, const ParameterVector<p4>& pp) {
  //Electron transport - limited photosynthetic rate (umol/(m^2s^1));

  double sco = npf(tl,pp[p4::ESco],pp[p4::Sco25]);
  double gammastar = 0.5/sco;
  double Rd  = npf(tl,pp[p4::ERd],pp[p4::Rd25]);
  double Rm  = Rd*0.5;
  double gbs = pfo(tl,pp[p4::Egbs],pp[p4::Toptgbs],pp[p4::Hgbs],pp[p4::gbs25]);
  double a0    = npf(tl,pp[p4::Ea0],pp[p4::a025]);
  double z   = pp[p4::z];

  // ps2 method
  double ps2a =  npf(tl,pp[p4::Eps2a],pp[p4::ps2a25]);
  double ps2b =  npf(tl,pp[p4::Eps2a],pp[p4::ps2b25]);
  double ps2c =  npf(tl,pp[p4::Eps2c],pp[p4::ps2c25]);
  double ps2phi  =   ps2a + (ps2b - ps2a) * exp( - PAR / ps2c );
  double Jmax = pfo(tl,pp[p4::EJmax],pp[p4::ToptJmax],pp[p4::HJmax],pp[p4::Jmax25]);
  double Jatp  =  (Jmax*PAR) / (ps2phi*pp[p4::Jscale] + PAR);
  // ps2 method

  double p4_x = pp[p4::x];
  double inv_p4_x = 1.0 - p4_x;
  double p1 = inv_p4_x * Jatp * z / 3.0;
  double p11 = p1 + 7.0 * Rd / 3.0;
  double p12 = p1 - Rd;
  double p22 = (p4_x * Jatp * z / 2.0) - Rm + gbs * cm;
  double a2 = 1 - 7 * gammastar * pp[p4::alpha] / (3.0 * a0);
  double b2 = p22 + p12 + gbs*( 7.0* gammastar * om  / 3.0) + pp[p4::alpha] * gammastar / a0 * p11;
  b2 *= -1;
  double c2 = (p22 * p12) - (gbs * gammastar * om * p11);
  double tosqrt = std::fmax(0.0, std::pow(b2,2.0) - 4.0*a2*c2);
  double Aj = (-b2 - sqrt( tosqrt ) ) / (2.0 * a2);

  return Aj;
}

// combined C3 function
inline double combined(double cm, double om, double tl, double PAR, const ParameterVector<p3>& pp) {
  double ac = acC3(  cm, om, tl, pp );
  double aj = ajC3(  cm, tl, PAR, pp );
  return std::min(ac,aj);
}

// combined C4 function
inline double combined(double cm, double obs, double tl, double PAR, const ParameterVector<p4>& pp) {
  double ac = acC4(  cm, obs, tl, pp );
  double aj = ajC4(  cm, obs, tl, PAR, pp );
  return std::min(ac,aj);
}

inline double gsc_calc(double ca, double tl, double a_temp, double h, const ParameterVector<p3> pp){
  double gsc;
  long bb_switch = std::lround(pp[p3::bb]);
  if(bb_switch){
    gsc = (pp[p3::m]*a_temp*h/(ca*10)+pp[p3::b])/1.6;
  }
  else{
    double vpd = VPDcalc(tl-273,h);
    gsc = pp[p3::g0] + 1.6*(1.0 + pp[p3::g1]/sqrt(vpd)) * a_temp/(ca*10.0);
  }
  return std::fmin(1, gsc);
}

inline double gsc_calc(double ca, double tl, double a_temp, double h, const ParameterVector<p4> pp){
  double gsc;
  long bb_switch = std::lround(pp[p4::bb]);
  if(bb_switch){
    gsc = (pp[p4::m]*a_temp*h/(ca*10.0)+pp[p4::b])/1.6;
  }
  else{
    double vpd = VPDcalc(tl-273.15,h);
    gsc = pp[p4::g0] + 1.6*(1.0 + pp[p4::g1]/sqrt(vpd)) * a_temp/(ca*10.0);
  }
  return gsc;
}

template <typename PTYPE>
struct photo_state{
  photo_state(ParameterVector<PTYPE> photo_params_in) : pp(photo_params_in) {}
  ParameterVector<PTYPE> pp;
  double oa = 21000;
  double h = 0.5; //relative humdity, for other applications this should be a variable
  double gsc = 0;

  double tl = 0;
  double par = 0;
  double ca = 0;

  //function to be optimised
  double optF(double cm){
    double Ad = 10.0*(ca-cm) / (1.0/gsc);  // *10 coverts from Pa to ppm (umol/mol)
    double A = combined( cm, oa, tl, par, pp );
    return std::abs(A-Ad);
  };

  //function to be handed to the optimisation function
  std::function<double(double)> opt = [this](double cm){return this->optF(cm);};

  double operator()(double leaf_temp_in, double par_in, double atm_co2_in){
    par = par_in;
    tl = leaf_temp_in;
    ca = atm_co2_in;
    using Rcpp::Rcout;

    double a_temp = combined(ca*0.9, oa, tl, par, pp); 

    gsc = gsc_calc(ca, tl, a_temp, h, pp);

    double ax = 0.1 < ca ? 0.1 : ca;
    double bx = 0.1 < ca ? ca : 0.1;
    double cm_opt = Brent_fmin_cpp(
      ax,
      bx,
      opt,
      0.2
    );

    double r = combined(cm_opt, oa, tl, par, pp);

    return r;
  }
};

}

#endif
