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

#include <memory>
#include <document.hxx>
#include <clipcontext.hxx>
#include <clipparam.hxx>
#include <table.hxx>
#include <tokenarray.hxx>
#include <listenercontext.hxx>
#include <tokenstringcontext.hxx>
#include <poolhelp.hxx>
#include <bcaslot.hxx>
#include <cellvalues.hxx>
#include <docpool.hxx>
#include <columniterator.hxx>

#include <refupdatecontext.hxx>
#include <sal/log.hxx>
#include <editeng/colritem.hxx>
#include <scitems.hxx>
#include <datamapper.hxx>

// Add totally brand-new methods to this source file.

bool ScDocument::IsMerged( const ScAddress& rPos ) const
{
    const ScTable* pTab = FetchTable(rPos.Tab());
    if (!pTab)
        return false;

    return pTab->IsMerged(rPos.Col(), rPos.Row());
}

sc::MultiDataCellState ScDocument::HasMultipleDataCells( const ScRange& rRange ) const
{
    if (rRange.aStart.Tab() != rRange.aEnd.Tab())
        // Currently we only support a single-sheet range.
        return sc::MultiDataCellState();

    const ScTable* pTab = FetchTable(rRange.aStart.Tab());
    if (!pTab)
        return sc::MultiDataCellState(sc::MultiDataCellState::Empty);

    const ScAddress& s = rRange.aStart;
    const ScAddress& e = rRange.aEnd;
    return pTab->HasMultipleDataCells(s.Col(), s.Row(), e.Col(), e.Row());
}

void ScDocument::DeleteBeforeCopyFromClip(
    sc::CopyFromClipContext& rCxt, const ScMarkData& rMark, sc::ColumnSpanSet& rBroadcastSpans )
{
    SCTAB nClipTab = 0;
    const TableContainer& rClipTabs = rCxt.getClipDoc()->maTabs;
    SCTAB nClipTabCount = rClipTabs.size();

    for (SCTAB nTab = rCxt.getTabStart(); nTab <= rCxt.getTabEnd(); ++nTab)
    {
        ScTable* pTab = FetchTable(nTab);
        if (!pTab)
            continue;

        if (!rMark.GetTableSelect(nTab))
            continue;

        while (!rClipTabs[nClipTab])
            nClipTab = (nClipTab+1) % nClipTabCount;

        pTab->DeleteBeforeCopyFromClip(rCxt, *rClipTabs[nClipTab], rBroadcastSpans);

        nClipTab = (nClipTab+1) % nClipTabCount;
    }
}

bool ScDocument::CopyOneCellFromClip(
    sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
{
    ScDocument* pClipDoc = rCxt.getClipDoc();
    if (pClipDoc->GetClipParam().mbCutMode)
        // We don't handle cut and paste or moving of cells here.
        return false;

    ScRange aClipRange = pClipDoc->GetClipParam().getWholeRange();
    if (aClipRange.aStart.Row() != aClipRange.aEnd.Row())
        // The source is not really a single row. Bail out.
        return false;

    SCCOL nSrcColSize = aClipRange.aEnd.Col() - aClipRange.aStart.Col() + 1;
    SCCOL nDestColSize = nCol2 - nCol1 + 1;
    if (nDestColSize < nSrcColSize)
        return false;

    if (pClipDoc->maTabs.size() > 1)
        // Copying from multiple source sheets is not handled here.
        return false;

    ScAddress aSrcPos = aClipRange.aStart;

    for (SCCOL nCol = aClipRange.aStart.Col(); nCol <= aClipRange.aEnd.Col(); ++nCol)
    {
        ScAddress aTestPos = aSrcPos;
        aTestPos.SetCol(nCol);
        if (pClipDoc->IsMerged(aTestPos))
            // We don't handle merged source cell for this.
            return false;
    }

    ScTable* pSrcTab = pClipDoc->FetchTable(aSrcPos.Tab());
    if (!pSrcTab)
        return false;

    rCxt.setSingleCellColumnSize(nSrcColSize);

    for (SCCOL nColOffset = 0; nColOffset < nSrcColSize; ++nColOffset, aSrcPos.IncCol())
    {
        const ScPatternAttr* pAttr = pClipDoc->GetPattern(aSrcPos);
        rCxt.setSingleCellPattern(nColOffset, pAttr);

        if ((rCxt.getInsertFlag() & (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES)) != InsertDeleteFlags::NONE)
            rCxt.setSingleCellNote(nColOffset, pClipDoc->GetNote(aSrcPos));

        ScColumn& rSrcCol = pSrcTab->aCol[aSrcPos.Col()];
        // Determine the script type of the copied single cell.
        rSrcCol.UpdateScriptTypes(aSrcPos.Row(), aSrcPos.Row());
        rCxt.setSingleCell(aSrcPos, rSrcCol);
    }

    // All good. Proceed with the pasting.

    SCTAB nTabEnd = rCxt.getTabEnd();
    for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < static_cast<SCTAB>(maTabs.size()); ++i)
    {
        maTabs[i]->CopyOneCellFromClip(rCxt, nCol1, nRow1, nCol2, nRow2,  aClipRange.aStart.Row(), pSrcTab);
    }

    sc::RefUpdateContext aRefCxt(*this);
    aRefCxt.maRange = ScRange(nCol1, nRow1, rCxt.getTabStart(), nCol2, nRow2, nTabEnd);
    aRefCxt.mnColDelta = nCol1 - aSrcPos.Col();
    aRefCxt.mnRowDelta = nRow1 - aSrcPos.Row();
    aRefCxt.mnTabDelta = rCxt.getTabStart() - aSrcPos.Tab();
    // Only Copy&Paste, for Cut&Paste we already bailed out early.
    aRefCxt.meMode = URM_COPY;
    UpdateReference(aRefCxt, rCxt.getUndoDoc(), false);

    return true;
}

void ScDocument::SetValues( const ScAddress& rPos, const std::vector<double>& rVals )
{
    ScTable* pTab = FetchTable(rPos.Tab());
    if (!pTab)
        return;

    pTab->SetValues(rPos.Col(), rPos.Row(), rVals);
}

void ScDocument::TransferCellValuesTo( const ScAddress& rTopPos, size_t nLen, sc::CellValues& rDest )
{
    ScTable* pTab = FetchTable(rTopPos.Tab());
    if (!pTab)
        return;

    pTab->TransferCellValuesTo(rTopPos.Col(), rTopPos.Row(), nLen, rDest);
}

void ScDocument::CopyCellValuesFrom( const ScAddress& rTopPos, const sc::CellValues& rSrc )
{
    ScTable* pTab = FetchTable(rTopPos.Tab());
    if (!pTab)
        return;

    pTab->CopyCellValuesFrom(rTopPos.Col(), rTopPos.Row(), rSrc);
}

std::set<Color> ScDocument::GetDocColors()
{
    std::set<Color> aDocColors;
    ScDocumentPool *pPool = GetPool();
    const sal_uInt16 pAttribs[] = {ATTR_BACKGROUND, ATTR_FONT_COLOR};
    for (sal_uInt16 nAttrib : pAttribs)
    {
        const sal_uInt32 nCount = pPool->GetItemCount2(nAttrib);
        for (sal_uInt32 j=0; j<nCount; j++)
        {
            const SvxColorItem *pItem = static_cast<const SvxColorItem*>(pPool->GetItem2(nAttrib, j));
            if (pItem == nullptr)
                continue;
            Color aColor( pItem->GetValue() );
            if (COL_AUTO != aColor)
                aDocColors.insert(aColor);
        }
    }
    return aDocColors;
}

void ScDocument::SetCalcConfig( const ScCalcConfig& rConfig )
{
    ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE);
    maCalcConfig = rConfig;
}

void ScDocument::ConvertFormulaToValue( const ScRange& rRange, sc::TableValues* pUndo )
{
    sc::EndListeningContext aCxt(*this);

    for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
    {
        ScTable* pTab = FetchTable(nTab);
        if (!pTab)
            continue;

        pTab->ConvertFormulaToValue(
            aCxt, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(),
            pUndo);
    }

    aCxt.purgeEmptyBroadcasters();
}

void ScDocument::SwapNonEmpty( sc::TableValues& rValues )
{
    const ScRange& rRange = rValues.getRange();
    if (!rRange.IsValid())
        return;

    std::shared_ptr<sc::ColumnBlockPositionSet> pPosSet(new sc::ColumnBlockPositionSet(*this));
    sc::StartListeningContext aStartCxt(*this, pPosSet);
    sc::EndListeningContext aEndCxt(*this, pPosSet);

    for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
    {
        ScTable* pTab = FetchTable(nTab);
        if (!pTab)
            continue;

        pTab->SwapNonEmpty(rValues, aStartCxt, aEndCxt);
    }

    aEndCxt.purgeEmptyBroadcasters();
}

void ScDocument::PreprocessAllRangeNamesUpdate( const std::map<OUString, std::unique_ptr<ScRangeName>>& rRangeMap )
{
    // Update all existing names with new names.
    // The prerequisites are that the name dialog preserves ScRangeData index
    // for changes and does not reuse free index slots for new names.
    // ScDocument::SetAllRangeNames() hereafter then will replace the
    // ScRangeName containers of ScRangeData instances with empty
    // ScRangeData::maNewName.
    std::map<OUString, ScRangeName*> aRangeNameMap;
    GetRangeNameMap( aRangeNameMap);
    for (const auto& itTab : aRangeNameMap)
    {
        ScRangeName* pOldRangeNames = itTab.second;
        if (!pOldRangeNames)
            continue;

        const auto& itNewTab( rRangeMap.find( itTab.first));
        if (itNewTab == rRangeMap.end())
            continue;

        const ScRangeName* pNewRangeNames = itNewTab->second.get();
        if (!pNewRangeNames)
            continue;

        for (const auto& rEntry : *pOldRangeNames)
        {
            ScRangeData* pOldData = rEntry.second.get();
            if (!pOldData)
                continue;

            const ScRangeData* pNewData = pNewRangeNames->findByIndex( pOldData->GetIndex());
            if (pNewData)
                pOldData->SetNewName( pNewData->GetName());
        }
    }

    sc::EndListeningContext aEndListenCxt(*this);
    sc::CompileFormulaContext aCompileCxt(this);

    for (const auto& rxTab : maTabs)
    {
        ScTable* p = rxTab.get();
        p->PreprocessRangeNameUpdate(aEndListenCxt, aCompileCxt);
    }
}

void ScDocument::PreprocessRangeNameUpdate()
{
    sc::EndListeningContext aEndListenCxt(*this);
    sc::CompileFormulaContext aCompileCxt(this);

    for (const auto& rxTab : maTabs)
    {
        ScTable* p = rxTab.get();
        p->PreprocessRangeNameUpdate(aEndListenCxt, aCompileCxt);
    }
}

void ScDocument::PreprocessDBDataUpdate()
{
    sc::EndListeningContext aEndListenCxt(*this);
    sc::CompileFormulaContext aCompileCxt(this);

    for (const auto& rxTab : maTabs)
    {
        ScTable* p = rxTab.get();
        p->PreprocessDBDataUpdate(aEndListenCxt, aCompileCxt);
    }
}

void ScDocument::CompileHybridFormula()
{
    sc::StartListeningContext aStartListenCxt(*this);
    sc::CompileFormulaContext aCompileCxt(this);
    for (const auto& rxTab : maTabs)
    {
        ScTable* p = rxTab.get();
        p->CompileHybridFormula(aStartListenCxt, aCompileCxt);
    }
}

void ScDocument::SharePooledResources( const ScDocument* pSrcDoc )
{
    ScMutationGuard aGuard(this, ScMutationGuardFlags::CORE);
    mxPoolHelper = pSrcDoc->mxPoolHelper;
    mpCellStringPool = pSrcDoc->mpCellStringPool;
}

void ScDocument::UpdateScriptTypes( const ScAddress& rPos, SCCOL nColSize, SCROW nRowSize )
{
    ScTable* pTab = FetchTable(rPos.Tab());
    if (!pTab)
        return;

    pTab->UpdateScriptTypes(rPos.Col(), rPos.Row(), rPos.Col()+nColSize-1, rPos.Row()+nRowSize-1);
}

bool ScDocument::HasUniformRowHeight( SCTAB nTab, SCROW nRow1, SCROW nRow2 ) const
{
    const ScTable* pTab = FetchTable(nTab);
    if (!pTab)
        return false;

    return pTab->HasUniformRowHeight(nRow1, nRow2);
}

void ScDocument::UnshareFormulaCells( SCTAB nTab, SCCOL nCol, std::vector<SCROW>& rRows )
{
    ScTable* pTab = FetchTable(nTab);
    if (!pTab)
        return;

    pTab->UnshareFormulaCells(nCol, rRows);
}

void ScDocument::RegroupFormulaCells( SCTAB nTab, SCCOL nCol )
{
    ScTable* pTab = FetchTable(nTab);
    if (!pTab)
        return;

    pTab->RegroupFormulaCells(nCol);
}

void ScDocument::RegroupFormulaCells( const ScRange& rRange )
{
    for( SCTAB tab = rRange.aStart.Tab(); tab <= rRange.aEnd.Tab(); ++tab )
        for( SCCOL col = rRange.aStart.Col(); col <= rRange.aEnd.Col(); ++col )
            RegroupFormulaCells( tab, col );
}

void ScDocument::DelayFormulaGrouping( bool delay )
{
    if( delay )
    {
        if( pDelayedFormulaGrouping.get() == nullptr )
            pDelayedFormulaGrouping.reset( new ScRange( ScAddress::INITIALIZE_INVALID ));
    }
    else
    {
        if( pDelayedFormulaGrouping.get() != nullptr && pDelayedFormulaGrouping->IsValid())
            RegroupFormulaCells( *pDelayedFormulaGrouping );
        pDelayedFormulaGrouping.reset();
    }
}

void ScDocument::AddDelayedFormulaGroupingCell( const ScFormulaCell* cell )
{
    if( !pDelayedFormulaGrouping->In( cell->aPos ))
        pDelayedFormulaGrouping->ExtendTo( cell->aPos );
}

void ScDocument::CollectAllAreaListeners(
    std::vector<SvtListener*>& rListener, const ScRange& rRange, sc::AreaOverlapType eType )
{
    if (!pBASM)
        return;

    std::vector<sc::AreaListener> aAL = pBASM->GetAllListeners(rRange, eType);
    for (const auto& rItem : aAL)
        rListener.push_back(rItem.mpListener);
}

bool ScDocument::HasFormulaCell( const ScRange& rRange ) const
{
    if (!rRange.IsValid())
        return false;

    for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
    {
        const ScTable* pTab = FetchTable(nTab);
        if (!pTab)
            continue;

        if (pTab->HasFormulaCell(rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()))
            return true;
    }

    return false;
}

void ScDocument::EndListeningIntersectedGroup(
    sc::EndListeningContext& rCxt, const ScAddress& rPos, std::vector<ScAddress>* pGroupPos )
{
    ScTable* pTab = FetchTable(rPos.Tab());
    if (!pTab)
        return;

    pTab->EndListeningIntersectedGroup(rCxt, rPos.Col(), rPos.Row(), pGroupPos);
}

void ScDocument::EndListeningIntersectedGroups(
    sc::EndListeningContext& rCxt, const ScRange& rRange, std::vector<ScAddress>* pGroupPos )
{
    for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
    {
        ScTable* pTab = FetchTable(nTab);
        if (!pTab)
            continue;

        pTab->EndListeningIntersectedGroups(
            rCxt, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(),
            pGroupPos);
    }
}

void ScDocument::EndListeningGroups( const std::vector<ScAddress>& rPosArray )
{
    sc::EndListeningContext aCxt(*this);
    for (const ScAddress& rPos : rPosArray)
    {
        ScTable* pTab = FetchTable(rPos.Tab());
        if (!pTab)
            return;

        pTab->EndListeningGroup(aCxt, rPos.Col(), rPos.Row());
    }

    aCxt.purgeEmptyBroadcasters();
}

void ScDocument::SetNeedsListeningGroups( const std::vector<ScAddress>& rPosArray )
{
    for (const ScAddress& rPos : rPosArray)
    {
        ScTable* pTab = FetchTable(rPos.Tab());
        if (!pTab)
            return;

        pTab->SetNeedsListeningGroup(rPos.Col(), rPos.Row());
    }
}

namespace {

class StartNeededListenersHandler
{
    std::shared_ptr<sc::StartListeningContext> mpCxt;
public:
    explicit StartNeededListenersHandler( ScDocument& rDoc ) : mpCxt(new sc::StartListeningContext(rDoc)) {}
    explicit StartNeededListenersHandler( ScDocument& rDoc, const std::shared_ptr<const sc::ColumnSet>& rpColSet ) :
        mpCxt(new sc::StartListeningContext(rDoc))
    {
        mpCxt->setColumnSet( rpColSet);
    }

    void operator() (const ScTableUniquePtr & p)
    {
        if (p)
            p->StartListeners(*mpCxt, false);
    }
};

}

void ScDocument::StartNeededListeners()
{
    std::for_each(maTabs.begin(), maTabs.end(), StartNeededListenersHandler(*this));
}

void ScDocument::StartNeededListeners( const std::shared_ptr<const sc::ColumnSet>& rpColSet )
{
    std::for_each(maTabs.begin(), maTabs.end(), StartNeededListenersHandler(*this, rpColSet));
}

void ScDocument::StartAllListeners( const ScRange& rRange )
{
    if (IsClipOrUndo() || GetNoListening())
        return;

    std::shared_ptr<sc::ColumnBlockPositionSet> pPosSet(new sc::ColumnBlockPositionSet(*this));
    sc::StartListeningContext aStartCxt(*this, pPosSet);
    sc::EndListeningContext aEndCxt(*this, pPosSet);

    for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
    {
        ScTable* pTab = FetchTable(nTab);
        if (!pTab)
            continue;

        pTab->StartListeningFormulaCells(
            aStartCxt, aEndCxt,
            rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
    }
}

void ScDocument::finalizeOutlineImport()
{
    for (auto& rxTab : maTabs)
    {
        ScTable* p = rxTab.get();
        p->finalizeOutlineImport();
    }
}

bool ScDocument::FindRangeNamesReferencingSheet( sc::UpdatedRangeNames& rIndexes,
        SCTAB nTokenTab, const sal_uInt16 nTokenIndex,
        SCTAB nGlobalRefTab, SCTAB nLocalRefTab, SCTAB nOldTokenTab, SCTAB nOldTokenTabReplacement,
        bool bSameDoc, int nRecursion) const
{
    if (nTokenTab < -1)
    {
        SAL_WARN("sc.core", "ScDocument::FindRangeNamesReferencingSheet - nTokenTab < -1 : " <<
                nTokenTab << ", nTokenIndex " << nTokenIndex << " Fix the creator!");
#if OSL_DEBUG_LEVEL > 0
        const ScRangeData* pData = FindRangeNameBySheetAndIndex( nTokenTab, nTokenIndex);
        SAL_WARN_IF( pData, "sc.core", "ScDocument::FindRangeNamesReferencingSheet - named expression is: " << pData->GetName());
#endif
        nTokenTab = -1;
    }
    SCTAB nRefTab = nGlobalRefTab;
    if (nTokenTab == nOldTokenTab)
    {
        nTokenTab = nOldTokenTabReplacement;
        nRefTab = nLocalRefTab;
    }
    else if (nTokenTab == nOldTokenTabReplacement)
    {
        nRefTab = nLocalRefTab;
    }

    if (rIndexes.isNameUpdated( nTokenTab, nTokenIndex))
        return true;

    ScRangeData* pData = FindRangeNameBySheetAndIndex( nTokenTab, nTokenIndex);
    if (!pData)
        return false;

    ScTokenArray* pCode = pData->GetCode();
    if (!pCode)
        return false;

    bool bRef = !bSameDoc;  // include every name used when copying to other doc
    if (nRecursion < 126)   // whatever.. 42*3
    {
        formula::FormulaTokenArrayPlainIterator aIter(*pCode);
        for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
        {
            if (p->GetOpCode() == ocName)
            {
                bRef |= FindRangeNamesReferencingSheet( rIndexes, p->GetSheet(), p->GetIndex(),
                        nGlobalRefTab, nLocalRefTab, nOldTokenTab, nOldTokenTabReplacement, bSameDoc, nRecursion+1);
            }
        }
    }

    if (!bRef)
    {
        SCTAB nPosTab = pData->GetPos().Tab();
        if (nPosTab == nOldTokenTab)
            nPosTab = nOldTokenTabReplacement;
        bRef = pCode->ReferencesSheet( nRefTab, nPosTab);
    }
    if (bRef)
        rIndexes.setUpdatedName( nTokenTab, nTokenIndex);

    return bRef;
}

namespace {

enum MightReferenceSheet
{
    UNKNOWN,
    NONE,
    CODE,
    NAME
};

MightReferenceSheet mightRangeNameReferenceSheet( ScRangeData* pData, SCTAB nRefTab)
{
    ScTokenArray* pCode = pData->GetCode();
    if (!pCode)
        return MightReferenceSheet::NONE;

    formula::FormulaTokenArrayPlainIterator aIter(*pCode);
    for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
    {
        if (p->GetOpCode() == ocName)
            return MightReferenceSheet::NAME;
    }

    return pCode->ReferencesSheet( nRefTab, pData->GetPos().Tab()) ?
        MightReferenceSheet::CODE : MightReferenceSheet::NONE;
}

ScRangeData* copyRangeName( const ScRangeData* pOldRangeData, ScDocument& rNewDoc, const ScDocument* pOldDoc,
        const ScAddress& rNewPos, const ScAddress& rOldPos, bool bGlobalNamesToLocal,
        SCTAB nOldSheet, const SCTAB nNewSheet, bool bSameDoc)
{
    ScAddress aRangePos( pOldRangeData->GetPos());
    if (nNewSheet >= 0)
        aRangePos.SetTab( nNewSheet);
    ScRangeData* pRangeData = new ScRangeData(*pOldRangeData, &rNewDoc, &aRangePos);
    pRangeData->SetIndex(0);    // needed for insert to assign a new index
    ScTokenArray* pRangeNameToken = pRangeData->GetCode();
    if (bSameDoc && nNewSheet >= 0)
    {
        if (bGlobalNamesToLocal && nOldSheet < 0)
        {
            nOldSheet = rOldPos.Tab();
            if (rNewPos.Tab() <= nOldSheet)
                // Sheet was inserted before and references already updated.
                ++nOldSheet;
        }
        pRangeNameToken->AdjustSheetLocalNameReferences( nOldSheet, nNewSheet);
    }
    if (!bSameDoc)
    {
        pRangeNameToken->ReadjustAbsolute3DReferences(pOldDoc, &rNewDoc, pRangeData->GetPos(), true);
        pRangeNameToken->AdjustAbsoluteRefs(pOldDoc, rOldPos, rNewPos, true);
    }

    bool bInserted;
    if (nNewSheet < 0)
        bInserted = rNewDoc.GetRangeName()->insert(pRangeData);
    else
        bInserted = rNewDoc.GetRangeName(nNewSheet)->insert(pRangeData);

    return bInserted ? pRangeData : nullptr;
}

struct SheetIndex
{
    SCTAB const       mnSheet;
    sal_uInt16 const  mnIndex;

    SheetIndex( SCTAB nSheet, sal_uInt16 nIndex ) : mnSheet(nSheet < -1 ? -1 : nSheet), mnIndex(nIndex) {}
    bool operator<( const SheetIndex& r ) const
    {
        // Ascending order sheet, index
        if (mnSheet < r.mnSheet)
            return true;
        if (mnSheet == r.mnSheet)
            return mnIndex < r.mnIndex;
        return false;
    }
};
typedef std::map< SheetIndex, SheetIndex > SheetIndexMap;

ScRangeData* copyRangeNames( SheetIndexMap& rSheetIndexMap, std::vector<ScRangeData*>& rRangeDataVec,
        const sc::UpdatedRangeNames& rReferencingNames, SCTAB nTab,
        const ScRangeData* pOldRangeData, ScDocument& rNewDoc, const ScDocument* pOldDoc,
        const ScAddress& rNewPos, const ScAddress& rOldPos, bool bGlobalNamesToLocal,
        const SCTAB nOldSheet, const SCTAB nNewSheet, bool bSameDoc)
{
    ScRangeData* pRangeData = nullptr;
    const ScRangeName* pOldRangeName = (nTab < 0 ? pOldDoc->GetRangeName() : pOldDoc->GetRangeName(nTab));
    if (pOldRangeName)
    {
        const ScRangeName* pNewRangeName = (nNewSheet < 0 ? rNewDoc.GetRangeName() : rNewDoc.GetRangeName(nNewSheet));
        sc::UpdatedRangeNames::NameIndicesType aSet( rReferencingNames.getUpdatedNames(nTab));
        for (auto const & rIndex : aSet)
        {
            const ScRangeData* pCopyData = pOldRangeName->findByIndex(rIndex);
            if (pCopyData)
            {
                // Match the original pOldRangeData to adapt the current
                // token's values later. For that no check for an already
                // copied name is needed as we only enter here if there was
                // none.
                if (pCopyData == pOldRangeData)
                {
                    pRangeData = copyRangeName( pCopyData, rNewDoc, pOldDoc, rNewPos, rOldPos,
                            bGlobalNamesToLocal, nOldSheet, nNewSheet, bSameDoc);
                    if (pRangeData)
                    {
                        rRangeDataVec.push_back(pRangeData);
                        rSheetIndexMap.insert( std::make_pair( SheetIndex( nOldSheet, pCopyData->GetIndex()),
                                    SheetIndex( nNewSheet, pRangeData->GetIndex())));
                    }
                }
                else
                {
                    // First check if the name is already available as copy.
                    const ScRangeData* pFoundData = pNewRangeName->findByUpperName( pCopyData->GetUpperName());
                    if (pFoundData)
                    {
                        // Just add the resulting sheet/index mapping.
                        rSheetIndexMap.insert( std::make_pair( SheetIndex( nOldSheet, pCopyData->GetIndex()),
                                    SheetIndex( nNewSheet, pFoundData->GetIndex())));
                    }
                    else
                    {
                        ScRangeData* pTmpData = copyRangeName( pCopyData, rNewDoc, pOldDoc, rNewPos, rOldPos,
                                bGlobalNamesToLocal, nOldSheet, nNewSheet, bSameDoc);
                        if (pTmpData)
                        {
                            rRangeDataVec.push_back(pTmpData);
                            rSheetIndexMap.insert( std::make_pair( SheetIndex( nOldSheet, pCopyData->GetIndex()),
                                        SheetIndex( nNewSheet, pTmpData->GetIndex())));
                        }
                    }
                }
            }
        }
    }
    return pRangeData;
}

}   // namespace

bool ScDocument::CopyAdjustRangeName( SCTAB& rSheet, sal_uInt16& rIndex, ScRangeData*& rpRangeData,
        ScDocument& rNewDoc, const ScAddress& rNewPos, const ScAddress& rOldPos, const bool bGlobalNamesToLocal,
        const bool bUsedByFormula ) const
{
    const bool bSameDoc = (rNewDoc.GetPool() == const_cast<ScDocument*>(this)->GetPool());
    if (bSameDoc && ((rSheet < 0 && !bGlobalNamesToLocal) || (rSheet >= 0 && rSheet != rOldPos.Tab())))
        // Same doc and global name, if not copied to local name, or
        // sheet-local name on other sheet stays the same.
        return false;

    // Ensure we don't fiddle with the references until exit.
    const SCTAB nOldSheet = rSheet;
    const sal_uInt16 nOldIndex = rIndex;

    SAL_WARN_IF( !bSameDoc && nOldSheet >= 0 && nOldSheet != rOldPos.Tab(),
            "sc.core", "adjustCopyRangeName - sheet-local name was on other sheet in other document");
    /* TODO: can we do something about that? e.g. loop over sheets? */

    OUString aRangeName;
    ScRangeData* pOldRangeData = nullptr;

    // XXX bGlobalNamesToLocal is also a synonym for copied sheet.
    bool bInsertingBefore = (bGlobalNamesToLocal && bSameDoc && rNewPos.Tab() <= rOldPos.Tab());

    // The Tab where an old local name is to be found or that a global name
    // references. May differ below from nOldSheet if a sheet was inserted
    // before the old position. Global names and local names other than on the
    // old sheet or new sheet are already updated, local names on the old sheet
    // or inserted sheet will be updated later. Confusing stuff. Watch out.
    SCTAB nOldTab = (nOldSheet < 0 ? rOldPos.Tab() : nOldSheet);
    if (bInsertingBefore)
        // Sheet was already inserted before old position.
        ++nOldTab;

    // Search the name of the RangeName.
    if (nOldSheet >= 0)
    {
        const ScRangeName* pNames = GetRangeName(nOldTab);
        pOldRangeData = pNames ? pNames->findByIndex(nOldIndex) : nullptr;
        if (!pOldRangeData)
            return false;     // might be an error in the formula array
        aRangeName = pOldRangeData->GetUpperName();
    }
    else
    {
        pOldRangeData = GetRangeName()->findByIndex(nOldIndex);
        if (!pOldRangeData)
            return false;     // might be an error in the formula array
        aRangeName = pOldRangeData->GetUpperName();
    }

    // Find corresponding range name in new document.
    // First search for local range name then global range names.
    SCTAB nNewSheet = rNewPos.Tab();
    ScRangeName* pNewNames = rNewDoc.GetRangeName(nNewSheet);
    // Search local range names.
    if (pNewNames)
    {
        rpRangeData = pNewNames->findByUpperName(aRangeName);
    }
    // Search global range names.
    if (!rpRangeData && !bGlobalNamesToLocal)
    {
        nNewSheet = -1;
        pNewNames = rNewDoc.GetRangeName();
        if (pNewNames)
            rpRangeData = pNewNames->findByUpperName(aRangeName);
    }
    // If no range name was found copy it.
    if (!rpRangeData)
    {
        // Do not copy global name if it doesn't reference sheet or is not used
        // by a formula copied to another document.
        bool bEarlyBailOut = (nOldSheet < 0 && (bSameDoc || !bUsedByFormula));
        MightReferenceSheet eMightReference = mightRangeNameReferenceSheet( pOldRangeData, nOldTab);
        if (bEarlyBailOut && eMightReference == MightReferenceSheet::NONE)
            return false;

        if (eMightReference == MightReferenceSheet::NAME)
        {
            // Name these to clarify what is passed where.
            const SCTAB nGlobalRefTab = nOldTab;
            const SCTAB nLocalRefTab = (bInsertingBefore ? nOldTab-1 : nOldTab);
            const SCTAB nOldTokenTab = (nOldSheet < 0 ? (bInsertingBefore ? nOldTab-1 : nOldTab) : nOldSheet);
            const SCTAB nOldTokenTabReplacement = nOldTab;
            sc::UpdatedRangeNames aReferencingNames;
            FindRangeNamesReferencingSheet( aReferencingNames, nOldSheet, nOldIndex,
                    nGlobalRefTab, nLocalRefTab, nOldTokenTab, nOldTokenTabReplacement, bSameDoc, 0);
            if (bEarlyBailOut && aReferencingNames.isEmpty(-1) && aReferencingNames.isEmpty(nOldTokenTabReplacement))
                return false;

            SheetIndexMap aSheetIndexMap;
            std::vector<ScRangeData*> aRangeDataVec;
            if (!aReferencingNames.isEmpty(nOldTokenTabReplacement))
            {
                const SCTAB nTmpOldSheet = (nOldSheet < 0 ? nOldTab : nOldSheet);
                nNewSheet = rNewPos.Tab();
                rpRangeData = copyRangeNames( aSheetIndexMap, aRangeDataVec, aReferencingNames, nOldTab,
                        pOldRangeData, rNewDoc, this, rNewPos, rOldPos,
                        bGlobalNamesToLocal, nTmpOldSheet, nNewSheet, bSameDoc);
            }
            if ((bGlobalNamesToLocal || !bSameDoc) && !aReferencingNames.isEmpty(-1))
            {
                const SCTAB nTmpOldSheet = -1;
                const SCTAB nTmpNewSheet = (bGlobalNamesToLocal ? rNewPos.Tab() : -1);
                ScRangeData* pTmpData = copyRangeNames( aSheetIndexMap, aRangeDataVec, aReferencingNames, -1,
                        pOldRangeData, rNewDoc, this, rNewPos, rOldPos,
                        bGlobalNamesToLocal, nTmpOldSheet, nTmpNewSheet, bSameDoc);
                if (!rpRangeData)
                {
                    rpRangeData = pTmpData;
                    nNewSheet = nTmpNewSheet;
                }
            }

            // Adjust copied nested names to new sheet/index.
            for (auto & iRD : aRangeDataVec)
            {
                ScTokenArray* pCode = iRD->GetCode();
                if (pCode)
                {
                    formula::FormulaTokenArrayPlainIterator aIter(*pCode);
                    for (formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
                    {
                        if (p->GetOpCode() == ocName)
                        {
                            auto it = aSheetIndexMap.find( SheetIndex( p->GetSheet(), p->GetIndex()));
                            if (it != aSheetIndexMap.end())
                            {
                                p->SetSheet( it->second.mnSheet);
                                p->SetIndex( it->second.mnIndex);
                            }
                            else if (!bSameDoc)
                            {
                                SAL_WARN("sc.core","adjustCopyRangeName - mapping to new name in other doc missing");
                                p->SetIndex(0);     // #NAME? error instead of arbitrary name.
                            }
                        }
                    }
                }
            }
        }
        else
        {
            nNewSheet = ((nOldSheet < 0 && !bGlobalNamesToLocal) ? -1 : rNewPos.Tab());
            rpRangeData = copyRangeName( pOldRangeData, rNewDoc, this, rNewPos, rOldPos, bGlobalNamesToLocal,
                    nOldSheet, nNewSheet, bSameDoc);
        }
    }
    rSheet = nNewSheet;
    rIndex = rpRangeData ? rpRangeData->GetIndex() : 0;     // 0 means not inserted
    return true;
}

bool ScDocument::IsEditActionAllowed(
    sc::ColRowEditAction eAction, SCTAB nTab, SCCOLROW nStart, SCCOLROW nEnd ) const
{
    const ScTable* pTab = FetchTable(nTab);
    if (!pTab)
        return false;

    return pTab->IsEditActionAllowed(eAction, nStart, nEnd);
}

bool ScDocument::IsEditActionAllowed(
    sc::ColRowEditAction eAction, const ScMarkData& rMark, SCCOLROW nStart, SCCOLROW nEnd ) const
{
    return std::all_of(rMark.begin(), rMark.end(),
        [this, &eAction, &nStart, &nEnd](const SCTAB& rTab) { return IsEditActionAllowed(eAction, rTab, nStart, nEnd); });
}

std::unique_ptr<sc::ColumnIterator> ScDocument::GetColumnIterator( SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2 ) const
{
    const ScTable* pTab = FetchTable(nTab);
    if (!pTab)
        return std::unique_ptr<sc::ColumnIterator>();

    return pTab->GetColumnIterator(nCol, nRow1, nRow2);
}

bool ScDocument::EnsureFormulaCellResults( const ScRange& rRange, bool bSkipRunning )
{
    bool bAnyDirty = false;
    for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
    {
        ScTable* pTab = FetchTable(nTab);
        if (!pTab)
            continue;

        bool bRet = pTab->EnsureFormulaCellResults(
            rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), bSkipRunning);
        bAnyDirty = bAnyDirty || bRet;
    }

    return bAnyDirty;
}

sc::ExternalDataMapper& ScDocument::GetExternalDataMapper()
{
    if (!mpDataMapper)
        mpDataMapper.reset(new sc::ExternalDataMapper(this));

    return *mpDataMapper;
}

void ScDocument::StoreTabToCache(SCTAB nTab, SvStream& rStrm) const
{
    const ScTable* pTab = FetchTable(nTab);
    if (!pTab)
        return;

    pTab->StoreToCache(rStrm);
}

void ScDocument::RestoreTabFromCache(SCTAB nTab, SvStream& rStrm)
{
    ScTable* pTab = FetchTable(nTab);
    if (!pTab)
        return;

    pTab->RestoreFromCache(rStrm);
}

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