// Boost.Units - A C++ library for zero-overhead dimensional analysis and 
// unit/quantity manipulation and conversion
//
// Copyright (C) 2003-2008 Matthias Christian Schabel
// Copyright (C) 2008 Steven Watanabe
//
// Distributed under 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)

#ifndef BOOST_UNITS_MEASUREMENT_HPP
#define BOOST_UNITS_MEASUREMENT_HPP

#include <cmath>
#include <cstdlib>
#include <iomanip>
#include <iostream>

#include <boost/io/ios_state.hpp>
#include <boost/units/static_rational.hpp>

namespace boost {

namespace units {

namespace sqr_namespace /**/ {

template<class Y>
constexpr
Y sqr(Y val)
{ return val*val; }

} // namespace

using sqr_namespace::sqr;

template<class Y>
class measurement
{    
    public:
        typedef measurement<Y>                  this_type;
        typedef Y                               value_type;
        
        constexpr measurement(const value_type& val = value_type(),
                    const value_type& err = value_type()) : 
            value_(val),
            uncertainty_(std::abs(err)) 
        { }
        
        constexpr measurement(const this_type& source) : 
            value_(source.value_),
            uncertainty_(source.uncertainty_) 
        { }
        
        //~measurement() { }
        
        constexpr this_type& operator=(const this_type& source)
        {
            if (this == &source) return *this;
            
            value_ = source.value_;
            uncertainty_ = source.uncertainty_;
            
            return *this;
        }
        
        constexpr operator value_type() const    { return value_; }
        
        constexpr value_type value() const       { return value_; }
        constexpr value_type uncertainty() const { return uncertainty_; }
        constexpr value_type lower_bound() const { return value_-uncertainty_; }
        constexpr value_type upper_bound() const { return value_+uncertainty_; }
        
        constexpr this_type& operator+=(const value_type& val)            
        { 
            value_ += val; 
            return *this; 
        }
        
        constexpr this_type& operator-=(const value_type& val)            
        { 
            value_ -= val; 
            return *this; 
        }
        
        constexpr this_type& operator*=(const value_type& val)            
        { 
            value_ *= val; 
            uncertainty_ *= val; 
            return *this; 
        }
        
        constexpr this_type& operator/=(const value_type& val)            
        { 
            value_ /= val; 
            uncertainty_ /= val; 
            return *this; 
        }
        
        constexpr this_type& operator+=(const this_type& /*source*/);
        constexpr this_type& operator-=(const this_type& /*source*/);        
        constexpr this_type& operator*=(const this_type& /*source*/);        
        constexpr this_type& operator/=(const this_type& /*source*/);

    private:
        value_type          value_,
                            uncertainty_;
};

}

}

#if BOOST_UNITS_HAS_BOOST_TYPEOF

BOOST_TYPEOF_REGISTER_TEMPLATE(boost::units::measurement, 1)

#endif

namespace boost {

namespace units {

template<class Y>
inline
constexpr
measurement<Y>&
measurement<Y>::operator+=(const this_type& source)
{
    uncertainty_ = std::sqrt(sqr(uncertainty_)+sqr(source.uncertainty_));
    value_ += source.value_;
    
    return *this;
}

template<class Y>
inline
constexpr
measurement<Y>&
measurement<Y>::operator-=(const this_type& source)
{
    uncertainty_ = std::sqrt(sqr(uncertainty_)+sqr(source.uncertainty_));
    value_ -= source.value_;
    
    return *this;
}

template<class Y>
inline
constexpr
measurement<Y>&
measurement<Y>::operator*=(const this_type& source)
{
    uncertainty_ = (value_*source.value_)*
              std::sqrt(sqr(uncertainty_/value_)+
                        sqr(source.uncertainty_/source.value_));
    value_ *= source.value_;
    
    return *this;
}

template<class Y>
inline
constexpr
measurement<Y>&
measurement<Y>::operator/=(const this_type& source)
{
    uncertainty_ = (value_/source.value_)*
              std::sqrt(sqr(uncertainty_/value_)+
                        sqr(source.uncertainty_/source.value_));
    value_ /= source.value_;
    
    return *this;
}

// value_type op measurement
template<class Y>
inline
constexpr
measurement<Y>
operator+(Y lhs,const measurement<Y>& rhs)
{
    return (measurement<Y>(lhs,Y(0))+=rhs);
}

template<class Y>
inline
constexpr
measurement<Y>
operator-(Y lhs,const measurement<Y>& rhs)
{
    return (measurement<Y>(lhs,Y(0))-=rhs);
}

template<class Y>
inline
constexpr
measurement<Y>
operator*(Y lhs,const measurement<Y>& rhs)
{
    return (measurement<Y>(lhs,Y(0))*=rhs);
}

template<class Y>
inline
constexpr
measurement<Y>
operator/(Y lhs,const measurement<Y>& rhs)
{
    return (measurement<Y>(lhs,Y(0))/=rhs);
}

// measurement op value_type
template<class Y>
inline
constexpr
measurement<Y>
operator+(const measurement<Y>& lhs,Y rhs)
{
    return (measurement<Y>(lhs)+=measurement<Y>(rhs,Y(0)));
}

template<class Y>
inline
constexpr
measurement<Y>
operator-(const measurement<Y>& lhs,Y rhs)
{
    return (measurement<Y>(lhs)-=measurement<Y>(rhs,Y(0)));
}

template<class Y>
inline
constexpr
measurement<Y>
operator*(const measurement<Y>& lhs,Y rhs)
{
    return (measurement<Y>(lhs)*=measurement<Y>(rhs,Y(0)));
}

template<class Y>
inline
constexpr
measurement<Y>
operator/(const measurement<Y>& lhs,Y rhs)
{
    return (measurement<Y>(lhs)/=measurement<Y>(rhs,Y(0)));
}

// measurement op measurement
template<class Y>
inline
constexpr
measurement<Y>
operator+(const measurement<Y>& lhs,const measurement<Y>& rhs)
{
    return (measurement<Y>(lhs)+=rhs);
}

template<class Y>
inline
constexpr
measurement<Y>
operator-(const measurement<Y>& lhs,const measurement<Y>& rhs)
{
    return (measurement<Y>(lhs)-=rhs);
}

template<class Y>
inline
constexpr
measurement<Y>
operator*(const measurement<Y>& lhs,const measurement<Y>& rhs)
{
    return (measurement<Y>(lhs)*=rhs);
}

template<class Y>
inline
constexpr
measurement<Y>
operator/(const measurement<Y>& lhs,const measurement<Y>& rhs)
{
    return (measurement<Y>(lhs)/=rhs);
}

/// specialize power typeof helper
template<class Y,long N,long D> 
struct power_typeof_helper<measurement<Y>,static_rational<N,D> >
{ 
    typedef measurement<
        typename power_typeof_helper<Y,static_rational<N,D> >::type
    > type; 
    
    static constexpr type value(const measurement<Y>& x)  
    { 
        const static_rational<N,D>  rat;

        const Y m = Y(rat.numerator())/Y(rat.denominator()),
                newval = std::pow(x.value(),m),
                err = newval*std::sqrt(std::pow(m*x.uncertainty()/x.value(),2));
        
        return type(newval,err);
    }
};

/// specialize root typeof helper
template<class Y,long N,long D> 
struct root_typeof_helper<measurement<Y>,static_rational<N,D> >                
{ 
    typedef measurement<
        typename root_typeof_helper<Y,static_rational<N,D> >::type
    > type; 
    
    static constexpr type value(const measurement<Y>& x)  
    { 
        const static_rational<N,D>  rat;

        const Y m = Y(rat.denominator())/Y(rat.numerator()),
                newval = std::pow(x.value(),m),
                err = newval*std::sqrt(std::pow(m*x.uncertainty()/x.value(),2));
        
        return type(newval,err);
    }
};

// stream output
template<class Y>
inline
std::ostream& operator<<(std::ostream& os,const measurement<Y>& val)
{
    boost::io::ios_precision_saver precision_saver(os);
    boost::io::ios_flags_saver flags_saver(os);
    
    os << val.value() << "(+/-" << val.uncertainty() << ")";
    
    return os;
}

} // namespace units

} // namespace boost

#endif // BOOST_UNITS_MEASUREMENT_HPP
