/* -*- 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 <sal/config.h>

#include <string_view>

#include <float.h>
#include <hintids.hxx>
#include <hints.hxx>
#include <fmtfld.hxx>
#include <txtfld.hxx>
#include <frmfmt.hxx>
#include <layfrm.hxx>
#include <cntfrm.hxx>
#include <tabfrm.hxx>
#include <doc.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <ndtxt.hxx>
#include <swtable.hxx>
#include <tblsel.hxx>
#include <cellfml.hxx>
#include <calc.hxx>
#include <expfld.hxx>
#include <usrfld.hxx>
#include <flddat.hxx>
#include <cellatr.hxx>
#include <ndindex.hxx>
#include <frameformats.hxx>
#include <comphelper/string.hxx>
#include <o3tl/string_view.hxx>
#include <o3tl/safeint.hxx>
#include <osl/diagnose.h>
#include <svl/numformat.hxx>
#include <utility>

namespace
{

const sal_Unicode cRelSeparator = ',';
const sal_Unicode cRelIdentifier = '\x12'; // CTRL-R

enum
{
    cMAXSTACKSIZE = 50
};

}

static const SwFrame* lcl_GetBoxFrame( const SwTableBox& rBox );
static sal_Int32 lcl_GetLongBoxNum( OUString& rStr );
static const SwTableBox* lcl_RelToBox( const SwTable& rTable,
                                       const SwTableBox* pRefBox,
                                       const OUString& sGetName);
static OUString lcl_BoxNmToRel( const SwTable& rTable,
                                const SwTableNode& rTableNd,
                                const OUString& sRefBoxNm,
                                const OUString& sGetStr,
                                bool bExtrnlNm);

/** Get value of this box.
 *
 * The value is comes from the first TextNode. If it starts with a number/
 * formula then calculate it, if it starts with a field then get the value.
 * All other conditions return 0 (and an error?).
 */
double SwTableBox::GetValue( SwTableCalcPara& rCalcPara ) const
{
    double nRet = 0;

    if( rCalcPara.m_rCalc.IsCalcError() )
        return nRet;            // stop if there is already an error set

    rCalcPara.m_rCalc.SetCalcError( SwCalcError::Syntax );    // default: error

    // no content box?
    if( !m_pStartNode  )
        return nRet;

    if( rCalcPara.IncStackCnt() )
        return nRet;

    rCalcPara.SetLastTableBox( this );

    // Does it create a recursion?
    SwTableBox* pBox = const_cast<SwTableBox*>(this);
    if( rCalcPara.m_pBoxStack->find( pBox ) != rCalcPara.m_pBoxStack->end() )
        return nRet;            // already on the stack: error

    // re-start with this box
    rCalcPara.SetLastTableBox( this );

    rCalcPara.m_pBoxStack->insert( pBox );      // add
    do {        // Middle-Check-Loop, so that we can jump from here. Used so that the box pointer
                // will be removed from stack at the end.
        SwDoc& rDoc = GetFrameFormat()->GetDoc();

        if( const SwTableBoxFormula* pFormulaItem = GetFrameFormat()->GetItemIfSet(
                                RES_BOXATR_FORMULA, false ) )
        {
            rCalcPara.m_rCalc.SetCalcError( SwCalcError::NONE ); // reset status
            if( !pFormulaItem->IsValid() )
            {
                // calculate
                const SwTable* pTmp = rCalcPara.m_pTable;
                rCalcPara.m_pTable = &pBox->GetSttNd()->FindTableNode()->GetTable();
                const_cast<SwTableBoxFormula*>(pFormulaItem)->Calc( rCalcPara, nRet );

                if( !rCalcPara.IsStackOverflow() )
                {
                    SwFrameFormat* pFormat = pBox->ClaimFrameFormat();
                    SfxItemSetFixed<RES_BOXATR_BEGIN,RES_BOXATR_END-1> aTmp( rDoc.GetAttrPool() );
                    aTmp.Put( SwTableBoxValue( nRet ) );
                    if( SfxItemState::SET != pFormat->GetItemState( RES_BOXATR_FORMAT ))
                        aTmp.Put( SwTableBoxNumFormat( 0 ));
                    pFormat->SetFormatAttr( aTmp );
                }
                rCalcPara.m_pTable = pTmp;
            }
            else
                nRet = GetFrameFormat()->GetTableBoxValue().GetValue();
            break;
        }
        else if( const SwTableBoxValue* pBoxValueItem = pBox->GetFrameFormat()->GetItemIfSet(
                                RES_BOXATR_VALUE, false ) )
        {
            rCalcPara.m_rCalc.SetCalcError( SwCalcError::NONE ); // reset status
            nRet = pBoxValueItem->GetValue();
            break;
        }

        SwTextNode* pTextNd = rDoc.GetNodes()[ m_pStartNode->GetIndex() + 1 ]->GetTextNode();
        if( !pTextNd )
            break;

        sal_Int32 nSttPos = 0;
        OUString sText = pTextNd->GetText();

        // use text of the tracked changes
        if ( sText.getLength() > 0 &&
                sText[0] != CH_TXTATR_BREAKWORD && sText[0] != CH_TXTATR_INWORD )
        {
            sText = pTextNd->GetRedlineText();
        }

        while ( nSttPos < sText.getLength() && ( sText[nSttPos]==' ' || sText[nSttPos]=='\t' ) )
            ++nSttPos;

        // if there is a calculation field at position 1, get the value of it
        const bool bOK = nSttPos<sText.getLength();
        const sal_Unicode Char = bOK ? sText[nSttPos] : 0;
        SwTextField * pTextField = nullptr;
        if ( bOK && (Char==CH_TXTATR_BREAKWORD || Char==CH_TXTATR_INWORD) )
        {
            pTextField = static_txtattr_cast<SwTextField*>(pTextNd->GetTextAttrForCharAt(nSttPos, RES_TXTATR_FIELD));
        }
        if ( pTextField != nullptr )
        {
            rCalcPara.m_rCalc.SetCalcError( SwCalcError::NONE ); // reset status

            const SwField* pField = pTextField->GetFormatField().GetField();
            switch ( pField->GetTyp()->Which() )
            {
            case SwFieldIds::SetExp:
                nRet = static_cast<const SwSetExpField*>(pField)->GetValue(rCalcPara.m_pLayout);
                break;
            case SwFieldIds::User:
                nRet = static_cast<const SwUserField*>(pField)->GetValue();
                break;
            case SwFieldIds::Table:
                {
                    SwTableField* pTableField = const_cast<SwTableField*>(static_cast<const SwTableField*>(pField));
                    if( !pTableField->IsValid() )
                    {
                        // use the right table!
                        const SwTable* pTmp = rCalcPara.m_pTable;
                        rCalcPara.m_pTable = &pTextNd->FindTableNode()->GetTable();
                        pTableField->CalcField( rCalcPara );
                        rCalcPara.m_pTable = pTmp;
                    }
                    nRet = pTableField->GetValue();
                }
                break;

            case SwFieldIds::DateTime:
                nRet = static_cast<const SwDateTimeField*>( pField )->GetValue();
                break;

            case SwFieldIds::JumpEdit:
                //JP 14.09.98: Bug 56112 - placeholder never have the right content!
                nRet = 0;
                break;

            default:
                nRet = rCalcPara.m_rCalc.Calculate( pField->ExpandField(true, nullptr) ).GetDouble();
            }
        }
        else if ( nSttPos < sText.getLength()
                  && Char == CH_TXT_ATR_INPUTFIELDSTART )
        {
            const SwTextInputField * pTextInputField =
                dynamic_cast< const SwTextInputField* >(
                    pTextNd->GetTextAttrAt( nSttPos, RES_TXTATR_INPUTFIELD ) );
            if ( pTextInputField == nullptr )
                break;
            nRet = rCalcPara.m_rCalc.Calculate( pTextInputField->GetFieldContent() ).GetDouble();
        }
        else if ( Char != CH_TXTATR_BREAKWORD )
        {
            // result is 0 but no error!
            rCalcPara.m_rCalc.SetCalcError( SwCalcError::NONE ); // reset status

            double aNum = 0.0;
            sText = bOK ? sText.copy( nSttPos ) : OUString();
            sal_uInt32 nFormatIndex = GetFrameFormat()->GetTableBoxNumFormat().GetValue();

            SvNumberFormatter* pNumFormatr = rDoc.GetNumberFormatter();

            const SvNumFormatType nFormatType = pNumFormatr->GetType( nFormatIndex );
            if( nFormatType == SvNumFormatType::TEXT )
                nFormatIndex = 0;
            // JP 22.04.98: Bug 49659 - special treatment for percentages
            else if( !sText.isEmpty() &&
                    SvNumFormatType::PERCENT == nFormatType)
            {
                sal_uInt32 nTmpFormat = 0;
                if( rDoc.IsNumberFormat( sText, nTmpFormat, aNum ) &&
                    SvNumFormatType::NUMBER == pNumFormatr->GetType( nTmpFormat ))
                    sText += "%";
            }

            if( rDoc.IsNumberFormat( sText, nFormatIndex, aNum ))
                nRet = aNum;
            else
                rCalcPara.m_rCalc.SetCalcError( SwCalcError::NaN ); // set for interoperability functions
        }
        // ?? otherwise it is an error
    } while( false );

    if( !rCalcPara.IsStackOverflow() )
    {
        rCalcPara.m_pBoxStack->erase( pBox );      // remove from stack
        rCalcPara.DecStackCnt();
    }

    //JP 12.01.99: error detection, Bug 60794
    if( DBL_MAX == nRet )
        rCalcPara.m_rCalc.SetCalcError( SwCalcError::Syntax ); // set error

    return nRet;
}

// structure needed for calculation of tables

SwTableCalcPara::SwTableCalcPara(SwCalc& rCalculator, const SwTable& rTable,
        SwRootFrame const*const pLayout)
    : m_pLastTableBox(nullptr)
    , m_nStackCount( 0 )
    , m_nMaxSize( cMAXSTACKSIZE )
    , m_pLayout(pLayout)
    , m_pBoxStack( new SwTableSortBoxes )
    , m_rCalc( rCalculator )
    , m_pTable( &rTable )
{
}

SwTableCalcPara::~SwTableCalcPara()
{
}

bool SwTableCalcPara::CalcWithStackOverflow()
{
    // If a stack overflow was detected, redo with last box.
    sal_uInt16 nSaveMaxSize = m_nMaxSize;

    m_nMaxSize = cMAXSTACKSIZE - 5;
    int nCnt = 0;
    SwTableBoxes aStackOverflows;
    do {
        SwTableBox* pBox = const_cast<SwTableBox*>(m_pLastTableBox);
        m_nStackCount = 0;
        m_rCalc.SetCalcError( SwCalcError::NONE );
        aStackOverflows.insert( aStackOverflows.begin() + nCnt++, pBox );

        m_pBoxStack->erase( pBox );
        pBox->GetValue( *this );
    } while( IsStackOverflow() );

    m_nMaxSize = cMAXSTACKSIZE - 3; // decrease at least one level

    // if recursion was detected
    m_nStackCount = 0;
    m_rCalc.SetCalcError( SwCalcError::NONE );
    m_pBoxStack->clear();

    while (!m_rCalc.IsCalcError() && nCnt > 0)
    {
        aStackOverflows[ --nCnt ]->GetValue( *this );
        if( IsStackOverflow() && !CalcWithStackOverflow() )
            break;
    }

    m_nMaxSize = nSaveMaxSize;
    aStackOverflows.clear();
    return !m_rCalc.IsCalcError();
}

SwTableFormula::SwTableFormula( OUString aFormula )
: m_sFormula( std::move(aFormula) )
, m_eNmType( EXTRNL_NAME )
, m_bValidValue( false )
{
}

SwTableFormula::~SwTableFormula()
{
}

void SwTableFormula::MakeFormula_( const SwTable& rTable, OUStringBuffer& rNewStr,
                    OUString& rFirstBox, OUString* pLastBox, void* pPara ) const
{
    SwTableCalcPara* pCalcPara = static_cast<SwTableCalcPara*>(pPara);
    if( pCalcPara->m_rCalc.IsCalcError() )        // stop if there is already an error set
        return;

    SwTableBox *pEndBox = nullptr;

    rFirstBox = rFirstBox.copy(1); // erase label of this box
    // a region in this area?
    if( pLastBox )
    {
        pEndBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(pLastBox->toInt64()));

        // Is it actually a valid pointer?
        if( rTable.GetTabSortBoxes().find( pEndBox ) == rTable.GetTabSortBoxes().end() )
            pEndBox = nullptr;
        rFirstBox = rFirstBox.copy( pLastBox->getLength()+1 );
    }
    SwTableBox* pSttBox = reinterpret_cast<SwTableBox*>(
                            sal::static_int_cast<sal_IntPtr>(rFirstBox.toInt64()));
    // Is it actually a valid pointer?
    if( rTable.GetTabSortBoxes().find( pSttBox ) == rTable.GetTabSortBoxes().end() )
        pSttBox = nullptr;

    rNewStr.append(" ");
    if( pEndBox && pSttBox )    // area?
    {
        // get all selected boxes via layout and calculate their values
        SwSelBoxes aBoxes;
        GetBoxes( *pSttBox, *pEndBox, aBoxes );

        // don't use empty cells or cells with text content as zeroes in interoperability functions
        sal_Int16 nUseOnlyNumber = -1;

        rNewStr.append("(");
        bool bDelim = false;
        for (size_t n = 0; n < aBoxes.size() &&
                           !pCalcPara->m_rCalc.IsCalcError(); ++n)
        {
            const SwTableBox* pTableBox = aBoxes[n];
            if ( pTableBox->getRowSpan() >= 1 )
            {
                double fVal = pTableBox->GetValue( *pCalcPara );

                if ( pCalcPara->m_rCalc.IsCalcNotANumber() )
                {
                    if ( nUseOnlyNumber == -1 )
                    {
                        OUString sFormula = rNewStr.toString().toAsciiUpperCase();
                        nUseOnlyNumber = sal_Int16(
                                sFormula.lastIndexOf("AVERAGE") > -1 ||
                                sFormula.lastIndexOf("COUNT") > -1 ||
                                sFormula.lastIndexOf("PRODUCT") > -1 );
                    }
                    if ( nUseOnlyNumber > 0 )
                        continue;
                }

                if( bDelim )
                    rNewStr.append(cListDelim);
                bDelim = true;
                rNewStr.append(pCalcPara->m_rCalc.GetStrResult( fVal ));
            }
        }
        rNewStr.append(")");
    }
    else if( pSttBox && !pLastBox ) // only the StartBox?
    {
        // JP 12.01.99: and no EndBox in the formula!
        // calculate the value of the box
        if ( pSttBox->getRowSpan() >= 1 )
        {
            rNewStr.append("(");
            double fVal = pSttBox->GetValue( *pCalcPara );
            // don't use empty cell or a cell with text content as zero in interoperability functions
            // (except PRODUCT, where the result is correct anyway)
            if ( !pCalcPara->m_rCalc.IsCalcNotANumber() ||
                 ( rNewStr.toString().toAsciiUpperCase().lastIndexOf("AVERAGE") == -1 &&
                   rNewStr.toString().toAsciiUpperCase().lastIndexOf("COUNT") == -1 ) )
            {
                rNewStr.append(pCalcPara->m_rCalc.GetStrResult( fVal ));
            }
            rNewStr.append(")");
        }
    }
    else
        pCalcPara->m_rCalc.SetCalcError( SwCalcError::Syntax );   // set error
    rNewStr.append(" ");
}

void SwTableFormula::RelNmsToBoxNms( const SwTable& rTable, OUStringBuffer& rNewStr,
            OUString& rFirstBox, OUString* pLastBox, void* pPara ) const
{
    // relative name w.r.t. box name (external presentation)
    SwNode* pNd = static_cast<SwNode*>(pPara);
    assert(pNd && "Field isn't in any TextNode");
    const SwTableBox *pBox = rTable.GetTableBox(
                    pNd->FindTableBoxStartNode()->GetIndex() );

    rNewStr.append(rFirstBox[0]); // get label for the box
    rFirstBox = rFirstBox.copy(1);
    if( pLastBox )
    {
        const SwTableBox *pRelLastBox = lcl_RelToBox( rTable, pBox, *pLastBox );
        if ( pRelLastBox )
            rNewStr.append(pRelLastBox->GetName());
        else
            rNewStr.append("A1");
        rNewStr.append(":");
        rFirstBox = rFirstBox.copy( pLastBox->getLength()+1 );
    }

    const SwTableBox *pRelFirstBox = lcl_RelToBox( rTable, pBox, rFirstBox );

    if (pRelFirstBox)
        rNewStr.append(pRelFirstBox->GetName());
    else
        rNewStr.append("A1");

    // get label for the box
    rNewStr.append(rFirstBox[ rFirstBox.getLength()-1 ]);
}

void SwTableFormula::RelBoxNmsToPtr( const SwTable& rTable, OUStringBuffer& rNewStr,
            OUString& rFirstBox, OUString* pLastBox, void* pPara ) const
{
    // relative name w.r.t. box name (internal presentation)
    SwNode* pNd = static_cast<SwNode*>(pPara);
    assert(pNd && "Field not placed in any Node");
    const SwTableBox *pBox = rTable.GetTableBox(
                    pNd->FindTableBoxStartNode()->GetIndex() );

    rNewStr.append(rFirstBox[0]); // get label for the box
    rFirstBox = rFirstBox.copy(1);
    if( pLastBox )
    {
        const SwTableBox *pRelLastBox = lcl_RelToBox( rTable, pBox, *pLastBox );
        if ( pRelLastBox )
            rNewStr.append(reinterpret_cast<sal_IntPtr>(pRelLastBox));
        else
            rNewStr.append("0");
        rNewStr.append(":");
        rFirstBox = rFirstBox.copy( pLastBox->getLength()+1 );
    }

    const SwTableBox *pRelFirstBox = lcl_RelToBox( rTable, pBox, rFirstBox );
    if ( pRelFirstBox )
        rNewStr.append(reinterpret_cast<sal_IntPtr>(pRelFirstBox));
    else
        rNewStr.append("0");

    // get label for the box
    rNewStr.append(rFirstBox[ rFirstBox.getLength()-1 ]);
}

void SwTableFormula::BoxNmsToRelNm( const SwTable& rTable, OUStringBuffer& rNewStr,
                    OUString& rFirstBox, OUString* pLastBox, void* pPara ) const
{
    // box name (external presentation) w.r.t. relative name
    SwNode* pNd = static_cast<SwNode*>(pPara);
    assert(pNd && "Field not placed in any Node");
    const SwTableNode* pTableNd = pNd->FindTableNode();

    OUString sRefBoxNm;
    if( &pTableNd->GetTable() == &rTable )
    {
        const SwTableBox *pBox = rTable.GetTableBox(
                pNd->FindTableBoxStartNode()->GetIndex() );
        assert(pBox && "Field not placed in any Table");
        sRefBoxNm = pBox->GetName();
    }

    rNewStr.append(rFirstBox[0]); // get label for the box
    rFirstBox = rFirstBox.copy(1);
    if( pLastBox )
    {
        rNewStr.append(lcl_BoxNmToRel( rTable, *pTableNd, sRefBoxNm, *pLastBox,
                                m_eNmType == EXTRNL_NAME ));
        rNewStr.append(":");
        rFirstBox = rFirstBox.copy( pLastBox->getLength()+1 );
    }

    rNewStr.append(lcl_BoxNmToRel( rTable, *pTableNd, sRefBoxNm, rFirstBox,
                            m_eNmType == EXTRNL_NAME ));

    // get label for the box
    rNewStr.append(rFirstBox[ rFirstBox.getLength()-1 ]);
}

void SwTableFormula::PtrToBoxNms( const SwTable& rTable, OUStringBuffer& rNewStr,
                        OUString& rFirstBox, OUString* pLastBox, void* ) const
{
    // area in these parentheses?
    SwTableBox* pBox;

    rNewStr.append(rFirstBox[0]); // get label for the box
    rFirstBox = rFirstBox.copy(1);
    if( pLastBox )
    {
        pBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(pLastBox->toInt64()));

        // Is it actually a valid pointer?
        if( rTable.GetTabSortBoxes().find( pBox ) != rTable.GetTabSortBoxes().end() )
            rNewStr.append(pBox->GetName());
        else
            rNewStr.append("?");
        rNewStr.append(":");
        rFirstBox = rFirstBox.copy( pLastBox->getLength()+1 );
    }

    pBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(rFirstBox.toInt64()));
    // Is it actually a valid pointer?
    if( rTable.GetTabSortBoxes().find( pBox ) != rTable.GetTabSortBoxes().end() )
        rNewStr.append(pBox->GetName());
    else
        rNewStr.append("?");

    // get label for the box
    rNewStr.append(rFirstBox[ rFirstBox.getLength()-1 ]);
}

void SwTableFormula::BoxNmsToPtr( const SwTable& rTable, OUStringBuffer& rNewStr,
                        OUString& rFirstBox, OUString* pLastBox, void* ) const
{
    // area in these parentheses?
    const SwTableBox* pBox;

    rNewStr.append(rFirstBox[0]); // get label for the box
    rFirstBox = rFirstBox.copy(1);
    if( pLastBox )
    {
        pBox = rTable.GetTableBox( *pLastBox );
        rNewStr.append(OUString::number(reinterpret_cast<sal_IntPtr>(pBox)) +
                    ":");
        rFirstBox = rFirstBox.copy( pLastBox->getLength()+1 );
    }

    pBox = rTable.GetTableBox( rFirstBox );
    rNewStr.append(OUString::number(reinterpret_cast<sal_IntPtr>(pBox))
        + OUStringChar(rFirstBox[ rFirstBox.getLength()-1 ])); // get label for the box
}

/// create external formula (for UI)
void SwTableFormula::PtrToBoxNm( const SwTable* pTable )
{
    const SwNode* pNd = nullptr;
    FnScanFormula fnFormula = nullptr;
    switch (m_eNmType)
    {
    case INTRNL_NAME:
        if( pTable )
            fnFormula = &SwTableFormula::PtrToBoxNms;
        break;
    case REL_NAME:
        if( pTable )
        {
            fnFormula = &SwTableFormula::RelNmsToBoxNms;
            pNd = GetNodeOfFormula();
        }
        break;
    case EXTRNL_NAME:
        return;
    }
    assert(pTable);
    m_sFormula = ScanString( fnFormula, *pTable, const_cast<void*>(static_cast<void const *>(pNd)) );
    m_eNmType = EXTRNL_NAME;
}

/// create internal formula (in CORE)
void SwTableFormula::BoxNmToPtr( const SwTable* pTable )
{
    const SwNode* pNd = nullptr;
    FnScanFormula fnFormula = nullptr;
    switch (m_eNmType)
    {
    case EXTRNL_NAME:
        if( pTable )
            fnFormula = &SwTableFormula::BoxNmsToPtr;
        break;
    case REL_NAME:
        if( pTable )
        {
            fnFormula = &SwTableFormula::RelBoxNmsToPtr;
            pNd = GetNodeOfFormula();
        }
        break;
    case INTRNL_NAME:
        return;
    }
    assert(pTable);
    m_sFormula = ScanString( fnFormula, *pTable, const_cast<void*>(static_cast<void const *>(pNd)) );
    m_eNmType = INTRNL_NAME;
}

/// create relative formula (for copy)
void SwTableFormula::ToRelBoxNm( const SwTable* pTable )
{
    const SwNode* pNd = nullptr;
    FnScanFormula fnFormula = nullptr;
    switch (m_eNmType)
    {
    case INTRNL_NAME:
    case EXTRNL_NAME:
        if( pTable )
        {
            fnFormula = &SwTableFormula::BoxNmsToRelNm;
            pNd = GetNodeOfFormula();
        }
        break;
    case REL_NAME:
        return;
    }
    assert(pTable);
    m_sFormula = ScanString( fnFormula, *pTable, const_cast<void*>(static_cast<void const *>(pNd)) );
    m_eNmType = REL_NAME;
}

OUString SwTableFormula::ScanString( FnScanFormula fnFormula, const SwTable& rTable,
                                     void* pPara ) const
{
    OUStringBuffer aStr;
    sal_Int32 nFormula = 0;
    sal_Int32 nEnd = 0;

    do {
        // If the formula is preceded by a name, use this table!
        const SwTable* pTable = &rTable;

        sal_Int32 nStt = m_sFormula.indexOf( '<', nFormula );
        if ( nStt>=0 )
        {
            while ( nStt>=0 )
            {
                const sal_Int32 nNxt = nStt+1;
                if (nNxt>=m_sFormula.getLength())
                {
                    nStt = -1;
                    break;
                }
                if ( m_sFormula[nNxt]!=' ' && m_sFormula[nNxt]!='=' )
                    break;
                nStt = m_sFormula.indexOf( '<', nNxt );
            }

            if ( nStt>=0 )
                // Start searching from current position, which is valid for sure
                nEnd = m_sFormula.indexOf( '>', nStt );
        }
        if (nStt<0 || nEnd<0 )
        {
            // set the rest and finish
            aStr.append(m_sFormula.subView(nFormula));
            break;
        }

        // write beginning
        aStr.append(m_sFormula.subView(nFormula, nStt - nFormula));

        if (fnFormula)
        {
            sal_Int32 nSeparator = 0;
            // Is a table name preceded?
            // JP 16.02.99: SplitMergeBoxNm take care of the name themself
            // JP 22.02.99: Linux compiler needs cast
            // JP 28.06.99: rel. BoxName has no preceding tablename!
            if( fnFormula != &SwTableFormula::SplitMergeBoxNm_ &&
                m_sFormula.getLength()>(nStt+1) && cRelIdentifier != m_sFormula[nStt+1] &&
                (nSeparator = m_sFormula.indexOf( '.', nStt ))>=0
                && nSeparator < nEnd )
            {
                OUString sTableNm( m_sFormula.copy( nStt, nEnd - nStt ));

                // If there are dots in the name, then they appear in pairs (e.g. A1.1.1)!
                if( (comphelper::string::getTokenCount(sTableNm, '.') - 1) & 1 )
                {
                    sTableNm = sTableNm.copy( 0, nSeparator - nStt );

                    // when creating a formula the table name is unwanted
                    if( fnFormula != &SwTableFormula::MakeFormula_ )
                        aStr.append(sTableNm);
                    nStt = nSeparator;

                    sTableNm = sTableNm.copy( 1 );   // delete separator
                    if( sTableNm != rTable.GetFrameFormat()->GetName() )
                    {
                        // then search for table
                        const SwTable* pFnd = FindTable(
                                                rTable.GetFrameFormat()->GetDoc(),
                                                sTableNm );
                        if( pFnd )
                            pTable = pFnd;
                        // ??
                        OSL_ENSURE( pFnd, "No table found. What now?" );
                    }
                }
            }

            OUString sBox( m_sFormula.copy( nStt, nEnd - nStt + 1 ));
            // area in these parentheses?
            nSeparator = m_sFormula.indexOf( ':', nStt );
            if ( nSeparator>=0 && nSeparator<nEnd )
            {
                // without opening parenthesis
                OUString aFirstBox( m_sFormula.copy( nStt+1, nSeparator - nStt - 1 ));
                (this->*fnFormula)( *pTable, aStr, sBox, &aFirstBox, pPara );
            }
            else
                (this->*fnFormula)( *pTable, aStr, sBox, nullptr, pPara );
        }

        nFormula = nEnd+1;
    } while( true );
    return aStr.makeStringAndClear();
}

const SwTable* SwTableFormula::FindTable( SwDoc& rDoc, std::u16string_view rNm )
{
    const sw::TableFrameFormats& rTableFormats = *rDoc.GetTableFrameFormats();
    const SwTable* pTmpTable = nullptr, *pRet = nullptr;
    for( auto nFormatCnt = rTableFormats.size(); nFormatCnt; )
    {
        SwTableFormat* pFormat = rTableFormats[ --nFormatCnt ];
        // if we are called from Sw3Writer, a number is dependent on the format name
        SwTableBox* pFBox;
        if ( rNm == o3tl::getToken(pFormat->GetName().toString(), 0, 0x0a) &&
            nullptr != ( pTmpTable = SwTable::FindTable( pFormat ) ) &&
            nullptr != (pFBox = pTmpTable->GetTabSortBoxes()[0] ) &&
            pFBox->GetSttNd() &&
            pFBox->GetSttNd()->GetNodes().IsDocNodes() )
        {
            // a table in the normal NodesArr
            pRet = pTmpTable;
            break;
        }
    }
    return pRet;
}

static const SwFrame* lcl_GetBoxFrame( const SwTableBox& rBox )
{
    SwNodeIndex aIdx( *rBox.GetSttNd() );
    SwContentNode* pCNd = SwNodes::GoNext(&aIdx);
    assert(pCNd && "Box has no TextNode");
    Point aPt;      // get the first frame of the layout - table headline
    std::pair<Point, bool> const tmp(aPt, false);
    return pCNd->getLayoutFrame(pCNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp);
}

static sal_Int32 lcl_GetLongBoxNum( OUString& rStr )
{
    sal_Int32 nRet;
    const sal_Int32 nPos = rStr.indexOf( cRelSeparator );
    if ( nPos<0 )
    {
        nRet = rStr.toInt32();
        rStr.clear();
    }
    else
    {
        nRet = o3tl::toInt32(rStr.subView( 0, nPos ));
        rStr = rStr.copy( nPos+1 );
    }
    return nRet;
}

static const SwTableBox* lcl_RelToBox( const SwTable& rTable,
                                    const SwTableBox* pRefBox,
                                    const OUString& _sGetName )
{
    // get line
    const SwTableBox* pBox = nullptr;
    OUString sGetName = _sGetName;

    // Is it really a relative value?
    if ( cRelIdentifier == sGetName[0] ) // yes
    {
        if( !pRefBox )
            return nullptr;

        sGetName = sGetName.copy( 1 );

        const SwTableLines* pLines = &rTable.GetTabLines();
        const SwTableBoxes* pBoxes;
        const SwTableLine* pLine;

        // determine starting values of the box,...
        pBox = pRefBox;
        pLine = pBox->GetUpper();
        while( pLine->GetUpper() )
        {
            pBox = pLine->GetUpper();
            pLine = pBox->GetUpper();
        }
        sal_uInt16 nSttBox = pLine->GetBoxPos( pBox );
        sal_uInt16 nSttLine = rTable.GetTabLines().GetPos( pLine );

        const sal_Int32 nBoxOffset = lcl_GetLongBoxNum( sGetName ) + nSttBox;
        const sal_Int32 nLineOffset = lcl_GetLongBoxNum( sGetName ) + nSttLine;

        if( nBoxOffset < 0 ||
            nLineOffset < 0 )
            return nullptr;

        if( o3tl::make_unsigned(nLineOffset) >= pLines->size() )
            return nullptr;

        pLine = (*pLines)[ nLineOffset ];

        // ... then search the box
        pBoxes = &pLine->GetTabBoxes();
        if( o3tl::make_unsigned(nBoxOffset) >= pBoxes->size() )
            return nullptr;
        pBox = (*pBoxes)[ nBoxOffset ];

        while (!sGetName.isEmpty())
        {
            nSttBox = SwTable::GetBoxNum( sGetName );
            pLines = &pBox->GetTabLines();
            if (nSttBox > 0)
                --nSttBox;

            nSttLine = SwTable::GetBoxNum( sGetName );

            // determine line
            if( !nSttLine || nSttLine > pLines->size() )
                break;
            pLine = (*pLines)[ nSttLine-1 ];

            // determine box
            pBoxes = &pLine->GetTabBoxes();
            if( nSttBox >= pBoxes->size() )
                break;
            pBox = (*pBoxes)[ nSttBox ];
        }

        if( pBox )
        {
            if( !pBox->GetSttNd() )
                // "bubble up" to first box
                while( !pBox->GetTabLines().empty() )
                    pBox = pBox->GetTabLines().front()->GetTabBoxes().front();
        }
    }
    else
    {
        // otherwise it is an absolute external presentation
        pBox = rTable.GetTableBox( sGetName );
    }
    return pBox;
}

static OUString lcl_BoxNmToRel( const SwTable& rTable, const SwTableNode& rTableNd,
                                const OUString& _sRefBoxNm, const OUString& _sTmp, bool bExtrnlNm )
{
    OUString sTmp = _sTmp;
    OUString sRefBoxNm = _sRefBoxNm;
    if( !bExtrnlNm )
    {
        // convert into external presentation
        SwTableBox* pBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(sTmp.toInt64()));
        if( rTable.GetTabSortBoxes().find( pBox ) == rTable.GetTabSortBoxes().end() )
            return OUString('?');
        sTmp = pBox->GetName();
    }

    // If the formula is spanning over a table then keep external presentation
    if( &rTable == &rTableNd.GetTable() )
    {
        tools::Long nBox = SwTable::GetBoxNum( sTmp, true );
        nBox -= SwTable::GetBoxNum( sRefBoxNm, true );
        tools::Long nLine = SwTable::GetBoxNum( sTmp );
        nLine -= SwTable::GetBoxNum( sRefBoxNm );

        const OUString sCpy = sTmp;        //JP 01.11.95: add rest from box name

        sTmp = OUStringChar(cRelIdentifier) + OUString::number( nBox )
             + OUStringChar(cRelSeparator) + OUString::number( nLine );

        if (!sCpy.isEmpty())
        {
            sTmp += OUStringChar(cRelSeparator) + sCpy;
        }
    }

    if (sTmp.endsWith(">"))
        return sTmp.copy(0, sTmp.getLength()-1 );

    return sTmp;
}

void SwTableFormula::GetBoxesOfFormula( const SwTable& rTable,
                                        SwSelBoxes& rBoxes )
{
    rBoxes.clear();

    BoxNmToPtr( &rTable );
    ScanString( &SwTableFormula::GetFormulaBoxes, rTable, &rBoxes );
}

void SwTableFormula::GetFormulaBoxes( const SwTable& rTable, OUStringBuffer& ,
                    OUString& rFirstBox, OUString* pLastBox, void* pPara ) const
{
    SwSelBoxes* pBoxes = static_cast<SwSelBoxes*>(pPara);
    SwTableBox* pEndBox = nullptr;

    rFirstBox = rFirstBox.copy(1); // delete box label
    // area in these parentheses?
    if( pLastBox )
    {
        pEndBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(pLastBox->toInt64()));

        // Is it actually a valid pointer?
        if( rTable.GetTabSortBoxes().find( pEndBox ) == rTable.GetTabSortBoxes().end() )
            pEndBox = nullptr;
        rFirstBox = rFirstBox.copy( pLastBox->getLength()+1 );
    }

    SwTableBox *pSttBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(rFirstBox.toInt64()));
    // Is it actually a valid pointer?
    if( !pSttBox || rTable.GetTabSortBoxes().find( pSttBox ) == rTable.GetTabSortBoxes().end() )
        return;

    if ( pEndBox ) // area?
    {
        // get all selected boxes via layout and calculate their values
        SwSelBoxes aBoxes;
        GetBoxes( *pSttBox, *pEndBox, aBoxes );
        pBoxes->insert( aBoxes );
    }
    else          // only the StartBox?
        pBoxes->insert( pSttBox );
}

void SwTableFormula::GetBoxes( const SwTableBox& rSttBox,
                                const SwTableBox& rEndBox,
                                SwSelBoxes& rBoxes )
{
    // get all selected boxes via layout
    const SwLayoutFrame *pStart, *pEnd;
    const SwFrame* pFrame = lcl_GetBoxFrame( rSttBox );
    pStart = pFrame ? pFrame->GetUpper() : nullptr;
    pFrame = lcl_GetBoxFrame( rEndBox );
    pEnd = pFrame ? pFrame->GetUpper() : nullptr;
    if( !pStart || !pEnd )
        return ;                        // no valid selection

    GetTableSel( pStart, pEnd, rBoxes, nullptr );

    const SwTable* pTable = pStart->FindTabFrame()->GetTable();

    // filter headline boxes
    if( pTable->GetRowsToRepeat() <= 0 )
        return;

    do {    // middle-check loop
        const SwTableLine* pLine = rSttBox.GetUpper();
        while( pLine->GetUpper() )
            pLine = pLine->GetUpper()->GetUpper();

        if( pTable->IsHeadline( *pLine ) )
            break;      // headline in this area!

        // maybe start and end are swapped
        pLine = rEndBox.GetUpper();
        while ( pLine->GetUpper() )
            pLine = pLine->GetUpper()->GetUpper();

        if( pTable->IsHeadline( *pLine ) )
            break;      // headline in this area!

        const SwTabFrame *pStartTable = pStart->FindTabFrame();
        const SwTabFrame *pEndTable = pEnd->FindTabFrame();

        if (pStartTable == pEndTable) // no split table
            break;

        // then remove table headers
        for (size_t n = 0; n < rBoxes.size(); ++n)
        {
            pLine = rBoxes[n]->GetUpper();
            while( pLine->GetUpper() )
                pLine = pLine->GetUpper()->GetUpper();

            if( pTable->IsHeadline( *pLine ) )
                rBoxes.erase( rBoxes.begin() + n-- );
        }
    } while( false );
}

/// Are all boxes valid that are referenced by the formula?
void SwTableFormula::HasValidBoxes_( const SwTable& rTable, OUStringBuffer& ,
                    OUString& rFirstBox, OUString* pLastBox, void* pPara ) const
{
    bool* pBValid = static_cast<bool*>(pPara);
    if( !(*pBValid) )      // wrong is wrong
        return;

    SwTableBox* pSttBox = nullptr, *pEndBox = nullptr;
    rFirstBox = rFirstBox.copy(1);       // delete identifier of box

    // area in this parenthesis?
    if( pLastBox )
        rFirstBox = rFirstBox.copy( pLastBox->getLength()+1 );

    switch (m_eNmType)
    {
    case INTRNL_NAME:
        if( pLastBox )
            pEndBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(pLastBox->toInt64()));
        pSttBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(rFirstBox.toInt64()));
        break;

    case REL_NAME:
        {
            const SwNode* pNd = GetNodeOfFormula();
            const SwTableBox* pBox = !pNd ? nullptr
                                           : const_cast<SwTableBox *>(rTable.GetTableBox(
                                pNd->FindTableBoxStartNode()->GetIndex() ));
            if( pLastBox )
                pEndBox = const_cast<SwTableBox*>(lcl_RelToBox( rTable, pBox, *pLastBox ));
            pSttBox = const_cast<SwTableBox*>(lcl_RelToBox( rTable, pBox, rFirstBox ));
        }
        break;

    case EXTRNL_NAME:
        if( pLastBox )
            pEndBox = const_cast<SwTableBox*>(rTable.GetTableBox( *pLastBox ));
        pSttBox = const_cast<SwTableBox*>(rTable.GetTableBox( rFirstBox ));
        break;
    }

    // Are these valid pointers?
    if( ( pLastBox &&
          ( !pEndBox || rTable.GetTabSortBoxes().find( pEndBox ) == rTable.GetTabSortBoxes().end() ) ) ||
        ( !pSttBox || rTable.GetTabSortBoxes().find( pSttBox ) == rTable.GetTabSortBoxes().end() ) )
            *pBValid = false;
}

bool SwTableFormula::HasValidBoxes() const
{
    bool bRet = true;
    const SwNode* pNd = GetNodeOfFormula();
    if( pNd && nullptr != ( pNd = pNd->FindTableNode() ) )
        ScanString( &SwTableFormula::HasValidBoxes_,
                        static_cast<const SwTableNode*>(pNd)->GetTable(), &bRet );
    return bRet;
}

sal_uInt16 SwTableFormula::GetLnPosInTable( const SwTable& rTable, const SwTableBox* pBox )
{
    sal_uInt16 nRet = USHRT_MAX;
    if( pBox )
    {
        const SwTableLine* pLn = pBox->GetUpper();
        while( pLn->GetUpper() )
            pLn = pLn->GetUpper()->GetUpper();
        nRet = rTable.GetTabLines().GetPos( pLn );
    }
    return nRet;
}

void SwTableFormula::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTableFormula"));
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("formula"), BAD_CAST(m_sFormula.toUtf8().getStr()));
    (void)xmlTextWriterEndElement(pWriter);
}

void SwTableFormula::SplitMergeBoxNm_( const SwTable& rTable, OUStringBuffer& rNewStr,
                    OUString& rFirstBox, OUString* pLastBox, void* pPara ) const
{
    SwTableFormulaUpdate& rTableUpd = *static_cast<SwTableFormulaUpdate*>(pPara);

    rNewStr.append(rFirstBox[0]);     // get label for the box
    rFirstBox = rFirstBox.copy(1);

    OUString sTableNm;
    const SwTable* pTable = &rTable;

    OUString* pTableNmBox = pLastBox ? pLastBox : &rFirstBox;

    const sal_Int32 nLastBoxLen = pTableNmBox->getLength();
    const sal_Int32 nSeparator = pTableNmBox->indexOf('.');
    if ( nSeparator>=0 &&
        // If there are dots in the name, then these appear in pairs (e.g. A1.1.1)!
        (comphelper::string::getTokenCount(*pTableNmBox, '.') - 1) & 1 )
    {
        sTableNm = pTableNmBox->copy( 0, nSeparator );
        *pTableNmBox = pTableNmBox->copy( nSeparator + 1); // remove dot
        const SwTable* pFnd = FindTable( rTable.GetFrameFormat()->GetDoc(), sTableNm );
        if( pFnd )
            pTable = pFnd;

        if( TBL_MERGETBL == rTableUpd.m_eFlags )
        {
            if( pFnd )
            {
                if( pFnd == rTableUpd.m_aData.pDelTable )
                {
                    if( rTableUpd.m_pTable != &rTable ) // not the current one
                        rNewStr.append(rTableUpd.m_pTable->GetFrameFormat()->GetName().toString() + "."); // set new table name
                    rTableUpd.m_bModified = true;
                }
                else if( pFnd != rTableUpd.m_pTable ||
                    ( rTableUpd.m_pTable != &rTable && &rTable != rTableUpd.m_aData.pDelTable))
                    rNewStr.append(sTableNm + "."); // keep table name
                else
                    rTableUpd.m_bModified = true;
            }
            else
                rNewStr.append(sTableNm + ".");     // keep table name
        }
    }
    if( pTableNmBox == pLastBox )
        rFirstBox = rFirstBox.copy( nLastBoxLen + 1 );

    SwTableBox* pSttBox = nullptr, *pEndBox = nullptr;
    switch (m_eNmType)
    {
    case INTRNL_NAME:
        if( pLastBox )
            pEndBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(pLastBox->toInt64()));
        pSttBox = reinterpret_cast<SwTableBox*>(sal::static_int_cast<sal_IntPtr>(rFirstBox.toInt64()));
        break;

    case REL_NAME:
        {
            const SwNode* pNd = GetNodeOfFormula();
            const SwTableBox* pBox = pNd ? pTable->GetTableBox(
                            pNd->FindTableBoxStartNode()->GetIndex() ) : nullptr;
            if( pLastBox )
                pEndBox = const_cast<SwTableBox*>(lcl_RelToBox( *pTable, pBox, *pLastBox ));
            pSttBox = const_cast<SwTableBox*>(lcl_RelToBox( *pTable, pBox, rFirstBox ));
        }
        break;

    case EXTRNL_NAME:
        if( pLastBox )
            pEndBox = const_cast<SwTableBox*>(pTable->GetTableBox( *pLastBox ));
        pSttBox = const_cast<SwTableBox*>(pTable->GetTableBox( rFirstBox ));
        break;
    }

    if( pLastBox && pTable->GetTabSortBoxes().find( pEndBox ) == pTable->GetTabSortBoxes().end() )
        pEndBox = nullptr;
    if( pTable->GetTabSortBoxes().find( pSttBox ) == pTable->GetTabSortBoxes().end() )
        pSttBox = nullptr;

    if( TBL_SPLITTBL == rTableUpd.m_eFlags )
    {
        // Where are the boxes - in the old or in the new table?
        bool bInNewTable = false;
        if( pLastBox )
        {
            // It is the "first" box in this selection. It determines if the formula is placed in
            // the new or the old table.
            sal_uInt16 nEndLnPos = SwTableFormula::GetLnPosInTable( *pTable, pEndBox ),
                    nSttLnPos = SwTableFormula::GetLnPosInTable( *pTable, pSttBox );

            if( USHRT_MAX != nSttLnPos && USHRT_MAX != nEndLnPos &&
                ((rTableUpd.m_nSplitLine <= nSttLnPos) ==
                (rTableUpd.m_nSplitLine <= nEndLnPos)) )
            {
                // stay in same table
                bInNewTable = rTableUpd.m_nSplitLine <= nEndLnPos &&
                                    pTable == rTableUpd.m_pTable;
            }
            else
            {
                // this is definitely an invalid formula, also mark as modified for Undo
                rTableUpd.m_bModified = true;
                if( pEndBox )
                    bInNewTable = USHRT_MAX != nEndLnPos &&
                                    rTableUpd.m_nSplitLine <= nEndLnPos &&
                                    pTable == rTableUpd.m_pTable;
            }
        }
        else
        {
            sal_uInt16 nSttLnPos = SwTableFormula::GetLnPosInTable( *pTable, pSttBox );
            // Put it in the new table?
            bInNewTable = USHRT_MAX != nSttLnPos &&
                            rTableUpd.m_nSplitLine <= nSttLnPos &&
                            pTable == rTableUpd.m_pTable;
        }

        // formula goes into new table
        if( rTableUpd.m_bBehindSplitLine )
        {
            if( !bInNewTable )
            {
                rTableUpd.m_bModified = true;
                rNewStr.append(rTableUpd.m_pTable->GetFrameFormat()->GetName().toString() + ".");
            }
            else if( !sTableNm.isEmpty() )
                rNewStr.append(sTableNm + ".");
        }
        else if( bInNewTable )
        {
            rTableUpd.m_bModified = true;
            rNewStr.append(rTableUpd.m_aData.pNewTableNm->toString() + ".");
        }
        else if( !sTableNm.isEmpty() )
            rNewStr.append(sTableNm + ".");
    }

    if( pLastBox )
        rNewStr.append(OUString::number(reinterpret_cast<sal_IntPtr>(pEndBox)) + ":");

    rNewStr.append(OUString::number(reinterpret_cast<sal_IntPtr>(pSttBox))
        + OUStringChar(rFirstBox[ rFirstBox.getLength()-1] ));
}

/// Create external formula but remember that the formula is placed in a split/merged table
void SwTableFormula::ToSplitMergeBoxNm( SwTableFormulaUpdate& rTableUpd )
{
    const SwTable* pTable;
    const SwNode* pNd = GetNodeOfFormula();
    if( pNd && nullptr != ( pNd = pNd->FindTableNode() ))
        pTable = &static_cast<const SwTableNode*>(pNd)->GetTable();
    else
        pTable = rTableUpd.m_pTable;

    m_sFormula = ScanString( &SwTableFormula::SplitMergeBoxNm_, *pTable, static_cast<void*>(&rTableUpd) );
    m_eNmType = INTRNL_NAME;
}

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