/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office 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 <externallinkbuffer.hxx>
#include <externalrefmgr.hxx>
#include <compiler.hxx>
#include <tokenarray.hxx>

#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/sheet/DDELinkInfo.hpp>
#include <com/sun/star/sheet/ExternalLinkRelType.hpp>
#include <com/sun/star/sheet/ExternalLinkType.hpp>
#include <com/sun/star/sheet/XDDELinks.hpp>
#include <com/sun/star/sheet/XDDELinkResults.hpp>
#include <com/sun/star/sheet/XExternalDocLinks.hpp>
#include <com/sun/star/sheet/XSpreadsheetDocument.hpp>

#include <comphelper/sequence.hxx>
#include <o3tl/safeint.hxx>
#include <osl/diagnose.h>
#include <sal/log.hxx>
#include <oox/core/filterbase.hxx>
#include <oox/helper/attributelist.hxx>
#include <oox/helper/binaryinputstream.hxx>
#include <oox/token/namespaces.hxx>
#include <oox/token/properties.hxx>
#include <oox/core/relations.hxx>
#include <oox/token/tokens.hxx>
#include <addressconverter.hxx>
#include <biffhelper.hxx>
#include <docuno.hxx>

namespace oox::xls {

using namespace ::com::sun::star::sheet;
using namespace ::com::sun::star::uno;
using namespace cpo::uno;

using ::oox::core::Relation;
using ::oox::core::Relations;

namespace {

const sal_uInt16 BIFF12_EXTERNALBOOK_BOOK   = 0;
const sal_uInt16 BIFF12_EXTERNALBOOK_DDE    = 1;
const sal_uInt16 BIFF12_EXTERNALBOOK_OLE    = 2;

const sal_uInt16 BIFF12_EXTNAME_AUTOMATIC   = 0x0002;
const sal_uInt16 BIFF12_EXTNAME_PREFERPIC   = 0x0004;
const sal_uInt16 BIFF12_EXTNAME_STDDOCNAME  = 0x0008;
const sal_uInt16 BIFF12_EXTNAME_OLEOBJECT   = 0x0010;
const sal_uInt16 BIFF12_EXTNAME_ICONIFIED   = 0x0020;

} // namespace

ExternalNameModel::ExternalNameModel() :
    mbNotify( false ),
    mbPreferPic( false ),
    mbStdDocName( false ),
    mbOleObj( false ),
    mbIconified( false )
{
}

ExternalName::ExternalName( const ExternalLink& rParentLink ) :
    DefinedNameBase( rParentLink ),
    mrParentLink( rParentLink ),
    mbDdeLinkCreated( false )
{
}

void ExternalName::importDefinedName( const AttributeList& rAttribs )
{
    maModel.maName = rAttribs.getXString( XML_name, OUString() );
    OSL_ENSURE( !maModel.maName.isEmpty(), "ExternalName::importDefinedName - empty name" );
    maModel.maFormula = rAttribs.getXString(XML_refersTo, OUString());
    // zero-based index into sheet list of externalBook
    maModel.mnSheet = rAttribs.getInteger( XML_sheetId, -1 );

    ScExternalRefManager* pRefMgr = getScDocument().GetExternalRefManager();
    sal_uInt16 nFileId = pRefMgr->getExternalFileId(mrParentLink.getTargetUrl());

    if (maModel.maFormula.isEmpty())
    {
        // Store the name with an empty token array so it survives roundtrip
        ScTokenArray aEmpty(getScDocument());
        pRefMgr->storeRangeNameTokens(nFileId, maModel.maName, aEmpty);
        return;
    }

    // cache external defined names and formulas
    ScCompiler aComp(getScDocument(), ScAddress(0, 0, maModel.mnSheet), formula::FormulaGrammar::GRAM_OOXML);
    aComp.SetExternalLinks(getExternalLinks().getLinkInfos());
    std::unique_ptr<ScTokenArray> pArray = aComp.CompileString(maModel.maFormula);
    FormulaError nErr = pArray->GetCodeError();
    aComp.CompileTokenArray();
    getScDocument().CheckLinkFormulaNeedingCheck(*pArray);
    pArray->DelRPN();
    pArray->SetCodeError(nErr);

    pRefMgr->storeRangeNameTokens(nFileId, maModel.maName, *pArray);
}

void ExternalName::importDdeItem( const AttributeList& rAttribs )
{
    maModel.maName = rAttribs.getXString( XML_name, OUString() );
    OSL_ENSURE( !maModel.maName.isEmpty(), "ExternalName::importDdeItem - empty name" );
    maExtNameModel.mbOleObj     = false;
    maExtNameModel.mbStdDocName = rAttribs.getBool( XML_ole, false );
    maExtNameModel.mbNotify     = rAttribs.getBool( XML_advise, false );
    maExtNameModel.mbPreferPic  = rAttribs.getBool( XML_preferPic, false );
}

void ExternalName::importValues( const AttributeList& rAttribs )
{
    setResultSize( rAttribs.getInteger( XML_cols, 1 ), rAttribs.getInteger( XML_rows, 1 ) );
}

void ExternalName::importOleItem( const AttributeList& rAttribs )
{
    maModel.maName = rAttribs.getXString( XML_name, OUString() );
    OSL_ENSURE( !maModel.maName.isEmpty(), "ExternalName::importOleItem - empty name" );
    maExtNameModel.mbOleObj    = true;
    maExtNameModel.mbNotify    = rAttribs.getBool( XML_advise, false );
    maExtNameModel.mbPreferPic = rAttribs.getBool( XML_preferPic, false );
    maExtNameModel.mbIconified = rAttribs.getBool( XML_icon, false );
}

void ExternalName::importExternalName( SequenceInputStream& rStrm )
{
    rStrm >> maModel.maName;
    OSL_ENSURE( !maModel.maName.isEmpty(), "ExternalName::importExternalName - empty name" );
}

void ExternalName::importExternalNameFlags( SequenceInputStream& rStrm )
{
    sal_uInt16 nFlags;
    sal_Int32 nSheetId;
    nFlags = rStrm.readuInt16();
    nSheetId = rStrm.readInt32();
    // index into sheet list of EXTSHEETNAMES (one-based in BIFF12)
    maModel.mnSheet = nSheetId - 1;
    // no flag for built-in names, as in OOXML...
    maExtNameModel.mbNotify     = getFlag( nFlags, BIFF12_EXTNAME_AUTOMATIC );
    maExtNameModel.mbPreferPic  = getFlag( nFlags, BIFF12_EXTNAME_PREFERPIC );
    maExtNameModel.mbStdDocName = getFlag( nFlags, BIFF12_EXTNAME_STDDOCNAME );
    maExtNameModel.mbOleObj     = getFlag( nFlags, BIFF12_EXTNAME_OLEOBJECT );
    maExtNameModel.mbIconified  = getFlag( nFlags, BIFF12_EXTNAME_ICONIFIED );
    OSL_ENSURE( (mrParentLink.getLinkType() == ExternalLinkType::OLE) == maExtNameModel.mbOleObj,
        "ExternalName::importExternalNameFlags - wrong OLE flag in external name" );
}

void ExternalName::importDdeItemValues( SequenceInputStream& rStrm )
{
    sal_Int32 nRows, nCols;
    nRows = rStrm.readInt32();
    nCols = rStrm.readInt32();
    setResultSize( nCols, nRows );
}

void ExternalName::importDdeItemBool( SequenceInputStream& rStrm )
{
    appendResultValue< double >( (rStrm.readuInt8() == 0) ? 0.0 : 1.0 );
}

void ExternalName::importDdeItemDouble( SequenceInputStream& rStrm )
{
    appendResultValue( rStrm.readDouble() );
}

void ExternalName::importDdeItemError( SequenceInputStream& rStrm )
{
    appendResultValue( BiffHelper::calcDoubleFromError( rStrm.readuInt8() ) );
}

void ExternalName::importDdeItemString( SequenceInputStream& rStrm )
{
    appendResultValue( BiffHelper::readString( rStrm ) );
}

bool ExternalName::getDdeItemInfo( DDEItemInfo& orItemInfo ) const
{
    if( (mrParentLink.getLinkType() == ExternalLinkType::DDE) && !maModel.maName.isEmpty() )
    {
        orItemInfo.Item = maModel.maName;
        orItemInfo.Results = ContainerHelper::matrixToSequenceSequence( maResults );
        return true;
    }
    return false;
}

bool ExternalName::getDdeLinkData( OUString& orDdeServer, OUString& orDdeTopic, OUString& orDdeItem )
{
    if( (mrParentLink.getLinkType() == ExternalLinkType::DDE) && !maModel.maName.isEmpty() )
    {
        // try to create a DDE link and to set the imported link results
        if( !mbDdeLinkCreated ) try
        {
            PropertySet aDocProps(( Reference< css::beans::XPropertySet >(getDocument()) ));
            Reference< XDDELinks > xDdeLinks( aDocProps.getAnyProperty( PROP_DDELinks ), UNO_QUERY_THROW );
            mxDdeLink = xDdeLinks->addDDELink( mrParentLink.getClassName(), mrParentLink.getTargetUrl(), maModel.maName, css::sheet::DDELinkMode_DEFAULT );
            mbDdeLinkCreated = true;    // ignore if setting results fails
            if( !maResults.empty() )
            {
                Reference< XDDELinkResults > xResults( mxDdeLink, UNO_QUERY_THROW );
                xResults->setResults( ContainerHelper::matrixToSequenceSequence( maResults ) );
            }
        }
        catch( Exception& )
        {
            OSL_FAIL( "ExternalName::getDdeLinkData - cannot create DDE link" );
        }
        // get link data from created DDE link
        if( mxDdeLink.is() )
        {
            orDdeServer = mxDdeLink->getApplication();
            orDdeTopic = mxDdeLink->getTopic();
            orDdeItem = mxDdeLink->getItem();
            return true;
        }
    }
    return false;
}

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

void ExternalName::setResultSize( sal_Int32 nColumns, sal_Int32 nRows )
{
    OSL_ENSURE( (mrParentLink.getLinkType() == ExternalLinkType::DDE) || (mrParentLink.getLinkType() == ExternalLinkType::OLE),
                "ExternalName::setResultSize - wrong link type" );
    OSL_ENSURE( (nRows > 0) && (nColumns > 0), "ExternalName::setResultSize - invalid matrix size" );
    const ScAddress& rMaxPos = getAddressConverter().getMaxApiAddress();
    if( (0 < nRows) && (nRows <= rMaxPos.Row() + 1) && (0 < nColumns) && (nColumns <= rMaxPos.Col() + 1) )
        maResults.resize( static_cast< size_t >( nColumns ), static_cast< size_t >( nRows ), Any( BiffHelper::calcDoubleFromError( BIFF_ERR_NA ) ) );
    else
        maResults.clear();
    maCurrIt = maResults.begin();
}

void LinkSheetRange::setDeleted()
{
    meType = LINKSHEETRANGE_INTERNAL;
    mnDocLink = mnFirst = mnLast = -1;
}

void LinkSheetRange::setSameSheet()
{
    meType = LINKSHEETRANGE_SAMESHEET;
    mnDocLink = -1;
    mnFirst = mnLast = 0;
}

void LinkSheetRange::setRange( sal_Int32 nFirst, sal_Int32 nLast )
{
    meType = LINKSHEETRANGE_INTERNAL;
    mnDocLink = -1;
    mnFirst = ::std::min( nFirst, nLast );
    mnLast = ::std::max( nFirst, nLast );
}

void LinkSheetRange::setExternalRange( sal_Int32 nDocLink, sal_Int32 nFirst, sal_Int32 nLast )
{
    if( nDocLink < 0 )
    {
        setDeleted();
    }
    else
    {
        meType = LINKSHEETRANGE_EXTERNAL;
        mnDocLink = nDocLink;
        mnFirst = ::std::min( nFirst, nLast );
        mnLast = ::std::max( nFirst, nLast );
    }
}

ExternalLink::ExternalLink( const WorkbookHelper& rHelper ) :
    WorkbookHelper( rHelper ),
    meLinkType( ExternalLinkType::Unknown ),
    meFuncLibType( FUNCLIB_UNKNOWN )
{
}

void ExternalLink::importExternalReference( const AttributeList& rAttribs )
{
    maRelId = rAttribs.getString( R_TOKEN( id ), OUString() );
}

void ExternalLink::importExternalBook( const Relations& rRelations, const AttributeList& rAttribs )
{
    parseExternalReference( rRelations, rAttribs.getString( R_TOKEN( id ), OUString() ) );
}

void ExternalLink::importSheetName( const AttributeList& rAttribs )
{
    insertExternalSheet( rAttribs.getXString( XML_val, OUString() ) );
}

void ExternalLink::importDefinedName( const AttributeList& rAttribs )
{
    createExternalName()->importDefinedName( rAttribs );
}

void ExternalLink::importDdeLink( const AttributeList& rAttribs )
{
    OUString aDdeService = rAttribs.getXString( XML_ddeService, OUString() );
    OUString aDdeTopic = rAttribs.getXString( XML_ddeTopic, OUString() );
    setDdeOleTargetUrl( aDdeService, aDdeTopic, ExternalLinkType::DDE );
}

ExternalNameRef ExternalLink::importDdeItem( const AttributeList& rAttribs )
{
    ExternalNameRef xExtName = createExternalName();
    xExtName->importDdeItem( rAttribs );
    return xExtName;
}

void ExternalLink::importOleLink( const Relations& rRelations, const AttributeList& rAttribs )
{
    OUString aProgId = rAttribs.getXString( XML_progId, OUString() );
    OUString aTargetUrl = rRelations.getExternalTargetFromRelId( rAttribs.getString( R_TOKEN( id ), OUString() ) );
    setDdeOleTargetUrl( aProgId, aTargetUrl, ExternalLinkType::OLE );
}

ExternalNameRef ExternalLink::importOleItem( const AttributeList& rAttribs )
{
    ExternalNameRef xExtName = createExternalName();
    xExtName->importOleItem( rAttribs );
    return xExtName;
}

void ExternalLink::importExternalRef( SequenceInputStream& rStrm )
{
    rStrm >> maRelId;
}

void ExternalLink::importExternalSelf( SequenceInputStream& )
{
    meLinkType = ExternalLinkType::Self;
}

void ExternalLink::importExternalSame( SequenceInputStream& )
{
    meLinkType = ExternalLinkType::Same;
}

void ExternalLink::importExternalAddin( SequenceInputStream& )
{
    meLinkType = ExternalLinkType::Unknown;
}

void ExternalLink::importExternalBook( const Relations& rRelations, SequenceInputStream& rStrm )
{
    switch( rStrm.readuInt16() )
    {
        case BIFF12_EXTERNALBOOK_BOOK:
            parseExternalReference( rRelations, BiffHelper::readString( rStrm ) );
        break;
        case BIFF12_EXTERNALBOOK_DDE:
        {
            OUString aDdeService, aDdeTopic;
            rStrm >> aDdeService >> aDdeTopic;
            setDdeOleTargetUrl( aDdeService, aDdeTopic, ExternalLinkType::DDE );
        }
        break;
        case BIFF12_EXTERNALBOOK_OLE:
        {
            OUString aTargetUrl = rRelations.getExternalTargetFromRelId( BiffHelper::readString( rStrm ) );
            OUString aProgId = BiffHelper::readString( rStrm );
            setDdeOleTargetUrl( aProgId, aTargetUrl, ExternalLinkType::OLE );
        }
        break;
        default:
            OSL_FAIL( "ExternalLink::importExternalBook - unknown link type" );
    }
}

void ExternalLink::importExtSheetNames( SequenceInputStream& rStrm )
{
    // load external sheet names and create the sheet caches in the Calc document
    SAL_WARN_IF( (meLinkType != ExternalLinkType::External) && (meLinkType != ExternalLinkType::Library),
        "sc.filter",
        "Invalid link type: " << meLinkType );
    if( meLinkType == ExternalLinkType::External )   // ignore sheets of external libraries
        for( sal_Int32 nSheet = 0, nCount = rStrm.readInt32(); !rStrm.isEof() && (nSheet < nCount); ++nSheet )
            insertExternalSheet( BiffHelper::readString( rStrm ) );
}

ExternalNameRef ExternalLink::importExternalName( SequenceInputStream& rStrm )
{
    ExternalNameRef xExtName = createExternalName();
    xExtName->importExternalName( rStrm );
    return xExtName;
}

ExternalLinkInfo ExternalLink::getLinkInfo() const
{
    ExternalLinkInfo aLinkInfo;
    switch( meLinkType )
    {
        case ExternalLinkType::Self:
        case ExternalLinkType::Same:
            aLinkInfo.Type = css::sheet::ExternalLinkType::SELF;
        break;
        case ExternalLinkType::External:
            aLinkInfo.Type = css::sheet::ExternalLinkType::DOCUMENT;
            aLinkInfo.Data <<= maTargetUrl;
        break;
        case ExternalLinkType::PathMissing:
        case ExternalLinkType::XlStartup:
            aLinkInfo.Type = css::sheet::ExternalLinkType::MISSINGDOCUMENT;
            aLinkInfo.Data <<= maTargetUrl;
        break;
        case ExternalLinkType::Library:
            // parser will return library function names in OPCODE_BAD string tokens
            aLinkInfo.Type = css::sheet::ExternalLinkType::SPECIAL;
        break;
        case ExternalLinkType::DDE:
        {
            aLinkInfo.Type = css::sheet::ExternalLinkType::DDE;
            DDELinkInfo aDdeLinkInfo;
            aDdeLinkInfo.Service = maClassName;
            aDdeLinkInfo.Topic = maTargetUrl;
            ::std::vector< DDEItemInfo > aItemInfos;
            DDEItemInfo aItemInfo;
            for( const auto& rxExtName : maExtNames )
                if( rxExtName->getDdeItemInfo( aItemInfo ) )
                    aItemInfos.push_back( aItemInfo );
            aDdeLinkInfo.Items = comphelper::containerToSequence( aItemInfos );
            aLinkInfo.Data <<= aDdeLinkInfo;
        }
        break;
        default:
            aLinkInfo.Type = css::sheet::ExternalLinkType::UNKNOWN;
    }
    return aLinkInfo;
}

FunctionLibraryType ExternalLink::getFuncLibraryType() const
{
    return (meLinkType == ExternalLinkType::Library) ? meFuncLibType : FUNCLIB_UNKNOWN;
}

sal_Int32 ExternalLink::getDocumentLinkIndex() const
{
    OSL_ENSURE(meLinkType == ExternalLinkType::External
                   || meLinkType == ExternalLinkType::PathMissing
                   || meLinkType == ExternalLinkType::XlStartup,
               "ExternalLink::getDocumentLinkIndex - invalid link type");
    return mxDocLink.is() ? mxDocLink->getTokenIndex() : -1;
}

sal_Int32 ExternalLink::getSheetCacheIndex( sal_Int32 nTabId ) const
{
    OSL_ENSURE(meLinkType == ExternalLinkType::External
                   || meLinkType == ExternalLinkType::PathMissing
                   || meLinkType == ExternalLinkType::XlStartup,
               "ExternalLink::getSheetCacheIndex - invalid link type");
    return ContainerHelper::getVectorElement( maSheetCaches, nTabId, -1 );
}

Reference< XExternalSheetCache > ExternalLink::getSheetCache( sal_Int32 nTabId ) const
{
    sal_Int32 nCacheIdx = getSheetCacheIndex( nTabId );
    if( mxDocLink.is() && (nCacheIdx >= 0) ) try
    {
        // existing mxDocLink implies that this is an external link
        Reference< XExternalSheetCache > xSheetCache( mxDocLink->getByIndex( nCacheIdx ), UNO_QUERY_THROW );
        return xSheetCache;
    }
    catch( Exception& )
    {
    }
    return nullptr;
}

void ExternalLink::getSheetRange( LinkSheetRange& orSheetRange, sal_Int32 nTabId1, sal_Int32 nTabId2 ) const
{
    switch( meLinkType )
    {
        case ExternalLinkType::Same:
            orSheetRange.setSameSheet();
        break;

        case ExternalLinkType::Self:
            orSheetRange.setRange( nTabId1, nTabId2 );
        break;

        case ExternalLinkType::External:
        case ExternalLinkType::PathMissing:
        case ExternalLinkType::XlStartup:
        {
            sal_Int32 nDocLinkIdx = getDocumentLinkIndex();
            // BIFF12: passed indexes point into sheet list of EXTSHEETLIST
            orSheetRange.setExternalRange( nDocLinkIdx, getSheetCacheIndex( nTabId1 ), getSheetCacheIndex( nTabId2 ) );
        }
        break;

        default:
            // unsupported/unexpected link type: #REF! error
            orSheetRange.setDeleted();
    }
}

ExternalNameRef ExternalLink::getNameByIndex( sal_Int32 nIndex ) const
{
    return maExtNames.get( nIndex );
}

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

void ExternalLink::setExternalTargetUrl( const OUString& rTargetUrl, const OUString& rTargetType )
{
    meLinkType = ExternalLinkType::Unknown;
    if( rTargetType == CREATE_OFFICEDOC_RELATION_TYPE( "externalLinkPath" ) ||
            rTargetType == CREATE_OFFICEDOC_RELATION_TYPE_STRICT( "externalLinkPath" ) )
    {
        maTargetUrl = getBaseFilter().getAbsoluteUrl( rTargetUrl );
        if( !maTargetUrl.isEmpty() )
            meLinkType = ExternalLinkType::External;
    }
    else if( rTargetType == CREATE_MSOFFICE_RELATION_TYPE( "xlExternalLinkPath/xlPathMissing" ) )
    {
        maTargetUrl = rTargetUrl;
        meLinkType = ExternalLinkType::PathMissing;
    }
    else if( rTargetType == CREATE_MSOFFICE_RELATION_TYPE( "xlExternalLinkPath/xlStartup" ) )
    {
        maTargetUrl = rTargetUrl;
        meLinkType = ExternalLinkType::XlStartup;
    }
    else if( rTargetType == CREATE_MSOFFICE_RELATION_TYPE( "xlExternalLinkPath/xlLibrary" ) )
    {
        meLinkType = ExternalLinkType::Library;
        meFuncLibType = FunctionProvider::getFuncLibTypeFromLibraryName( rTargetUrl );
    }
    SAL_WARN_IF( meLinkType == ExternalLinkType::Unknown, "sc.filter", "Empty target URL or unknown target type, URL='" << rTargetUrl << "', type='" << rTargetType << "'" );

    // create the external document link API object that will contain the sheet caches
    if (meLinkType == ExternalLinkType::External || meLinkType == ExternalLinkType::PathMissing
        || meLinkType == ExternalLinkType::XlStartup) try
    {
        PropertySet aDocProps(( Reference< css::beans::XPropertySet >(getDocument()) ));
        Reference< XExternalDocLinks > xDocLinks( aDocProps.getAnyProperty( PROP_ExternalDocLinks ), UNO_QUERY_THROW );
        if (meLinkType == ExternalLinkType::PathMissing)
            mxDocLink = xDocLinks->addSpecialDocLink(maTargetUrl, ExternalLinkRelType::PATH_MISSING);
        else if (meLinkType == ExternalLinkType::XlStartup)
            mxDocLink = xDocLinks->addSpecialDocLink(maTargetUrl, ExternalLinkRelType::XL_STARTUP);
        else
            mxDocLink = xDocLinks->addDocLink(maTargetUrl);
    }
    catch( Exception& )
    {
    }
}

void ExternalLink::setDdeOleTargetUrl( const OUString& rClassName, const OUString& rTargetUrl, ExternalLinkType eLinkType )
{
    maClassName = rClassName;
    maTargetUrl = rTargetUrl;
    meLinkType = (maClassName.isEmpty() || maTargetUrl.isEmpty()) ?  ExternalLinkType::Unknown : eLinkType;
    OSL_ENSURE( meLinkType == eLinkType, "ExternalLink::setDdeOleTargetUrl - missing classname or target" );
}

void ExternalLink::parseExternalReference( const Relations& rRelations, const OUString& rRelId )
{
    if( const Relation* pRelation = rRelations.getRelationFromRelId( rRelId ) )
        setExternalTargetUrl( pRelation->maTarget, pRelation->maType );
}

void ExternalLink::insertExternalSheet( const OUString& rSheetName )
{
    OSL_ENSURE( !rSheetName.isEmpty(), "ExternalLink::insertExternalSheet - empty sheet name" );
    if( mxDocLink.is() )
    {
        Reference< XExternalSheetCache > xSheetCache = mxDocLink->addSheetCache( rSheetName, false );
        sal_Int32 nCacheIdx = xSheetCache.is() ? xSheetCache->getTokenIndex() : -1;
        maSheetCaches.push_back( nCacheIdx );
    }
}

ExternalNameRef ExternalLink::createExternalName()
{
    ExternalNameRef xExtName = std::make_shared<ExternalName>( *this );
    maExtNames.push_back( xExtName );
    return xExtName;
}

RefSheetsModel::RefSheetsModel() :
    mnExtRefId( -1 ),
    mnTabId1( -1 ),
    mnTabId2( -1 )
{
}

void RefSheetsModel::readBiff12Data( SequenceInputStream& rStrm )
{
    mnExtRefId = rStrm.readInt32();
    mnTabId1 = rStrm.readInt32();
    mnTabId2 = rStrm.readInt32();
}

ExternalLinkBuffer::ExternalLinkBuffer( const WorkbookHelper& rHelper ) :
    WorkbookHelper( rHelper ),
    mxSelfRef( std::make_shared<ExternalLink>( rHelper ) ),
    mbUseRefSheets( false )
{
    mxSelfRef->setSelfLinkType();
}

ExternalLinkRef ExternalLinkBuffer::importExternalReference( const AttributeList& rAttribs )
{
    ExternalLinkRef xExtLink = createExternalLink();
    xExtLink->importExternalReference( rAttribs );
    maExtLinks.push_back( xExtLink );
    return xExtLink;
}

ExternalLinkRef ExternalLinkBuffer::importExternalRef( SequenceInputStream& rStrm )
{
    mbUseRefSheets = true;
    ExternalLinkRef xExtLink = createExternalLink();
    xExtLink->importExternalRef( rStrm );
    maExtLinks.push_back( xExtLink );
    return xExtLink;
}

void ExternalLinkBuffer::importExternalSelf( SequenceInputStream& rStrm )
{
    mbUseRefSheets = true;
    createExternalLink()->importExternalSelf( rStrm );
}

void ExternalLinkBuffer::importExternalSame( SequenceInputStream& rStrm )
{
    mbUseRefSheets = true;
    createExternalLink()->importExternalSame( rStrm );
}

void ExternalLinkBuffer::importExternalAddin( SequenceInputStream& rStrm )
{
    mbUseRefSheets = true;
    createExternalLink()->importExternalAddin( rStrm );
}

void ExternalLinkBuffer::importExternalSheets( SequenceInputStream& rStrm )
{
    OSL_ENSURE( mbUseRefSheets, "ExternalLinkBuffer::importExternalSheets - missing EXTERNALREFS records" );
    mbUseRefSheets = true;
    OSL_ENSURE( maRefSheets.empty(), "ExternalLinkBuffer::importExternalSheets - multiple EXTERNALSHEETS records" );
    maRefSheets.clear();
    sal_Int32 nRefCount;
    nRefCount = rStrm.readInt32();
    size_t nMaxCount = getLimitedValue< size_t, sal_Int64 >( nRefCount, 0, rStrm.getRemaining() / 12 );
    maRefSheets.reserve( nMaxCount );
    for( size_t nRefId = 0; !rStrm.isEof() && (nRefId < nMaxCount); ++nRefId )
    {
        RefSheetsModel aRefSheets;
        aRefSheets.readBiff12Data( rStrm );
        maRefSheets.push_back( aRefSheets );
    }
}

Sequence< ExternalLinkInfo > ExternalLinkBuffer::getLinkInfos() const
{
    ::std::vector< ExternalLinkInfo > aLinkInfos;
    // add entry for implicit index 0 (self reference to this document)
    aLinkInfos.push_back( mxSelfRef->getLinkInfo() );
    for( const auto& rxExtLink : maExtLinks )
        aLinkInfos.push_back( rxExtLink->getLinkInfo() );
    return comphelper::containerToSequence( aLinkInfos );
}

ExternalLinkRef ExternalLinkBuffer::getExternalLink( sal_Int32 nRefId, bool bUseRefSheets ) const
{
    ExternalLinkRef xExtLink;
    // OOXML: 0 = this document, otherwise one-based index into link list
    if( !bUseRefSheets || !mbUseRefSheets )
        xExtLink = (nRefId == 0) ? mxSelfRef : maLinks.get( nRefId - 1 );
    // BIFF12: zero-based index into ref-sheets list
    else if( const RefSheetsModel* pRefSheets = getRefSheets( nRefId ) )
        xExtLink = maLinks.get( pRefSheets->mnExtRefId );
    return xExtLink;
}

LinkSheetRange ExternalLinkBuffer::getSheetRange( sal_Int32 nRefId ) const
{
    OSL_ENSURE( mbUseRefSheets, "ExternalLinkBuffer::getSheetRange - wrong BIFF version" );
    LinkSheetRange aSheetRange;
    if( const ExternalLink* pExtLink = getExternalLink( nRefId ).get() )
        if( const RefSheetsModel* pRefSheets = getRefSheets( nRefId ) )
            pExtLink->getSheetRange( aSheetRange, pRefSheets->mnTabId1, pRefSheets->mnTabId2 );
    return aSheetRange;
}

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

ExternalLinkRef ExternalLinkBuffer::createExternalLink()
{
    ExternalLinkRef xExtLink = std::make_shared<ExternalLink>( *this );
    maLinks.push_back( xExtLink );
    return xExtLink;
}

const RefSheetsModel* ExternalLinkBuffer::getRefSheets( sal_Int32 nRefId ) const
{
    return ((0 <= nRefId) && (o3tl::make_unsigned( nRefId ) < maRefSheets.size())) ?
        &maRefSheets[ static_cast< size_t >( nRefId ) ] : nullptr;
}

} // namespace oox

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