/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

#include "oox/xls/externallinkbuffer.hxx"
#include <rtl/strbuf.hxx>
#include <com/sun/star/sheet/ComplexReference.hpp>
#include <com/sun/star/sheet/DDELinkInfo.hpp>
#include <com/sun/star/sheet/ExternalLinkType.hpp>
#include <com/sun/star/sheet/ExternalReference.hpp>
#include <com/sun/star/sheet/ReferenceFlags.hpp>
#include <com/sun/star/sheet/SingleReference.hpp>
#include <com/sun/star/sheet/XDDELinks.hpp>
#include <com/sun/star/sheet/XDDELink.hpp>
#include <com/sun/star/sheet/XDDELinkResults.hpp>
#include <com/sun/star/sheet/XExternalDocLink.hpp>
#include <com/sun/star/sheet/XExternalDocLinks.hpp>
#include "oox/helper/attributelist.hxx"
#include "oox/core/filterbase.hxx"
#include "oox/xls/addressconverter.hxx"
#include "oox/xls/biffinputstream.hxx"
#include "oox/xls/excelhandlers.hxx"
#include "oox/xls/formulaparser.hxx"
#include "oox/xls/worksheetbuffer.hxx"

using ::rtl::OString;
using ::rtl::OStringBuffer;
using ::rtl::OStringToOUString;
using ::rtl::OUString;
using ::com::sun::star::uno::Any;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::Sequence;
using ::com::sun::star::uno::Exception;
using ::com::sun::star::uno::UNO_QUERY_THROW;
using ::com::sun::star::table::CellAddress;
using ::com::sun::star::sheet::ComplexReference;
using ::com::sun::star::sheet::DDEItemInfo;
using ::com::sun::star::sheet::DDELinkInfo;
using ::com::sun::star::sheet::ExternalLinkInfo;
using ::com::sun::star::sheet::ExternalReference;
using ::com::sun::star::sheet::SingleReference;
using ::com::sun::star::sheet::XDDELinks;
using ::com::sun::star::sheet::XDDELinkResults;
using ::com::sun::star::sheet::XExternalDocLinks;
using ::com::sun::star::sheet::XExternalSheetCache;
using ::oox::core::Relation;
using ::oox::core::Relations;

namespace oox {
namespace xls {

// ============================================================================

namespace {

const sal_uInt16 OOBIN_EXTERNALBOOK_BOOK    = 0;
const sal_uInt16 OOBIN_EXTERNALBOOK_DDE     = 1;
const sal_uInt16 OOBIN_EXTERNALBOOK_OLE     = 2;

const sal_uInt16 OOBIN_EXTNAME_AUTOMATIC    = 0x0002;
const sal_uInt16 OOBIN_EXTNAME_PREFERPIC    = 0x0004;
const sal_uInt16 OOBIN_EXTNAME_STDDOCNAME   = 0x0008;
const sal_uInt16 OOBIN_EXTNAME_OLEOBJECT    = 0x0010;
const sal_uInt16 OOBIN_EXTNAME_ICONIFIED    = 0x0020;

const sal_uInt16 BIFF_EXTNAME_BUILTIN       = 0x0001;
const sal_uInt16 BIFF_EXTNAME_AUTOMATIC     = 0x0002;
const sal_uInt16 BIFF_EXTNAME_PREFERPIC     = 0x0004;
const sal_uInt16 BIFF_EXTNAME_STDDOCNAME    = 0x0008;
const sal_uInt16 BIFF_EXTNAME_OLEOBJECT     = 0x0010;
const sal_uInt16 BIFF_EXTNAME_ICONIFIED     = 0x8000;

} // namespace

// ============================================================================

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

// ============================================================================

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

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

void ExternalName::importDdeItem( const AttributeList& rAttribs )
{
    maModel.maName = rAttribs.getXString( XML_name, OUString() );
    OSL_ENSURE( maModel.maName.getLength() > 0, "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.getLength() > 0, "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( RecordInputStream& rStrm )
{
    rStrm >> maModel.maName;
    OSL_ENSURE( maModel.maName.getLength() > 0, "ExternalName::importExternalName - empty name" );
}

void ExternalName::importExternalNameFlags( RecordInputStream& rStrm )
{
    sal_uInt16 nFlags;
    sal_Int32 nSheetId;
    rStrm >> nFlags >> nSheetId;
    // index into sheet list of EXTSHEETNAMES (one-based in OOBIN)
    maModel.mnSheet = nSheetId - 1;
    // no flag for built-in names, as in OOX...
    maExtNameModel.mbNotify     = getFlag( nFlags, OOBIN_EXTNAME_AUTOMATIC );
    maExtNameModel.mbPreferPic  = getFlag( nFlags, OOBIN_EXTNAME_PREFERPIC );
    maExtNameModel.mbStdDocName = getFlag( nFlags, OOBIN_EXTNAME_STDDOCNAME );
    maExtNameModel.mbOleObj     = getFlag( nFlags, OOBIN_EXTNAME_OLEOBJECT );
    maExtNameModel.mbIconified  = getFlag( nFlags, OOBIN_EXTNAME_ICONIFIED );
    OSL_ENSURE( (mrParentLink.getLinkType() == LINKTYPE_OLE) == maExtNameModel.mbOleObj,
        "ExternalName::importExternalNameFlags - wrong OLE flag in external name" );
}

void ExternalName::importDdeItemValues( RecordInputStream& rStrm )
{
    sal_Int32 nRows, nCols;
    rStrm >> nRows >> nCols;
    setResultSize( nCols, nRows );
}

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

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

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

void ExternalName::importDdeItemString( RecordInputStream& rStrm )
{
    appendResultValue( rStrm.readString() );
}

void ExternalName::importExternalName( BiffInputStream& rStrm )
{
    sal_uInt16 nFlags = 0;
    if( getBiff() >= BIFF3 )
    {
        rStrm >> nFlags;
        maExtNameModel.mbBuiltIn    = getFlag( nFlags, BIFF_EXTNAME_BUILTIN );
        maExtNameModel.mbNotify     = getFlag( nFlags, BIFF_EXTNAME_AUTOMATIC );
        maExtNameModel.mbPreferPic  = getFlag( nFlags, BIFF_EXTNAME_PREFERPIC );

        // BIFF5-BIFF8: sheet index for sheet-local names, OLE settings
        if( getBiff() >= BIFF5 )
        {
            maExtNameModel.mbStdDocName = getFlag( nFlags, BIFF_EXTNAME_STDDOCNAME );
            maExtNameModel.mbOleObj     = getFlag( nFlags, BIFF_EXTNAME_OLEOBJECT );
            maExtNameModel.mbIconified  = getFlag( nFlags, BIFF_EXTNAME_ICONIFIED );

            if( maExtNameModel.mbOleObj )
            {
                rStrm >> mnStorageId;
            }
            else
            {
                /*  Import the reference ID for names that are sheet-local in
                    the external document. This index will be resolved later to
                    the index of the external sheet cache which is able to
                    provide the name of the sheet related to this defined name.
                    - BIFF5: one-based index to EXTERNSHEET record containing
                        the document and sheet name
                    - BIFF8: one-based index into EXTERNALBOOK sheet name list
                    The value zero means this external name is a global name.
                 */
                rStrm.skip( 2 );
                maModel.mnSheet = rStrm.readuInt16();
            }
        }
    }

    maModel.maName = (getBiff() == BIFF8) ?
        rStrm.readUniStringBody( rStrm.readuInt8() ) :
        rStrm.readByteStringUC( false, getTextEncoding() );
    OSL_ENSURE( maModel.maName.getLength() > 0, "ExternalName::importExternalName - empty name" );

    // load cell references that are stored in hidden external names (seen in BIFF3-BIFF4)
    bool bHiddenRef = (getBiff() <= BIFF4) && (maModel.maName.getLength() > 1) && (maModel.maName[ 0 ] == '\x01') && (rStrm.getRemaining() > 2);
    switch( mrParentLink.getLinkType() )
    {
        case LINKTYPE_INTERNAL:
            // cell references to other internal sheets are stored in hidden external names
            if( bHiddenRef && (getBiff() == BIFF4) && isWorkbookFile() )
            {
                TokensFormulaContext aContext( true, true );
                importBiffFormula( aContext, mrParentLink.getCalcSheetIndex(), rStrm );
                extractReference( aContext.getTokens() );
            }
        break;

        case LINKTYPE_EXTERNAL:
            // cell references to other documents are stored in hidden external names
            if( bHiddenRef )
            {
                TokensFormulaContext aContext( true, true );
                importBiffFormula( aContext, 0, rStrm );
                extractExternalReference( aContext.getTokens() );
            }
        break;

        case LINKTYPE_DDE:
        case LINKTYPE_OLE:
        case LINKTYPE_MAYBE_DDE_OLE:
            // DDE/OLE link results
            if( rStrm.getRemaining() > 3 )
            {
                bool bBiff8 = getBiff() == BIFF8;
                sal_Int32 nCols = rStrm.readuInt8();
                sal_Int32 nRows = rStrm.readuInt16();
                if( bBiff8 ) { ++nCols; ++nRows; } else if( nCols == 0 ) nCols = 256;
                setResultSize( nCols, nRows );

                bool bLoop = true;
                while( bLoop && !rStrm.isEof() && (maCurrIt != maResults.end()) )
                {
                    switch( rStrm.readuInt8() )
                    {
                        case BIFF_DATATYPE_EMPTY:
                            appendResultValue( OUString() );
                            rStrm.skip( 8 );
                        break;
                        case BIFF_DATATYPE_DOUBLE:
                            appendResultValue( rStrm.readDouble() );
                        break;
                        case BIFF_DATATYPE_STRING:
                            appendResultValue( bBiff8 ? rStrm.readUniString() : rStrm.readByteStringUC( false, getTextEncoding() ) );
                        break;
                        case BIFF_DATATYPE_BOOL:
                            appendResultValue< double >( (rStrm.readuInt8() == 0) ? 0.0 : 1.0 );
                            rStrm.skip( 7 );
                        break;
                        case BIFF_DATATYPE_ERROR:
                            appendResultValue( BiffHelper::calcDoubleFromError( rStrm.readuInt8() ) );
                            rStrm.skip( 7 );
                        break;
                        default:
                            bLoop = false;
                    }
                }
                OSL_ENSURE( bLoop && !rStrm.isEof() && (maCurrIt == maResults.end()),
                    "ExternalName::importExternalName - stream error in result set" );
            }
        break;

        default:;
    }
}

#if 0
sal_Int32 ExternalName::getSheetCacheIndex() const
{
    OSL_ENSURE( mrParentLink.getLinkType() == LINKTYPE_DDE, "ExternalName::getSheetCacheIndex - unexpected link type" );
    sal_Int32 nCacheIdx = -1;
    switch( getFilterType() )
    {
        case FILTER_OOX:
            // OOXML/OOBIN: zero-based index into sheet list, -1 means global name
            if( maModel.mnSheet >= 0 )
                nCacheIdx = mrParentLink.getSheetIndex( maModel.mnSheet );
        break;
        case FILTER_BIFF:
            switch( getBiff() )
            {
                case BIFF2:
                case BIFF3:
                case BIFF4:
                break;
                case BIFF5:
                    if( maModel.mnSheet > 0 )
                        if( const ExternalLink* pExtLink = getExternalLinks().getExternalLink( maModel.mnSheet ).get() )
                            if( pExtLink->getLinkType() == LINKTYPE_EXTERNAL )
                                nCacheIdx = pExtLink->getSheetIndex();
                break;
                case BIFF8:
                    if( maModel.mnSheet > 0 )
                        nCacheIdx = mrParentLink.getSheetIndex( maModel.mnSheet - 1 );
                break;
                case BIFF_UNKNOWN:
                break;
            }
        break;
        case FILTER_UNKNOWN:
        break;
    }
    return nCacheIdx;
}
#endif

bool ExternalName::getDdeItemInfo( DDEItemInfo& orItemInfo ) const
{
    if( (mrParentLink.getLinkType() == LINKTYPE_DDE) && (maModel.maName.getLength() > 0) )
    {
        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() == LINKTYPE_DDE) && (maModel.maName.getLength() > 0) )
    {
        // try to create a DDE link and to set the imported link results
        if( !mbDdeLinkCreated ) try
        {
            Reference< XDDELinks > xDdeLinks( getDdeLinks(), UNO_QUERY_THROW );
            mxDdeLink = xDdeLinks->addDDELink( mrParentLink.getClassName(), mrParentLink.getTargetUrl(), maModel.maName, ::com::sun::star::sheet::DDELinkMode_DEFAULT );
            if( !maResults.empty() )
            {
                Reference< XDDELinkResults > xResults( mxDdeLink, UNO_QUERY_THROW );
                xResults->setResults( ContainerHelper::matrixToSequenceSequence( maResults ) );
            }
            mbDdeLinkCreated = true;
        }
        catch( Exception& )
        {
            OSL_ENSURE( false, "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 --------------------------------------------------------------------

namespace {

void lclSetSheetCacheIndex( SingleReference& orApiRef, sal_Int32 nCacheIdx )
{
    using namespace ::com::sun::star::sheet::ReferenceFlags;
    setFlag( orApiRef.Flags, SHEET_RELATIVE, false );
    setFlag( orApiRef.Flags, SHEET_3D, true );
    orApiRef.Sheet = nCacheIdx;
}

} // namespace

void ExternalName::extractExternalReference( const ApiTokenSequence& rTokens )
{
    OSL_ENSURE( (getFilterType() == FILTER_BIFF) && (getBiff() <= BIFF4), "ExternalName::setExternalReference - unexpected call" );
    sal_Int32 nDocLinkIdx = mrParentLink.getDocumentLinkIndex();
    sal_Int32 nCacheIdx = mrParentLink.getSheetCacheIndex();
    if( (nDocLinkIdx >= 0) && (nCacheIdx >= 0) )
    {
        ExternalReference aExtApiRef;
        aExtApiRef.Index = nDocLinkIdx;

        Any aRefAny = getFormulaParser().extractReference( rTokens );
        if( aRefAny.has< SingleReference >() )
        {
            SingleReference aApiRef;
            aRefAny >>= aApiRef;
            lclSetSheetCacheIndex( aApiRef, nCacheIdx );
            aExtApiRef.Reference <<= aApiRef;
            maRefAny <<= aExtApiRef;
        }
        else if( aRefAny.has< ComplexReference >() )
        {
            ComplexReference aApiRef;
            aRefAny >>= aApiRef;
            lclSetSheetCacheIndex( aApiRef.Reference1, nCacheIdx );
            lclSetSheetCacheIndex( aApiRef.Reference2, nCacheIdx );
            aExtApiRef.Reference <<= aApiRef;
            maRefAny <<= aExtApiRef;
        }
    }
}

void ExternalName::setResultSize( sal_Int32 nColumns, sal_Int32 nRows )
{
    OSL_ENSURE( (mrParentLink.getLinkType() == LINKTYPE_DDE) || (mrParentLink.getLinkType() == LINKTYPE_OLE) ||
        (mrParentLink.getLinkType() == LINKTYPE_MAYBE_DDE_OLE), "ExternalName::setResultSize - wrong link type" );
    OSL_ENSURE( (nRows > 0) && (nColumns > 0), "ExternalName::setResultSize - invalid matrix size" );
    const CellAddress& rMaxPos = getAddressConverter().getMaxApiAddress();
    if( (0 < nRows) && (nRows <= rMaxPos.Row + 1) && (0 < nColumns) && (nColumns <= rMaxPos.Column + 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( LINKTYPE_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, LINKTYPE_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, LINKTYPE_OLE );
}

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

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

void ExternalLink::importExternalSelf( RecordInputStream& )
{
    meLinkType = LINKTYPE_SELF;
}

void ExternalLink::importExternalSame( RecordInputStream& )
{
    meLinkType = LINKTYPE_SAME;
}

void ExternalLink::importExternalAddin( RecordInputStream& )
{
    meLinkType = LINKTYPE_UNKNOWN;
}

void ExternalLink::importExternalBook( const Relations& rRelations, RecordInputStream& rStrm )
{
    switch( rStrm.readuInt16() )
    {
        case OOBIN_EXTERNALBOOK_BOOK:
            parseExternalReference( rRelations, rStrm.readString() );
        break;
        case OOBIN_EXTERNALBOOK_DDE:
        {
            OUString aDdeService, aDdeTopic;
            rStrm >> aDdeService >> aDdeTopic;
            setDdeOleTargetUrl( aDdeService, aDdeTopic, LINKTYPE_DDE );
        }
        break;
        case OOBIN_EXTERNALBOOK_OLE:
        {
            OUString aTargetUrl = rRelations.getExternalTargetFromRelId( rStrm.readString() );
            OUString aProgId = rStrm.readString();
            setDdeOleTargetUrl( aProgId, aTargetUrl, LINKTYPE_OLE );
        }
        break;
        default:
            OSL_ENSURE( false, "ExternalLink::importExternalBook - unknown link type" );
    }
}

void ExternalLink::importExtSheetNames( RecordInputStream& rStrm )
{
    // load external sheet names and create the sheet caches in the Calc document
    OSL_ENSURE( (meLinkType == LINKTYPE_EXTERNAL) || (meLinkType == LINKTYPE_LIBRARY),
        "ExternalLink::importExtSheetNames - invalid link type" );
    if( meLinkType == LINKTYPE_EXTERNAL )   // ignore sheets of external libraries
        for( sal_Int32 nSheet = 0, nCount = rStrm.readInt32(); !rStrm.isEof() && (nSheet < nCount); ++nSheet )
            insertExternalSheet( rStrm.readString() );
}

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

void ExternalLink::importExternSheet( BiffInputStream& rStrm )
{
    OStringBuffer aTargetBuffer( rStrm.readByteString( false, true ) );
    // references to own sheets have wrong string length field (off by 1)
    if( (aTargetBuffer.getLength() > 0) && (aTargetBuffer[ 0 ] == 3) )
        aTargetBuffer.append( static_cast< sal_Char >( rStrm.readuInt8() ) );
    // parse the encoded URL
    OUString aBiffTarget = OStringToOUString( aTargetBuffer.makeStringAndClear(), getTextEncoding() );
    OUString aSheetName = parseBiffTargetUrl( aBiffTarget );
    switch( meLinkType )
    {
        case LINKTYPE_INTERNAL:
            maCalcSheets.push_back( getWorksheets().getCalcSheetIndex( aSheetName ) );
        break;
        case LINKTYPE_EXTERNAL:
            insertExternalSheet( (aSheetName.getLength() > 0) ? aSheetName : WorksheetBuffer::getBaseFileName( maTargetUrl ) );
        break;
        default:;
    }
}

void ExternalLink::importExternalBook( BiffInputStream& rStrm )
{
    OUString aTarget;
    sal_uInt16 nSheetCount;
    rStrm >> nSheetCount;
    if( rStrm.getRemaining() == 2 )
    {
        if( rStrm.readuInt8() == 1 )
        {
            sal_Char cChar = static_cast< sal_Char >( rStrm.readuInt8() );
            if( cChar != 0 )
                aTarget = OStringToOUString( OString( cChar ), getTextEncoding() );
        }
    }
    else if( rStrm.getRemaining() >= 3 )
    {
        // NUL characters may occur
        aTarget = rStrm.readUniString( true );
    }

    // parse the encoded URL
    OUString aDummySheetName = parseBiffTargetUrl( aTarget );
    OSL_ENSURE( aDummySheetName.getLength() == 0, "ExternalLink::importExternalBook - sheet name in encoded URL" );
    (void)aDummySheetName;  // prevent compiler warning

    // load external sheet names and create the sheet caches in the Calc document
    if( meLinkType == LINKTYPE_EXTERNAL )
        for( sal_uInt16 nSheet = 0; !rStrm.isEof() && (nSheet < nSheetCount); ++nSheet )
            insertExternalSheet( rStrm.readUniString() );
}

void ExternalLink::importExternalName( BiffInputStream& rStrm )
{
    ExternalNameRef xExtName = createExternalName();
    xExtName->importExternalName( rStrm );
    switch( meLinkType )
    {
        case LINKTYPE_DDE:
            OSL_ENSURE( !xExtName->isOleObject(), "ExternalLink::importExternalName - OLE object in DDE link" );
        break;
        case LINKTYPE_OLE:
            OSL_ENSURE( xExtName->isOleObject(), "ExternalLink::importExternalName - anything but OLE object in OLE link" );
        break;
        case LINKTYPE_MAYBE_DDE_OLE:
            meLinkType = xExtName->isOleObject() ? LINKTYPE_OLE : LINKTYPE_DDE;
        break;
        default:
            OSL_ENSURE( !xExtName->isOleObject(), "ExternalLink::importExternalName - OLE object in external name" );
    }
}

ExternalLinkInfo ExternalLink::getLinkInfo() const
{
    ExternalLinkInfo aLinkInfo;
    switch( meLinkType )
    {
        case LINKTYPE_EXTERNAL:
            aLinkInfo.Type = ::com::sun::star::sheet::ExternalLinkType::DOCUMENT;
            aLinkInfo.Data <<= maTargetUrl;
        break;
        case LINKTYPE_DDE:
        {
            aLinkInfo.Type = ::com::sun::star::sheet::ExternalLinkType::DDE;
            DDELinkInfo aDdeLinkInfo;
            aDdeLinkInfo.Service = maClassName;
            aDdeLinkInfo.Topic = maTargetUrl;
            ::std::vector< DDEItemInfo > aItemInfos;
            DDEItemInfo aItemInfo;
            for( ExternalNameVector::const_iterator aIt = maExtNames.begin(), aEnd = maExtNames.end(); aIt != aEnd; ++aIt )
                if( (*aIt)->getDdeItemInfo( aItemInfo ) )
                    aItemInfos.push_back( aItemInfo );
            aDdeLinkInfo.Items = ContainerHelper::vectorToSequence( aItemInfos );
            aLinkInfo.Data <<= aDdeLinkInfo;
        }
        break;
        default:
            aLinkInfo.Type = ::com::sun::star::sheet::ExternalLinkType::UNKNOWN;
    }
    return aLinkInfo;
}

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

sal_Int16 ExternalLink::getCalcSheetIndex( sal_Int32 nTabId ) const
{
    OSL_ENSURE( meLinkType == LINKTYPE_INTERNAL, "ExternalLink::getCalcSheetIndex - invalid link type" );
    OSL_ENSURE( (nTabId == 0) || (getFilterType() == FILTER_OOX) || (getBiff() == BIFF8),
        "ExternalLink::getCalcSheetIndex - invalid sheet index" );
    return ContainerHelper::getVectorElement< sal_Int16 >( maCalcSheets, nTabId, -1 );
}

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

sal_Int32 ExternalLink::getSheetCacheIndex( sal_Int32 nTabId ) const
{
    OSL_ENSURE( meLinkType == LINKTYPE_EXTERNAL, "ExternalLink::getSheetCacheIndex - invalid link type" );
    OSL_ENSURE( (nTabId == 0) || (getFilterType() == FILTER_OOX) || (getBiff() == BIFF8),
        "ExternalLink::getSheetCacheIndex - invalid sheet index" );
    return ContainerHelper::getVectorElement< sal_Int32 >( 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 0;
}

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

        case LINKTYPE_SELF:
        case LINKTYPE_INTERNAL:
            orSheetRange.setRange( nTabId1, nTabId2 );
        break;

        case LINKTYPE_EXTERNAL:
        {
            sal_Int32 nDocLinkIdx = getDocumentLinkIndex();
            switch( getFilterType() )
            {
                case FILTER_OOX:
                    // OOBIN: passed indexes point into sheet list of EXTSHEETLIST
                    orSheetRange.setExternalRange( nDocLinkIdx, getSheetCacheIndex( nTabId1 ), getSheetCacheIndex( nTabId2 ) );
                break;
                case FILTER_BIFF:
                    switch( getBiff() )
                    {
                        case BIFF2:
                        case BIFF3:
                        case BIFF4:
                            orSheetRange.setExternalRange( nDocLinkIdx, getSheetCacheIndex( nTabId1 ), getSheetCacheIndex( nTabId2 ) );
                        break;
                        case BIFF5:
                            // BIFF5: first sheet from this external link, last sheet is passed in nTabId2
                            if( const ExternalLink* pExtLink2 = getExternalLinks().getExternalLink( nTabId2 ).get() )
                                if( (pExtLink2->getLinkType() == LINKTYPE_EXTERNAL) && (maTargetUrl == pExtLink2->getTargetUrl()) )
                                    orSheetRange.setExternalRange( nDocLinkIdx, getSheetCacheIndex(), pExtLink2->getSheetCacheIndex() );
                        break;
                        case BIFF8:
                            // BIFF8: passed indexes point into sheet list of EXTERNALBOOK
                            orSheetRange.setExternalRange( nDocLinkIdx, getSheetCacheIndex( nTabId1 ), getSheetCacheIndex( nTabId2 ) );
                        break;
                        case BIFF_UNKNOWN: break;
                    }
                break;
                case FILTER_UNKNOWN: break;
            }
        }
        break;

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

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

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

#define OOX_TARGETTYPE_EXTLINK      CREATE_OFFICEDOC_RELATIONSTYPE( "externalLinkPath" )
#define OOX_TARGETTYPE_LIBRARY      CREATE_MSOFFICE_RELATIONSTYPE( "xlExternalLinkPath/xlLibrary" )

void ExternalLink::setExternalTargetUrl( const OUString& rTargetUrl, const OUString& rTargetType )
{
    meLinkType = LINKTYPE_UNKNOWN;
    if( rTargetType == OOX_TARGETTYPE_EXTLINK )
    {
        maTargetUrl = getBaseFilter().getAbsoluteUrl( rTargetUrl );
        if( maTargetUrl.getLength() > 0 )
            meLinkType = LINKTYPE_EXTERNAL;
    }
    else if( rTargetType == OOX_TARGETTYPE_LIBRARY )
    {
        meLinkType = LINKTYPE_LIBRARY;
        meFuncLibType = getFormulaParser().getFuncLibTypeFromLibraryName( rTargetUrl );
    }
    OSL_ENSURE( meLinkType != LINKTYPE_UNKNOWN, "ExternalLink::setExternalTargetUrl - empty target URL or unknown target type" );

    // create the external document link API object that will contain the sheet caches
    if( meLinkType == LINKTYPE_EXTERNAL )
    {
        Reference< XExternalDocLinks > xDocLinks = getExternalDocLinks();
        if( xDocLinks.is() )
            mxDocLink = xDocLinks->addDocLink( maTargetUrl );
    }
}

void ExternalLink::setDdeOleTargetUrl( const OUString& rClassName, const OUString& rTargetUrl, ExternalLinkType eLinkType )
{
    maClassName = rClassName;
    maTargetUrl = rTargetUrl;
    meLinkType = ((maClassName.getLength() > 0) && (maTargetUrl.getLength() > 0)) ? eLinkType : LINKTYPE_UNKNOWN;
    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 );
}

OUString ExternalLink::parseBiffTargetUrl( const OUString& rBiffTargetUrl )
{
    meLinkType = LINKTYPE_UNKNOWN;

    OUString aClassName, aTargetUrl, aSheetName;
    switch( getAddressConverter().parseBiffTargetUrl( aClassName, aTargetUrl, aSheetName, rBiffTargetUrl ) )
    {
        case BIFF_TARGETTYPE_URL:
            if( aTargetUrl.getLength() == 0 )
            {
                meLinkType = (aSheetName.getLength() > 0) ? LINKTYPE_INTERNAL : LINKTYPE_SELF;
            }
            else if( (aTargetUrl.getLength() == 1) && (aTargetUrl[ 0 ] == ':') )
            {
                if( getBiff() >= BIFF4 )
                    meLinkType = LINKTYPE_ANALYSIS;
            }
            else if( (aTargetUrl.getLength() > 1) || (aTargetUrl[ 0 ] != ' ') )
            {
                setExternalTargetUrl( aTargetUrl, OOX_TARGETTYPE_EXTLINK );
            }
        break;

        case BIFF_TARGETTYPE_SAMESHEET:
            OSL_ENSURE( (aTargetUrl.getLength() == 0) && (aSheetName.getLength() == 0), "ExternalLink::parseBiffTargetUrl - unexpected target or sheet name" );
            meLinkType = LINKTYPE_SAME;
        break;

        case BIFF_TARGETTYPE_LIBRARY:
            OSL_ENSURE( aSheetName.getLength() == 0, "ExternalLink::parseBiffTargetUrl - unexpected sheet name" );
            setExternalTargetUrl( aTargetUrl, OOX_TARGETTYPE_LIBRARY );
        break;

        case BIFF_TARGETTYPE_DDE_OLE:
            setDdeOleTargetUrl( aClassName, aTargetUrl, LINKTYPE_MAYBE_DDE_OLE );
        break;

        case BIFF_TARGETTYPE_UNKNOWN:
        break;
    }
    return aSheetName;
}

void ExternalLink::insertExternalSheet( const OUString& rSheetName )
{
    OSL_ENSURE( rSheetName.getLength() > 0, "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( new ExternalName( *this ) );
    maExtNames.push_back( xExtName );
    return xExtName;
}

// ============================================================================

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

void RefSheetsModel::readOobData( RecordInputStream& rStrm )
{
    rStrm >> mnExtRefId >> mnTabId1 >> mnTabId2;
}

void RefSheetsModel::readBiff8Data( BiffInputStream& rStrm )
{
    mnExtRefId = rStrm.readuInt16();
    mnTabId1 = rStrm.readInt16();
    mnTabId2 = rStrm.readInt16();
}

// ----------------------------------------------------------------------------

ExternalLinkBuffer::ExternalLinkBuffer( const WorkbookHelper& rHelper ) :
    WorkbookHelper( rHelper ),
    mbUseRefSheets( false )
{
}

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

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

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

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

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

void ExternalLinkBuffer::importExternalSheets( RecordInputStream& 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;
    rStrm >> nRefCount;
    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.readOobData( rStrm );
        maRefSheets.push_back( aRefSheets );
    }
}

ExternalLinkRef ExternalLinkBuffer::importExternSheet( BiffInputStream& rStrm )
{
    OSL_ENSURE( getBiff() <= BIFF5, "ExternalLinkBuffer::importExternSheet - wrong BIFF version" );
    ExternalLinkRef xExtLink = createExternalLink();
    xExtLink->importExternSheet( rStrm );
    return xExtLink;
}

ExternalLinkRef ExternalLinkBuffer::importExternalBook( BiffInputStream& rStrm )
{
    ExternalLinkRef xExtLink = createExternalLink();
    xExtLink->importExternalBook( rStrm );
    return xExtLink;
}

void ExternalLinkBuffer::importExternalName( BiffInputStream& rStrm )
{
    if( !maLinks.empty() )
        maLinks.back()->importExternalName( rStrm );
}

void ExternalLinkBuffer::importExternSheet8( BiffInputStream& rStrm )
{
    OSL_ENSURE( getBiff() == BIFF8, "ExternalLinkBuffer::importExternSheet8 - wrong BIFF version" );

    sal_uInt16 nRefCount;
    rStrm >> nRefCount;
    OSL_ENSURE( static_cast< sal_Int64 >( nRefCount * 6 ) == rStrm.getRemaining(), "ExternalLinkBuffer::importExternSheet8 - invalid count" );
    nRefCount = static_cast< sal_uInt16 >( ::std::min< sal_Int64 >( nRefCount, rStrm.getRemaining() / 6 ) );

    /*  #i104057# A weird external XLS generator writes multiple EXTERNSHEET
        records instead of only one as expected. Surprisingly, Excel seems to
        insert the entries of the second record before the entries of the first
        record. */
    maRefSheets.insert( maRefSheets.begin(), nRefCount, RefSheetsModel() );
    for( RefSheetsModelVec::iterator aIt = maRefSheets.begin(); !rStrm.isEof() && (nRefCount > 0); --nRefCount )
        aIt->readBiff8Data( rStrm );
}

Sequence< ExternalLinkInfo > ExternalLinkBuffer::getLinkInfos() const
{
    ::std::vector< ExternalLinkInfo > aLinkInfos;
    // dummy entry for index 0
    aLinkInfos.push_back( ExternalLinkInfo( ::com::sun::star::sheet::ExternalLinkType::UNKNOWN, Any() ) );
    for( ExternalLinkVec::const_iterator aIt = maExtLinks.begin(), aEnd = maExtLinks.end(); aIt != aEnd; ++aIt )
        aLinkInfos.push_back( (*aIt)->getLinkInfo() );
    return ContainerHelper::vectorToSequence( aLinkInfos );
}

ExternalLinkRef ExternalLinkBuffer::getExternalLink( sal_Int32 nRefId ) const
{
    ExternalLinkRef xExtLink;
    switch( getFilterType() )
    {
        case FILTER_OOX:
            // OOXML: one-based index
            if( !mbUseRefSheets )
                xExtLink = maLinks.get( nRefId - 1 );
            // OOBIN: zero-based index into ref-sheets list
            else if( const RefSheetsModel* pRefSheets = getRefSheets( nRefId ) )
                xExtLink = maLinks.get( pRefSheets->mnExtRefId );
        break;
        case FILTER_BIFF:
            switch( getBiff() )
            {
                case BIFF2:
                case BIFF3:
                case BIFF4:
                    // one-based index to EXTERNSHEET records
                    xExtLink = maLinks.get( nRefId - 1 );
                break;
                case BIFF5:
                    if( nRefId < 0 )
                    {
                        // internal links in formula tokens have negative index
                        xExtLink = maLinks.get( -nRefId - 1 );
                        if( xExtLink.get() && !xExtLink->isInternalLink() )
                            xExtLink.reset();
                    }
                    else
                    {
                        // one-based index to EXTERNSHEET records
                        xExtLink = maLinks.get( nRefId - 1 );
                    }
                break;
                case BIFF8:
                    // zero-based index into REF list in EXTERNSHEET record
                    if( const RefSheetsModel* pRefSheets = getRefSheets( nRefId ) )
                        xExtLink = maLinks.get( pRefSheets->mnExtRefId );
                break;
                case BIFF_UNKNOWN: break;
            }
        break;
        case FILTER_UNKNOWN: break;
    }
    return xExtLink;
}

LinkSheetRange ExternalLinkBuffer::getSheetRange( sal_Int32 nRefId, sal_Int16 nTabId1, sal_Int16 nTabId2 ) const
{
    OSL_ENSURE( getBiff() <= BIFF5, "ExternalLinkBuffer::getSheetRange - wrong BIFF version" );
    LinkSheetRange aSheetRange;
    if( const ExternalLink* pExtLink = getExternalLink( nRefId ).get() )
        pExtLink->getSheetRange( aSheetRange, nTabId1, nTabId2 );
    return aSheetRange;
}

LinkSheetRange ExternalLinkBuffer::getSheetRange( sal_Int32 nRefId ) const
{
    OSL_ENSURE( ((getFilterType() == FILTER_OOX) && mbUseRefSheets) || (getBiff() == BIFF8), "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( new ExternalLink( *this ) );
    maLinks.push_back( xExtLink );
    return xExtLink;
}

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

// ============================================================================

} // namespace xls
} // namespace oox

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