#ifndef squarewell_h
#define squarewell_h

#include <valarray>
#include <map>

namespace teqp{
namespace squarewell{

#include "teqp/types.hpp"

/**
 Rodolfo Espíndola-Heredia, Fernando del Río and Anatol Malijevsky
 Optimized equation of the state of the
 square-well fluid of variable range based on
 a fourth-order free-energy expansion
 J. Chem. Phys. 130, 024509 (2009); https://doi.org/10.1063/1.3054361
 
 Note: if needed, all the terms that don't depend on T or rho could be pre-calculated at model
 initialization for a small speed boost
 */
class EspindolaHeredia2009{
private:
    
    const double m_pi = 3.1415926535897932384626433;
    
    auto __factorial(int i) const{ return tgamma(i+1); }
    
    const double lambda;
    
    const std::map<int, std::valarray<double>> phivals = {
        {1, {-1320.19, 5124.1, -8145.37, 6895.8, -3381.42, 968.739, -151.255, 9.98592}},
        {2, {1049.76, -4023.29, 6305.95, -5265.42, 2553.84, -727.3, 113.631, -7.56266}}
    };
    const std::map<int, std::valarray<double>> thetavals = {
        {3, {0.0, -945.597, 1326.61, -471.688, 0.0, 23.2271, -2.63477, 0.0}},
        {4, {0.0, 4131.09,-10501.1,8909.18,-2521.96,-16.7882,19.5315,-1.27373}}
    };
    
    const std::map<int, std::valarray<double>> gammanvals = {
        {1, {0, -59.0464, 26.098, 26.4454, 7.40136, 11.0743, -5.49152, 0.781823, -0.0319751, 0.827621, 0.605635, -0.254959, 0.0377111, -0.00210896 , 0.0000452328}},
        {2, {0, 214.316, -88.1394, 273.3, 95.9759, 71.1228, -40.2656, 5.94069,  -0.23842, -2.17558,  -1.29255,  0.554993, -0.0857543, 0.00492511, -0.000107067 }},
        {3, {0, -225.479, 88.8202, 250.472, 90.2606, 57.0274, -33.2376, 4.99527, -0.195714, 1.84677, 0.99813, -0.440314, 0.0708793, -0.00416274, 0.0000917291 }},
        {4, {0, 65.0504, -25.096, 74.3095, 26.2153, 18.4397, -10.0891, 1.50243, -0.057694, -1.87154, -1.01682, 0.445247, -0.0725107, 0.00427862, -0.0000949723}}
    };
    
    template<typename GType>
    auto Rn(const GType &gn, double lambda_) const{
        auto o = gn[3];
        for (auto j = 4; j < 9; ++j){
            o += gn[j]*pow(pow(lambda_,3)-1, j-2);
        }
        return o;
    }
    
    template<typename GType>
    auto Qn(const GType &gn, double lambda_) const{
        auto o = gn[9];
        for (auto j = 10; j < 15; ++j){
            o += gn[j]*pow(pow(lambda_,3)-1, j-7);
        }
        return o;
    }

    auto gamman(int n, double lambda_) const{
        const auto& gn = gammanvals.at(n);
        return gn[1]*lambda_ + gn[2]*pow(lambda_,2) + Rn(gn, lambda_)/Qn(gn, lambda_);
    }
    
    auto phii(int i, double lambda_) const{
        const auto& phivalsi = phivals.at(i);
        double o = 0.0;
        for (auto n = 0; n < 8; ++n){
            o += phivalsi[n]*pow(lambda_, n);
        }
        return o;
    };
    
    auto P1(double lambda_) const{return pow(lambda_,6) - 18*pow(lambda_,4) + 32*pow(lambda_,3) - 15;}
    auto P2(double lambda_) const{return -2*pow(lambda_,6) + 36*pow(lambda_,4) - 32*pow(lambda_,3) - 18*pow(lambda_,2) + 16;}
    auto P3(double lambda_) const{return 6*pow(lambda_,6) - 18*pow(lambda_,4) + 18*pow(lambda_,2)-6;}
    auto P4(double lambda_) const{return 32*pow(lambda_,3) - 18*pow(lambda_,2) - 48;}
    auto P5(double lambda_) const{return 5*pow(lambda_,6) - 32*pow(lambda_,3) + 18*pow(lambda_,2) + 26;};
    
    auto a2i(int i, double lambda_) const{ return -2*m_pi/(3*__factorial(i))*(pow(lambda_, 3)-1); };
    
    auto a31(double lambda_) const{ return -pow(m_pi/6, 2)*(P1(std::min(lambda_, 2.0)));};
    
    auto a32(double lambda_) const {
        if (lambda_ <= 2)
            return pow(m_pi/6,2)*(P2(lambda_) - P1(lambda_)/2);
        else
            return pow(m_pi/6,2)*(-17/2 + P4(lambda_));
    }

    auto a33(double lambda_) const {
        if (lambda_ <= 2)
            return pow(m_pi/6,2)*(P2(lambda_) - P1(lambda_)/6 - P3(lambda_));
        else
            return pow(m_pi/6,2)*(-17/6 + P4(lambda_) - P5(lambda_));
    }

    auto a34(double lambda_) const{
        if (lambda_ <= 2)
            return pow(m_pi/6,2)*(-P1(lambda_)/24 + 7*P2(lambda_)/12 - 3*P3(lambda_)/2);
        else
            return pow(m_pi/6,2)*(-17/24 + 7*P4(lambda_)/12 - 3*P5(lambda_)/2);
    }
        
    auto xi2(double lambda_) const{ return a32(lambda_)/a2i(2, lambda_); }
    auto xi3(double lambda_) const{ return a33(lambda_)/a2i(3, lambda_); }
    auto xi4(double lambda_) const{ return a34(lambda_)/a2i(4, lambda_); }
    
    template<typename RhoType>
    auto Ki(int i, const RhoType & rhostar, double lambda_) const{
        const auto & thetai = thetavals.at(i);
        RhoType num = 0.0;
        for (auto n = 1; n < 5; ++n){
            num += thetai[n]*pow(lambda_, n);
        }
        num *= pow(rhostar, 2);
        RhoType den = 0;
        for (auto n = 5; n < 8; ++n){
            den += thetai[n]*pow(lambda_, n-4);
        }
        den = 1.0 + rhostar*den;
        return forceeval(num/den);
    }
    
    template<typename RhoType>
    auto Chi(const RhoType & rhostar, double lambda_) const { return forceeval(a2i(2, lambda_)*rhostar*(1.0-pow(rhostar,2)/1.5129)); }
    
    template<typename RhoType>
    auto aHS(const RhoType & rhostar) const{
        return forceeval(-3.0*m_pi*rhostar*(m_pi*rhostar-8.0)/pow(m_pi*rhostar-6.0, 2));
    }
    
    template<typename RhoType>
    auto get_a1(const RhoType & rhostar, double lambda_) const{
        RhoType o = a2i(1, lambda_)*pow(rhostar, 2-1) + a31(lambda_)*pow(rhostar, 3-1);
        for (auto i = 1; i < 5; ++i){
            o = o + gamman(i, lambda_)*pow(rhostar, i+2);
        }
        return forceeval(o);
    };
    
    template<typename RhoType>
    auto get_a2(const RhoType & rhostar, double lambda_) const{
        return forceeval(Chi(rhostar, lambda_)*exp(xi2(lambda_)*rhostar + phii(1, lambda_)*pow(rhostar,3) + phii(2,lambda_)*pow(rhostar,4)));
    };
    
    template<typename RhoType>
    auto get_a3(const RhoType & rhostar, double lambda_) const {
        return forceeval(a2i(3, lambda_)*rhostar*exp(xi3(lambda_)*rhostar + Ki(3, rhostar, lambda_)));
    };
    
    template<typename RhoType>
    auto get_a4(const RhoType & rhostar, double lambda_) const {
        return forceeval(a2i(4, lambda_)*rhostar*exp(xi4(lambda_)*rhostar + Ki(4, rhostar, lambda_)));
    };
    
public:
    EspindolaHeredia2009(double lambda) : lambda(lambda){};
    
    // We are in "simulation units", so R is 1.0, and T and rho are T^* and rho^*
    template<typename MoleFracType>
    double R(const MoleFracType &) const { return 1.0; }
    
    /**
        \param Tstar: \f$T^*=T/\epsilon/k \f$
        \param rhostar: \f$\rho^*=\rho_{\rm N}\sigma^3 \f$
     */
    template<typename TType, typename RhoType, typename MoleFracType>
    auto alphar(const TType& Tstar,
        const RhoType& rhostar,
        const MoleFracType& molefrac) const
    {
        auto a1 = get_a1(rhostar, lambda);
        auto a2 = get_a2(rhostar, lambda);
        auto a3 = get_a3(rhostar, lambda);
        auto a4 = get_a4(rhostar, lambda);
        
        return forceeval(aHS(rhostar)
            + a1/Tstar
            + a2/pow(Tstar, 2)
            + a3/pow(Tstar, 3)
            + a4/pow(Tstar, 4));
    }
};

}
}

#endif /* squarewell_h */