//  Copyright Thijs van den Berg, 2008.
//  Copyright John Maddock 2008.

//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

// This module implements the Laplace distribution.
// http://en.wikipedia.org/wiki/Laplace_distribution

#ifndef BOOST_STATS_LAPLACE_HPP
#define BOOST_STATS_LAPLACE_HPP

#include <boost/math/distributions/detail/common_error_handling.hpp>

namespace boost{ namespace math{

template <class RealType = double, class Policy = policies::policy<> >
class laplace_distribution
{
public:
   // ----------------------------------
   // public Types
   // ----------------------------------
   typedef RealType value_type;
   typedef Policy policy_type;

   // ----------------------------------
   // Constructor(s)
   // ----------------------------------
   laplace_distribution(RealType location = 0, RealType scale = 1)
      : m_location(location), m_scale(scale)
   {
      RealType result;
      const char* function = "boost::math::laplace_distribution<%1%>::laplace_distribution";
      check_parameters(function, result, Policy());
   }

   // ----------------------------------------------
   // CONCEPT #1
   // Runtime access to *set* distribution parameters
   //
   // Setting distribution variables at run-time
   // is something that would be needed when 
   // e.g. distribution data is read from disk
   //
   // ----------------------------------------------
   bool operator ()(const RealType& location, const RealType& scale)
   {
      m_location = location;
      m_scale = scale;

      RealType result;
      const char* function = "boost::math::laplace_distribution<%1%>::operator()";
      return check_parameters(function, result, Policy());
   }

   // ----------------------------------
   // Public functions
   // ----------------------------------

   RealType location() const 
   { 
      return m_location; 
   }

   RealType scale() const 
   { 
      return m_scale; 
   }

   // ----------------------------------------------
   // CONCEPT #2
   // Centralized checking of parameters in a member 
   // function.
   //
   // This function is called in the constructor,
   // as well as in the () operator.
   //
   // This function cannot be "const" as it might
   // store (cache) checking results in private data.
   //
   // We pass result by reference instead of a pointer
   // to ensure that is has a valid adres.
   // ----------------------------------------------
   bool check_parameters(const char* function, RealType& result, const Policy& pol) 
   {
      if(false == detail::check_scale(function, m_scale, &result, pol))   return false;
      if(false == detail::check_location(function, m_location, &result, pol)) return false;
      return true;
   }

private:
   RealType m_location;
   RealType m_scale;

}; // class laplace_distribution



//
// Convenient type synonym
//
typedef laplace_distribution<double> laplace;

//
// Non member functions
//
template <class RealType, class Policy>
inline const std::pair<RealType, RealType> range(const laplace_distribution<RealType, Policy>& /*dist*/)
{
   using boost::math::tools::max_value;
   return std::pair<RealType, RealType>(-max_value<RealType>(), max_value<RealType>());
}

template <class RealType, class Policy>
inline const std::pair<RealType, RealType> support(const laplace_distribution<RealType, Policy>&)
{
   using boost::math::tools::max_value;
   return std::pair<RealType, RealType>(-max_value<RealType>(),  max_value<RealType>());
}

template <class RealType, class Policy>
inline RealType pdf(const laplace_distribution<RealType, Policy>& dist, const RealType& x)
{
   BOOST_MATH_STD_USING // for ADL of std functions

   // Checking function argument
   RealType result;
   const char* function = "boost::math::pdf(boost::math::pdf(const laplace_distribution<%1%>&, %1%))";
   if(false == detail::check_x(function, x, &result, Policy())) return result;

   // ----------------------------------------------
   // CONCEPT #3
   //
   // If annon-member functions need to check parameters
   // it can use a simular syntax as it does for checking
   // function arguments: 
   // 
   // CHECK ARG  -> if (false == detail::check_x(      function, x, &result, Policy())) return result;
   // CHECK DIST -> if (false == dist.check_parameters(function,    &result, Policy())) return result;
   // ----------------------------------------------

   // Pdf boundary values 
   if((boost::math::isinf)(x)) return 0; // pdf + and - infinity is zero.

   // Pdf interval values
   RealType scale = dist.scale();
   RealType location = dist.location();

   RealType exponent = x - location;
   if (exponent>0) exponent = -exponent;
   exponent /= scale;

   result = exp(exponent);
   result /= 2 * scale;

   return result;
} // pdf

template <class RealType, class Policy>
inline RealType cdf(const laplace_distribution<RealType, Policy>& dist, const RealType& x)
{
   BOOST_MATH_STD_USING  // for ADL of std functions

   // Checking function argument
   RealType result;
   const char* function = "boost::math::cdf(const laplace_distribution<%1%>&, %1%)";
   if(false == detail::check_x(function, x, &result, Policy())) return result;

   // Special cdf values
   if((boost::math::isinf)(x))
   {
     if(x < 0) return 0; // -infinity
     return 1; // + infinity
   }

   // Cdf interval values
   RealType scale = dist.scale();
   RealType location = dist.location();

   if (x < location)	result = exp( (x-location)/scale )/2;
   else result = 1 - exp( (location-x)/scale )/2;

   return result;
} // cdf


template <class RealType, class Policy>
inline RealType quantile(const laplace_distribution<RealType, Policy>& dist, const RealType& p)
{
   BOOST_MATH_STD_USING // for ADL of std functions

   // Checking function argument
   RealType result;
   const char* function = "boost::math::quantile(const laplace_distribution<%1%>&, %1%)";
   if(false == detail::check_probability(function, p, &result, Policy())) return result;

   // Calculate Quantile
   RealType scale = dist.scale();
   RealType location = dist.location();
   
   if (p - 0.5 < 0) result = location + scale*(p - 0.5) * log(2*p);
   else result = location - scale*(p - 0.5) * log(2 - 2*p);
   
   return result;
} // quantile


template <class RealType, class Policy>
inline RealType cdf(const complemented2_type<laplace_distribution<RealType, Policy>, RealType>& c)
{
   BOOST_MATH_STD_USING // for ADL of std functions

   // Checking function argument
   RealType result;
   const char* function = "boost::math::cdf(const complemented2_type<laplace_distribution<%1%>, %1%>&)";
   if(false == detail::check_x(function, x, &result, Policy()))return result;

   // Calculate cdf
   RealType scale = dist.scale();
   RealType location = dist.location();
   RealType x = c.param;

   // Special cdf value
   if((boost::math::isinf)(x))
   {
     if(x < 0) return 1; // cdf complement -infinity is unity.
     return 0; // cdf complement +infinity is zero
   }

   // Cdf interval value
   if (x < location) result = 1 - exp( (x-location)/scale )/2;
   else result = exp( (location-x)/scale )/2;
 
   return result;
} // cdf complement


template <class RealType, class Policy>
inline RealType quantile(const complemented2_type<laplace_distribution<RealType, Policy>, RealType>& c)
{
   BOOST_MATH_STD_USING // for ADL of std functions

   // Checking function argument
   RealType result;
   const char* function = "quantile(const complemented2_type<laplace_distribution<%1%>, %1%>&)";
   if(false == detail::check_probability(function, q, &result, Policy())) return result;

   // Calculate quantile
   RealType sd = c.dist.standard_deviation();
   RealType mean = c.dist.mean();
   RealType q = c.param;
   
   if (0.5 - q < 0) result = location + scale*(0.5 - q) * log(2 - q);
   else result = location - scale*(0.5 - q) * log(2 * q);
   
   return result;
} // quantile

template <class RealType, class Policy>
inline RealType mean(const laplace_distribution<RealType, Policy>& dist)
{
   return dist.location();
}

template <class RealType, class Policy>
inline RealType standard_deviation(const laplace_distribution<RealType, Policy>& dist)
{
   return constants::root_two<RealType>() * dist.scale();
}

template <class RealType, class Policy>
inline RealType mode(const laplace_distribution<RealType, Policy>& dist)
{
   return dist.location();
}

template <class RealType, class Policy>
inline RealType median(const laplace_distribution<RealType, Policy>& dist)
{
   return dist.location();
}

template <class RealType, class Policy>
inline RealType skewness(const laplace_distribution<RealType, Policy>& /*dist*/)
{
   return 0;
}

template <class RealType, class Policy>
inline RealType kurtosis(const laplace_distribution<RealType, Policy>& /*dist*/)
{
   return 6;
}

template <class RealType, class Policy>
inline RealType kurtosis_excess(const laplace_distribution<RealType, Policy>& /*dist*/)
{
   return 3;
}

} // namespace math
} // namespace boost

// This include must be at the end, *after* the accessors
// for this distribution have been defined, in order to
// keep compilers that support two-phase lookup happy.
#include <boost/math/distributions/detail/derived_accessors.hpp>

#endif // BOOST_STATS_LAPLACE_HPP



