/* -*- 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 .
 */

#include <config_features.h>
#include <config_fuzzers.h>

#include <calc.hxx>
#include <cfloat>
#include <climits>
#include <memory>
#include <comphelper/processfactory.hxx>
#include <comphelper/string.hxx>
#include <cstdlib>
#include <dbmgr.hxx>
#include <docfld.hxx>
#include <docstat.hxx>
#include <doc.hxx>
#include <IDocumentFieldsAccess.hxx>
#include <IDocumentStatistics.hxx>
#include <editeng/langitem.hxx>
#include <expfld.hxx>
#include <hintids.hxx>
#include <o3tl/temporary.hxx>
#include <osl/diagnose.h>
#include <rtl/math.hxx>
#include <shellres.hxx>
#include <svl/numformat.hxx>
#include <svl/languageoptions.hxx>
#include <swmodule.hxx>
#include <swtypes.hxx>
#include <unotools/charclass.hxx>
#include <unotools/localedatawrapper.hxx>
#include <unotools/useroptions.hxx>
#include <usrfld.hxx>
#include <utility>
#include <viewsh.hxx>
#include <com/sun/star/i18n/KParseTokens.hpp>
#include <com/sun/star/i18n/KParseType.hpp>

using namespace ::com::sun::star;


// ATTENTION: sorted list of all operators
struct CalcOp
{
    OUString aName;
    SwCalcOper eOp;
};

CalcOp constexpr aOpTable[] = {
/* ABS */     {{sCalc_Abs},        CALC_ABS},   // Abs (since LibreOffice 7.1)
/* ACOS */    {{sCalc_Acos},       CALC_ACOS},  // Arc cosine
/* ADD */     {{sCalc_Add},        CALC_PLUS},  // Addition
/* AND */     {{sCalc_And},        CALC_AND},   // log. AND
/* ASIN */    {{sCalc_Asin},       CALC_ASIN},  // Arc sine
/* ATAN */    {{sCalc_Atan},       CALC_ATAN},  // Arc tangent
/* AVERAGE */ {{sCalc_Average},    CALC_AVERAGE}, // Average (since LibreOffice 7.1)
/* COS */     {{sCalc_Cos},        CALC_COS},   // Cosine
/* COUNT */   {{sCalc_Count},      CALC_COUNT}, // Count (since LibreOffice 7.1)
/* DATE */    {{sCalc_Date},       CALC_DATE},  // Date
/* DIV */     {{sCalc_Div},        CALC_DIV},   // Division
/* EQ */      {{sCalc_Eq},         CALC_EQ},    // Equality
/* G */       {{sCalc_G},          CALC_GRE},   // Greater than
/* GEQ */     {{sCalc_Geq},        CALC_GEQ},   // Greater or equal
/* INT */     {{sCalc_Int},        CALC_INT},   // Int (since LibreOffice 7.4)
/* L */       {{sCalc_L},          CALC_LES},   // Less than
/* LEQ */     {{sCalc_Leq},        CALC_LEQ},   // Less or equal
/* MAX */     {{sCalc_Max},        CALC_MAX},   // Maximum value
/* MEAN */    {{sCalc_Mean},       CALC_MEAN},  // Mean
/* MIN */     {{sCalc_Min},        CALC_MIN},   // Minimum value
/* MOD */     {{sCalc_Mod},        CALC_MOD},   // Modulo value
/* MUL */     {{sCalc_Mul},        CALC_MUL},   // Multiplication
/* NEQ */     {{sCalc_Neq},        CALC_NEQ},   // Not equal
/* NOT */     {{sCalc_Not},        CALC_NOT},   // log. NOT
/* OR */      {{sCalc_Or},         CALC_OR},    // log. OR
/* PHD */     {{sCalc_Phd},        CALC_PHD},   // Percentage
/* POW */     {{sCalc_Pow},        CALC_POW},   // Exponentiation
/* PRODUCT */ {{sCalc_Product},    CALC_PRODUCT}, // Product (since LibreOffice 7.1)
/* ROUND */   {{sCalc_Round},      CALC_ROUND}, // Rounding
/* SIGN */    {{sCalc_Sign},       CALC_SIGN},  // Sign (since LibreOffice 7.1)
/* SIN */     {{sCalc_Sin},        CALC_SIN},   // Sine
/* SQRT */    {{sCalc_Sqrt},       CALC_SQRT},  // Square root
/* SUB */     {{sCalc_Sub},        CALC_MINUS}, // Subtraction
/* SUM */     {{sCalc_Sum},        CALC_SUM},   // Sum
/* TAN */     {{sCalc_Tan},        CALC_TAN},   // Tangent
/* XOR */     {{sCalc_Xor},        CALC_XOR}    // log. XOR
};

double const nRoundVal[] = {
    5.0e+0, 0.5e+0, 0.5e-1, 0.5e-2, 0.5e-3, 0.5e-4, 0.5e-5, 0.5e-6,
    0.5e-7, 0.5e-8, 0.5e-9, 0.5e-10,0.5e-11,0.5e-12,0.5e-13,0.5e-14,
    0.5e-15,0.5e-16
};

// First character may be any alphabetic or underscore.
const sal_Int32 coStartFlags =
        i18n::KParseTokens::ANY_LETTER_OR_NUMBER |
        i18n::KParseTokens::ASC_UNDERSCORE |
        i18n::KParseTokens::IGNORE_LEADING_WS;

// Continuing characters may be any alphanumeric, underscore, or dot.
const sal_Int32 coContFlags =
    (coStartFlags | i18n::KParseTokens::ASC_DOT | i18n::KParseTokens::GROUP_SEPARATOR_IN_NUMBER_3)
        & ~i18n::KParseTokens::IGNORE_LEADING_WS;

extern "C" {
static int OperatorCompare(const void *pA, const void *pB)
{
    const CalcOp *pFirst = static_cast<const CalcOp*>(pA);
    const CalcOp *pSecond = static_cast<const CalcOp*>(pB);

    int nRet = 0;
    if( CALC_NAME == pFirst->eOp )
    {
        nRet = pFirst->aName.compareTo(pSecond->aName );
    }
    else
    {
        if( CALC_NAME == pSecond->eOp )
            nRet = -1 * pSecond->aName.compareTo(pFirst->aName);
        else
            nRet = pFirst->aName.compareTo(pSecond->aName);
    }
    return nRet;
}
}// extern "C"

CalcOp* FindOperator( const OUString& rSrch )
{
    CalcOp aSrch;
    aSrch.aName = rSrch;
    aSrch.eOp = CALC_NAME;

    return static_cast<CalcOp*>(bsearch( static_cast<void*>(&aSrch),
                              static_cast<void const *>(aOpTable),
                              SAL_N_ELEMENTS( aOpTable ),
                              sizeof( CalcOp ),
                              OperatorCompare ));
}

// static
LanguageType SwCalc::GetDocAppScriptLang( SwDoc const & rDoc )
{
    TypedWhichId<SvxLanguageItem> nWhich =
               GetWhichOfScript( RES_CHRATR_LANGUAGE,
                                 SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() ));
    return rDoc.GetDefault(nWhich).GetLanguage();
}

static double lcl_ConvertToDateValue( SwDoc& rDoc, sal_Int32 nDate )
{
    double nRet = 0;
    SvNumberFormatter* pFormatter = rDoc.GetNumberFormatter();
    if( pFormatter )
    {
        const Date& rNull = pFormatter->GetNullDate();
        Date aDate( nDate >> 24, (nDate& 0x00FF0000) >> 16, nDate& 0x0000FFFF );
        nRet = aDate - rNull;
    }
    return nRet;
}

static SwCalcExp& lcl_GetCalcExp(std::unordered_map<OUString, SwCalcExp>& rVarTable, const OUString& rType)
{
    auto it = rVarTable.find(rType);
    assert(it != rVarTable.end());
    return it->second;
}

SwCalc::SwCalc( SwDoc& rD )
    : m_aErrExpr( SwSbxValue(), nullptr )
    , m_nCommandPos(0)
    , m_rDoc( rD )
    , m_pCharClass( &GetAppCharClass() )
    , m_nListPor( 0 )
    , m_bHasNumber( false )
    , m_eCurrOper( CALC_NAME )
    , m_eCurrListOper( CALC_NAME )
    , m_eError( SwCalcError::NONE )
{
    LanguageType eLang = GetDocAppScriptLang( m_rDoc );
    LanguageTag aLanguageTag( eLang );

    if( eLang != m_pCharClass->getLanguageTag().getLanguageType() )
    {
        m_pCharClass = new CharClass( ::comphelper::getProcessComponentContext(), aLanguageTag );
    }
    m_xLocaleDataWrapper.reset(new LocaleDataWrapper( std::move(aLanguageTag) ));

    m_sCurrSym = comphelper::string::strip(m_xLocaleDataWrapper->getCurrSymbol(), ' ');
    m_sCurrSym  = m_pCharClass->lowercase( m_sCurrSym );

    static constexpr OUString sNTypeTab[]
    {
        u"false"_ustr,
        u"true"_ustr,
        u"pi"_ustr,
        u"e"_ustr,
        u"tables"_ustr,
        u"graf"_ustr,
        u"ole"_ustr,
        u"page"_ustr,
        u"para"_ustr,
        u"word"_ustr,
        u"char"_ustr,

        u"user_firstname"_ustr,
        u"user_lastname"_ustr,
        u"user_initials"_ustr,
        u"user_company"_ustr,
        u"user_street"_ustr,
        u"user_country"_ustr,
        u"user_zipcode"_ustr,
        u"user_city"_ustr,
        u"user_title"_ustr,
        u"user_position"_ustr,
        u"user_tel_work"_ustr,
        u"user_tel_home"_ustr,
        u"user_fax"_ustr,
        u"user_email"_ustr,
        u"user_state"_ustr,
        u"graph"_ustr
    };
    static UserOptToken const aAdrToken[ 12 ] =
    {
        UserOptToken::Company, UserOptToken::Street, UserOptToken::Country, UserOptToken::Zip,
        UserOptToken::City, UserOptToken::Title, UserOptToken::Position, UserOptToken::TelephoneWork,
        UserOptToken::TelephoneHome, UserOptToken::Fax, UserOptToken::Email, UserOptToken::State
    };

    static sal_uInt32 SwDocStat::* const aDocStat[ 7 ] =
    {
        &SwDocStat::nTable,
        &SwDocStat::nGrf,
        &SwDocStat::nOLE,
        &SwDocStat::nPage,
        &SwDocStat::nPara,
        &SwDocStat::nWord,
        &SwDocStat::nChar
    };

    const SwDocStat& rDocStat = m_rDoc.getIDocumentStatistics().GetDocStat();

    SwSbxValue nVal;
    sal_uInt32 n;

    for( n = 0; n < 25; ++n )
        m_aVarTable.insert( { sNTypeTab[n], SwCalcExp( nVal, nullptr ) } );

    lcl_GetCalcExp(m_aVarTable, sNTypeTab[0]).nValue.PutBool( false );
    lcl_GetCalcExp(m_aVarTable, sNTypeTab[1]).nValue.PutBool( true );
    lcl_GetCalcExp(m_aVarTable, sNTypeTab[2]).nValue.PutDouble( M_PI );
    lcl_GetCalcExp(m_aVarTable, sNTypeTab[3]).nValue.PutDouble( M_E );

    for( n = 0; n < 7; ++n )
        lcl_GetCalcExp(m_aVarTable, sNTypeTab[n + 4]).nValue.PutLong( rDocStat.*aDocStat[ n ] );

    SvtUserOptions& rUserOptions = SwModule::get()->GetUserOptions();

    lcl_GetCalcExp(m_aVarTable, sNTypeTab[11]).nValue.PutString( rUserOptions.GetFirstName() );
    lcl_GetCalcExp(m_aVarTable, sNTypeTab[12]).nValue.PutString( rUserOptions.GetLastName() );
    lcl_GetCalcExp(m_aVarTable, sNTypeTab[13]).nValue.PutString( rUserOptions.GetID() );

    for( n = 0; n < 11; ++n )
        lcl_GetCalcExp(m_aVarTable, sNTypeTab[n + 14] ).nValue.PutString(
                                        rUserOptions.GetToken( aAdrToken[ n ] ));

    nVal.PutString( rUserOptions.GetToken( aAdrToken[ 11 ] ));
    m_aVarTable.insert( { sNTypeTab[ 25 ], SwCalcExp( std::move(nVal), nullptr ) } );

} // SwCalc::SwCalc

void SwCalc::ImplDestroy()
{
    if( m_pCharClass != &GetAppCharClass() )
        delete m_pCharClass;
}

SwCalc::~SwCalc()
{
    suppress_fun_call_w_exception(ImplDestroy());
}

SwSbxValue SwCalc::Calculate( const OUString& rStr )
{
    m_eError = SwCalcError::NONE;
    SwSbxValue nResult;

    if( rStr.isEmpty() )
        return nResult;

    m_nListPor = 0;
    m_eCurrListOper = CALC_PLUS; // default: sum

    m_sCommand = rStr;
    m_nCommandPos = 0;

    for (;;)
    {
        m_eCurrOper = GetToken();
        if (m_eCurrOper == CALC_ENDCALC || m_eError != SwCalcError::NONE )
            break;
        nResult = Expr();
    }

    if( m_eError != SwCalcError::NONE)
        nResult.PutDouble( DBL_MAX );

    return nResult;
}

OUString SwCalc::GetStrResult( const SwSbxValue& rVal )
{
    if( !rVal.IsDouble() )
    {
        return rVal.GetOUString();
    }
    return GetStrResult( rVal.GetDouble() );
}

OUString SwCalc::GetStrResult( double nValue )
{
    if( nValue >= DBL_MAX )
        switch( m_eError )
        {
        case SwCalcError::Syntax          :   return SwViewShell::GetShellRes()->aCalc_Syntax;
        case SwCalcError::DivByZero       :   return SwViewShell::GetShellRes()->aCalc_ZeroDiv;
        case SwCalcError::FaultyBrackets  :   return SwViewShell::GetShellRes()->aCalc_Brack;
        case SwCalcError::OverflowInPower :   return SwViewShell::GetShellRes()->aCalc_Pow;
        case SwCalcError::Overflow        :   return SwViewShell::GetShellRes()->aCalc_Overflow;
        default                           :   return SwViewShell::GetShellRes()->aCalc_Default;
        }

    const sal_Int32 nDecPlaces = 15;
    OUString aRetStr( ::rtl::math::doubleToUString(
                        nValue,
                        rtl_math_StringFormat_Automatic,
                        nDecPlaces,
                        m_xLocaleDataWrapper->getNumDecimalSep()[0],
                        true ));
    return aRetStr;
}

SwCalcExp* SwCalc::VarInsert( const OUString &rStr )
{
    OUString aStr = m_pCharClass->lowercase( rStr );
    return VarLook( aStr, true );
}

SwCalcExp* SwCalc::VarLook( const OUString& rStr, bool bIns )
{
    m_aErrExpr.nValue.SetVoidValue(false);

    OUString aStr = m_pCharClass->lowercase( rStr );
    SwCalcExp* pFnd = nullptr;
    auto it = m_aVarTable.find(aStr);
    if (it != m_aVarTable.end())
        pFnd = &it->second;

    if( !pFnd )
    {
        // then check doc
        std::unordered_multimap<OUString, const SwFieldType*> & rDocTable
            = m_rDoc.getIDocumentFieldsAccess().GetUpdateFields().GetFieldTypeTable();
        auto docIt = rDocTable.find(aStr);
        if (docIt != rDocTable.end())
        {
            const SwFieldType* pFieldType = docIt->second;
            it = m_aVarTable.insert( { aStr, SwCalcExp( SwSbxValue(), pFieldType ) } ).first;
            pFnd = &it->second;
        }
    }

    if( pFnd )
    {
        if( pFnd->pFieldType && pFnd->pFieldType->Which() == SwFieldIds::User )
        {
            SwUserFieldType* pUField = const_cast<SwUserFieldType*>(static_cast<const SwUserFieldType*>(pFnd->pFieldType));
            if( SwUserType::String & pUField->GetType() )
            {
                pFnd->nValue.PutString( pUField->GetContent() );
            }
            else if( !pUField->IsValid() )
            {
                // Save the current values...
                sal_uInt16 nListPor = m_nListPor;
                bool bHasNumber = m_bHasNumber;
                SwSbxValue nLastLeft = m_nLastLeft;
                SwSbxValue nNumberValue = m_nNumberValue;
                sal_Int32 nCommandPos = m_nCommandPos;
                SwCalcOper eCurrOper = m_eCurrOper;
                SwCalcOper eCurrListOper = m_eCurrListOper;
                OUString sCurrCommand = m_sCommand;

                pFnd->nValue.PutDouble( pUField->GetValue( *this ) );

                // ...and write them back.
                m_nListPor = nListPor;
                m_bHasNumber = bHasNumber;
                m_nLastLeft = std::move(nLastLeft);
                m_nNumberValue = std::move(nNumberValue);
                m_nCommandPos = nCommandPos;
                m_eCurrOper = eCurrOper;
                m_eCurrListOper = eCurrListOper;
                m_sCommand = std::move(sCurrCommand);
            }
            else
            {
                pFnd->nValue.PutDouble( pUField->GetValue() );
            }
        }
        else if ( !pFnd->pFieldType && pFnd->nValue.IsDBvalue() )
        {
            if ( pFnd->nValue.IsString() )
                m_aErrExpr.nValue.PutString( pFnd->nValue.GetOUString() );
            else if ( pFnd->nValue.IsDouble() )
                m_aErrExpr.nValue.PutDouble( pFnd->nValue.GetDouble() );
            pFnd = &m_aErrExpr;
        }
        return pFnd;
    }

    // At this point the "real" case variable has to be used
    OUString const sTmpName( ::ReplacePoint(rStr) );

    if( !bIns )
    {
#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS
        SwDBManager *pMgr = m_rDoc.GetDBManager();
        OUString sDBName(GetDBName( sTmpName ));
        OUString sSourceName(sDBName.getToken(0, DB_DELIM));
        OUString sTableName(sDBName.getToken(0, ';').getToken(1, DB_DELIM));
        if( pMgr && !sSourceName.isEmpty() && !sTableName.isEmpty() &&
            pMgr->OpenDataSource(sSourceName, sTableName))
        {
            OUString sColumnName( GetColumnName( sTmpName ));
            OSL_ENSURE(!sColumnName.isEmpty(), "Missing DB column name");

            OUString sDBNum( SwFieldType::GetTypeStr(SwFieldTypesEnum::DatabaseSetNumber) );
            sDBNum = m_pCharClass->lowercase(sDBNum);

            // Initialize again because this doesn't happen in docfld anymore for
            // elements != SwFieldIds::Database. E.g. if there is an expression field before
            // a DB_Field in a document.
            const sal_uInt32 nTmpRec = pMgr->GetSelectedRecordId(sSourceName, sTableName);
            VarChange(sDBNum, nTmpRec);

            if( sDBNum.equalsIgnoreAsciiCase(sColumnName) )
            {
                m_aErrExpr.nValue.PutULong(nTmpRec);
                return &m_aErrExpr;
            }

            OUString sResult;
            double nNumber = DBL_MAX;

            LanguageType nLang = m_xLocaleDataWrapper->getLanguageTag().getLanguageType();
            if(pMgr->GetColumnCnt( sSourceName, sTableName, sColumnName,
                                    nTmpRec, nLang, sResult, &nNumber ))
            {
                if (nNumber != DBL_MAX)
                    m_aErrExpr.nValue.PutDouble( nNumber );
                else
                    m_aErrExpr.nValue.PutString( sResult );

                return &m_aErrExpr;
            }
        }
        else
#endif
        {
            //data source was not available - set return to "NoValue"
            m_aErrExpr.nValue.SetVoidValue(true);
        }
        // NEVER save!
        return &m_aErrExpr;
    }

    SwCalcExp* pNewExp = &m_aVarTable.insert( { aStr, SwCalcExp( SwSbxValue(), nullptr ) } ).first->second;

    OUString sColumnName( GetColumnName( sTmpName ));
    OSL_ENSURE( !sColumnName.isEmpty(), "Missing DB column name" );
    if( sColumnName.equalsIgnoreAsciiCase(
                            SwFieldType::GetTypeStr( SwFieldTypesEnum::DatabaseSetNumber ) ))
    {
#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS
        SwDBManager *pMgr = m_rDoc.GetDBManager();
        OUString sDBName(GetDBName( sTmpName ));
        OUString sSourceName(sDBName.getToken(0, DB_DELIM));
        OUString sTableName(sDBName.getToken(0, ';').getToken(1, DB_DELIM));
        if( pMgr && !sSourceName.isEmpty() && !sTableName.isEmpty() &&
            pMgr->OpenDataSource(sSourceName, sTableName) &&
            !pMgr->IsInMerge())
        {
            pNewExp->nValue.PutULong( pMgr->GetSelectedRecordId(sSourceName, sTableName));
        }
        else
#endif
        {
            pNewExp->nValue.SetVoidValue(true);
        }
    }

    return pNewExp;
}

void SwCalc::VarChange( const OUString& rStr, double nValue )
{
    SwSbxValue aVal( nValue );
    VarChange( rStr, aVal );
}

void SwCalc::VarChange( const OUString& rStr, const SwSbxValue& rValue )
{
    OUString aStr = m_pCharClass->lowercase( rStr );

    auto it = m_aVarTable.find( aStr );
    if (it != m_aVarTable.end())
        it->second.nValue = rValue;
    else
        m_aVarTable.insert( { aStr, SwCalcExp( rValue, nullptr ) } );
}

bool SwCalc::Push( const SwUserFieldType* pUserFieldType )
{
    if( m_aRekurStack.end() != std::find(m_aRekurStack.begin(), m_aRekurStack.end(), pUserFieldType ) )
        return false;

    m_aRekurStack.push_back( pUserFieldType );
    return true;
}

void SwCalc::Pop()
{
    OSL_ENSURE( m_aRekurStack.size(), "SwCalc: Pop on an invalid pointer" );

    m_aRekurStack.pop_back();
}

const CharClass* SwCalc::GetCharClass() const
{
    return m_pCharClass;
}

void SwCalc::SetCharClass(const LanguageTag& rLanguageTag)
{
    m_pCharClass = new CharClass( ::comphelper::getProcessComponentContext(), rLanguageTag );
}

SwCalcOper SwCalc::GetToken()
{
    if( m_nCommandPos >= m_sCommand.getLength() )
    {
        m_eCurrOper = CALC_ENDCALC;
        return m_eCurrOper;
    }

    using namespace ::com::sun::star::i18n;
    {
        // Parse any token.
        ParseResult aRes = m_pCharClass->parseAnyToken( m_sCommand, m_nCommandPos,
                                                      coStartFlags, OUString(),
                                                      coContFlags, OUString());

        bool bSetError = true;
        sal_Int32 nRealStt = m_nCommandPos + aRes.LeadingWhiteSpace;
        if( aRes.TokenType & (KParseType::ASC_NUMBER | KParseType::UNI_NUMBER) )
        {
            m_nNumberValue.PutDouble( aRes.Value );
            m_eCurrOper = CALC_NUMBER;
            bSetError = false;
        }
        else if( aRes.TokenType & KParseType::IDENTNAME )
        {
            OUString aName( m_sCommand.copy( nRealStt,
                            aRes.EndPos - nRealStt ) );
            //#101436#: The variable may contain a database name. It must not be
            // converted to lower case! Instead all further comparisons must be
            // done case-insensitive
            OUString sLowerCaseName = m_pCharClass->lowercase( aName );
            // catch currency symbol
            if( sLowerCaseName == m_sCurrSym )
            {
                m_nCommandPos = aRes.EndPos;
                return GetToken(); // call again
            }

            // catch operators
            CalcOp* pFnd = ::FindOperator( sLowerCaseName );
            if( pFnd )
            {
                m_eCurrOper = pFnd->eOp;
                switch( m_eCurrOper )
                {
                case CALC_SUM:
                case CALC_MEAN:
                case CALC_AVERAGE:
                case CALC_COUNT:
                    m_eCurrListOper = CALC_PLUS;
                    break;
                case CALC_MIN:
                    m_eCurrListOper = CALC_MIN_IN;
                    break;
                case CALC_MAX:
                    m_eCurrListOper = CALC_MAX_IN;
                    break;
                case CALC_MOD:
                    m_eCurrListOper = CALC_MOD_IN;
                    break;
                case CALC_DATE:
                    m_eCurrListOper = CALC_MONTH;
                    break;
                case CALC_PRODUCT:
                    m_eCurrListOper = CALC_MUL;
                    break;
                default:
                    break;
                }
                m_nCommandPos = aRes.EndPos;
                return m_eCurrOper;
            }
            m_aVarName = aName;
            m_eCurrOper = CALC_NAME;
            bSetError = false;
        }
        else if ( aRes.TokenType & KParseType::DOUBLE_QUOTE_STRING )
        {
            m_nNumberValue.PutString( aRes.DequotedNameOrString );
            m_eCurrOper = CALC_NUMBER;
            bSetError = false;
        }
        else if( aRes.TokenType & KParseType::ONE_SINGLE_CHAR )
        {
            std::u16string_view aName( m_sCommand.subView( nRealStt,
                              aRes.EndPos - nRealStt ));
            if( 1 == aName.size() )
            {
                bSetError = false;
                sal_Unicode ch = aName[0];
                switch( ch )
                {
                case ';':
                    if( CALC_MONTH == m_eCurrListOper || CALC_DAY == m_eCurrListOper )
                    {
                        m_eCurrOper = m_eCurrListOper;
                        break;
                    }
                    [[fallthrough]];
                case '\n':
                    m_eCurrOper = CALC_PRINT;
                    break;

                case '%':
                case '^':
                case '*':
                case '/':
                case '+':
                case '-':
                case '(':
                case ')':
                    m_eCurrOper = SwCalcOper(ch);
                    break;

                case '=':
                case '!':
                    {
                        SwCalcOper eTmp2;
                        if( '=' == ch )
                        {
                            m_eCurrOper = SwCalcOper('=');
                            eTmp2 = CALC_EQ;
                        }
                        else
                        {
                            m_eCurrOper = CALC_NOT;
                            eTmp2 = CALC_NEQ;
                        }

                        if( aRes.EndPos < m_sCommand.getLength() &&
                            '=' == m_sCommand[aRes.EndPos] )
                        {
                            m_eCurrOper = eTmp2;
                            ++aRes.EndPos;
                        }
                    }
                    break;

                case cListDelim:
                    m_eCurrOper = m_eCurrListOper;
                    break;

                case '[':
                    if( aRes.EndPos < m_sCommand.getLength() )
                    {
                        m_aVarName.setLength(0);
                        sal_Int32 nFndPos = aRes.EndPos,
                                  nSttPos = nFndPos;

                        do {
                            nFndPos = m_sCommand.indexOf( ']', nFndPos );
                            if( -1 != nFndPos )
                            {
                                // ignore the ]
                                if ('\\' == m_sCommand[nFndPos-1])
                                {
                                    m_aVarName.append(m_sCommand.subView(nSttPos,
                                                    nFndPos - nSttPos - 1) );
                                    nSttPos = ++nFndPos;
                                }
                                else
                                    break;
                            }
                        } while( nFndPos != -1 );

                        if( nFndPos != -1 )
                        {
                            if( nSttPos != nFndPos )
                                m_aVarName.append(m_sCommand.subView(nSttPos,
                                                    nFndPos - nSttPos) );
                            aRes.EndPos = nFndPos + 1;
                            m_eCurrOper = CALC_NAME;
                        }
                        else
                            bSetError = true;
                    }
                    else
                    {
                        bSetError = true;
                    }
                    break;

                default:
                    bSetError = true;
                    break;
                }
            }
        }
        else if( aRes.TokenType & KParseType::BOOLEAN )
        {
            std::u16string_view aName( m_sCommand.subView( nRealStt,
                                         aRes.EndPos - nRealStt ));
            if( !aName.empty() )
            {
                sal_Unicode ch = aName[0];

                bSetError = true;
                if ('<' == ch || '>' == ch)
                {
                    bSetError = false;

                    SwCalcOper eTmp2 = ('<' == ch) ? CALC_LEQ : CALC_GEQ;
                    m_eCurrOper = ('<' == ch) ? CALC_LES : CALC_GRE;

                    if( 2 == aName.size() && '=' == aName[1] )
                        m_eCurrOper = eTmp2;
                    else if( 1 != aName.size() )
                        bSetError = true;
                }
            }
        }
        else if( nRealStt == m_sCommand.getLength() )
        {
            m_eCurrOper = CALC_ENDCALC;
            bSetError = false;
        }

        if( bSetError )
        {
            m_eError = SwCalcError::Syntax;
            m_eCurrOper = CALC_PRINT;
        }
        m_nCommandPos = aRes.EndPos;
    };

    return m_eCurrOper;
}

SwSbxValue SwCalc::Term()
{
    SwSbxValue left( Prim() );
    m_nLastLeft = left;
    for(;;)
    {
        sal_uInt16 nSbxOper = USHRT_MAX;

        switch( m_eCurrOper )
        {
        case CALC_AND:
            {
                GetToken();
                bool bB = Prim().GetBool();
                left.PutBool( left.GetBool() && bB );
            }
            break;
        case CALC_OR:
            {
                GetToken();
                bool bB = Prim().GetBool();
                left.PutBool( left.GetBool() || bB );
            }
            break;
        case CALC_XOR:
            {
                GetToken();
                bool bR = Prim().GetBool();
                bool bL = left.GetBool();
                left.PutBool(bL != bR);
            }
            break;

        case CALC_EQ:   nSbxOper = SbxEQ;   break;
        case CALC_NEQ:  nSbxOper = SbxNE;   break;
        case CALC_LEQ:  nSbxOper = SbxLE;   break;
        case CALC_GEQ:  nSbxOper = SbxGE;   break;
        case CALC_GRE:  nSbxOper = SbxGT;   break;
        case CALC_LES:  nSbxOper = SbxLT;   break;

        case CALC_MUL:  nSbxOper = SbxMUL;  break;
        case CALC_DIV:  nSbxOper = SbxDIV;  break;

        case CALC_MIN_IN:
            {
                GetToken();
                SwSbxValue e = Prim();
                left = left.GetDouble() < e.GetDouble() ? left : e;
            }
            break;
        case CALC_MAX_IN:
            {
                GetToken();
                SwSbxValue e = Prim();
                left = left.GetDouble() > e.GetDouble() ? left : e;
            }
            break;
        case CALC_MOD_IN:
            {
                GetToken();
                SwSbxValue e = Prim();
                if ( e.GetDouble() == 0.0)
                    m_eError = SwCalcError::DivByZero;
                else
                    left.PutLong(left.GetLong() % e.GetLong());
            }
            break;
        case CALC_MONTH:
            {
                GetToken();
                SwSbxValue e = Prim();
                sal_Int32 nYear = static_cast<sal_Int32>(floor( left.GetDouble() ));
                nYear = nYear & 0x0000FFFF;
                sal_Int32 nMonth = static_cast<sal_Int32>(floor( e.GetDouble() ));
                nMonth = ( nMonth & 0x000000FF ) << 16;
                left.PutLong( nMonth + nYear );
                m_eCurrOper = CALC_DAY;
            }
            break;
        case CALC_DAY:
            {
                GetToken();
                SwSbxValue e = Prim();
                sal_Int32 nYearMonth = static_cast<sal_Int32>(floor( left.GetDouble() ));
                nYearMonth = nYearMonth & 0x00FFFFFF;
                sal_Int32 nDay = static_cast<sal_Int32>(floor( e.GetDouble() ));
                nDay = ( nDay & 0x000000FF ) << 24;
                left = lcl_ConvertToDateValue( m_rDoc, nDay + nYearMonth );
            }
            break;
        case CALC_ROUND:
            {
                GetToken();
                SwSbxValue e = Prim();

                double fVal = 0;
                double fFac = 1;
                sal_Int32 nDec = static_cast<sal_Int32>(floor( e.GetDouble() ));
                if( nDec < -20 || nDec > 20 )
                {
                    m_eError = SwCalcError::Overflow;
                    left.Clear();
                    return left;
                }
                fVal = left.GetDouble();
                if( nDec >= 0)
                {
                    for (sal_Int32 i = 0; i < nDec; ++i )
                        fFac *= 10.0;
                }
                else
                {
                    for (sal_Int32 i = 0; i < -nDec; ++i )
                        fFac /= 10.0;
                }

                fVal *= fFac;
                bool bSign;
                if (fVal < 0.0)
                {
                    fVal *= -1.0;
                    bSign = true;
                }
                else
                {
                    bSign = false;
                }

                // rounding
                double fNum = fVal; // find the exponent
                int nExp = 0;
                if( fNum > 0 )
                {
                    while( fNum < 1.0 )
                    {
                        fNum *= 10.0;
                        --nExp;
                    }
                    while( fNum >= 10.0 )
                    {
                        fNum /= 10.0;
                        ++nExp;
                    }
                }
                nExp = 15 - nExp;
                if( nExp > 15 )
                    nExp = 15;
                else if( nExp <= 1 )
                    nExp = 0;
                fVal = floor( fVal+ 0.5 + nRoundVal[ nExp ] );

                if (bSign)
                    fVal *= -1.0;

                fVal /= fFac;

                left.PutDouble( fVal );
            }
            break;

//#77448# (=2*3^2 != 18)

        default:
            return left;
        }

        if( USHRT_MAX != nSbxOper )
        {
            // #i47706: cast to SbxOperator AFTER comparing to USHRT_MAX
            SbxOperator eSbxOper = static_cast<SbxOperator>(nSbxOper);

            GetToken();
            if( SbxEQ <= eSbxOper && eSbxOper <= SbxGE )
            {
                left.PutBool( left.Compare( eSbxOper, Prim() ));
            }
            else
            {
                SwSbxValue aRight( Prim() );
                aRight.MakeDouble();
                left.MakeDouble();

                if( SbxDIV == eSbxOper && !aRight.GetDouble() )
                    m_eError = SwCalcError::DivByZero;
                else
                    left.Compute( eSbxOper, aRight );
            }
        }
    }
}

SwSbxValue SwCalc::StdFunc(pfCalc pFnc, bool bChkTrig)
{
    SwSbxValue nErg;
    GetToken();
    double nVal = Prim().GetDouble();
    if( !bChkTrig || ( nVal > -1 && nVal < 1 ) )
        nErg.PutDouble( (*pFnc)( nVal ) );
    else
        m_eError = SwCalcError::Overflow;
    return nErg;
}

SwSbxValue SwCalc::PrimFunc(bool &rChkPow)
{
    rChkPow = false;

    switch (m_eCurrOper)
    {
        case CALC_SIN:
            SAL_INFO("sw.calc", "sin");
            return StdFunc(&sin, false);
        case CALC_COS:
            SAL_INFO("sw.calc", "cos");
            return StdFunc(&cos, false);
        case CALC_TAN:
            SAL_INFO("sw.calc", "tan");
            return StdFunc(&tan, false);
        case CALC_ATAN:
            SAL_INFO("sw.calc", "atan");
            return StdFunc(&atan, false);
        case CALC_ASIN:
            SAL_INFO("sw.calc", "asin");
            return StdFunc(&asin, true);
        case CALC_ACOS:
            SAL_INFO("sw.calc", "acos");
            return StdFunc(&acos, true);
        case CALC_ABS:
            SAL_INFO("sw.calc", "abs");
            return StdFunc(&abs, false);
        case CALC_SIGN:
        {
            SAL_INFO("sw.calc", "sign");
            SwSbxValue nErg;
            GetToken();
            double nVal = Prim().GetDouble();
            nErg.PutDouble( int(0 < nVal) - int(nVal < 0) );
            return nErg;
        }
        case CALC_INT:
        {
            SAL_INFO("sw.calc", "int");
            SwSbxValue nErg;
            GetToken();
            sal_Int32 nVal = static_cast<sal_Int32>( Prim().GetDouble() );
            nErg.PutDouble( nVal );
            return nErg;
        }
        case CALC_NOT:
        {
            SAL_INFO("sw.calc", "not");
            GetToken();
            SwSbxValue nErg = Prim();
            if( SbxSTRING == nErg.GetType() )
            {
                nErg.PutBool( nErg.GetOUString().isEmpty() );
            }
            else if(SbxBOOL == nErg.GetType() )
            {
                nErg.PutBool(!nErg.GetBool());
            }
            // Evaluate arguments manually so that the binary NOT below does not
            // get called. We want a BOOLEAN NOT.
            else if (nErg.IsNumeric())
            {
                nErg.PutLong( nErg.GetDouble() == 0.0 ? 1 : 0 );
            }
            else
            {
                OSL_FAIL( "unexpected case. computing binary NOT" );
                //!! computes a binary NOT
                nErg.Compute( SbxNOT, nErg );
            }
            return nErg;
        }
        case CALC_NUMBER:
        {
            SAL_INFO("sw.calc", "number: " << m_nNumberValue.GetDouble());
            SwSbxValue nErg;
            m_bHasNumber = true;
            if( GetToken() == CALC_PHD )
            {
                double aTmp = m_nNumberValue.GetDouble();
                aTmp *= 0.01;
                nErg.PutDouble( aTmp );
                GetToken();
            }
            else if( m_eCurrOper == CALC_NAME )
            {
                m_eError = SwCalcError::Syntax;
            }
            else
            {
                nErg = m_nNumberValue;
                rChkPow = true;
            }
            return nErg;
        }
        case CALC_NAME:
        {
            SAL_INFO("sw.calc", "name");
            SwSbxValue nErg;
            switch(SwCalcOper eOper = GetToken())
            {
                case CALC_ASSIGN:
                {
                    SwCalcExp* n = VarInsert(m_aVarName.toString());
                    GetToken();
                    nErg = n->nValue = Expr();
                    break;
                }
                default:
                    nErg = VarLook(m_aVarName.toString())->nValue;
                    // Explicitly disallow unknown function names (followed by "("),
                    // allow unknown variable names (equal to zero)
                    if (nErg.IsVoidValue() && (eOper == CALC_LP))
                        m_eError = SwCalcError::Syntax;
                    else
                        rChkPow = true;
                    break;
            }
            return nErg;
        }
        case CALC_MINUS:
        {
            SAL_INFO("sw.calc", "-");
            SwSbxValue nErg;
            GetToken();
            nErg.PutDouble( -(Prim().GetDouble()) );
            return nErg;
        }
        case CALC_LP:
        {
            SAL_INFO("sw.calc", "(");
            GetToken();
            SwSbxValue nErg = Expr();
            if( m_eCurrOper != CALC_RP )
            {
                m_eError = SwCalcError::FaultyBrackets;
            }
            else
            {
                GetToken();
                rChkPow = true; // in order for =(7)^2 to work
            }
            return nErg;
        }
        case CALC_RP:
            // ignore, see tdf#121962
            SAL_INFO("sw.calc", ")");
            break;
        case CALC_MEAN:
        case CALC_AVERAGE:
        {
            SAL_INFO("sw.calc", "mean");
            m_nListPor = 1;
            m_bHasNumber = CALC_MEAN == m_eCurrOper;
            GetToken();
            SwSbxValue nErg = Expr();
            double aTmp = nErg.GetDouble();
            aTmp /= m_nListPor;
            if ( !m_bHasNumber )
                m_eError = SwCalcError::DivByZero;
            else
                nErg.PutDouble( aTmp );
            return nErg;
        }
        case CALC_COUNT:
        {
            SAL_INFO("sw.calc", "count");
            m_nListPor = 1;
            m_bHasNumber = false;
            GetToken();
            SwSbxValue nErg = Expr();
            nErg.PutDouble( m_bHasNumber ? m_nListPor : 0 );
            return nErg;
        }
        case CALC_SQRT:
        {
            SAL_INFO("sw.calc", "sqrt");
            GetToken();
            SwSbxValue nErg = Prim();
            if( nErg.GetDouble() < 0 )
                m_eError = SwCalcError::Overflow;
            else
                nErg.PutDouble( sqrt( nErg.GetDouble() ));
            return nErg;
        }
        case CALC_SUM:
        case CALC_PRODUCT:
        case CALC_DATE:
        case CALC_MIN:
        case CALC_MAX:
        case CALC_MOD:
        {
            SAL_INFO("sw.calc", "sum/product/date/min/max/mod");
            GetToken();
            SwSbxValue nErg = Expr();
            return nErg;
        }
        case CALC_ENDCALC:
        {
            SAL_INFO("sw.calc", "endcalc");
            SwSbxValue nErg;
            nErg.Clear();
            return nErg;
        }
        default:
            SAL_INFO("sw.calc", "syntax error");
            m_eError = SwCalcError::Syntax;
            break;
    }

    return SwSbxValue();
}

SwSbxValue SwCalc::Prim()
{
    bool bChkPow;
    SwSbxValue nErg = PrimFunc(bChkPow);

    if (bChkPow && m_eCurrOper == CALC_POW)
    {
        double dleft = nErg.GetDouble();
        GetToken();
        double right = Prim().GetDouble();

        double fraction;
        fraction = modf( right, &o3tl::temporary(double()) );
        if( ( dleft < 0.0 && 0.0 != fraction ) ||
            ( 0.0 == dleft && right < 0.0 ) )
        {
            m_eError = SwCalcError::Overflow;
            nErg.Clear();
        }
        else
        {
            dleft = pow(dleft, right );
            if( dleft == HUGE_VAL )
            {
                m_eError = SwCalcError::OverflowInPower;
                nErg.Clear();
            }
            else
            {
                nErg.PutDouble( dleft );
            }
        }
    }

    return nErg;
}

SwSbxValue  SwCalc::Expr()
{
    SwSbxValue left = Term();
    m_nLastLeft = left;
    for(;;)
    {
        switch(m_eCurrOper)
        {
            case CALC_PLUS:
            {
                GetToken();
                left.MakeDouble();
                SwSbxValue right(Term());
                right.MakeDouble();
                left.Compute(SbxPLUS, right);
                m_nListPor++;
                break;
            }
            case CALC_MINUS:
            {
                GetToken();
                left.MakeDouble();
                SwSbxValue right(Term());
                right.MakeDouble();
                left.Compute(SbxMINUS, right);
                break;
            }
            default:
            {
                return left;
            }
        }
    }
}

OUString SwCalc::GetColumnName(const OUString& rName)
{
    sal_Int32 nPos = rName.indexOf(DB_DELIM);
    if( -1 != nPos )
    {
        nPos = rName.indexOf(DB_DELIM, nPos + 1);

        if( -1 != nPos )
            return rName.copy(nPos + 1);
    }
    return rName;
}

OUString SwCalc::GetDBName(std::u16string_view rName)
{
    size_t nPos = rName.find(DB_DELIM);
    if( std::u16string_view::npos != nPos )
    {
        nPos = rName.find(DB_DELIM, nPos + 1);

        if( std::u16string_view::npos != nPos )
            return OUString(rName.substr( 0, nPos ));
    }
    SwDBData aData = m_rDoc.GetDBData();
    return aData.sDataSource + OUStringChar(DB_DELIM) + aData.sCommand;
}

namespace
{
    bool lcl_Str2Double( const OUString& rCommand, sal_Int32& rCommandPos,
                                double& rVal,
                                const LocaleDataWrapper* const pLclData )
    {
        assert(pLclData);
        const sal_Unicode nCurrCmdPos = rCommandPos;
        rtl_math_ConversionStatus eStatus;
        const sal_Unicode* pEnd;
        rVal = pLclData->stringToDouble( rCommand.getStr() + rCommandPos,
                                         rCommand.getStr() + rCommand.getLength(),
                                         true,
                                         &eStatus,
                                         &pEnd );
        rCommandPos = static_cast<sal_Int32>(pEnd - rCommand.getStr());

        return rtl_math_ConversionStatus_Ok == eStatus &&
               nCurrCmdPos != rCommandPos;
    }
}

bool SwCalc::Str2Double( const OUString& rCommand, sal_Int32& rCommandPos,
                         double& rVal )
{
    const SvtSysLocale aSysLocale;
    return lcl_Str2Double( rCommand, rCommandPos, rVal, &aSysLocale.GetLocaleData() );
}

bool SwCalc::Str2Double( const OUString& rCommand, sal_Int32& rCommandPos,
                         double& rVal, SwDoc const * const pDoc )
{
    const SvtSysLocale aSysLocale;
    std::unique_ptr<const LocaleDataWrapper> pLclD;
    if( pDoc )
    {
        LanguageType eLang = GetDocAppScriptLang( *pDoc );
        if (eLang != aSysLocale.GetLanguageTag().getLanguageType())
        {
            pLclD.reset( new LocaleDataWrapper( LanguageTag( eLang )) );
        }
    }

    bool const bRet = lcl_Str2Double(rCommand, rCommandPos, rVal,
                                     pLclD ? pLclD.get() : &aSysLocale.GetLocaleData());

    return bRet;
}

bool SwCalc::IsValidVarName( const OUString& rStr, OUString* pValidName )
{
    bool bRet = false;
    using namespace ::com::sun::star::i18n;
    {
        // Parse any token.
        ParseResult aRes = GetAppCharClass().parseAnyToken( rStr, 0,
                                                    coStartFlags, OUString(),
                                                     coContFlags, OUString() );

        if( aRes.TokenType & KParseType::IDENTNAME )
        {
            bRet = aRes.EndPos == rStr.getLength();
            if( pValidName )
            {
                *pValidName = rStr.copy( aRes.LeadingWhiteSpace,
                                         aRes.EndPos - aRes.LeadingWhiteSpace );
            }
        }
        else if( pValidName )
            pValidName->clear();
    }
    return bRet;
}

SwCalcExp::SwCalcExp(SwSbxValue aVal, const SwFieldType* pType)
    : nValue(std::move(aVal))
    , pFieldType(pType)
{
}

bool SwSbxValue::GetBool() const
{
    return SbxSTRING == GetType() ? !GetOUString().isEmpty()
                                  : SbxValue::GetBool();
}

double SwSbxValue::GetDouble() const
{
    double nRet;
    if( SbxSTRING == GetType() )
    {
        sal_Int32 nStt = 0;
        SwCalc::Str2Double( GetOUString(), nStt, nRet );
    }
    else if (IsBool())
    {
        nRet = GetBool() ? 1.0 : 0.0;
    }
    else
    {
        nRet = SbxValue::GetDouble();
    }
    return nRet;
}

SwSbxValue& SwSbxValue::MakeDouble()
{
    if( GetType() == SbxSTRING  || GetType() == SbxBOOL )
        PutDouble( GetDouble() );
    return *this;
}

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