/* -*- 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 <sal/config.h>
#include <sal/log.hxx>

#include <com/sun/star/embed/XTransactedObject.hpp>
#include <com/sun/star/embed/ElementModes.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/io/NotConnectedException.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/embed/XHierarchicalStorageAccess.hpp>
#include <comphelper/fileformat.h>
#include <comphelper/graphicmimetype.hxx>
#include <comphelper/compbase.hxx>
#include <cppuhelper/implbase.hxx>
#include <cppuhelper/supportsservice.hxx>

#include <rtl/ref.hxx>
#include <unotools/ucbstreamhelper.hxx>
#include <unotools/streamwrap.hxx>
#include <unotools/tempfile.hxx>
#include <unotools/saveopt.hxx>
#include <vcl/filter/SvmWriter.hxx>
#include <vcl/gfxlink.hxx>
#include <vcl/metaact.hxx>
#include <tools/debug.hxx>
#include <tools/zcodec.hxx>
#include <comphelper/diagnose_ex.hxx>

#include <vcl/GraphicObject.hxx>
#include <vcl/graphicfilter.hxx>
#include <xmloff/xmlgrhlp.hxx>
#include <svx/xmleohlp.hxx>

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

using namespace com::sun::star;
using namespace com::sun::star::uno;
using namespace com::sun::star::io;

namespace com::sun::star::uno { class XComponentContext; }

constexpr OUStringLiteral XML_GRAPHICSTORAGE_NAME = u"Pictures";
constexpr OUStringLiteral XML_GRAPHICOBJECT_URL_BASE = u"vnd.sun.star.GraphicObject:";

namespace {

const MetaCommentAction* ImplCheckForEPS( GDIMetaFile const & rMtf )
{
    const MetaCommentAction* pComment = nullptr;

    if ( rMtf.GetActionSize() >= 2
            && rMtf.GetAction(0)->GetType() == MetaActionType::EPS
            && rMtf.GetAction(1)->GetType() == MetaActionType::COMMENT
            && ( static_cast<const MetaCommentAction*>(rMtf.GetAction( 1 ))->GetComment() == "EPSReplacementGraphic" ) )
        pComment = static_cast<const MetaCommentAction*>(rMtf.GetAction( 1 ));

    return pComment;
}

class GraphicInputStream : public cppu::WeakImplHelper<XInputStream>
{
private:
    virtual sal_Int32 SAL_CALL readBytes(Sequence<sal_Int8> & aData, sal_Int32 nBytesToRead) override;
    virtual sal_Int32 SAL_CALL readSomeBytes(Sequence<sal_Int8> & aData, sal_Int32 nMaxBytesToRead) override;
    virtual void SAL_CALL skipBytes(sal_Int32 nBytesToSkip) override;
    virtual sal_Int32 SAL_CALL available() override;
    virtual void SAL_CALL closeInput() override;

private:
    utl::TempFileFast maTempFile;
    rtl::Reference<::utl::OInputStreamWrapper> mxStreamWrapper;

public:

    explicit GraphicInputStream(GraphicObject const & raGraphicObject, const OUString & rMimeType);
    GraphicInputStream(const GraphicInputStream&) = delete;

    GraphicInputStream& operator=(const GraphicInputStream&) = delete;

    bool exists() const
    {
        return mxStreamWrapper.is();
    }
};


GraphicInputStream::GraphicInputStream(GraphicObject const & aGraphicObject, const OUString & rMimeType)
{
    if (aGraphicObject.GetType() == GraphicType::NONE)
        return;

    SvStream* pStream = maTempFile.GetStream(StreamMode::READWRITE);

    if (!pStream)
        return;

    const Graphic& aGraphic(aGraphicObject.GetGraphic());
    const GfxLink aGfxLink(aGraphic.GetGfxLink());
    bool bRet = false;

    if (aGfxLink.GetDataSize() && aGfxLink.GetData())
    {
        if (rMimeType.isEmpty())
        {
            pStream->WriteBytes(aGfxLink.GetData(), aGfxLink.GetDataSize());
            bRet = (pStream->GetError() == ERRCODE_NONE);
        }
        else
        {
            GraphicFilter &rFilter = GraphicFilter::GetGraphicFilter();
            bRet = (rFilter.ExportGraphic(aGraphic, u"", *pStream, rFilter.GetExportFormatNumberForMediaType(rMimeType)) == ERRCODE_NONE);
        }
    }
    else
    {
        if (aGraphic.GetType() == GraphicType::Bitmap)
        {
            GraphicFilter & rFilter = GraphicFilter::GetGraphicFilter();
            OUString aFormat = rMimeType;

            if (aGraphic.IsAnimated())
                aFormat = "image/gif";
            else if (aFormat.isEmpty())
                aFormat = "image/png";

            bRet = (rFilter.ExportGraphic(aGraphic, u"", *pStream, rFilter.GetExportFormatNumberForMediaType(aFormat)) == ERRCODE_NONE);
        }
        else if (rMimeType.isEmpty() && aGraphic.GetType() == GraphicType::GdiMetafile)
        {
            pStream->SetVersion(SOFFICE_FILEFORMAT_8);
            pStream->SetCompressMode(SvStreamCompressFlags::ZBITMAP);
            SvmWriter aWriter(*pStream);
            aWriter.Write(aGraphic.GetGDIMetaFile());
            bRet = (pStream->GetError() == ERRCODE_NONE);
        }
        else if (!rMimeType.isEmpty())
        {
            GraphicFilter & rFilter = GraphicFilter::GetGraphicFilter();
            bRet = ( rFilter.ExportGraphic( aGraphic, u"", *pStream, rFilter.GetExportFormatNumberForMediaType( rMimeType ) ) == ERRCODE_NONE );
        }
    }

    if (bRet)
    {
        pStream->Seek( 0 );
        mxStreamWrapper = new ::utl::OInputStreamWrapper(*pStream);
    }
}

sal_Int32 SAL_CALL GraphicInputStream::readBytes(Sequence<sal_Int8> & rData, sal_Int32 nBytesToRead)
{
    if (!mxStreamWrapper.is())
        throw NotConnectedException();

    return mxStreamWrapper->readBytes(rData, nBytesToRead);
}

sal_Int32 SAL_CALL GraphicInputStream::readSomeBytes(Sequence<sal_Int8>& rData, sal_Int32 nMaxBytesToRead )
{
    if (!mxStreamWrapper.is())
        throw NotConnectedException() ;

    return mxStreamWrapper->readSomeBytes(rData, nMaxBytesToRead);
}

void SAL_CALL GraphicInputStream::skipBytes(sal_Int32 nBytesToSkip)
{
    if (!mxStreamWrapper.is())
        throw NotConnectedException();

    mxStreamWrapper->skipBytes(nBytesToSkip);
}

sal_Int32 SAL_CALL GraphicInputStream::available()
{
    if (!mxStreamWrapper.is())
        throw NotConnectedException();

    return mxStreamWrapper->available();
}

void SAL_CALL GraphicInputStream::closeInput()
{
    if (!mxStreamWrapper.is())
        throw NotConnectedException();

    mxStreamWrapper->closeInput();
}

class SvXMLGraphicOutputStream:
    public cppu::WeakImplHelper<XOutputStream>
{
private:

    // XOutputStream
    virtual void SAL_CALL           writeBytes( const Sequence< sal_Int8 >& rData ) override;
    virtual void SAL_CALL           flush() override;
    virtual void SAL_CALL           closeOutput() override;

private:

    std::optional<::utl::TempFileFast> moTmp;
    SvStream*                        mpOStm;
    rtl::Reference< ::utl::OOutputStreamWrapper > mxStmWrapper;
    std::optional<GraphicObject>     moGrfObj;
    bool                             mbClosed;

public:

                                    SvXMLGraphicOutputStream();
    virtual                         ~SvXMLGraphicOutputStream() override;
                                    SvXMLGraphicOutputStream(const SvXMLGraphicOutputStream&) = delete;
    SvXMLGraphicOutputStream&       operator=(const SvXMLGraphicOutputStream&) = delete;

    bool                            Exists() const { return mxStmWrapper.is(); }
    const GraphicObject&            GetGraphicObject();
    Graphic GetGraphic(sal_Int32 nPageNum = -1);
};

SvXMLGraphicOutputStream::SvXMLGraphicOutputStream()
    : moTmp(std::in_place)
    , moGrfObj(std::in_place)
    , mbClosed(false)
{
    mpOStm = moTmp->GetStream( StreamMode::READWRITE );

    if( mpOStm )
        mxStmWrapper = new ::utl::OOutputStreamWrapper( *mpOStm );
}

SvXMLGraphicOutputStream::~SvXMLGraphicOutputStream()
{
    moTmp.reset();
}

void SAL_CALL SvXMLGraphicOutputStream::writeBytes( const Sequence< sal_Int8 >& rData )
{
    if( !mxStmWrapper.is() )
        throw NotConnectedException() ;

    mxStmWrapper->writeBytes( rData );
}

void SAL_CALL SvXMLGraphicOutputStream::flush()
{
    if( !mxStmWrapper.is() )
        throw NotConnectedException() ;

    mxStmWrapper->flush();
}

void SAL_CALL SvXMLGraphicOutputStream::closeOutput()
{
    if( !mxStmWrapper.is() )
        throw NotConnectedException() ;

    mxStmWrapper->closeOutput();
    mxStmWrapper.clear();

    mbClosed = true;
}

Graphic SvXMLGraphicOutputStream::GetGraphic(sal_Int32 nPageNum)
{
    Graphic aGraphic;

    if (mbClosed && moGrfObj->GetType() == GraphicType::NONE && mpOStm)
    {
        mpOStm->Seek( 0 );
        sal_uInt16 nFormat = GRFILTER_FORMAT_DONTKNOW;
        sal_uInt16 nDeterminedFormat = GRFILTER_FORMAT_DONTKNOW;
        GraphicFilter::GetGraphicFilter().ImportGraphic( aGraphic, u"", *mpOStm ,nFormat,&nDeterminedFormat,
                GraphicFilterImportFlags::NONE, nPageNum);

        if (nDeterminedFormat == GRFILTER_FORMAT_DONTKNOW)
        {
            //Read the first two byte to check whether it is a gzipped stream, is so it may be in wmz or emz format
            //unzip them and try again

            sal_uInt8    sFirstBytes[ 2 ];

            sal_uInt64 nStreamLen = mpOStm->TellEnd();
            mpOStm->Seek( 0 );

            if ( nStreamLen == 0 )
            {
                SvLockBytes* pLockBytes = mpOStm->GetLockBytes();
                if ( pLockBytes  )
                    pLockBytes->SetSynchronMode();

                nStreamLen = mpOStm->TellEnd();
                mpOStm->Seek( 0 );
            }
            if( nStreamLen >= 2 )
            {
                //read two byte
                mpOStm->ReadBytes(sFirstBytes, 2);

                if( sFirstBytes[0] == 0x1f && sFirstBytes[1] == 0x8b )
                {
                    SvMemoryStream aDest;
                    ZCodec aZCodec( 0x8000, 0x8000 );
                    aZCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true);
                    mpOStm->Seek( 0 );
                    aZCodec.Decompress( *mpOStm, aDest );

                    if (aZCodec.EndCompression())
                    {
                        sal_uInt64 nStreamLen_ = aDest.TellEnd();
                        if (nStreamLen_ > 0)
                        {
                            aDest.Seek(0);
                            GraphicFilter::GetGraphicFilter().ImportGraphic( aGraphic, u"", aDest ,nFormat,&nDeterminedFormat,
                                    GraphicFilterImportFlags::NONE, nPageNum);
                        }
                    }
                }
            }
        }
    }

    if (aGraphic.GetType() != GraphicType::NONE)
    {
        mpOStm = nullptr;
        moTmp.reset();
    }
    return aGraphic;
}

const GraphicObject& SvXMLGraphicOutputStream::GetGraphicObject()
{
    Graphic aGraphic(GetGraphic());
    if (aGraphic.GetType() != GraphicType::NONE)
    {
        moGrfObj.emplace(std::move(aGraphic));
    }
    return *moGrfObj;
}

}

SvXMLGraphicHelper::SvXMLGraphicHelper(SvXMLGraphicHelperMode eCreateMode)
{
    Init( nullptr, eCreateMode );
}

SvXMLGraphicHelper::SvXMLGraphicHelper()
    : meCreateMode(SvXMLGraphicHelperMode::Read)
{
}

SvXMLGraphicHelper::~SvXMLGraphicHelper()
{
}

void SvXMLGraphicHelper::splitObjectURL(const OUString& _aURLNoPar,
    OUString& rContainerStorageName,
    OUString& rObjectStorageName)
{
    DBG_ASSERT(_aURLNoPar.isEmpty() || '#' != _aURLNoPar[0], "invalid object URL" );
    OUString aURLNoPar = _aURLNoPar;

    sal_Int32 _nPos = aURLNoPar.lastIndexOf( '/' );
    if( -1 == _nPos )
    {
        rContainerStorageName.clear();
        rObjectStorageName = aURLNoPar;
    }
    else
    {
        //eliminate 'superfluous' slashes at start and end
        //#i103076# load objects with all allowed xlink:href syntaxes
        {
            //eliminate './' at start
            sal_Int32 nStart = 0;
            sal_Int32 nCount = aURLNoPar.getLength();
            if( aURLNoPar.startsWith( "./" ) )
            {
                nStart = 2;
                nCount -= 2;
            }

            //eliminate '/' at end
            sal_Int32 nEnd = aURLNoPar.lastIndexOf( '/' );
            if( nEnd == aURLNoPar.getLength()-1 && nEnd != (nStart-1) )
                nCount--;

            aURLNoPar = aURLNoPar.copy( nStart, nCount );
        }

        _nPos = aURLNoPar.lastIndexOf( '/' );
        if( _nPos >= 0 )
            rContainerStorageName = aURLNoPar.copy( 0, _nPos );
        rObjectStorageName = aURLNoPar.copy( _nPos+1 );
    }
}

bool SvXMLGraphicHelper::ImplGetStreamNames( const OUString& rURLStr,
                                                 OUString& rPictureStorageName,
                                                 OUString& rPictureStreamName )
{
    if (rURLStr.isEmpty())
        return false;

    const OUString aURLStr {rURLStr.copy(rURLStr.lastIndexOf(':')+1)};

    if( !aURLStr.isEmpty() && aURLStr.indexOf('/')<0 ) // just one token?
    {
        rPictureStorageName = OUString();
        rPictureStreamName = aURLStr;
    }
    else
        SvXMLGraphicHelper::splitObjectURL(aURLStr, rPictureStorageName, rPictureStreamName);

    SAL_WARN_IF(rPictureStreamName.isEmpty(), "svx", "SvXMLGraphicHelper::ImplInsertGraphicURL: invalid scheme: " << rURLStr);

    return !rPictureStreamName.isEmpty();
}

uno::Reference < embed::XStorage > SvXMLGraphicHelper::ImplGetGraphicStorage( const OUString& rStorageName )
{
    uno::Reference < embed::XStorage > xRetStorage;
    if( mxRootStorage.is() )
    {
        try
        {
            maCurStorageName = rStorageName;
            xRetStorage = mxRootStorage->openStorageElement(
                maCurStorageName,
                ( SvXMLGraphicHelperMode::Write == meCreateMode )
                    ? embed::ElementModes::READWRITE
                    : embed::ElementModes::READ );
        }
        catch ( uno::Exception& )
        {
        }
        //#i43196# try again to open the storage element - this time readonly
        if(!xRetStorage.is())
        {
            try
            {
                maCurStorageName = rStorageName;
                xRetStorage = mxRootStorage->openStorageElement( maCurStorageName, embed::ElementModes::READ );
            }
            catch ( uno::Exception& )
            {
            }
        }
    }

    return xRetStorage;
}

SvxGraphicHelperStream_Impl SvXMLGraphicHelper::ImplGetGraphicStream( const OUString& rPictureStorageName,
                                                              const OUString& rPictureStreamName )
{
    SvxGraphicHelperStream_Impl aRet;
    if (!rPictureStorageName.isEmpty())
        aRet.xStorage = ImplGetGraphicStorage(rPictureStorageName);
    else
        aRet.xStorage = mxRootStorage;

    sal_Int32 nMode = embed::ElementModes::READ;
    if (SvXMLGraphicHelperMode::Write == meCreateMode)
    {
        nMode = embed::ElementModes::READWRITE;
    }

    if (aRet.xStorage.is())
    {
        aRet.xStream = aRet.xStorage->openStreamElement( rPictureStreamName, nMode );
    }
    else if (rPictureStorageName.indexOf('/') != -1)
    {
        uno::Reference<embed::XHierarchicalStorageAccess> xHierRootStorage(mxRootStorage,
                                                                           uno::UNO_QUERY);
        if (xHierRootStorage.is())
        {
            try
            {
                aRet.xStream = xHierRootStorage->openStreamElementByHierarchicalName(
                    rPictureStorageName + "/" + rPictureStreamName, nMode);
                aRet.xStorage = mxRootStorage;
            }
            catch (const uno::Exception&)
            {
                TOOLS_WARN_EXCEPTION("svx",
                                     "SvXMLGraphicHelper::ImplGetGraphicStream: failed to open "
                                         << rPictureStreamName);
            }
        }
    }

    if (aRet.xStream.is() && (SvXMLGraphicHelperMode::Write == meCreateMode))
    {
        uno::Reference<beans::XPropertySet> xProps(aRet.xStream, uno::UNO_QUERY);
        xProps->setPropertyValue(u"UseCommonStoragePasswordEncryption"_ustr, uno::Any(true));
    }

    return aRet;
}

OUString SvXMLGraphicHelper::ImplGetGraphicMimeType( std::u16string_view rFileName )
{
    if( ( rFileName.size() >= 4 ) && ( rFileName[ rFileName.size() - 4 ] == '.' ) )
    {
        const OString aExt(OUStringToOString(rFileName.substr(rFileName.size() - 3),
            RTL_TEXTENCODING_ASCII_US));
        return comphelper::GraphicMimeTypeHelper::GetMimeTypeForExtension( aExt );
    }

    return OUString();
}

Graphic SvXMLGraphicHelper::ImplReadGraphic(const OUString& rPictureStorageName,
                                            const OUString& rPictureStreamName, sal_Int32 nPage)
{
    Graphic aReturnGraphic;
    SvxGraphicHelperStream_Impl aStream( ImplGetGraphicStream( rPictureStorageName, rPictureStreamName ) );
    if (aStream.xStream.is())
    {
        GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
        std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(aStream.xStream));
        Graphic aGraphic = rGraphicFilter.ImportUnloadedGraphic(*pStream, 0, nullptr, nPage);
        if (!aGraphic.IsNone())
            aReturnGraphic = std::move(aGraphic);
        else
            rGraphicFilter.ImportGraphic(aReturnGraphic, u"", *pStream, GRFILTER_FORMAT_DONTKNOW,
                                         nullptr, GraphicFilterImportFlags::NONE, nPage);
    }

    return aReturnGraphic;
}

void SvXMLGraphicHelper::Init( const uno::Reference < embed::XStorage >& rXMLStorage,
                               SvXMLGraphicHelperMode eCreateMode,
                               const OUString& rGraphicMimeType )
{
    mxRootStorage = rXMLStorage;
    meCreateMode = eCreateMode;
    maOutputMimeType = rGraphicMimeType;
}

rtl::Reference<SvXMLGraphicHelper> SvXMLGraphicHelper::Create( const uno::Reference < embed::XStorage >& rXMLStorage,
                                                SvXMLGraphicHelperMode eCreateMode )
{
    rtl::Reference<SvXMLGraphicHelper> pThis = new SvXMLGraphicHelper;
    pThis->Init( rXMLStorage, eCreateMode, OUString() );

    return pThis;
}

rtl::Reference<SvXMLGraphicHelper> SvXMLGraphicHelper::Create( SvXMLGraphicHelperMode eCreateMode,
                                              const OUString& rGraphicMimeType )
{
    rtl::Reference<SvXMLGraphicHelper> pThis = new SvXMLGraphicHelper;

    pThis->Init( nullptr, eCreateMode, rGraphicMimeType );

    return pThis;
}

namespace
{

void splitUserDataFromURL(OUString const & rWholeURL, OUString & rJustURL, OUString & rUserData)
{
    sal_Int32 nUser = rWholeURL.indexOf('?');
    if (nUser >= 0)
    {
        rJustURL = rWholeURL.copy(0, nUser);
        nUser++;
        rUserData = rWholeURL.copy(nUser);
    }
    else
    {
        rJustURL = rWholeURL;
    }
}

} // end anonymous namespace

// XGraphicObjectResolver
OUString SAL_CALL SvXMLGraphicHelper::resolveGraphicObjectURL( const OUString& /*rURL*/ )
{
    throw uno::RuntimeException(u"XGraphicObjectResolver has been removed in LibreOffice 6.1"_ustr);
}

// XGraphicStorageHandler
uno::Reference<graphic::XGraphic> SAL_CALL SvXMLGraphicHelper::loadGraphic(OUString const & rURL)
{
    return loadGraphicAtPage(rURL, -1);
}

uno::Reference<graphic::XGraphic>
    SvXMLGraphicHelper::loadGraphicAtPage(OUString const& rURL, sal_Int32 nPage)
{
    std::unique_lock aGuard(m_aMutex);

    uno::Reference<graphic::XGraphic> xGraphic;

    OUString aURLOnly;
    OUString aUserData;
    splitUserDataFromURL(rURL, aURLOnly, aUserData);

    size_t nIndex = (nPage >= 0 ? nPage : 0);
    auto aIterator = maGraphicObjects.find(aURLOnly);
    if (aIterator != maGraphicObjects.end() && aIterator->second.size() > nIndex
        && aIterator->second[nIndex].is())
    {
        return aIterator->second[nIndex];
    }

    OUString aPictureStorageName, aPictureStreamName;

    if (ImplGetStreamNames(aURLOnly, aPictureStorageName, aPictureStreamName))
    {
        const GraphicObject aGraphicObject(
            ImplReadGraphic(aPictureStorageName, aPictureStreamName, nPage));

        if (aGraphicObject.GetType() != GraphicType::NONE)
        {
            xGraphic = aGraphicObject.GetGraphic().GetXGraphic();
            if (aIterator != maGraphicObjects.end())
            {
                if (aIterator->second.size() <= nIndex)
                    aIterator->second.resize(nIndex + 1);
                aIterator->second[nIndex] = xGraphic;
            }
            else
            {
                maGraphicObjects.emplace(
                    aURLOnly, std::vector<uno::Reference<graphic::XGraphic>>(nIndex + 1));
                maGraphicObjects[aURLOnly][nIndex] = xGraphic;
            }
        }
    }

    return xGraphic;
}

uno::Reference<graphic::XGraphic> SvXMLGraphicHelper::loadGraphicFromOutputStreamAtPage(uno::Reference<io::XOutputStream> const & rxOutputStream, sal_Int32 nPageNum)
{
    std::unique_lock aGuard(m_aMutex);

    uno::Reference<graphic::XGraphic> xGraphic;

    if ((SvXMLGraphicHelperMode::Read == meCreateMode) && rxOutputStream.is())
    {
        SvXMLGraphicOutputStream* pGraphicOutputStream = static_cast<SvXMLGraphicOutputStream*>(rxOutputStream.get());
        if (pGraphicOutputStream)
        {
            xGraphic = pGraphicOutputStream->GetGraphic(nPageNum).GetXGraphic();
        }
    }
    return xGraphic;
}

// XGraphicStorageHandler
uno::Reference<graphic::XGraphic> SvXMLGraphicHelper::loadGraphicFromOutputStream(uno::Reference<io::XOutputStream> const & rxOutputStream)
{
    return loadGraphicFromOutputStreamAtPage(rxOutputStream, -1);
}

OUString SAL_CALL SvXMLGraphicHelper::saveGraphicByName(css::uno::Reference<css::graphic::XGraphic> const & rxGraphic,
                                                        OUString & rOutSavedMimeType, OUString const & rRequestName)
{
    return implSaveGraphic(rxGraphic, rOutSavedMimeType, rRequestName);
}

OUString SAL_CALL SvXMLGraphicHelper::saveGraphic(css::uno::Reference<css::graphic::XGraphic> const & rxGraphic)
{
    OUString aOutMimeType;
    return implSaveGraphic(rxGraphic, aOutMimeType, std::u16string_view());
}

OUString SvXMLGraphicHelper::implSaveGraphic(css::uno::Reference<css::graphic::XGraphic> const & rxGraphic,
                                             OUString & rOutSavedMimeType, std::u16string_view rRequestName)
{
    Graphic aGraphic(rxGraphic);

    SAL_INFO("svx", "implSaveGraphic: entry");
    auto aIterator = maExportGraphics.find(aGraphic);
    if (aIterator != maExportGraphics.end())
    {
        auto const & aURLAndMimePair = aIterator->second;
        rOutSavedMimeType = aURLAndMimePair.second;
        return aURLAndMimePair.first;
    }

    GraphicObject aGraphicObject(aGraphic);

    if (aGraphicObject.GetType() != GraphicType::NONE)
    {
        const GfxLink aGfxLink(aGraphic.GetGfxLink());
        OUString aExtension;
        bool bUseGfxLink = true;

        SAL_INFO("svx", "implSaveGraphic: Type!=None");
        if (aGfxLink.GetDataSize())
        {
            switch (aGfxLink.GetType())
            {
                case GfxLinkType::EpsBuffer: aExtension = ".eps"; break;
                case GfxLinkType::NativeGif: aExtension = ".gif"; break;
                // #i15508# added BMP type for better exports (checked, works)
                case GfxLinkType::NativeBmp: aExtension = ".bmp"; break;
                case GfxLinkType::NativeJpg: aExtension = ".jpg"; break;
                case GfxLinkType::NativePng: aExtension = ".png"; break;
                case GfxLinkType::NativeTif: aExtension = ".tif"; break;
                case GfxLinkType::NativeWmf:
                    if (aGfxLink.IsEMF())
                        aExtension = ".emf";
                    else
                        aExtension = ".wmf";
                    break;
                case GfxLinkType::NativeMet: aExtension = ".met"; break;
                case GfxLinkType::NativePct: aExtension = ".pct"; break;
                case GfxLinkType::NativeSvg:
                {
                    // backward-compat kludge: since no released OOo
                    // version to date can handle svg properly, wrap it up
                    // into an svm. slight catch22 here, since strict ODF
                    // conformance _recommends_ svg - then again, most old
                    // ODF consumers are believed to be OOo
                    auto nSaneVersion = GetODFSaneDefaultVersion();
                    if ( nSaneVersion < SvtSaveOptions::ODFSVER_012
                        || nSaneVersion == SvtSaveOptions::ODFSVER_012_EXT_COMPAT)
                    {
                        bUseGfxLink = false;
                        aExtension = ".svm";
                    }
                    else
                    {
                        aExtension = ".svg";
                    }
                    break;
                }
                case GfxLinkType::NativePdf: aExtension = ".pdf"; break;
                case GfxLinkType::NativeWebp: aExtension = ".webp"; break;

                default:
                    aExtension = ".grf";
                break;
            }
        }
        else
        {
            SAL_INFO("svx", "implSaveGraphic: Link !size");
            if (aGraphicObject.GetType() == GraphicType::Bitmap)
            {
                if (aGraphicObject.IsAnimated())
                    aExtension = ".gif";
                else
                    aExtension = ".png";
            }
            else if (aGraphicObject.GetType() == GraphicType::GdiMetafile)
            {
                // SJ: first check if this metafile is just an eps file, then we will store the eps instead of svm
                GDIMetaFile& rMetafile(const_cast<GDIMetaFile&>(aGraphic.GetGDIMetaFile()));

                if (ImplCheckForEPS(rMetafile))
                    aExtension = ".eps";
                else
                    aExtension = ".svm";
            }
        }

        SAL_INFO("svx", "implSaveGraphic: Extension:" << aExtension);

        OUString rPictureStreamName;
        if (!rRequestName.empty())
        {
            rPictureStreamName = rRequestName + aExtension;
        }
        else
        {
            OUString sId = OStringToOUString(aGraphicObject.GetUniqueID(), RTL_TEXTENCODING_ASCII_US);
            rPictureStreamName = sId + aExtension;
        }

        SvxGraphicHelperStream_Impl aStream(ImplGetGraphicStream(XML_GRAPHICSTORAGE_NAME, rPictureStreamName));

        if (aStream.xStream.is())
        {
            const OUString aMimeType(ImplGetGraphicMimeType(rPictureStreamName));
            uno::Reference<beans::XPropertySet> xProps(aStream.xStream, uno::UNO_QUERY);

            // set stream properties (MediaType/Compression)
            if (!aMimeType.isEmpty())
            {
                xProps->setPropertyValue(u"MediaType"_ustr, uno::Any(aMimeType));
            }

            // picture formats that actually _do_ benefit from zip
            // storage compression
            // .svm pics gets compressed via ZBITMAP old-style stream
            // option below
            static const char* const aCompressiblePics[] =
            {
                "image/svg+xml",
                "image/x-emf",
                "image/x-wmf",
                "image/tiff",
                "image/x-eps",
                "image/bmp",
                "image/x-pict"
            };

            bool bSuccess = false;

            bool bCompressed = aMimeType.isEmpty();
            if( !bCompressed )
            {
                for(const char* p : aCompressiblePics)
                {
                    if( aMimeType.equalsIgnoreAsciiCaseAscii(p) )
                    {
                        bCompressed = true;
                        break;
                    }
                }
            }

            xProps->setPropertyValue(u"Compressed"_ustr, Any(bCompressed));

            std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(aStream.xStream));
            if (bUseGfxLink && aGfxLink.GetDataSize() && aGfxLink.GetData())
            {
                SAL_INFO("svx", "implSaveGraphic: link");
                pStream->WriteBytes(aGfxLink.GetData(), aGfxLink.GetDataSize());
                rOutSavedMimeType = aMimeType;
                bSuccess = (pStream->GetError() == ERRCODE_NONE);
            }
            else
            {
                SAL_INFO("svx", "implSaveGraphic: !link");
                if (aGraphic.GetType() == GraphicType::Bitmap)
                {
                    SAL_INFO("svx", "implSaveGraphic: !link - Bitmap");
                    GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
                    OUString aFormat;

                    if (aGraphic.IsAnimated())
                    {
                        aFormat = "gif";
                    }
                    else
                    {
                        aFormat = "png";
                    }
                    rOutSavedMimeType = comphelper::GraphicMimeTypeHelper::GetMimeTypeForExtension(aFormat.toUtf8());

                    bSuccess = (rFilter.ExportGraphic(aGraphic, u"", *pStream, rFilter.GetExportFormatNumberForShortName(aFormat)) == ERRCODE_NONE);
                }
                else if (aGraphic.GetType() == GraphicType::GdiMetafile)
                {
                    SAL_INFO("svx", "implSaveGraphic: !link - Gdi");
                    pStream->SetVersion(SOFFICE_FILEFORMAT_8);
                    pStream->SetCompressMode(SvStreamCompressFlags::ZBITMAP);
                    rOutSavedMimeType = comphelper::GraphicMimeTypeHelper::GetMimeTypeForExtension("svm");

                    // SJ: first check if this metafile is just an eps file, then we will store the eps instead of svm
                    GDIMetaFile& rMtf(const_cast<GDIMetaFile&>(aGraphic.GetGDIMetaFile()));
                    const MetaCommentAction* pComment = ImplCheckForEPS(rMtf);
                    if (pComment)
                    {
                        sal_uInt32 nSize = pComment->GetDataSize();
                        const sal_uInt8* pData = pComment->GetData();
                        if (nSize && pData)
                            pStream->WriteBytes(pData, nSize);

                        const MetaEPSAction* pAct = static_cast<const MetaEPSAction*>(rMtf.FirstAction());
                        const GfxLink& rLink = pAct->GetLink();

                        pStream->WriteBytes(rLink.GetData(), rLink.GetDataSize());
                    }
                    else
                    {
                        SvmWriter aWriter(*pStream);
                        aWriter.Write(rMtf);
                    }

                    bSuccess = (pStream->GetError() == ERRCODE_NONE);
                }
            }
            SAL_INFO("svx", "implSaveGraphic: (mid)");

            if (!bSuccess)
                return OUString();

            uno::Reference<embed::XTransactedObject> xStorage(aStream.xStorage, uno::UNO_QUERY);
            pStream.reset();
            aStream.xStream->getOutputStream()->closeOutput();
            if (xStorage.is())
                xStorage->commit();

            OUString aStoragePath = "Pictures/" + rPictureStreamName;

            // put into cache
            maExportGraphics[aGraphic] = std::make_pair(aStoragePath, rOutSavedMimeType);
            SAL_INFO("svx", "implSaveGraphic: exit: Path: " << aStoragePath <<
                            " Mime: " << rOutSavedMimeType);

            return aStoragePath;
        }
    }

    return OUString();
}

uno::Reference<io::XInputStream> SAL_CALL SvXMLGraphicHelper::createInputStream(uno::Reference<graphic::XGraphic> const & rxGraphic)
{
    Reference<XInputStream> xInputStream;

    GraphicObject aGraphicObject((Graphic(rxGraphic)));

    if (SvXMLGraphicHelperMode::Write == meCreateMode)
    {
        OUString sMimeType = comphelper::GraphicMimeTypeHelper::GetMimeTypeForExtension(OUStringToOString(maOutputMimeType, RTL_TEXTENCODING_ASCII_US));
        rtl::Reference<GraphicInputStream> pInputStream(new GraphicInputStream(aGraphicObject, sMimeType));

        // We release the pointer from unique_ptr and assign it to the input stream return type.
        // In case the stream doesn't exists, unique_ptr will delete the pointer when we go out of scope.
        if (pInputStream->exists())
            xInputStream = pInputStream.get();
    }

    return xInputStream;
}

// XBinaryStreamResolver
Reference< XInputStream > SAL_CALL SvXMLGraphicHelper::getInputStream( const OUString& /*rURL*/ )
{
    Reference<XInputStream> xRet;
    return xRet;
}

Reference< XOutputStream > SAL_CALL SvXMLGraphicHelper::createOutputStream()
{
    Reference< XOutputStream > xRet;

    if( SvXMLGraphicHelperMode::Read == meCreateMode )
    {
        rtl::Reference<SvXMLGraphicOutputStream> pOutputStream(new SvXMLGraphicOutputStream);

        if( pOutputStream->Exists() )
        {
            xRet = pOutputStream.get();
            maGrfStms.push_back( xRet );
        }
    }

    return xRet;
}

OUString SAL_CALL SvXMLGraphicHelper::resolveOutputStream( const Reference< XOutputStream >& rxBinaryStream )
{
    OUString aRet;

    if( ( SvXMLGraphicHelperMode::Read == meCreateMode ) && rxBinaryStream.is() )
    {
        if( ::std::find( maGrfStms.begin(), maGrfStms.end(), rxBinaryStream ) != maGrfStms.end() )
        {
            SvXMLGraphicOutputStream* pOStm = static_cast< SvXMLGraphicOutputStream* >( rxBinaryStream.get() );

            if( pOStm )
            {
                const GraphicObject& rGrfObj = pOStm->GetGraphicObject();
                const OUString aId(OStringToOUString(
                    rGrfObj.GetUniqueID(), RTL_TEXTENCODING_ASCII_US));

                if( !aId.isEmpty() )
                {
                    aRet = XML_GRAPHICOBJECT_URL_BASE + aId;
                }
            }
        }
    }

    return aRet;
}

// for instantiation via service manager
namespace {

namespace impl
{
typedef comphelper::WeakComponentImplHelper<lang::XInitialization,
                                        document::XGraphicObjectResolver,
                                        document::XGraphicStorageHandler,
                                        document::XBinaryStreamResolver,
                                        lang::XServiceInfo>
    SvXMLGraphicImportExportHelper_Base;

} // namespace impl

class SvXMLGraphicImportExportHelper :
    public impl::SvXMLGraphicImportExportHelper_Base
{
public:
    explicit SvXMLGraphicImportExportHelper( SvXMLGraphicHelperMode eMode );

protected:
    // is called from WeakComponentImplHelper when XComponent::dispose() was
    // called from outside
    virtual void disposing(std::unique_lock<std::mutex>&) override;

    // ____ XInitialization ____
    // one argument is allowed, which is the XStorage
    virtual void SAL_CALL initialize( const Sequence< Any >& aArguments ) override;

    // ____ XGraphicObjectResolver ____
    virtual OUString SAL_CALL resolveGraphicObjectURL( const OUString& aURL ) override;

    // ____ XGraphicStorageHandler ____
    virtual css::uno::Reference<css::graphic::XGraphic> SAL_CALL
        loadGraphic(const OUString& aURL) override;

    // ____ XGraphicStorageHandler ____
    virtual css::uno::Reference<css::graphic::XGraphic> SAL_CALL
        loadGraphicFromOutputStream(css::uno::Reference<css::io::XOutputStream> const & rxOutputStream) override;

    virtual OUString SAL_CALL
        saveGraphic(css::uno::Reference<css::graphic::XGraphic> const & rxGraphic) override;

    virtual OUString SAL_CALL
        saveGraphicByName(css::uno::Reference<css::graphic::XGraphic> const & rxGraphic, OUString & rOutSavedMimeType, OUString const & rRequestName) override;

    virtual css::uno::Reference<css::io::XInputStream> SAL_CALL
        createInputStream(css::uno::Reference<css::graphic::XGraphic> const & rxGraphic) override;

    // ____ XBinaryStreamResolver ____
    virtual Reference< io::XInputStream > SAL_CALL getInputStream( const OUString& aURL ) override;
    virtual Reference< io::XOutputStream > SAL_CALL createOutputStream() override;
    virtual OUString SAL_CALL resolveOutputStream( const Reference< io::XOutputStream >& aBinaryStream ) override;

    // ____ XServiceInfo ____
    virtual OUString SAL_CALL getImplementationName() override;
    virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
    virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override;

private:
    SvXMLGraphicHelperMode              m_eGraphicHelperMode;
    rtl::Reference<SvXMLGraphicHelper>  m_xXMLGraphicHelper;
};

SvXMLGraphicImportExportHelper::SvXMLGraphicImportExportHelper( SvXMLGraphicHelperMode eMode ) :
        m_eGraphicHelperMode( eMode )
{}

void SvXMLGraphicImportExportHelper::disposing(std::unique_lock<std::mutex>&)
{
    if (m_xXMLGraphicHelper)
    {
        m_xXMLGraphicHelper->dispose();
        m_xXMLGraphicHelper.clear();
    }
}

// ____ XInitialization ____
void SAL_CALL SvXMLGraphicImportExportHelper::initialize(
    const Sequence< Any >& aArguments )
{
    Reference< embed::XStorage > xStorage;
    if( aArguments.hasElements() )
        aArguments[0] >>= xStorage;

    m_xXMLGraphicHelper = SvXMLGraphicHelper::Create( xStorage, m_eGraphicHelperMode );
}

// ____ XGraphicObjectResolver ____
OUString SAL_CALL SvXMLGraphicImportExportHelper::resolveGraphicObjectURL( const OUString& aURL )
{
    return m_xXMLGraphicHelper->resolveGraphicObjectURL( aURL );
}

// ____ XGraphicStorageHandler ____
uno::Reference<graphic::XGraphic> SAL_CALL SvXMLGraphicImportExportHelper::loadGraphic(OUString const & rURL)
{
    return m_xXMLGraphicHelper->loadGraphic(rURL);
}

// ____ XGraphicStorageHandler ____
uno::Reference<graphic::XGraphic> SAL_CALL SvXMLGraphicImportExportHelper::loadGraphicFromOutputStream(uno::Reference<io::XOutputStream> const & rxOutputStream)
{
    return m_xXMLGraphicHelper->loadGraphicFromOutputStream(rxOutputStream);
}

OUString SAL_CALL SvXMLGraphicImportExportHelper::saveGraphic(css::uno::Reference<css::graphic::XGraphic> const & rxGraphic)
{
    return m_xXMLGraphicHelper->saveGraphic(rxGraphic);
}

OUString SAL_CALL SvXMLGraphicImportExportHelper::saveGraphicByName(css::uno::Reference<css::graphic::XGraphic> const & rxGraphic,
                                                                    OUString & rOutSavedMimeType, OUString const & rRequestName)
{
    return m_xXMLGraphicHelper->saveGraphicByName(rxGraphic, rOutSavedMimeType, rRequestName);
}

uno::Reference<io::XInputStream> SAL_CALL SvXMLGraphicImportExportHelper::createInputStream(uno::Reference<graphic::XGraphic> const & rxGraphic)
{
    return m_xXMLGraphicHelper->createInputStream(rxGraphic);
}

// ____ XBinaryStreamResolver ____
Reference< io::XInputStream > SAL_CALL SvXMLGraphicImportExportHelper::getInputStream( const OUString& aURL )
{
    return m_xXMLGraphicHelper->getInputStream( aURL );
}
Reference< io::XOutputStream > SAL_CALL SvXMLGraphicImportExportHelper::createOutputStream()
{
    return m_xXMLGraphicHelper->createOutputStream();
}
OUString SAL_CALL SvXMLGraphicImportExportHelper::resolveOutputStream( const Reference< io::XOutputStream >& aBinaryStream )
{
    return m_xXMLGraphicHelper->resolveOutputStream( aBinaryStream );
}

// ____ XServiceInfo ____
OUString SAL_CALL SvXMLGraphicImportExportHelper::getImplementationName()
{
    if( m_eGraphicHelperMode == SvXMLGraphicHelperMode::Read )
        return u"com.sun.star.comp.Svx.GraphicImportHelper"_ustr;
    return u"com.sun.star.comp.Svx.GraphicExportHelper"_ustr;
}

sal_Bool SAL_CALL SvXMLGraphicImportExportHelper::supportsService( const OUString& ServiceName )
{
    return cppu::supportsService(this, ServiceName);
}

Sequence< OUString > SAL_CALL SvXMLGraphicImportExportHelper::getSupportedServiceNames()
{
    return { u"com.sun.star.document.GraphicObjectResolver"_ustr,
             u"com.sun.star.document.GraphicStorageHandler"_ustr,
             u"com.sun.star.document.BinaryStreamResolver"_ustr };
}

}

/** Create this with createInstanceWithArguments. service name
    "com.sun.star.comp.Svx.GraphicImportHelper", one argument which is the
    XStorage.  Without arguments no helper class is created.  With an empty
    argument the helper class is created and initialized like in the CTOR to
    SvXMLGraphicHelper that only gets the create mode.

    You should call dispose after you no longer need this component.

    uses eCreateMode == SvXMLGraphicHelperMode::Read, bDirect == sal_True in
    SvXMLGraphicHelper
 */
extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
com_sun_star_comp_Svx_GraphicImportHelper_get_implementation(
    css::uno::XComponentContext *,
    css::uno::Sequence<css::uno::Any> const &)
{
    return cppu::acquire(new SvXMLGraphicImportExportHelper(SvXMLGraphicHelperMode::Read));
}

/** Create this with createInstanceWithArguments. service name
    "com.sun.star.comp.Svx.GraphicExportHelper", one argument which is the
    XStorage.  Without arguments no helper class is created.  With an empty
    argument the helper class is created and initialized like in the CTOR to
    SvXMLGraphicHelper that only gets the create mode

    To write the Pictures stream, you have to call dispose at this component.
    Make sure you call dispose before you commit the parent storage.

    uses eCreateMode == SvXMLGraphicHelperMode::Write, bDirect == sal_True in
    SvXMLGraphicHelper
 */
extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
com_sun_star_comp_Svx_GraphicExportHelper_get_implementation(
    css::uno::XComponentContext *,
    css::uno::Sequence<css::uno::Any> const &)
{
    return cppu::acquire(new SvXMLGraphicImportExportHelper(SvXMLGraphicHelperMode::Write));
}

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