/*
  Copyright (C) 2005 Steven L. Scott

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library 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
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/

        /* Utilities for `dpq' handling (density/probability/quantile) */

#ifndef BOOM_RMATH_DPQ_HPP_
#define BOOM_RMATH_DPQ_HPP_

#include "cpputil/report_error.hpp"

/* give_log in "d";  log_p in "p" & "q" : */
#define give_log log_p
                                                        /* "DEFAULT" */
                                                        /* --------- */
#define R_D__0  (log_p ? ML_NEGINF : 0.)                /* 0 */
#define R_D__1  (log_p ? 0. : 1.)                       /* 1 */
#define R_DT_0  (lower_tail ? R_D__0 : R_D__1)          /* 0 */
#define R_DT_1  (lower_tail ? R_D__1 : R_D__0)          /* 1 */

#define R_D_Lval(p)     (lower_tail ? (p) : (0.5 - (p) + 0.5))  /*  p  */
#define R_D_Cval(p)     (lower_tail ? (0.5 - (p) + 0.5) : (p))  /*  1 - p */

#define R_DT_Log(p)     (lower_tail? (p) : R_Log1_Exp(p))

#define R_D_val(x)      (log_p  ? log(x) : (x))         /*  x  in pF(x,..) */
#define R_D_qIv(p)      (log_p  ? exp(p) : (p))         /*  p  in qF(p,..) */
#define R_D_exp(x)      (log_p  ?  (x)   : exp(x))      /* exp(x) */
#define R_D_log(p)      (log_p  ?  (p)   : log(p))      /* log(p) */

#define R_DT_val(x)     R_D_val(R_D_Lval(x))            /*  x  in pF */
#define R_DT_Cval(x)    R_D_val(R_D_Cval(x))            /*  1 - x */
/*#define R_DT_qIv(p)   R_D_Lval(R_D_qIv(p))             *  p  in qF ! */
#define R_DT_qIv(p)     (log_p ? (lower_tail ? exp(p) : - expm1(p)) \
                               : R_D_Lval(p))

/*#define R_DT_CIv(p)   R_D_Cval(R_D_qIv(p))             *  1 - p in qF */
#define R_DT_CIv(p)     (log_p ? (lower_tail ? -expm1(p) : exp(p)) \
                               : R_D_Cval(p))

/* log(1 - exp(x))  in more stable form than log1p(- R_D_qIv(x))) : */
#define R_Log1_Exp(x)   ((x) > -M_LN2 ? log(-expm1(x)) : log1p(-exp(x)))

/* log(1-exp(x)):  R_D_LExp(x) == (log1p(- R_D_qIv(x))) but even more stable:*/
#define R_D_LExp(x)     (log_p ? R_Log1_Exp(x) : log1p(-x))

#define R_DT_exp(x)     R_D_exp(R_D_Lval(x))            /* exp(x) */
#define R_DT_Cexp(x)    R_D_exp(R_D_Cval(x))            /* exp(1 - x) */

#define R_DT_log(p)     (lower_tail ? R_D_log(p) :              \
                         log1p(- (log_p ? exp(p) : p)))/* log(p)        in qF */

#define R_DT_Clog(p)    (lower_tail ?                           \
                         log1p(- (log_p ? exp(p) : p)) :        \
                         R_D_log(p))                    /* log(1 - p)   in qF */

#define R_Q_P01_check(p)                        \
    if ((log_p  && p > 0) ||                    \
        (!log_p && (p < 0 || p > 1)) )          \
        ML_ERR_return_NAN


/* additions for density functions (C.Loader) */
#define R_D_fexp(f,x)     (give_log ? -0.5*log(f)+(x) : exp(x)/sqrt(f))
#define R_D_forceint(x)   floor((x) + 0.5)
#define R_D_nonint(x)     (fabs((x) - floor((x)+0.5)) > 1e-7)
#define R_D_notnnegint(x) (x < 0. || R_D_nonint(x))

#define R_D_nonint_check(x)                             \
  if(R_D_nonint(x)) {                                   \
    std::ostringstream err;                             \
    err << "found non-integer x = " << x << ".";        \
    report_error(err.str());                            \
   }

/* Do the boundaries exactly for q*() functions :
 * Often  _LEFT_ = ML_NEGINF , and very often _RIGHT_ = ML_POSINF;
 *
 * R_Q_P01_boundaries(p, _LEFT_, _RIGHT_)  :<==>
 *
 *     R_Q_P01_check(p);
 *     if (p == R_DT_0) return _LEFT_ ;
 *     if (p == R_DT_1) return _RIGHT_;
 *
 * the following implementation should be more efficient (less tests):
 */
#define R_Q_P01_boundaries(p, _LEFT_, _RIGHT_)          \
    if (log_p) {                                        \
        if(p > 0)                                       \
            ML_ERR_return_NAN;                          \
        if(p == 0) /* upper bound*/                     \
            return lower_tail ? _RIGHT_ : _LEFT_;       \
        if(p == ML_NEGINF)                              \
            return lower_tail ? _LEFT_ : _RIGHT_;       \
    }                                                   \
    else { /* !log_p */                                 \
        if(p < 0 || p > 1)                              \
            ML_ERR_return_NAN;                          \
        if(p == 0)                                      \
            return lower_tail ? _LEFT_ : _RIGHT_;       \
        if(p == 1)                                      \
            return lower_tail ? _RIGHT_ : _LEFT_;       \
    }

#define R_P_bounds_01(x, x_min, x_max)  \
    if(x <= x_min) return R_DT_0;               \
    if(x >= x_max) return R_DT_1
/* is typically not quite optimal for (-Inf,Inf) where
 * you'd rather have */
#define R_P_bounds_Inf_01(x)                    \
    if(!R_FINITE(x)) {                          \
        if (x > 0) return R_DT_1;               \
        /* x < 0 */return R_DT_0;               \
    }

#define R_D_qIv(p)      (log_p  ? exp(p) : (p))         /*  p  in qF(p,..) */

#endif // BOOM_RMATH_DPQ_HPP_
