/* -*- 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 <utility>
#include <workbookfragment.hxx>

#include <oox/core/filterbase.hxx>
#include <oox/core/xmlfilterbase.hxx>
#include <oox/drawingml/themefragmenthandler.hxx>
#include <oox/helper/attributelist.hxx>
#include <oox/helper/binaryinputstream.hxx>
#include <oox/helper/progressbar.hxx>
#include <oox/ole/olestorage.hxx>
#include <oox/token/namespaces.hxx>
#include <oox/token/tokens.hxx>

#include <chartsheetfragment.hxx>
#include <connectionsfragment.hxx>
#include <externallinkbuffer.hxx>
#include <externallinkfragment.hxx>
#include <formulabuffer.hxx>
#include <pivotcachebuffer.hxx>
#include <sharedstringsfragment.hxx>
#include <revisionfragment.hxx>
#include <stylesfragment.hxx>
#include <tablebuffer.hxx>
#include <themebuffer.hxx>
#include <viewsettings.hxx>
#include <workbooksettings.hxx>
#include <worksheetbuffer.hxx>
#include <worksheethelper.hxx>
#include <worksheetfragment.hxx>
#include <extlstcontext.hxx>
#include <documentimport.hxx>
#include <biffhelper.hxx>

#include <document.hxx>
#include <drwlayer.hxx>
#include <docsh.hxx>
#include <calcconfig.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <scmod.hxx>
#include <formulaopt.hxx>

#include <vcl/svapp.hxx>
#include <vcl/timer.hxx>
#include <vcl/weld.hxx>

#include <config_emscripten.h>
#include <oox/core/fastparser.hxx>
#include <svx/svdpage.hxx>
#include <comphelper/threadpool.hxx>
#include <sal/log.hxx>
#include <osl/diagnose.h>

#include <memory>

#include <oox/ole/vbaproject.hxx>

#include <comphelper/processfactory.hxx>
#include <officecfg/Office/Calc.hxx>

#include <xestream.hxx>
#include <ucbhelper/content.hxx>

namespace oox::xls {

using namespace ::com::sun::star::io;
using namespace ::com::sun::star::table;
using namespace ::com::sun::star::uno;
using namespace ::oox::core;

using ::oox::drawingml::ThemeFragmentHandler;

namespace {

const double PROGRESS_LENGTH_GLOBALS        = 0.1;      /// 10% of progress bar for globals import.

} // namespace

WorkbookFragment::WorkbookFragment( const WorkbookHelper& rHelper, const OUString& rFragmentPath ) :
    WorkbookFragmentBase( rHelper, rFragmentPath )
{
}

ContextHandlerRef WorkbookFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
{
    switch( getCurrentElement() )
    {
        case XML_ROOT_CONTEXT:
            if( nElement == XLS_TOKEN( workbook ) ) return this;
        break;

        case XLS_TOKEN( workbook ):
            switch( nElement )
            {
                case XLS_TOKEN( sheets ):
                case XLS_TOKEN( bookViews ):
                case XLS_TOKEN( externalReferences ):
                case XLS_TOKEN( definedNames ):
                case XLS_TOKEN( pivotCaches ):          return this;

                case XLS_TOKEN( fileSharing ):          getWorkbookSettings().importFileSharing( rAttribs );    break;
                case XLS_TOKEN( fileVersion ):          getWorkbookSettings().importFileVersion( rAttribs );    break;
                case XLS_TOKEN( workbookPr ):           getWorkbookSettings().importWorkbookPr( rAttribs );     break;
                case XLS_TOKEN( calcPr ):               getWorkbookSettings().importCalcPr( rAttribs );         break;
                case XLS_TOKEN( oleSize ):              getViewSettings().importOleSize( rAttribs );            break;

                case XLS_TOKEN( extLst ):               return new ExtLstGlobalWorkbookContext( *this );
            }
        break;

        case XLS_TOKEN( sheets ):
            if( nElement == XLS_TOKEN( sheet ) ) getWorksheets().importSheet( rAttribs );
        break;
        case XLS_TOKEN( bookViews ):
            if( nElement == XLS_TOKEN( workbookView ) ) getViewSettings().importWorkbookView( rAttribs );
        break;
        case XLS_TOKEN( externalReferences ):
            if( nElement == XLS_TOKEN( externalReference ) ) importExternalReference( rAttribs );
        break;
        case XLS_TOKEN( definedNames ):
            if( nElement == XLS_TOKEN( definedName ) ) { importDefinedName( rAttribs ); return this; } // collect formula
        break;
        case XLS_TOKEN( pivotCaches ):
            if( nElement == XLS_TOKEN( pivotCache ) ) importPivotCache( rAttribs );
        break;
    }
    return nullptr;
}

void WorkbookFragment::onCharacters( const OUString& rChars )
{
    if( isCurrentElement( XLS_TOKEN( definedName ) ) && mxCurrName )
        mxCurrName->setFormula( rChars );
}

ContextHandlerRef WorkbookFragment::onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm )
{
    switch( getCurrentElement() )
    {
        case XML_ROOT_CONTEXT:
            if( nRecId == BIFF12_ID_WORKBOOK ) return this;
        break;

        case BIFF12_ID_WORKBOOK:
            switch( nRecId )
            {
                case BIFF12_ID_SHEETS:
                case BIFF12_ID_BOOKVIEWS:
                case BIFF12_ID_EXTERNALREFS:
                case BIFF12_ID_PIVOTCACHES:     return this;

                case BIFF12_ID_FILESHARING:     getWorkbookSettings().importFileSharing( rStrm );   break;
                case BIFF12_ID_WORKBOOKPR:      getWorkbookSettings().importWorkbookPr( rStrm );    break;
                case BIFF12_ID_CALCPR:          getWorkbookSettings().importCalcPr( rStrm );        break;
                case BIFF12_ID_OLESIZE:         getViewSettings().importOleSize( rStrm );           break;
                case BIFF12_ID_DEFINEDNAME:     getDefinedNames().importDefinedName( rStrm );       break;
            }
        break;

        case BIFF12_ID_SHEETS:
            if( nRecId == BIFF12_ID_SHEET ) getWorksheets().importSheet( rStrm );
        break;
        case BIFF12_ID_BOOKVIEWS:
            if( nRecId == BIFF12_ID_WORKBOOKVIEW ) getViewSettings().importWorkbookView( rStrm );
        break;

        case BIFF12_ID_EXTERNALREFS:
            switch( nRecId )
            {
                case BIFF12_ID_EXTERNALREF:     importExternalRef( rStrm );                         break;
                case BIFF12_ID_EXTERNALSELF:    getExternalLinks().importExternalSelf( rStrm );     break;
                case BIFF12_ID_EXTERNALSAME:    getExternalLinks().importExternalSame( rStrm );     break;
                case BIFF12_ID_EXTERNALADDIN:   getExternalLinks().importExternalAddin( rStrm );    break;
                case BIFF12_ID_EXTERNALSHEETS:  getExternalLinks().importExternalSheets( rStrm );   break;
            }
        break;

        case BIFF12_ID_PIVOTCACHES:
            if( nRecId == BIFF12_ID_PIVOTCACHE ) importPivotCache( rStrm );
    }
    return nullptr;
}

const RecordInfo* WorkbookFragment::getRecordInfos() const
{
    static const RecordInfo spRecInfos[] =
    {
        { BIFF12_ID_BOOKVIEWS,      BIFF12_ID_BOOKVIEWS + 1         },
        { BIFF12_ID_EXTERNALREFS,   BIFF12_ID_EXTERNALREFS + 1      },
        { BIFF12_ID_FUNCTIONGROUPS, BIFF12_ID_FUNCTIONGROUPS + 2    },
        { BIFF12_ID_PIVOTCACHE,     BIFF12_ID_PIVOTCACHE + 1        },
        { BIFF12_ID_PIVOTCACHES,    BIFF12_ID_PIVOTCACHES + 1       },
        { BIFF12_ID_SHEETS,         BIFF12_ID_SHEETS + 1            },
        { BIFF12_ID_WORKBOOK,       BIFF12_ID_WORKBOOK + 1          },
        { -1,                       -1                              }
    };
    return spRecInfos;
}

namespace {

typedef std::pair<WorksheetGlobalsRef, FragmentHandlerRef> SheetFragmentHandler;
typedef std::vector<SheetFragmentHandler> SheetFragmentVector;

class WorkerThread : public comphelper::ThreadTask
{
    sal_Int32 &mrSheetsLeft;
    WorkbookFragment& mrWorkbookHandler;
    rtl::Reference<FragmentHandler> mxHandler;

public:
    WorkerThread( const std::shared_ptr<comphelper::ThreadTaskTag> & pTag,
                  WorkbookFragment& rWorkbookHandler,
                  rtl::Reference<FragmentHandler> xHandler,
                  sal_Int32 &rSheetsLeft ) :
        comphelper::ThreadTask( pTag ),
        mrSheetsLeft( rSheetsLeft ),
        mrWorkbookHandler( rWorkbookHandler ),
        mxHandler(std::move( xHandler ))
    {
    }

    virtual void doWork() override
    {
        // We hold the solar mutex in all threads except for
        // the small safe section of the inner loop in
        // sheetdatacontext.cxx
        SAL_INFO( "sc.filter",  "start wait on solar" );
        SolarMutexGuard aGuard;
        SAL_INFO( "sc.filter",  "got solar" );

        std::unique_ptr<oox::core::FastParser> xParser(
                oox::core::XmlFilterBase::createParser() );

        SAL_INFO( "sc.filter",  "start import" );
        mrWorkbookHandler.importOoxFragment( mxHandler, *xParser );
        SAL_INFO( "sc.filter",  "end import, release solar" );
        mrSheetsLeft--;
        assert( mrSheetsLeft >= 0 );
        if( mrSheetsLeft == 0 )
            Application::EndYield();
    }
};

class ProgressBarTimer : private Timer
{
    // FIXME: really we should unify all sheet loading
    // progress reporting into something pleasant.
    class ProgressWrapper : public ISegmentProgressBar
    {
        double mfPosition;
        ISegmentProgressBarRef mxWrapped;
    public:
        explicit ProgressWrapper(ISegmentProgressBarRef xRef)
            : mfPosition(0.0)
            , mxWrapped(std::move(xRef))
        {
        }

        // IProgressBar
        virtual double getPosition() const override { return mfPosition; }
        virtual void   setPosition( double fPosition ) override { mfPosition = fPosition; }
        // ISegmentProgressBar
        virtual double getFreeLength() const override { return 0.0; }
        virtual ISegmentProgressBarRef createSegment( double /* fLength */ ) override
        {
            return ISegmentProgressBarRef();
        }
        void UpdateBar()
        {
            mxWrapped->setPosition( mfPosition );
        }
    };
    std::vector< ISegmentProgressBarRef > aSegments;
public:
    ProgressBarTimer() : Timer("sc ProgressBarTimer")
    {
        SetTimeout( 500 );
    }
    virtual ~ProgressBarTimer() override
    {
        aSegments.clear();
    }
    const ISegmentProgressBarRef& wrapProgress( const ISegmentProgressBarRef &xProgress )
    {
        aSegments.push_back( std::make_shared<ProgressWrapper>( xProgress ) );
        return aSegments.back();
    }
    virtual void Invoke() override
    {
        for(std::shared_ptr<ISegmentProgressBar> & pSegment : aSegments)
            static_cast< ProgressWrapper *>( pSegment.get() )->UpdateBar();
    }
};

void importSheetFragments( WorkbookFragment& rWorkbookHandler, SheetFragmentVector& rSheets )
{
    rWorkbookHandler.getDocImport().initForSheets();

    // test sequential read in this mode
    comphelper::ThreadPool &rSharedPool = comphelper::ThreadPool::getSharedOptimalPool();
    std::shared_ptr<comphelper::ThreadTaskTag> pTag = comphelper::ThreadPool::createThreadTaskTag();

    sal_Int32 nSheetsLeft = 0;
    ProgressBarTimer aProgressUpdater;
    for( auto& [rxSheetGlob, rxFragment] : rSheets )
    {
         // getting at the WorksheetGlobals is rather unpleasant
         IWorksheetProgress *pProgress = WorksheetHelper::getWorksheetInterface( rxSheetGlob );
         pProgress->setCustomRowProgress(
                     aProgressUpdater.wrapProgress(
                             pProgress->getRowProgress() ) );
         rSharedPool.pushTask( std::make_unique<WorkerThread>( pTag, rWorkbookHandler, rxFragment,
                                           /* ref */ nSheetsLeft ) );
         nSheetsLeft++;
    }

#if defined EMSCRIPTEN && !HAVE_EMSCRIPTEN_JSPI
    // Hack around Application::Yield() deliberately calling std::abort() in the standard (non-JSPI)
    // Emscripten case; so instead of yielding (which implicitly releases the SolarMutex), just
    // release the SolarMutex so that the WorkerThreads can proceed:
    SolarMutexReleaser rel;
#else
    // coverity[loop_top] - this isn't an infinite loop where nSheetsLeft gets decremented by the above threads
    while( nSheetsLeft > 0 && !Application::IsQuit())
    {
        // This is a much more controlled re-enterancy hazard than
        // allowing a yield deeper inside the filter code for progress
        // bar updating.
        Application::Yield();
    }
#endif
    rSharedPool.waitUntilDone(pTag);

    // threads joined in ThreadPool destructor
}

}

void WorkbookFragment::finalizeImport()
{
    ISegmentProgressBarRef xGlobalSegment = getProgressBar().createSegment( PROGRESS_LENGTH_GLOBALS );

    // read the theme substream
    OUString aThemeFragmentPath = getFragmentPathFromFirstTypeFromOfficeDoc( u"theme" );
    auto& rOoxTheme = getTheme();

    auto pTheme = rOoxTheme.oox::drawingml::Theme::getTheme(); // needed full name here because a conflict with WorkbookHelper and Theme in ThemeBuffer
    if (!pTheme)
    {
        pTheme = std::make_shared<model::Theme>();
        rOoxTheme.setTheme(pTheme);
    }

    if( !aThemeFragmentPath.isEmpty() )
        importOoxFragment(new ThemeFragmentHandler(getFilter(), aThemeFragmentPath, rOoxTheme, *pTheme));
    xGlobalSegment->setPosition( 0.25 );

    // read the styles substream (requires finalized theme buffer)
    OUString aStylesFragmentPath = getFragmentPathFromFirstTypeFromOfficeDoc( u"styles" );
    if( !aStylesFragmentPath.isEmpty() )
        importOoxFragment( new StylesFragment( *this, aStylesFragmentPath ) );
    xGlobalSegment->setPosition( 0.5 );

    // read the shared string table substream (requires finalized styles buffer)
    OUString aSstFragmentPath = getFragmentPathFromFirstTypeFromOfficeDoc( u"sharedStrings" );
    if( !aSstFragmentPath.isEmpty() )
        if (!importOoxFragment( new SharedStringsFragment( *this, aSstFragmentPath ) ))
            importOoxFragment(new SharedStringsFragment(*this, aSstFragmentPath.replaceFirst("sharedStrings","SharedStrings")));
    xGlobalSegment->setPosition( 0.75 );

    // read the connections substream
    OUString aConnFragmentPath = getFragmentPathFromFirstTypeFromOfficeDoc( u"connections" );
    if( !aConnFragmentPath.isEmpty() )
    {
        importOoxFragment( new ConnectionsFragment( *this, aConnFragmentPath ) );
        getScDocument().setHasConnectionXml(true);
    }

    // read the customXml substream
    OUString aCustomXmlFragmentPath = getFragmentPathFromFirstTypeFromOfficeDoc( u"customXml" );
    if (!aCustomXmlFragmentPath.isEmpty())
        getScDocument().setHasCustomXml(true, aCustomXmlFragmentPath);

    // read the xmlMaps substream (from xl/_rels/workbook.xml.rels)
    OUString aXmlMapsFragmentPath = getFragmentPathFromFirstTypeFromOfficeDoc( u"xmlMaps" );
    if (!aXmlMapsFragmentPath.isEmpty())
    {
        // read xmlMaps.xml
        std::string sXmlMapsContent;
        size_t nBufferSize = 4096;

        Reference<XInputStream> xXmlMapsInputStream(
            getBaseFilter().openInputStream(aXmlMapsFragmentPath), UNO_SET_THROW);

        std::ostringstream aStrmBuf;
        Sequence<sal_Int8> aBytes;
        size_t nBytesRead = 0;

        do
        {
            nBytesRead = xXmlMapsInputStream->readBytes(aBytes, nBufferSize);
            const sal_Int8* p = aBytes.getConstArray();
            aStrmBuf << std::string(p, p + nBytesRead);
        } while (nBytesRead == nBufferSize);

        // put raw content of xmlMaps.xml into sXmlMapsContent
        sXmlMapsContent = aStrmBuf.str();

        if (!sXmlMapsContent.empty())
            getScDocument().setHasXmlMaps(true, sXmlMapsContent);
    }

    xGlobalSegment->setPosition( 1.0 );

    /*  Create fragments for all sheets, before importing them. Needed to do
        some preprocessing in the fragment constructors, e.g. loading the table
        fragments for all sheets that are needed before the cell formulas are
        loaded. Additionally, the instances of the WorkbookGlobals structures
        have to be stored for every sheet. */
    SheetFragmentVector aSheetFragments;
    std::vector<WorksheetHelper*> aHelpers;
    WorksheetBuffer& rWorksheets = getWorksheets();
    sal_Int32 nWorksheetCount = rWorksheets.getWorksheetCount();
    for( sal_Int32 nWorksheet = 0; nWorksheet < nWorksheetCount; ++nWorksheet )
    {
        sal_Int16 nCalcSheet = rWorksheets.getCalcSheetIndex( nWorksheet );
        const Relation* pRelation = getRelations().getRelationFromRelId( rWorksheets.getWorksheetRelId( nWorksheet ) );
        if( (nCalcSheet >= 0) && pRelation )
        {
            // get fragment path of the sheet
            OUString aFragmentPath = getFragmentPathFromRelation( *pRelation );
            OSL_ENSURE( !aFragmentPath.isEmpty(), "WorkbookFragment::finalizeImport - cannot access sheet fragment" );
            if( !aFragmentPath.isEmpty() )
            {
                // leave space for formula processing ( calculate the segments as
                // if there is an extra sheet )
                double fSegmentLength = getProgressBar().getFreeLength() / (nWorksheetCount - ( nWorksheet - 1) );
                ISegmentProgressBarRef xSheetSegment = getProgressBar().createSegment( fSegmentLength );

                // get the sheet type according to the relations type
                WorksheetType eSheetType = WorksheetType::Empty;
                if( pRelation->maType == CREATE_OFFICEDOC_RELATION_TYPE( "worksheet" ) ||
                        pRelation->maType == CREATE_OFFICEDOC_RELATION_TYPE_STRICT( "worksheet" ))
                    eSheetType = WorksheetType::Work;
                else if( pRelation->maType == CREATE_OFFICEDOC_RELATION_TYPE( "chartsheet" ) ||
                        pRelation->maType == CREATE_OFFICEDOC_RELATION_TYPE_STRICT( "chartsheet" ))
                    eSheetType = WorksheetType::Chart;
                else if( (pRelation->maType == CREATE_MSOFFICE_RELATION_TYPE( "xlMacrosheet" )) ||
                         (pRelation->maType == CREATE_MSOFFICE_RELATION_TYPE( "xlIntlMacrosheet" )) )
                    eSheetType = WorksheetType::Macro;
                else if( pRelation->maType == CREATE_OFFICEDOC_RELATION_TYPE( "dialogsheet" ) ||
                        pRelation->maType == CREATE_OFFICEDOC_RELATION_TYPE_STRICT(" dialogsheet" ))
                    eSheetType = WorksheetType::Dialog;
                OSL_ENSURE( eSheetType != WorksheetType::Empty, "WorkbookFragment::finalizeImport - unknown sheet type" );
                if( eSheetType != WorksheetType::Empty )
                {
                    // create the WorksheetGlobals object
                    WorksheetGlobalsRef xSheetGlob = WorksheetHelper::constructGlobals( *this, xSheetSegment, eSheetType, nCalcSheet );
                    OSL_ENSURE( xSheetGlob, "WorkbookFragment::finalizeImport - missing sheet in document" );
                    if( xSheetGlob )
                    {
                        // create the sheet fragment handler
                        ::rtl::Reference< WorksheetFragmentBase > xFragment;
                        switch( eSheetType )
                        {
                            case WorksheetType::Work:
                            case WorksheetType::Macro:
                            case WorksheetType::Dialog:
                                xFragment.set( new WorksheetFragment( *xSheetGlob, aFragmentPath ) );
                            break;
                            case WorksheetType::Chart:
                                xFragment.set( new ChartsheetFragment( *xSheetGlob, aFragmentPath ) );
                            break;
                            // coverity[dead_error_begin] - following conditions exist to avoid compiler warning
                            case WorksheetType::Empty:
                                break;
                        }

                        // insert the fragment into the map
                        if( xFragment.is() )
                        {
                            aSheetFragments.emplace_back( xSheetGlob, xFragment.get() );
                            aHelpers.push_back(xFragment.get());
                        }
                    }
                }
            }
        }
    }

    // setup structure sizes for the number of sheets
    getFormulaBuffer().SetSheetCount( nWorksheetCount );

    // create all database ranges and defined names, in that order
    getTables().finalizeImport();
    getTables().applyTableColumns();
    getDefinedNames().finalizeImport();
    // open the VBA project storage
    OUString aVbaFragmentPath = getFragmentPathFromFirstType( CREATE_MSOFFICE_RELATION_TYPE( "vbaProject" ) );
    if( !aVbaFragmentPath.isEmpty() )
    {
        Reference< XInputStream > xInStrm = getBaseFilter().openInputStream( aVbaFragmentPath );
        if( xInStrm.is() )
        {
            StorageRef xPrjStrg = std::make_shared<::oox::ole::OleStorage>( getBaseFilter().getComponentContext(), xInStrm, false );
            setVbaProjectStorage( xPrjStrg );
            getBaseFilter().getVbaProject().readVbaModules( *xPrjStrg );
        }
    }

    // lock the model to prevent broadcasting, speeds up load a lot
    getScDocument().InitDrawLayer();
    auto pModel = getScDocument().GetDrawLayer();
    bool bWasLocked = pModel->isLocked();
    pModel->setLock(true);

    // load all worksheets
    importSheetFragments(*this, aSheetFragments);

    if (pTheme && !pTheme->GetName().isEmpty())
    {
        pModel->setTheme(pTheme);
    }

    // assumes getTables().finalizeImport ( which creates the DatabaseRanges )
    // has been called already
    getTables().applyAutoFilters();

    sal_Int16 nActiveSheet = getViewSettings().getActiveCalcSheet();
    getWorksheets().finalizeImport( nActiveSheet );

    // final conversions, e.g. calculation settings and view settings
    finalizeWorkbookImport();

    //stop preventing establishment of listeners as is done in
    //ScDocShell::AfterXMLLoading() for ods
    getScDocument().SetInsertingFromOtherDoc(false);

    for( WorksheetHelper* pHelper : aHelpers )
    {
        pHelper->finalizeDrawingImport();
    }

    for( auto& [rxSheetGlob, rxFragment] : aSheetFragments )
    {
        // delete fragment object and WorkbookGlobals object, will free all allocated sheet buffers
        rxFragment.clear();
        rxSheetGlob.reset();
    }


    getDocImport().finalize();

    recalcFormulaCells();

    OUString aRevHeadersPath = getFragmentPathFromFirstType(CREATE_OFFICEDOC_RELATION_TYPE("revisionHeaders"));
    if (!aRevHeadersPath.isEmpty())
    {
        std::unique_ptr<oox::core::FastParser> xParser(oox::core::XmlFilterBase::createParser());
        rtl::Reference<oox::core::FragmentHandler> xFragment(new RevisionHeadersFragment(*this, aRevHeadersPath));
        importOoxFragment(xFragment, *xParser);
    }

    // attach macros to registered objects now that all objects have been created.
    getBaseFilter().getVbaProject().attachMacros();

    pModel->setLock(bWasLocked);
}

namespace {

ScDocShell& getDocShell(const ScDocument& rDoc)
{
    return static_cast<ScDocShell&>(*rDoc.GetDocumentShell());
}

class MessageWithCheck : public weld::MessageDialogController
{
private:
    std::unique_ptr<weld::CheckButton> m_xWarningOnBox;
public:
    MessageWithCheck(weld::Window *pParent, const OUString& rUIFile, const OUString& rDialogId)
        : MessageDialogController(pParent, rUIFile, rDialogId, u"ask"_ustr)
        , m_xWarningOnBox(m_xBuilder->weld_check_button(u"ask"_ustr))
    {
    }
    bool get_active() const { return m_xWarningOnBox->get_active(); }
    void hide_ask() const { m_xWarningOnBox->set_visible(false); };
};

}

void WorkbookFragment::recalcFormulaCells()
{
    // Recalculate formula cells.
    ScDocument& rDoc = getScDocument();
    ScDocShell& rDocSh = getDocShell(rDoc);
    ScRecalcOptions nRecalcMode =
        static_cast<ScRecalcOptions>(officecfg::Office::Calc::Formula::Load::OOXMLRecalcMode::get());
    bool bHardRecalc = false;
    if (nRecalcMode == RECALC_ASK)
    {
        if (rDoc.IsUserInteractionEnabled())
        {
            // Ask the user if full re-calculation is desired.
            MessageWithCheck aQueryBox(ScDocShell::GetActiveDialogParent(), u"modules/scalc/ui/recalcquerydialog.ui"_ustr, u"RecalcQueryDialog"_ustr);
            aQueryBox.set_primary_text(ScResId(STR_QUERY_FORMULA_RECALC_ONLOAD_XLS));
            aQueryBox.set_default_response(RET_YES);

            if ( officecfg::Office::Calc::Formula::Load::OOXMLRecalcMode::isReadOnly() )
                aQueryBox.hide_ask();

            bHardRecalc = aQueryBox.run() == RET_YES;

            if (aQueryBox.get_active())
            {
                // Always perform selected action in the future.
                std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
                officecfg::Office::Calc::Formula::Load::OOXMLRecalcMode::set(sal_Int32(0), batch);
                ScModule* mod = ScModule::get();
                ScFormulaOptions aOpt = mod->GetFormulaOptions();
                aOpt.SetOOXMLRecalcOptions(bHardRecalc ? RECALC_ALWAYS : RECALC_NEVER);
                /* XXX  is this really supposed to set the ScModule options?
                 *      Not the ScDocShell options? */
                mod->SetFormulaOptions(aOpt);

                batch->commit();
            }
        }
    }
    else if (nRecalcMode == RECALC_ALWAYS)
        bHardRecalc = true;
    else if (!hasCalculatedFormulaCells())
    {
        // Did not encounter any other formula result than 0.0 (or none / no
        // formula cells at all, in which case recalculation is almost a no-op)
        // in a non-known-good-generator document.
        bHardRecalc = true;
    }

    if (bHardRecalc)
        rDocSh.DoHardRecalc();
    else
    {
        getDocImport().broadcastRecalcAfterImport();
        // Full ScDocument::CalcFormulaTree() of all dirty cells is not
        // necessary here, the View will recalculate them in the visible area,
        // or any other method accessing formula cell results.
    }
}

// private --------------------------------------------------------------------

void WorkbookFragment::importExternalReference( const AttributeList& rAttribs )
{
    if( ExternalLink* pExtLink = getExternalLinks().importExternalReference( rAttribs ).get() )
        importExternalLinkFragment( *pExtLink );
}

void WorkbookFragment::importDefinedName( const AttributeList& rAttribs )
{
    mxCurrName = getDefinedNames().importDefinedName( rAttribs );
}

void WorkbookFragment::importPivotCache( const AttributeList& rAttribs )
{
    sal_Int32 nCacheId = rAttribs.getInteger( XML_cacheId, -1 );
    OUString aRelId = rAttribs.getString( R_TOKEN( id ), OUString() );
    importPivotCacheDefFragment( aRelId, nCacheId );
}

void WorkbookFragment::importExternalRef( SequenceInputStream& rStrm )
{
    if( ExternalLink* pExtLink = getExternalLinks().importExternalRef( rStrm ).get() )
        importExternalLinkFragment( *pExtLink );
}

void WorkbookFragment::importPivotCache( SequenceInputStream& rStrm )
{
    sal_Int32 nCacheId = rStrm.readInt32();
    OUString aRelId = BiffHelper::readString( rStrm );
    importPivotCacheDefFragment( aRelId, nCacheId );
}

void WorkbookFragment::importExternalLinkFragment( ExternalLink& rExtLink )
{
    OUString aFragmentPath = getFragmentPathFromRelId( rExtLink.getRelId() );
    if( !aFragmentPath.isEmpty() )
        importOoxFragment( new ExternalLinkFragment( *this, aFragmentPath, rExtLink ) );
}

void WorkbookFragment::importPivotCacheDefFragment( const OUString& rRelId, sal_Int32 nCacheId )
{
    // pivot caches will be imported on demand, here we just store the fragment path in the buffer
    getPivotCaches().registerPivotCacheFragment( nCacheId, getFragmentPathFromRelId( rRelId ) );
}

} // namespace oox

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