/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

/*
 * This file is part of LibreOffice published API.
 */

#ifndef INCLUDED_RTL_MATH_HXX
#define INCLUDED_RTL_MATH_HXX

#include "rtl/math.h"
#include "rtl/strbuf.hxx"
#include "rtl/string.hxx"
#include "rtl/ustring.hxx"
#include "rtl/ustrbuf.hxx"
#include "sal/mathconf.h"
#include "sal/types.h"

#include <cstddef>
#include <math.h>

namespace rtl {

namespace math {

/** A wrapper around rtl_math_doubleToString.
 */
inline rtl::OString doubleToString(double fValue, rtl_math_StringFormat eFormat,
                                   sal_Int32 nDecPlaces,
                                   char cDecSeparator,
                                   sal_Int32 const * pGroups,
                                   char cGroupSeparator,
                                   bool bEraseTrailingDecZeros = false)
{
    rtl::OString aResult;
    rtl_math_doubleToString(&aResult.pData, NULL, 0, fValue, eFormat, nDecPlaces,
                            cDecSeparator, pGroups, cGroupSeparator,
                            bEraseTrailingDecZeros);
    return aResult;
}

/** A wrapper around rtl_math_doubleToString, with no grouping.
 */
inline rtl::OString doubleToString(double fValue, rtl_math_StringFormat eFormat,
                                   sal_Int32 nDecPlaces,
                                   char cDecSeparator,
                                   bool bEraseTrailingDecZeros = false)
{
    rtl::OString aResult;
    rtl_math_doubleToString(&aResult.pData, NULL, 0, fValue, eFormat, nDecPlaces,
                            cDecSeparator, NULL, 0, bEraseTrailingDecZeros);
    return aResult;
}

/** A wrapper around rtl_math_doubleToString that appends to an
    rtl::OStringBuffer.

    @since LibreOffice 5.4
*/
inline void doubleToStringBuffer(
    rtl::OStringBuffer& rBuffer, double fValue, rtl_math_StringFormat eFormat,
    sal_Int32 nDecPlaces, char cDecSeparator, sal_Int32 const * pGroups,
    char cGroupSeparator, bool bEraseTrailingDecZeros = false)
{
    rtl_String ** pData;
    sal_Int32 * pCapacity;
    rBuffer.accessInternals(&pData, &pCapacity);
    rtl_math_doubleToString(
        pData, pCapacity, rBuffer.getLength(), fValue, eFormat, nDecPlaces,
        cDecSeparator, pGroups, cGroupSeparator, bEraseTrailingDecZeros);
}

/** A wrapper around rtl_math_doubleToString that appends to an
    rtl::OStringBuffer, with no grouping.

    @since LibreOffice 5.4
*/
inline void doubleToStringBuffer(
    rtl::OStringBuffer& rBuffer, double fValue, rtl_math_StringFormat eFormat,
    sal_Int32 nDecPlaces, char cDecSeparator,
    bool bEraseTrailingDecZeros = false)
{
    rtl_String ** pData;
    sal_Int32 * pCapacity;
    rBuffer.accessInternals(&pData, &pCapacity);
    rtl_math_doubleToString(
        pData, pCapacity, rBuffer.getLength(), fValue, eFormat, nDecPlaces,
        cDecSeparator, NULL, 0, bEraseTrailingDecZeros);
}

/** A wrapper around rtl_math_doubleToUString.
 */
inline rtl::OUString doubleToUString(double fValue,
                                     rtl_math_StringFormat eFormat,
                                     sal_Int32 nDecPlaces,
                                     sal_Unicode cDecSeparator,
                                     sal_Int32 const * pGroups,
                                     sal_Unicode cGroupSeparator,
                                     bool bEraseTrailingDecZeros = false)
{
    rtl::OUString aResult;
    rtl_math_doubleToUString(&aResult.pData, NULL, 0, fValue, eFormat, nDecPlaces,
                             cDecSeparator, pGroups, cGroupSeparator,
                             bEraseTrailingDecZeros);
    return aResult;
}

/** A wrapper around rtl_math_doubleToUString, with no grouping.
 */
inline rtl::OUString doubleToUString(double fValue,
                                     rtl_math_StringFormat eFormat,
                                     sal_Int32 nDecPlaces,
                                     sal_Unicode cDecSeparator,
                                     bool bEraseTrailingDecZeros = false)
{
    rtl::OUString aResult;
    rtl_math_doubleToUString(&aResult.pData, NULL, 0, fValue, eFormat, nDecPlaces,
                             cDecSeparator, NULL, 0, bEraseTrailingDecZeros);
    return aResult;
}

/** A wrapper around rtl_math_doubleToUString that appends to an
    rtl::OUStringBuffer.
 */
inline void doubleToUStringBuffer( rtl::OUStringBuffer& rBuffer, double fValue,
                                   rtl_math_StringFormat eFormat,
                                   sal_Int32 nDecPlaces,
                                   sal_Unicode cDecSeparator,
                                   sal_Int32 const * pGroups,
                                   sal_Unicode cGroupSeparator,
                                   bool bEraseTrailingDecZeros = false)
{
    rtl_uString ** pData;
    sal_Int32 * pCapacity;
    rBuffer.accessInternals( &pData, &pCapacity );
    rtl_math_doubleToUString( pData, pCapacity, rBuffer.getLength(), fValue,
                              eFormat, nDecPlaces, cDecSeparator, pGroups,
                              cGroupSeparator, bEraseTrailingDecZeros);
}

/** A wrapper around rtl_math_doubleToUString that appends to an
    rtl::OUStringBuffer, with no grouping.
 */
inline void doubleToUStringBuffer( rtl::OUStringBuffer& rBuffer, double fValue,
                                   rtl_math_StringFormat eFormat,
                                   sal_Int32 nDecPlaces,
                                   sal_Unicode cDecSeparator,
                                   bool bEraseTrailingDecZeros = false)
{
    rtl_uString ** pData;
    sal_Int32 * pCapacity;
    rBuffer.accessInternals( &pData, &pCapacity );
    rtl_math_doubleToUString( pData, pCapacity, rBuffer.getLength(), fValue,
                              eFormat, nDecPlaces, cDecSeparator, NULL, 0,
                              bEraseTrailingDecZeros);
}

/** A wrapper around rtl_math_stringToDouble.
 */
#ifdef LIBO_INTERNAL_ONLY
inline double stringToDouble(std::string_view aString,
                             char cDecSeparator, char cGroupSeparator,
                             rtl_math_ConversionStatus * pStatus = NULL,
                             sal_Int32 * pParsedEnd = NULL)
{
    char const * pBegin = aString.data();
    char const * pEnd;
    double fResult = rtl_math_stringToDouble(pBegin,
                                             pBegin + aString.size(),
                                             cDecSeparator, cGroupSeparator,
                                             pStatus, &pEnd);
    if (pParsedEnd != NULL)
        *pParsedEnd = static_cast<sal_Int32>(pEnd - pBegin);
    return fResult;
}
#else
inline double stringToDouble(rtl::OString const & rString,
                             char cDecSeparator, char cGroupSeparator,
                             rtl_math_ConversionStatus * pStatus = NULL,
                             sal_Int32 * pParsedEnd = NULL)
{
    char const * pBegin = rString.getStr();
    char const * pEnd;
    double fResult = rtl_math_stringToDouble(pBegin,
                                             pBegin + rString.getLength(),
                                             cDecSeparator, cGroupSeparator,
                                             pStatus, &pEnd);
    if (pParsedEnd != NULL)
        *pParsedEnd = static_cast<sal_Int32>(pEnd - pBegin);
    return fResult;
}
#endif


/** A wrapper around rtl_math_uStringToDouble.
 */
#ifdef LIBO_INTERNAL_ONLY
inline double stringToDouble(std::u16string_view aString,
                             sal_Unicode cDecSeparator,
                             sal_Unicode cGroupSeparator,
                             rtl_math_ConversionStatus * pStatus = NULL,
                             sal_Int32 * pParsedEnd = NULL)
{
    sal_Unicode const * pBegin = aString.data();
    sal_Unicode const * pEnd;
    double fResult = rtl_math_uStringToDouble(pBegin,
                                              pBegin + aString.size(),
                                              cDecSeparator, cGroupSeparator,
                                              pStatus, &pEnd);
    if (pParsedEnd != NULL)
        *pParsedEnd = static_cast<sal_Int32>(pEnd - pBegin);
    return fResult;
}
#else
inline double stringToDouble(rtl::OUString const & rString,
                             sal_Unicode cDecSeparator,
                             sal_Unicode cGroupSeparator,
                             rtl_math_ConversionStatus * pStatus = NULL,
                             sal_Int32 * pParsedEnd = NULL)
{
    sal_Unicode const * pBegin = rString.getStr();
    sal_Unicode const * pEnd;
    double fResult = rtl_math_uStringToDouble(pBegin,
                                              pBegin + rString.getLength(),
                                              cDecSeparator, cGroupSeparator,
                                              pStatus, &pEnd);
    if (pParsedEnd != NULL)
        *pParsedEnd = static_cast<sal_Int32>(pEnd - pBegin);
    return fResult;
}
#endif

/** A wrapper around rtl_math_round.
 */
inline double round(
    double fValue, int nDecPlaces = 0,
    rtl_math_RoundingMode eMode = rtl_math_RoundingMode_Corrected)
{
    return rtl_math_round(fValue, nDecPlaces, eMode);
}

/** A wrapper around rtl_math_pow10Exp.
 */
inline double pow10Exp(double fValue, int nExp)
{
    return rtl_math_pow10Exp(fValue, nExp);
}

/** A wrapper around rtl_math_approxValue.
 */
inline double approxValue(double fValue)
{
    return rtl_math_approxValue(fValue);
}

/** A wrapper around rtl_math_expm1.
 */
inline double expm1(double fValue)
{
    return rtl_math_expm1(fValue);
}

/** A wrapper around rtl_math_log1p.
 */
inline double log1p(double fValue)
{
    return rtl_math_log1p(fValue);
}

/** A wrapper around rtl_math_atanh.
 */
inline double atanh(double fValue)
{
    return rtl_math_atanh(fValue);
}

/** A wrapper around rtl_math_erf.
 */
inline double erf(double fValue)
{
    return rtl_math_erf(fValue);
}

/** A wrapper around rtl_math_erfc.
 */
inline double erfc(double fValue)
{
    return rtl_math_erfc(fValue);
}

/** A wrapper around rtl_math_asinh.
 */
inline double asinh(double fValue)
{
    return rtl_math_asinh(fValue);
}

/** A wrapper around rtl_math_acosh.
 */
inline double acosh(double fValue)
{
    return rtl_math_acosh(fValue);
}

/** A wrapper around rtl_math_approxEqual.
 */
inline bool approxEqual(double a, double b)
{
    return rtl_math_approxEqual( a, b );
}

/** Test equality of two values with an accuracy defined by nPrec

    @attention
    approxEqual( value!=0.0, 0.0 ) _never_ yields true.
 */
inline bool approxEqual(double a, double b, sal_Int16 nPrec)
{
    if ( a == b )
        return true;
    double x = a - b;
    return (x < 0.0 ? -x : x)
        < ((a < 0.0 ? -a : a) * (1.0 / (pow(2.0, nPrec))));
}

/** Add two values.

    If signs differ and the absolute values are equal according to approxEqual()
    the method returns 0.0 instead of calculating the sum.

    If you wanted to sum up multiple values it would be convenient not to call
    approxAdd() for each value but instead remember the first value not equal to
    0.0, add all other values using normal + operator, and with the result and
    the remembered value call approxAdd().
 */
inline double approxAdd(double a, double b)
{
    if ( ((a < 0.0 && b > 0.0) || (b < 0.0 && a > 0.0))
         && approxEqual( a, -b ) )
        return 0.0;
    return a + b;
}

/** Subtract two values (a-b).

    If signs are identical and the values are equal according to approxEqual()
    the method returns 0.0 instead of calculating the subtraction.
 */
inline double approxSub(double a, double b)
{
    if ( ((a < 0.0 && b < 0.0) || (a > 0.0 && b > 0.0)) && approxEqual( a, b ) )
        return 0.0;
    return a - b;
}

/** floor() method taking approxValue() into account.

    Use for expected integer values being calculated by double functions.
 */
inline double approxFloor(double a)
{
    return floor( approxValue( a ));
}

/** ceil() method taking approxValue() into account.

    Use for expected integer values being calculated by double functions.
 */
inline double approxCeil(double a)
{
    return ceil( approxValue( a ));
}

/** Tests whether a value is neither INF nor NAN.
 */
inline bool isFinite(double d)
{
    return SAL_MATH_FINITE(d);
}

/** If a value represents +INF or -INF.

    The sign bit may be queried with isSignBitSet().

    If isFinite(d)==false and isInf(d)==false then NAN.
 */
inline bool isInf(double d)
{
    // exponent==0x7ff fraction==0
    return !SAL_MATH_FINITE(d) &&
        (reinterpret_cast< sal_math_Double * >(&d)->inf_parts.fraction_hi == 0)
        && (reinterpret_cast< sal_math_Double * >(&d)->inf_parts.fraction_lo
            == 0);
}

/** Test on any QNAN or SNAN.
 */
inline bool isNan(double d)
{
    // exponent==0x7ff fraction!=0
    return !SAL_MATH_FINITE(d) && (
        (reinterpret_cast< sal_math_Double * >(&d)->inf_parts.fraction_hi != 0)
        || (reinterpret_cast< sal_math_Double * >(&d)->inf_parts.fraction_lo
            != 0) );
}

/** If the sign bit is set.
 */
inline bool isSignBitSet(double d)
{
    return reinterpret_cast< sal_math_Double * >(&d)->inf_parts.sign != 0;
}

/** Set to +INF if bNegative==false or -INF if bNegative==true.
 */
inline void setInf(double * pd, bool bNegative)
{
    sal_math_Double& md = *reinterpret_cast<sal_math_Double*>(pd);
    md.w32_parts.msw = bNegative ? 0xFFF00000 : 0x7FF00000;
    md.w32_parts.lsw = 0;
}

/** Set a QNAN.
 */
inline void setNan(double * pd)
{
    sal_math_Double& md = *reinterpret_cast<sal_math_Double*>(pd);
    md.w32_parts.msw = 0x7FFFFFFF;
    md.w32_parts.lsw = 0xFFFFFFFF;
}

/** If a value is a valid argument for sin(), cos(), tan().

    IEEE 754 specifies that absolute values up to 2^64 (=1.844e19) for the
    radian must be supported by trigonometric functions.  Unfortunately, at
    least on x86 architectures, the FPU doesn't generate an error pattern for
    values >2^64 but produces erroneous results instead and sets only the
    "invalid operation" (IM) flag in the status word :-(  Thus the application
    has to handle it itself.
 */
inline bool isValidArcArg(double d)
{
    return fabs(d)
        <= (static_cast< double >(static_cast< unsigned long >(0x80000000))
            * static_cast< double >(static_cast< unsigned long >(0x80000000))
            * 4);
}

/** Safe sin(), returns NAN if not valid.
 */
inline double sin(double d)
{
    if ( isValidArcArg( d ) )
        return ::sin( d );
    setNan( &d );
    return d;
}

/** Safe cos(), returns NAN if not valid.
 */
inline double cos(double d)
{
    if ( isValidArcArg( d ) )
        return ::cos( d );
    setNan( &d );
    return d;
}

/** Safe tan(), returns NAN if not valid.
 */
inline double tan(double d)
{
    if ( isValidArcArg( d ) )
        return ::tan( d );
    setNan( &d );
    return d;
}

}

}

#endif // INCLUDED_RTL_MATH_HXX

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
