/* -*- 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 <calbck.hxx>
#include <doc.hxx>
#include <docary.hxx>
#include <editeng/tstpitem.hxx>
#include <hintids.hxx>
#include <hints.hxx>
#include <ndtxt.hxx>
#include <paratr.hxx>
#include <rootfrm.hxx>
#include <scriptinfo.hxx>
#include <strings.hrc>
#include <swtypes.hxx>
#include <tox.hxx>
#include <txtfrm.hxx>
#include <txttxmrk.hxx>
#include <unoidx.hxx>

#include <optional>
#include <sal/log.hxx>
#include <o3tl/string_view.hxx>
#include <osl/diagnose.h>

#include <algorithm>
#include <string_view>
#include <utility>


const sal_Unicode C_NUM_REPL      = '@';
const sal_Unicode C_END_PAGE_NUM   = '~';

namespace
{
void lcl_FillAuthPattern(SwFormTokens &rAuthTokens, sal_uInt16 nTypeId)
{
    rAuthTokens.reserve(9); // Worst case: Start+Sep1+Auth+3*(Sep2+Auth)

    SwFormToken aStartToken( TOKEN_AUTHORITY );
    aStartToken.nAuthorityField = AUTH_FIELD_IDENTIFIER;
    rAuthTokens.push_back( aStartToken );
    SwFormToken aSeparatorToken( TOKEN_TEXT );
    aSeparatorToken.sText = ": ";
    rAuthTokens.push_back( aSeparatorToken );

    --nTypeId; // compensate +1 offset introduced by caller

    SwFormToken aTextToken( TOKEN_TEXT );
    aTextToken.sText = ", ";

    const ToxAuthorityField nVals[4] = {
        AUTH_FIELD_AUTHOR,
        AUTH_FIELD_TITLE,
        AUTH_FIELD_YEAR,
        nTypeId == AUTH_TYPE_WWW ? AUTH_FIELD_URL : AUTH_FIELD_END
    };

    for(size_t i = 0; i < SAL_N_ELEMENTS(nVals); ++i)
    {
        if(nVals[i] == AUTH_FIELD_END)
            break;
        if( i > 0 )
            rAuthTokens.push_back( aTextToken );

        // -> #i21237#
        SwFormToken aToken(TOKEN_AUTHORITY);

        aToken.nAuthorityField = nVals[i];
        rAuthTokens.push_back(aToken);
        // <- #i21237#
    }
}
}

/// pool default constructor
SwTOXMark::SwTOXMark()
    : SfxPoolItem(RES_TXTATR_TOXMARK)
    , m_pType(nullptr)
    , m_pTextAttr(nullptr)
    , m_nLevel(0)
    , m_bAutoGenerated(false)
    , m_bMainEntry(false)
{
    setNonShareable();
}

SwTOXMark::SwTOXMark(const SwTOXType* pType)
    : SfxPoolItem(RES_TXTATR_TOXMARK)
    , m_pType(pType)
    , m_pTextAttr(nullptr)
    , m_nLevel(0)
    , m_bAutoGenerated(false)
    , m_bMainEntry(false)
{
    setNonShareable();
    StartListening(const_cast<SwTOXType*>(m_pType)->GetNotifier());
}

SwTOXMark::SwTOXMark(const SwTOXMark& rCopy)
    : SfxPoolItem(RES_TXTATR_TOXMARK)
    , SvtListener()
    , m_pType(rCopy.m_pType)
    , m_aPrimaryKey(rCopy.m_aPrimaryKey)
    , m_aSecondaryKey(rCopy.m_aSecondaryKey)
    , m_aTextReading(rCopy.m_aTextReading)
    , m_aPrimaryKeyReading(rCopy.m_aPrimaryKeyReading)
    , m_aSecondaryKeyReading(rCopy.m_aSecondaryKeyReading)
    , m_pTextAttr(nullptr)
    , m_nLevel(rCopy.m_nLevel)
    , m_bAutoGenerated(rCopy.m_bAutoGenerated)
    , m_bMainEntry(rCopy.m_bMainEntry)
{
    setNonShareable();
    if(m_pType)
        StartListening(const_cast<SwTOXType*>(m_pType)->GetNotifier());
    // Copy AlternativString
    m_aAltText = rCopy.m_aAltText;
}

SwTOXMark::~SwTOXMark()
{
}

void SwTOXMark::SetXTOXMark(rtl::Reference<SwXDocumentIndexMark> const& xMark)
{ m_wXDocumentIndexMark = xMark.get(); }

void SwTOXMark::RegisterToTOXType(SwTOXType& rType)
{
    SvtListener::EndListeningAll();
    m_pType = &rType;
    StartListening(rType.GetNotifier());
}

bool SwTOXMark::operator==( const SfxPoolItem& rAttr ) const
{
    assert(SfxPoolItem::operator==(rAttr));
    // tdf#158783 this item was never 'pooled', so operator== was not really
    // ever used. We discussed to implement it (there is quite some
    // content), but we came to the point that it's only safe to say
    // instances are equal when same instance -> fallback to ptr compare.
    // NOTE: Do *not* use areSfxPoolItemPtrsEqual here, with DBG_UTIL
    //   active the control/test code there would again call operator==
    return this == &rAttr;
}

SwTOXMark* SwTOXMark::Clone( SfxItemPool* ) const
{
    return new SwTOXMark( *this );
}

void SwTOXMark::Notify(const SfxHint& rHint)
{
    if (rHint.GetId() == SfxHintId::SwCollectTextMarks)
    {
        auto pCollectHint = static_cast<const sw::CollectTextMarksHint*>(&rHint);
        if(GetTextTOXMark())
            pCollectHint->m_rMarks.push_back(this);
    }
    else if (rHint.GetId() == SfxHintId::SwCollectTextTOXMarksForLayout)
    {
        auto pCollectLayoutHint = static_cast<const sw::CollectTextTOXMarksForLayoutHint*>(&rHint);
        if(!GetTextTOXMark())
            return;
        auto& rTextMark = *GetTextTOXMark();
        auto& rNode = rTextMark.GetTextNode();
        auto pLayout = pCollectLayoutHint->m_pLayout;
        // Check basic sanity and that it is part of our layout and not in undo
        if(!rNode.GetNodes().IsDocNodes() || !rNode.GetText().getLength() || !rNode.HasWriterListeners() || !rNode.getLayoutFrame(pLayout))
            return;
        // Check for being hidden
        if(rNode.IsHiddenByParaField() || SwScriptInfo::IsInHiddenRange(rNode, rTextMark.GetStart()))
            return;
        // Check for being hidden by hidden redlines
        if (pLayout && pLayout->HasMergedParas() && sw::IsMarkHintHidden(*pLayout, rNode, rTextMark))
            return;
        // Check for being hidden by hidden sections
        if (auto pFrame(rNode.getLayoutFrame(pLayout)); !pFrame || pFrame->IsHiddenNow())
            return;
        pCollectLayoutHint->m_rMarks.push_back(rTextMark);
    }
}

void SwTOXMark::InvalidateTOXMark()
{
    if (auto xUnoIndexMark = m_wXDocumentIndexMark.get())
    {
        xUnoIndexMark->OnSwTOXMarkDeleted();
        m_wXDocumentIndexMark.clear();
    }
}

OUString SwTOXMark::GetText(SwRootFrame const*const pLayout) const
{
    if( !m_aAltText.isEmpty() )
        return m_aAltText;

    if( m_pTextAttr && m_pTextAttr->GetpTextNd() )
    {
        const sal_Int32* pEndIdx = m_pTextAttr->GetEnd();
        OSL_ENSURE( pEndIdx, "TOXMark without mark!");
        if( pEndIdx )
        {
            const sal_Int32 nStt = m_pTextAttr->GetStart();
            return m_pTextAttr->GetpTextNd()->GetExpandText(pLayout, nStt, *pEndIdx-nStt);
        }
    }

    return OUString();
}

// Manage types of TOX
SwTOXType::SwTOXType(SwDoc& rDoc, TOXTypes eTyp, OUString aName)
    : m_rDoc(rDoc)
    , m_aName(std::move(aName))
    , m_eType(eTyp)
{
}

SwTOXType::SwTOXType(const SwTOXType& rCopy)
    : m_rDoc(rCopy.m_rDoc)
    , m_aName(rCopy.m_aName)
    , m_eType(rCopy.m_eType)
{
    if (auto pRegisteredIn = const_cast<SwTOXType&>(rCopy).GetRegisteredIn())
        pRegisteredIn->Add(*this);
}

const TranslateId STR_POOLCOLL_TOX_ARY[] =
{
    // Subcategory Index-Directories
    STR_POOLCOLL_TOX_IDXH,
    STR_POOLCOLL_TOX_IDX1,
    STR_POOLCOLL_TOX_IDX2,
    STR_POOLCOLL_TOX_IDX3,
    STR_POOLCOLL_TOX_IDXBREAK
};

const TranslateId STR_POOLCOLL_TOX_CNTNT_ARY[] =
{
    // Subcategory Tables of Contents
    STR_POOLCOLL_TOX_CNTNTH,
    STR_POOLCOLL_TOX_CNTNT1,
    STR_POOLCOLL_TOX_CNTNT2,
    STR_POOLCOLL_TOX_CNTNT3,
    STR_POOLCOLL_TOX_CNTNT4,
    STR_POOLCOLL_TOX_CNTNT5
};

const TranslateId STR_POOLCOLL_TOX_CNTNT_EXTRA_ARY[] =
{
    // Subcategory Table of Contents more Levels 5 - 10
    STR_POOLCOLL_TOX_CNTNT6,
    STR_POOLCOLL_TOX_CNTNT7,
    STR_POOLCOLL_TOX_CNTNT8,
    STR_POOLCOLL_TOX_CNTNT9,
    STR_POOLCOLL_TOX_CNTNT10
};

const TranslateId STR_POOLCOLL_TOX_USER_ARY[] =
{
    // Subcategory User-Directories:
    STR_POOLCOLL_TOX_USERH,
    STR_POOLCOLL_TOX_USER1,
    STR_POOLCOLL_TOX_USER2,
    STR_POOLCOLL_TOX_USER3,
    STR_POOLCOLL_TOX_USER4,
    STR_POOLCOLL_TOX_USER5
};

const TranslateId STR_POOLCOLL_TOX_USER_EXTRA_ARY[] =
{
    // Subcategory User-Directories more Levels 5 - 10
    STR_POOLCOLL_TOX_USER6,
    STR_POOLCOLL_TOX_USER7,
    STR_POOLCOLL_TOX_USER8,
    STR_POOLCOLL_TOX_USER9,
    STR_POOLCOLL_TOX_USER10
};

const TranslateId STR_POOLCOLL_TOX_ILLUS_ARY[] =
{
    // Illustrations Index
    STR_POOLCOLL_TOX_ILLUSH,
    STR_POOLCOLL_TOX_ILLUS1
};

const TranslateId STR_POOLCOLL_TOX_OBJECT_ARY[] =
{
    //  Object Index
    STR_POOLCOLL_TOX_OBJECTH,
    STR_POOLCOLL_TOX_OBJECT1
};

const TranslateId STR_POOLCOLL_TOX_TABLES_ARY[] =
{
    //  Tables Index
    STR_POOLCOLL_TOX_TABLESH,
    STR_POOLCOLL_TOX_TABLES1
};

const TranslateId STR_POOLCOLL_TOX_AUTHORITIES_ARY[] =
{
    //  Index of Authorities
    STR_POOLCOLL_TOX_AUTHORITIESH,
    STR_POOLCOLL_TOX_AUTHORITIES1
};

const TranslateId STR_POOLCOLL_TOX_CITATION_ARY[] =
{
    STR_POOLCOLL_TOX_CITATION
};

// Edit forms
SwForm::SwForm( TOXTypes eTyp ) // #i21237#
    : m_eType( eTyp ), m_nFormMaxLevel( SwForm::GetFormMaxLevel( eTyp )),
//  nFirstTabPos( lNumberIndent ),
    m_bCommaSeparated(false)
{
    //bHasFirstTabPos =
    m_bIsRelTabPos = true;

    // The table of contents has a certain number of headlines + headings
    // The user has 10 levels + headings
    // Keyword has 3 levels + headings+ separator
    // Indexes of tables, object illustrations and authorities consist of a heading and one level

    const TranslateId* pPoolId;
    switch( m_eType )
    {
    case TOX_INDEX:         pPoolId = STR_POOLCOLL_TOX_ARY;    break;
    case TOX_USER:          pPoolId = STR_POOLCOLL_TOX_USER_ARY;   break;
    case TOX_CONTENT:       pPoolId = STR_POOLCOLL_TOX_CNTNT_ARY;  break;
    case TOX_ILLUSTRATIONS: pPoolId = STR_POOLCOLL_TOX_ILLUS_ARY;  break;
    case TOX_OBJECTS      : pPoolId = STR_POOLCOLL_TOX_OBJECT_ARY; break;
    case TOX_TABLES       : pPoolId = STR_POOLCOLL_TOX_TABLES_ARY; break;
    case TOX_AUTHORITIES  : pPoolId = STR_POOLCOLL_TOX_AUTHORITIES_ARY;    break;
    case TOX_CITATION  : pPoolId = STR_POOLCOLL_TOX_CITATION_ARY; break;
    default:
        OSL_ENSURE( false, "invalid TOXTyp");
        return ;
    }

    SwFormTokens aTokens;
    const bool bNeedsLink
        = TOX_CONTENT == m_eType || TOX_ILLUSTRATIONS == m_eType || TOX_TABLES == m_eType;
    if (bNeedsLink)
    {
        SwFormToken aLinkStt (TOKEN_LINK_START);
        aLinkStt.sCharStyleName = UIName(SwResId(STR_POOLCHR_TOXJUMP));
        aTokens.push_back(aLinkStt);
    }

    if (TOX_CONTENT == m_eType)
    {
        aTokens.emplace_back(TOKEN_ENTRY_NO);
        aTokens.emplace_back(TOKEN_ENTRY_TEXT);
    }
    else
        aTokens.emplace_back(TOKEN_ENTRY);

    if (TOX_AUTHORITIES != m_eType)
    {
        SwFormToken aToken(TOKEN_TAB_STOP);
        aToken.nTabStopPosition = 0;

        // #i36870# right aligned tab for all
        aToken.cTabFillChar = '.';
        aToken.eTabAlign = SvxTabAdjust::End;

        aTokens.push_back(aToken);
        aTokens.emplace_back(TOKEN_PAGE_NUMS);
    }

    if (bNeedsLink)
        aTokens.emplace_back(TOKEN_LINK_END);

    SetTemplate(0, UIName(SwResId(*pPoolId++)));

    if(TOX_INDEX == m_eType)
    {
        for( sal_uInt16 i = 1; i < 5; ++i  )
        {
            if(1 == i)
            {
                SwFormTokens aTmpTokens;
                SwFormToken aTmpToken(TOKEN_ENTRY);
                aTmpTokens.push_back(aTmpToken);

                SetPattern( i, std::move(aTmpTokens) );
                SetTemplate(i, UIName(SwResId(STR_POOLCOLL_TOX_IDXBREAK)));
            }
            else
            {
                SetPattern( i, std::vector(aTokens) );
                SetTemplate(i, UIName(SwResId(STR_POOLCOLL_TOX_ARY[i - 1])));
            }
        }
    }
    else
    {
        for (sal_uInt16 i = 1; i < GetFormMax(); ++i, ++pPoolId)    // Number 0 is the title
        {
            if (TOX_AUTHORITIES == m_eType)
            {
                SwFormTokens aAuthTokens;
                lcl_FillAuthPattern(aAuthTokens, i);
                SetPattern(i, std::move(aAuthTokens));
            }
            else
                SetPattern( i, std::vector(aTokens) );

            if( TOX_CONTENT == m_eType && 6 == i )
                pPoolId = STR_POOLCOLL_TOX_CNTNT_EXTRA_ARY;
            else if( TOX_USER == m_eType && 6 == i )
                pPoolId = STR_POOLCOLL_TOX_USER_EXTRA_ARY;
            else if( TOX_AUTHORITIES == m_eType ) //reuse the same STR_POOLCOLL_TOX_AUTHORITIES1 id each time
                pPoolId = STR_POOLCOLL_TOX_AUTHORITIES_ARY + 1;
            SetTemplate(i, UIName(SwResId(*pPoolId)));
        }
    }
}

SwForm::SwForm(const SwForm& rForm)
    : m_eType( rForm.m_eType )
{
    *this = rForm;
}

SwForm& SwForm::operator=(const SwForm& rForm)
{
    m_eType = rForm.m_eType;
    m_nFormMaxLevel = rForm.m_nFormMaxLevel;
//  nFirstTabPos = rForm.nFirstTabPos;
//  bHasFirstTabPos = rForm.bHasFirstTabPos;
    m_bIsRelTabPos = rForm.m_bIsRelTabPos;
    m_bCommaSeparated = rForm.m_bCommaSeparated;
    for(sal_uInt16 i=0; i < m_nFormMaxLevel; ++i)
    {
        m_aPattern[i] = rForm.m_aPattern[i];
        m_aTemplate[i] = rForm.m_aTemplate[i];
    }
    return *this;
}

sal_uInt16 SwForm::GetFormMaxLevel( TOXTypes eTOXType )
{
    switch( eTOXType )
    {
        case TOX_INDEX:
            return 5;
        case TOX_USER:
        case TOX_CONTENT:
            return MAXLEVEL + 1;
        case TOX_ILLUSTRATIONS:
        case TOX_OBJECTS:
        case TOX_TABLES:
            return 2;
        case TOX_BIBLIOGRAPHY:
        case TOX_CITATION:
        case TOX_AUTHORITIES:
            return AUTH_TYPE_END + 1;
    }
    return 0;
}

void SwForm::AdjustTabStops( SwDoc const & rDoc ) // #i21237#
{
    const sal_uInt16 nFormMax = GetFormMax();
    for ( sal_uInt16 nLevel = 1; nLevel < nFormMax; ++nLevel )
    {
        SwTextFormatColl* pColl = rDoc.FindTextFormatCollByName( GetTemplate(nLevel) );
        if( pColl == nullptr )
        {
            // Paragraph Style for this level has not been created.
            // --> No need to propagate default values
            continue;
        }

        const SvxTabStopItem& rTabStops = pColl->GetTabStops(false);
        const sal_uInt16 nTabCount = rTabStops.Count();
        if (nTabCount != 0)
        {
            SwFormTokens aCurrentPattern = GetPattern(nLevel);
            SwFormTokens::iterator aIt = aCurrentPattern.begin();

            bool bChanged = false;
            for(sal_uInt16 nTab = 0; nTab < nTabCount; ++nTab)
            {
                const SvxTabStop& rTab = rTabStops[nTab];

                if ( rTab.GetAdjustment() == SvxTabAdjust::Default )
                    continue; // ignore the default tab stop

                aIt = find_if( aIt, aCurrentPattern.end(), SwFormTokenEqualToFormTokenType(TOKEN_TAB_STOP) );
                if ( aIt != aCurrentPattern.end() )
                {
                    bChanged = true;
                    aIt->nTabStopPosition = rTab.GetTabPos();
                    aIt->eTabAlign =
                        ( nTab == nTabCount - 1
                          && rTab.GetAdjustment() == SvxTabAdjust::Right )
                        ? SvxTabAdjust::End
                        : rTab.GetAdjustment();
                    aIt->cTabFillChar = rTab.GetFill();
                    ++aIt;
                }
                else
                    break; // no more tokens to replace
            }

            if ( bChanged )
                SetPattern( nLevel, std::move(aCurrentPattern) );
        }
    }
}

OUString SwForm::GetFormEntry()       {return u"<E>"_ustr;}
OUString SwForm::GetFormTab()         {return u"<T>"_ustr;}
OUString SwForm::GetFormPageNums()    {return u"<#>"_ustr;}
OUString SwForm::GetFormLinkStt()     {return u"<LS>"_ustr;}
OUString SwForm::GetFormLinkEnd()     {return u"<LE>"_ustr;}
OUString SwForm::GetFormEntryNum()    {return u"<E#>"_ustr;}
OUString SwForm::GetFormEntryText()    {return u"<ET>"_ustr;}
OUString SwForm::GetFormChapterMark() {return u"<C>"_ustr;}
OUString SwForm::GetFormText()        {return u"<X>"_ustr;}
OUString SwForm::GetFormAuth()        {return u"<A>"_ustr;}

SwTOXBase::SwTOXBase(const SwTOXType* pTyp, const SwForm& rForm,
                     SwTOXElement nCreaType, OUString aTitle )
    : SwClient(const_cast<sw::BroadcastingModify*>(static_cast<sw::BroadcastingModify const *>(pTyp)))
    , m_aForm(rForm)
    , m_aTitle(std::move(aTitle))
    , m_eLanguage(::GetAppLanguage())
    , m_nCreateType(nCreaType)
    , m_nOLEOptions(SwTOOElements::NONE)
    , m_eCaptionDisplay(SwCaptionDisplay::Complete)
    , m_bProtected( true )
    , m_bFromChapter(false)
    , m_bFromObjectNames(false)
    , m_bLevelFromChapter(false)
    , maMSTOCExpression()
    , mbKeepExpression(true)
{
    m_aData.nOptions = SwTOIOptions::NONE;
}

SwTOXBase::SwTOXBase( const SwTOXBase& rSource, SwDoc* pDoc )
    : SwClient( rSource.GetRegisteredInNonConst() )
    , mbKeepExpression(true)
{
    CopyTOXBase( pDoc, rSource );
}

void SwTOXBase::RegisterToTOXType( SwTOXType& rType )
{
    rType.Add(*this);
}

void SwTOXBase::CopyTOXBase( SwDoc* pDoc, const SwTOXBase& rSource )
{
    maMSTOCExpression = rSource.maMSTOCExpression;
    SwTOXType* pType = const_cast<SwTOXType*>(rSource.GetTOXType());
    if( pDoc &&
        std::find_if(pDoc->GetTOXTypes().begin(), pDoc->GetTOXTypes().end(),
                     [=](const std::unique_ptr<SwTOXType> & p) { return p.get() == pType; })
            == pDoc->GetTOXTypes().end())
    {
        // type not in pDoc, so create it now
        const SwTOXTypes& rTypes = pDoc->GetTOXTypes();
        bool bFound = false;
        for( size_t n = rTypes.size(); n; )
        {
            const SwTOXType* pCmp = rTypes[ --n ].get();
            if( pCmp->GetType() == pType->GetType() &&
                pCmp->GetTypeName() == pType->GetTypeName() )
            {
                pType = const_cast<SwTOXType*>(pCmp);
                bFound = true;
                break;
            }
        }

        if( !bFound )
            pType = const_cast<SwTOXType*>(pDoc->InsertTOXType( *pType ));
    }
    pType->Add(*this);

    m_nCreateType = rSource.m_nCreateType;
    m_aTitle      = rSource.m_aTitle;
    m_aForm       = rSource.m_aForm;
    m_aBookmarkName = rSource.m_aBookmarkName;
    m_bProtected  = rSource.m_bProtected;
    m_bFromChapter = rSource.m_bFromChapter;
    m_bFromObjectNames = rSource.m_bFromObjectNames;
    m_sMainEntryCharStyle = rSource.m_sMainEntryCharStyle;
    m_sSequenceName = rSource.m_sSequenceName;
    m_eCaptionDisplay = rSource.m_eCaptionDisplay;
    m_nOLEOptions = rSource.m_nOLEOptions;
    m_eLanguage = rSource.m_eLanguage;
    m_sSortAlgorithm = rSource.m_sSortAlgorithm;
    m_bLevelFromChapter = rSource.m_bLevelFromChapter;

    for( sal_uInt16 i = 0; i < MAXLEVEL; ++i )
        m_aStyleNames[i] = rSource.m_aStyleNames[i];

    // it's the same data type!
    m_aData.nOptions =  rSource.m_aData.nOptions;

    if( !pDoc || pDoc->IsCopyIsMove() )
        m_aName = rSource.GetTOXName();
    else
        m_aName = UIName(pDoc->GetUniqueTOXBaseName( *pType, rSource.GetTOXName().toString() ));
}

// TOX specific functions
SwTOXBase::~SwTOXBase()
{
//    if( GetTOXType()->GetType() == TOX_USER  )
//        delete aData.pTemplateName;
}

void SwTOXBase::SetTitle(const OUString& rTitle)
    {   m_aTitle = rTitle; }

void SwTOXBase::SetBookmarkName(const OUString& bName)
{
     m_aBookmarkName = bName;
}

SwTOXBase & SwTOXBase::operator = (const SwTOXBase & rSource)
{
    m_aForm = rSource.m_aForm;
    m_aName = rSource.m_aName;
    m_aTitle = rSource.m_aTitle;
    m_aBookmarkName = rSource.m_aBookmarkName;
    m_sMainEntryCharStyle = rSource.m_sMainEntryCharStyle;
    for(sal_uInt16 nLevel = 0; nLevel < MAXLEVEL; nLevel++)
        m_aStyleNames[nLevel] = rSource.m_aStyleNames[nLevel];
    m_sSequenceName = rSource.m_sSequenceName;
    m_eLanguage = rSource.m_eLanguage;
    m_sSortAlgorithm = rSource.m_sSortAlgorithm;
    m_aData = rSource.m_aData;
    m_nCreateType = rSource.m_nCreateType;
    m_nOLEOptions = rSource.m_nOLEOptions;
    m_eCaptionDisplay = rSource.m_eCaptionDisplay;
    m_bProtected = rSource.m_bProtected;
    m_bFromChapter = rSource.m_bFromChapter;
    m_bFromObjectNames = rSource.m_bFromObjectNames;
    m_bLevelFromChapter = rSource.m_bLevelFromChapter;

    if (rSource.GetAttrSet())
        SetAttrSet(*rSource.GetAttrSet());

    return *this;
}

OUString SwFormToken::GetString() const
{
    OUString sToken;

    switch( eTokenType )
    {
        case TOKEN_ENTRY_NO:
            sToken = SwForm::GetFormEntryNum();
        break;
        case TOKEN_ENTRY_TEXT:
            sToken = SwForm::GetFormEntryText();
        break;
        case TOKEN_ENTRY:
            sToken = SwForm::GetFormEntry();
        break;
        case TOKEN_TAB_STOP:
            sToken = SwForm::GetFormTab();
        break;
        case TOKEN_TEXT:
            // Return a Token only if Text is not empty!
            if( sText.isEmpty() )
            {
                return OUString();
            }
            sToken = SwForm::GetFormText();
        break;
        case TOKEN_PAGE_NUMS:
            sToken = SwForm::GetFormPageNums();
        break;
        case TOKEN_CHAPTER_INFO:
            sToken = SwForm::GetFormChapterMark();
        break;
        case TOKEN_LINK_START:
            sToken = SwForm::GetFormLinkStt();
        break;
        case TOKEN_LINK_END:
            sToken = SwForm::GetFormLinkEnd();
        break;
        case TOKEN_AUTHORITY:
        {
            sToken = SwForm::GetFormAuth();
        }
        break;
        case TOKEN_END:
        break;
    }

    OUString sData = " " + sCharStyleName.toString() + "," + OUString::number( nPoolId ) + ",";

    // TabStopPosition and TabAlign or ChapterInfoFormat
    switch (eTokenType)
    {
        case TOKEN_TAB_STOP:
            sData += OUString::number( nTabStopPosition ) + ","
                  +  OUString::number( static_cast< sal_Int32 >(eTabAlign) ) + ","
                  +  OUStringChar(cTabFillChar) + ","
                  +  OUString::number( bWithTab ? 1 : 0 );
            break;
        case TOKEN_CHAPTER_INFO:
        case TOKEN_ENTRY_NO:
            // add also maximum permitted level
            sData += OUString::number( static_cast<sal_uInt16>(nChapterFormat) ) + ","
                  +  OUString::number( nOutlineLevel );
            break;
        case TOKEN_TEXT:
            sData += OUStringChar(TOX_STYLE_DELIMITER)
                  +  sText.replaceAll(OUStringChar(TOX_STYLE_DELIMITER), "")
                  +  OUStringChar(TOX_STYLE_DELIMITER);
            break;
        case TOKEN_AUTHORITY:
            if (nAuthorityField<10)
            {
                 sData = "0" + OUString::number( nAuthorityField ) + sData;
            }
            else
            {
                 sData = OUString::number( nAuthorityField ) + sData;
            }
            break;
        default:
            break;
    }

    return sToken.subView(0, sToken.getLength()-1) + sData + sToken.subView(sToken.getLength()-1);
}

// -> #i21237#

/**
   Returns the type of a token.

   @param sToken     the string representation of the token
   @param rTokenLen  return parameter the length of the head of the token

   @return the type of the token
*/
static FormTokenType lcl_GetTokenType(std::u16string_view sToken,
                                      sal_Int32 & rTokenLen)
{
    static struct
    {
        OUString sTokenStart;
        sal_Int16 nTokenLength;
        FormTokenType eTokenType;
    } const aTokenArr[] = {
        { SwForm::GetFormTab().copy(0, 2),         3, TOKEN_TAB_STOP },
        { SwForm::GetFormPageNums().copy(0, 2),    3, TOKEN_PAGE_NUMS },
        { SwForm::GetFormLinkStt().copy(0, 3),     4, TOKEN_LINK_START },
        { SwForm::GetFormLinkEnd().copy(0, 3),     4, TOKEN_LINK_END },
        { SwForm::GetFormEntryNum().copy(0, 3),    4, TOKEN_ENTRY_NO },
        { SwForm::GetFormEntryText().copy(0, 3),   4, TOKEN_ENTRY_TEXT },
        { SwForm::GetFormChapterMark().copy(0, 2), 3, TOKEN_CHAPTER_INFO },
        { SwForm::GetFormText().copy(0, 2),        3, TOKEN_TEXT },
        { SwForm::GetFormEntry().copy(0, 2),       3, TOKEN_ENTRY },
        { SwForm::GetFormAuth().copy(0, 2),        5, TOKEN_AUTHORITY }
    };

    for(const auto & i : aTokenArr)
    {
        if( o3tl::starts_with( sToken, i.sTokenStart ) )
        {
            rTokenLen = i.nTokenLength;
            return i.eTokenType;
        }
    }

    SAL_WARN("sw.core", "SwFormTokensHelper: invalid token");
    return TOKEN_END;
}

/**
   Returns the string of a token.

   @param sPattern    the whole pattern
   @param nStt        starting position of the token

   @return   the string representation of the token
*/
static std::u16string_view
lcl_SearchNextToken(std::u16string_view sPattern, sal_Int32 const nStt)
{
    size_t nEnd = sPattern.find( u'>', nStt );
    if (nEnd != std::u16string_view::npos)
    {
        // apparently the TOX_STYLE_DELIMITER act as a bracketing for
        // TOKEN_TEXT tokens so that the user can have '>' inside the text...
        const size_t nTextSeparatorFirst = sPattern.find( TOX_STYLE_DELIMITER, nStt );
        if (    nTextSeparatorFirst != std::u16string_view::npos
            &&  nTextSeparatorFirst + 1 < sPattern.size()
            &&  nTextSeparatorFirst < nEnd)
        {
            const size_t nTextSeparatorSecond = sPattern.find( TOX_STYLE_DELIMITER,
                                                                     nTextSeparatorFirst + 1 );
            // Since nEnd>=0 we don't need to check if nTextSeparatorSecond==std::u16string_view::npos!
            if( nEnd < nTextSeparatorSecond )
                nEnd = sPattern.find( '>', nTextSeparatorSecond );
            // FIXME: No check to verify that nEnd is still >=0?
            assert(nEnd != std::u16string_view::npos);
        }

        ++nEnd;

        return sPattern.substr( nStt, nEnd - nStt );
    }

    return std::u16string_view();
}

/**
   Builds a token from its string representation.

   @sPattern          the whole pattern
   @nCurPatternPos    starting position of the token

   @return the token
 */
static std::optional<SwFormToken>
lcl_BuildToken(std::u16string_view sPattern, size_t & nCurPatternPos)
{
    std::u16string_view sToken( lcl_SearchNextToken(sPattern, nCurPatternPos) );
    nCurPatternPos += sToken.size();
    sal_Int32 nTokenLen = 0;
    FormTokenType const eTokenType = lcl_GetTokenType(sToken, nTokenLen);
    if (TOKEN_END == eTokenType) // invalid input? skip it
    {
        nCurPatternPos = sPattern.size();
        return std::optional<SwFormToken>();
    }

    // at this point sPattern contains the
    // character style name, the PoolId, tab stop position, tab stop alignment, chapter info format
    // the form is: CharStyleName, PoolId[, TabStopPosition|ChapterInfoFormat[, TabStopAlignment[, TabFillChar]]]
    // in text tokens the form differs from the others: CharStyleName, PoolId[,\0xffinserted text\0xff]
    SwFormToken eRet( eTokenType );
    const std::u16string_view sAuthFieldEnum = sToken.substr( 2, 2 );
    sToken = sToken.substr( nTokenLen, sToken.size() - nTokenLen - 1);

    sal_Int32 nIdx{ 0 };
    eRet.sCharStyleName = UIName(OUString(o3tl::getToken(sToken, 0, ',', nIdx )));
    std::u16string_view sTmp( o3tl::getToken(sToken, 0, ',', nIdx ));
    if( !sTmp.empty() )
        eRet.nPoolId = o3tl::narrowing<sal_uInt16>(o3tl::toInt32(sTmp));

    switch( eTokenType )
    {
//i53420
    case TOKEN_CHAPTER_INFO:
//i53420
    case TOKEN_ENTRY_NO:
        sTmp = o3tl::getToken(sToken, 0, ',', nIdx ); // token 2
        if( !sTmp.empty() )
            eRet.nChapterFormat = static_cast<SwChapterFormat>(o3tl::toInt32(sTmp));
        sTmp = o3tl::getToken(sToken, 0, ',', nIdx ); // token 3
        if( !sTmp.empty() )
            eRet.nOutlineLevel = o3tl::narrowing<sal_uInt16>(o3tl::toInt32(sTmp)); //the maximum outline level to examine
        break;

    case TOKEN_TEXT:
        {
            const size_t nStartText = sToken.find( TOX_STYLE_DELIMITER );
            if( nStartText != std::u16string_view::npos && nStartText+1<sToken.size())
            {
                const size_t nEndText = sToken.find( TOX_STYLE_DELIMITER,
                                                           nStartText + 1);
                if( nEndText != std::u16string_view::npos )
                {
                    eRet.sText = sToken.substr( nStartText + 1,
                                                nEndText - nStartText - 1);
                }
            }
        }
        break;

    case TOKEN_TAB_STOP:
        sTmp = o3tl::getToken(sToken, 0, ',', nIdx ); // token 2
        if( !sTmp.empty() )
            eRet.nTabStopPosition = o3tl::toInt32(sTmp);

        sTmp = o3tl::getToken(sToken, 0, ',', nIdx ); // token 3
        if( !sTmp.empty() )
            eRet.eTabAlign = static_cast<SvxTabAdjust>(o3tl::toInt32(sTmp));

        sTmp = o3tl::getToken(sToken, 0, ',', nIdx ); // token 4
        if( !sTmp.empty() )
            eRet.cTabFillChar = sTmp[0];

        sTmp = o3tl::getToken(sToken, 0, ',', nIdx ); // token 5
        if( !sTmp.empty() )
            eRet.bWithTab = 0 != o3tl::toInt32(sTmp);
        break;

    case TOKEN_AUTHORITY:
        eRet.nAuthorityField = o3tl::narrowing<sal_uInt16>(o3tl::toInt32(sAuthFieldEnum));
        break;
    default: break;
    }
    return eRet;
}

SwFormTokensHelper::SwFormTokensHelper(std::u16string_view aPattern)
{
    size_t nCurPatternPos = 0;

    while (nCurPatternPos < aPattern.size())
    {
        std::optional<SwFormToken> const oToken(
                lcl_BuildToken(aPattern, nCurPatternPos));
        if (oToken)
            m_Tokens.push_back(*oToken);
    }
}

// <- #i21237#

void SwForm::SetPattern(sal_uInt16 nLevel, SwFormTokens&& rTokens)
{
    OSL_ENSURE(nLevel < GetFormMax(), "Index >= FORM_MAX");
    m_aPattern[nLevel] = std::move(rTokens);
}

void SwForm::SetPattern(sal_uInt16 nLevel, std::u16string_view aStr)
{
    OSL_ENSURE(nLevel < GetFormMax(), "Index >= FORM_MAX");

    SwFormTokensHelper aHelper(aStr);
    m_aPattern[nLevel] = aHelper.GetTokens();
}

const SwFormTokens& SwForm::GetPattern(sal_uInt16 nLevel) const
{
    OSL_ENSURE(nLevel < GetFormMax(), "Index >= FORM_MAX");
    return m_aPattern[nLevel];
}

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