/* -*- 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 <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/XPropertySetInfo.hpp>
#include <com/sun/star/form/FormButtonType.hpp>

#include <tools/urlobj.hxx>
#include <sfx2/docfile.hxx>
#include <svx/svdograf.hxx>
#include <svx/svdouno.hxx>
#include <osl/diagnose.h>

#include <seltrans.hxx>
#include <transobj.hxx>
#include <drwtrans.hxx>
#include <scmod.hxx>
#include <dbfunc.hxx>
#include <docsh.hxx>
#include <drawview.hxx>
#include <drwlayer.hxx>
#include <markdata.hxx>

using namespace com::sun::star;

static bool lcl_IsURLButton( SdrObject* pObject )
{
    bool bRet = false;

    SdrUnoObj* pUnoCtrl = dynamic_cast<SdrUnoObj*>( pObject );
    if (pUnoCtrl && SdrInventor::FmForm == pUnoCtrl->GetObjInventor())
    {
        const uno::Reference<awt::XControlModel>& xControlModel = pUnoCtrl->GetUnoControlModel();
        OSL_ENSURE( xControlModel.is(), "uno control without model" );
        if ( xControlModel.is() )
        {
            uno::Reference< beans::XPropertySet > xPropSet( xControlModel, uno::UNO_QUERY );
            uno::Reference< beans::XPropertySetInfo > xInfo = xPropSet->getPropertySetInfo();

            OUString sPropButtonType( u"ButtonType"_ustr );
            if(xInfo->hasPropertyByName( sPropButtonType ))
            {
                uno::Any aAny = xPropSet->getPropertyValue( sPropButtonType );
                form::FormButtonType eTmp;
                if ( (aAny >>= eTmp) && eTmp == form::FormButtonType_URL )
                    bRet = true;
            }
        }
    }

    return bRet;
}

rtl::Reference<ScSelectionTransferObj> ScSelectionTransferObj::CreateFromView( ScTabView* pView )
{
    rtl::Reference<ScSelectionTransferObj> pRet;

    try
    {
        if ( pView )
        {
            ScSelectionTransferMode eMode = SC_SELTRANS_INVALID;

            SdrView* pSdrView = pView->GetScDrawView();
            if ( pSdrView )
            {
                //  handle selection on drawing layer
                const SdrMarkList& rMarkList = pSdrView->GetMarkedObjectList();
                const size_t nMarkCount = rMarkList.GetMarkCount();
                if ( nMarkCount )
                {
                    if ( nMarkCount == 1 )
                    {
                        SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj();
                        SdrObjKind nSdrObjKind = pObj->GetObjIdentifier();

                        if ( nSdrObjKind == SdrObjKind::Graphic )
                        {
                            if ( static_cast<SdrGrafObj*>(pObj)->GetGraphic().GetType() == GraphicType::Bitmap )
                                eMode = SC_SELTRANS_DRAW_BITMAP;
                            else
                                eMode = SC_SELTRANS_DRAW_GRAPHIC;
                        }
                        else if ( nSdrObjKind == SdrObjKind::OLE2 )
                            eMode = SC_SELTRANS_DRAW_OLE;
                        else if ( lcl_IsURLButton( pObj ) )
                            eMode = SC_SELTRANS_DRAW_BOOKMARK;
                    }

                    if ( eMode == SC_SELTRANS_INVALID )
                        eMode = SC_SELTRANS_DRAW_OTHER;     // something selected but no special selection
                }
            }
            if ( eMode == SC_SELTRANS_INVALID )             // no drawing object selected
            {
                ScViewData& rViewData = pView->GetViewData();
                const ScMarkData& rMark = rViewData.GetMarkData();
                //  allow MultiMarked because GetSimpleArea may be able to merge into a simple range
                //  (GetSimpleArea modifies a local copy of MarkData)
                // Also allow simple filtered area.
                if ( rMark.IsMarked() || rMark.IsMultiMarked() )
                {
                    ScRange aRange;
                    ScMarkType eMarkType = rViewData.GetSimpleArea( aRange );
                    if (eMarkType == SC_MARK_SIMPLE || eMarkType == SC_MARK_SIMPLE_FILTERED)
                    {
                        //  only for "real" selection, cursor alone isn't used
                        if ( aRange.aStart == aRange.aEnd )
                            eMode = SC_SELTRANS_CELL;
                        else
                            eMode = SC_SELTRANS_CELLS;
                    }
                }
            }

            if ( eMode != SC_SELTRANS_INVALID )
                pRet = new ScSelectionTransferObj( pView, eMode );
        }
    }
    catch (...)
    {
    }

    return pRet;
}

ScSelectionTransferObj::ScSelectionTransferObj( ScTabView* pSource, ScSelectionTransferMode eNewMode ) :
    pView( pSource ),
    eMode( eNewMode )
{
    //! store range for StillValid
}

ScSelectionTransferObj::~ScSelectionTransferObj()
{
    ScModule* pScMod = ScModule::get();
    if (pScMod && pScMod->GetSelectionTransfer() == this)
    {
        //  this is reached when the object wasn't really copied to the selection
        //  (CopyToSelection has no effect under Windows)

        ForgetView();
        pScMod->SetSelectionTransfer( nullptr );
    }

    OSL_ENSURE( !pView, "ScSelectionTransferObj dtor: ForgetView not called" );
}

void ScSelectionTransferObj::ForgetView()
{
    pView = nullptr;
    eMode = SC_SELTRANS_INVALID;

    mxCellData.clear();
    mxDrawData.clear();
}

void ScSelectionTransferObj::AddSupportedFormats()
{
    //  AddSupportedFormats must work without actually creating the
    //  "real" transfer object

    switch (eMode)
    {
        case SC_SELTRANS_CELL:
        case SC_SELTRANS_CELLS:
            //  same formats as in ScTransferObj::AddSupportedFormats
            AddFormat( SotClipboardFormatId::EMBED_SOURCE );
            AddFormat( SotClipboardFormatId::OBJECTDESCRIPTOR );
            AddFormat( SotClipboardFormatId::GDIMETAFILE );
            AddFormat( SotClipboardFormatId::PNG );
            AddFormat( SotClipboardFormatId::BITMAP );
            AddFormat( SotClipboardFormatId::HTML );
            AddFormat( SotClipboardFormatId::SYLK );
            AddFormat( SotClipboardFormatId::LINK );
            AddFormat( SotClipboardFormatId::DIF );
            AddFormat( SotClipboardFormatId::STRING );
            AddFormat( SotClipboardFormatId::STRING_TSVC );
            AddFormat( SotClipboardFormatId::RTF );
            AddFormat( SotClipboardFormatId::RICHTEXT );
            if ( eMode == SC_SELTRANS_CELL )
            {
                AddFormat( SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT );
            }
            break;

        // different graphic formats as in ScDrawTransferObj::AddSupportedFormats:

        case SC_SELTRANS_DRAW_BITMAP:
            AddFormat( SotClipboardFormatId::OBJECTDESCRIPTOR );
            AddFormat( SotClipboardFormatId::SVXB );
            AddFormat( SotClipboardFormatId::PNG );
            AddFormat( SotClipboardFormatId::BITMAP );
            AddFormat( SotClipboardFormatId::GDIMETAFILE );
            break;

        case SC_SELTRANS_DRAW_GRAPHIC:
            AddFormat( SotClipboardFormatId::OBJECTDESCRIPTOR );
            AddFormat( SotClipboardFormatId::SVXB );
            AddFormat( SotClipboardFormatId::GDIMETAFILE );
            AddFormat( SotClipboardFormatId::PNG );
            AddFormat( SotClipboardFormatId::BITMAP );
             break;

        case SC_SELTRANS_DRAW_BOOKMARK:
            AddFormat( SotClipboardFormatId::OBJECTDESCRIPTOR );
            AddFormat( SotClipboardFormatId::SOLK );
            AddFormat( SotClipboardFormatId::STRING );
            AddFormat( SotClipboardFormatId::UNIFORMRESOURCELOCATOR );
            AddFormat( SotClipboardFormatId::NETSCAPE_BOOKMARK );
            AddFormat( SotClipboardFormatId::DRAWING );
            break;

        case SC_SELTRANS_DRAW_OLE:
            AddFormat( SotClipboardFormatId::EMBED_SOURCE );
            AddFormat( SotClipboardFormatId::OBJECTDESCRIPTOR );
            AddFormat( SotClipboardFormatId::GDIMETAFILE );
            break;

        case SC_SELTRANS_DRAW_OTHER:
            //  other drawing objects
            AddFormat( SotClipboardFormatId::EMBED_SOURCE );
            AddFormat( SotClipboardFormatId::OBJECTDESCRIPTOR );
            AddFormat( SotClipboardFormatId::DRAWING );
            AddFormat( SotClipboardFormatId::PNG );
            AddFormat( SotClipboardFormatId::BITMAP );
            AddFormat( SotClipboardFormatId::GDIMETAFILE );
            break;

        default:
        {
            // added to avoid warnings
        }
    }
}

void ScSelectionTransferObj::CreateCellData()
{
    OSL_ENSURE( !mxCellData.is(), "CreateCellData twice" );
    if ( pView )
    {
        ScViewData& rViewData = pView->GetViewData();
        ScMarkData aNewMark( rViewData.GetMarkData() );    // use local copy for MarkToSimple
        aNewMark.MarkToSimple();

        //  similar to ScViewFunctionSet::BeginDrag
        if ( aNewMark.IsMarked() && !aNewMark.IsMultiMarked() )
        {
            ScDocShell* pDocSh = rViewData.GetDocShell();

            const ScRange& aSelRange = aNewMark.GetMarkArea();
            ScDocShellRef aDragShellRef;
            if ( pDocSh->GetDocument().HasOLEObjectsInArea( aSelRange, &aNewMark ) )
            {
                aDragShellRef = new ScDocShell;     // DocShell needs a Ref immediately
                aDragShellRef->DoInitNew();
            }
            ScDrawLayer::SetGlobalDrawPersist( aDragShellRef.get() );

            ScDocumentUniquePtr pClipDoc(new ScDocument( SCDOCMODE_CLIP ));
            // bApi = sal_True -> no error messages
            // #i18364# bStopEdit = sal_False -> don't end edit mode
            // (this may be called from pasting into the edit line)
            bool bCopied = rViewData.GetView()->CopyToClip( pClipDoc.get(), false, true, true, false );

            ScDrawLayer::SetGlobalDrawPersist(nullptr);

            if ( bCopied )
            {
                TransferableObjectDescriptor aObjDesc;
                pDocSh->FillTransferableObjectDescriptor( aObjDesc );
                aObjDesc.maDisplayName = pDocSh->GetMedium()->GetURLObject().GetURLNoPass();
                // maSize is set in ScTransferObj ctor

                rtl::Reference<ScTransferObj> xTransferObj = new ScTransferObj( std::move(pClipDoc), std::move(aObjDesc) );

                // SetDragHandlePos is not used - there is no mouse position
                //? xTransferObj->SetVisibleTab( nTab );

                xTransferObj->SetDrawPersist(aDragShellRef); // keep persist for ole objects alive

                xTransferObj->SetDragSource( pDocSh, aNewMark );

                mxCellData = std::move(xTransferObj);
            }
        }
    }
    OSL_ENSURE( mxCellData.is(), "can't create CellData" );
}

void ScSelectionTransferObj::CreateDrawData()
{
    OSL_ENSURE( !mxDrawData.is(), "CreateDrawData twice" );
    if ( pView )
    {
        //  similar to ScDrawView::BeginDrag

        ScDrawView* pDrawView = pView->GetScDrawView();
        if ( pDrawView )
        {
            bool bAnyOle, bOneOle;
            const SdrMarkList& rMarkList = pDrawView->GetMarkedObjectList();
            ScDrawView::CheckOle( rMarkList, bAnyOle, bOneOle );

            ScDocShellRef aDragShellRef;
            if (bAnyOle)
            {
                aDragShellRef = new ScDocShell; // Without Ref the DocShell does not live
                aDragShellRef->DoInitNew();
            }

            ScDrawLayer::SetGlobalDrawPersist( aDragShellRef.get() );
            std::unique_ptr<SdrModel> pModel(pDrawView->CreateMarkedObjModel());
            ScDrawLayer::SetGlobalDrawPersist(nullptr);

            ScViewData& rViewData = pView->GetViewData();
            ScDocShell* pDocSh = rViewData.GetDocShell();

            TransferableObjectDescriptor aObjDesc;
            pDocSh->FillTransferableObjectDescriptor( aObjDesc );
            aObjDesc.maDisplayName = pDocSh->GetMedium()->GetURLObject().GetURLNoPass();
            // maSize is set in ScDrawTransferObj ctor

            rtl::Reference<ScDrawTransferObj> pTransferObj = new ScDrawTransferObj( std::move(pModel), *pDocSh, std::move(aObjDesc) );

            pTransferObj->SetDrawPersist(aDragShellRef); // keep persist for ole objects alive
            pTransferObj->SetDragSource( pDrawView );       // copies selection

            mxDrawData = std::move(pTransferObj);
        }
    }
    OSL_ENSURE( mxDrawData.is(), "can't create DrawData" );
}

ScTransferObj* ScSelectionTransferObj::GetCellData()
{
    if ( !mxCellData.is() && ( eMode == SC_SELTRANS_CELL || eMode == SC_SELTRANS_CELLS ) )
        CreateCellData();
    return mxCellData.get();
}

ScDrawTransferObj* ScSelectionTransferObj::GetDrawData()
{
    if ( !mxDrawData.is() && ( eMode == SC_SELTRANS_DRAW_BITMAP || eMode == SC_SELTRANS_DRAW_GRAPHIC ||
                               eMode == SC_SELTRANS_DRAW_BOOKMARK || eMode == SC_SELTRANS_DRAW_OLE ||
                               eMode == SC_SELTRANS_DRAW_OTHER ) )
        CreateDrawData();
    return mxDrawData.get();
}

bool ScSelectionTransferObj::GetData(
    const css::datatransfer::DataFlavor& rFlavor, const OUString& rDestDoc )
{
    bool bOK = false;

    uno::Reference<datatransfer::XTransferable> xSource;
    switch (eMode)
    {
        case SC_SELTRANS_CELL:
        case SC_SELTRANS_CELLS:
            xSource = GetCellData();
            break;
        case SC_SELTRANS_DRAW_BITMAP:
        case SC_SELTRANS_DRAW_GRAPHIC:
        case SC_SELTRANS_DRAW_BOOKMARK:
        case SC_SELTRANS_DRAW_OLE:
        case SC_SELTRANS_DRAW_OTHER:
            xSource = GetDrawData();
            break;
        default:
        {
            // added to avoid warnings
        }
    }

    if ( xSource.is() )
    {
        TransferableDataHelper aHelper( xSource );
        uno::Any aAny = aHelper.GetAny(rFlavor, rDestDoc);
        bOK = SetAny( aAny );
    }

    return bOK;
}

void ScSelectionTransferObj::ObjectReleased()
{
    //  called when another selection is set from outside

    ForgetView();

    ScModule* pScMod = ScModule::get();
    if ( pScMod->GetSelectionTransfer() == this )
        pScMod->SetSelectionTransfer( nullptr );

    TransferableHelper::ObjectReleased();
}

sal_Bool SAL_CALL ScSelectionTransferObj::isComplex()
{
    switch (eMode)
    {
    case SC_SELTRANS_CELL:
    case SC_SELTRANS_CELLS:
        return false;
    default:
        return true;
    }
}

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