/* -*- 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 "DocumentHelper.hxx"

#include <drawdoc.hxx>
#include <DrawDocShell.hxx>
#include <unomodel.hxx>
#include <sdpage.hxx>
#include <glob.hxx>
#include <unmovss.hxx>
#include <strings.hrc>
#include <sdresid.hxx>
#include <undoback.hxx>
#include <ViewShell.hxx>
#include <ViewShellBase.hxx>
#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
#include <com/sun/star/drawing/XDrawPages.hpp>
#include <stlpool.hxx>
#include <svx/xfillit0.hxx>
#include <svx/svdundo.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <xmloff/autolayout.hxx>
#include <sal/log.hxx>

using namespace ::com::sun::star;

namespace sd::sidebar {

SdPage* DocumentHelper::CopyMasterPageToLocalDocument (
    SdDrawDocument& rTargetDocument,
    SdPage* pMasterPage)
{
    SdPage* pNewMasterPage = nullptr;

    do
    {
        if (pMasterPage == nullptr)
            break;

        // Check the presence of the source document.
        SdDrawDocument& rSourceDocument(static_cast< SdDrawDocument& >(pMasterPage->getSdrModelFromSdrPage()));

        // When the given master page already belongs to the target document
        // then there is nothing more to do.
        if (&rSourceDocument == &rTargetDocument)
        {
            pNewMasterPage = pMasterPage;
            break;
        }

        // Test if the master pages of both the slide and its notes page are
        // present.  This is not the case when we are called during the
        // creation of the slide master page because then the notes master
        // page is not there.
        sal_uInt16 nSourceMasterPageCount = rSourceDocument.GetMasterPageCount();
        if (nSourceMasterPageCount%2 == 0)
            // There should be 1 handout page + n slide masters + n notes
            // masters = 2*n+1.  An even value indicates that a new slide
            // master but not yet the notes master has been inserted.
            break;
        sal_uInt16 nIndex = pMasterPage->GetPageNum();
        if (nSourceMasterPageCount <= nIndex+1)
            break;
        // Get the slide master page.
        if (pMasterPage != static_cast<SdPage*>(
            rSourceDocument.GetMasterPage(nIndex)))
            break;
        // Get the notes master page.
        SdPage* pNotesMasterPage = static_cast<SdPage*>(
            rSourceDocument.GetMasterPage(nIndex+1));
        if (pNotesMasterPage == nullptr)
            break;

        // Check if a master page with the same name as that of the given
        // master page already exists.
        bool bPageExists (false);
        sal_uInt16 nMasterPageCount(rTargetDocument.GetMasterSdPageCount(PageKind::Standard));
        for (sal_uInt16 nMaster=0; nMaster<nMasterPageCount; nMaster++)
        {
            SdPage* pCandidate = rTargetDocument.GetMasterSdPage (nMaster, PageKind::Standard);
            if (pCandidate->GetName() == pMasterPage->GetName())
            {
                bPageExists = true;
                pNewMasterPage = pCandidate;
                break;
            }
        }
        if (bPageExists)
            break;

        // Create a new slide (and its notes page.)
        rtl::Reference<SdXImpressDocument> xSlideSupplier(rTargetDocument.getUnoModel());
        if ( ! xSlideSupplier.is())
            break;
        uno::Reference<drawing::XDrawPages> xSlides =
            xSlideSupplier->getDrawPages();
        if ( ! xSlides.is())
            break;
        xSlides->insertNewByIndex (xSlides->getCount());

        // Set a layout.
        SdPage* pSlide = rTargetDocument.GetSdPage(
            rTargetDocument.GetSdPageCount(PageKind::Standard)-1,
            PageKind::Standard);
        if (pSlide == nullptr)
            break;
        pSlide->SetAutoLayout(AUTOLAYOUT_TITLE, true);

        // Create a copy of the master page and the associated notes
        // master page and insert them into our document.
        pNewMasterPage = AddMasterPage(rTargetDocument, pMasterPage);
        if (pNewMasterPage==nullptr)
            break;
        SdPage* pNewNotesMasterPage
            = AddMasterPage(rTargetDocument, pNotesMasterPage);
        if (pNewNotesMasterPage==nullptr)
            break;

        // Make the connection from the new slide to the master page
        // (and do the same for the notes page.)
        rTargetDocument.SetMasterPage (
            rTargetDocument.GetSdPageCount(PageKind::Standard)-1,
            pNewMasterPage->GetName(),
            &rTargetDocument,
            false, // Connect the new master page with the new slide but
                   // do not modify other (master) pages.
            true);
    }
    while (false);

    // We are not interested in any automatisms for our modified internal
    // document.
    rTargetDocument.SetChanged(false);

    return pNewMasterPage;
}

SdPage* DocumentHelper::GetSlideForMasterPage (SdPage const * pMasterPage)
{
    SdPage* pCandidate = nullptr;

    SdDrawDocument* pDocument = nullptr;
    if (pMasterPage != nullptr)
        pDocument = dynamic_cast< SdDrawDocument* >(&pMasterPage->getSdrModelFromSdrPage());

    // Iterate over all pages and check if it references the given master
    // page.
    if (pDocument!=nullptr && pDocument->GetSdPageCount(PageKind::Standard) > 0)
    {
        // In most cases a new slide has just been inserted so start with
        // the last page.
        sal_uInt16 nPageIndex (pDocument->GetSdPageCount(PageKind::Standard)-1);
        while (true)
        {
            pCandidate = pDocument->GetSdPage(
                nPageIndex,
                PageKind::Standard);
            if (pCandidate != nullptr)
            {
                if (static_cast<SdPage*>(&pCandidate->TRG_GetMasterPage())
                    == pMasterPage)
                {
                    break;
                }
            }

            if (nPageIndex == 0)
            {
                // If no page was found, that referenced the given master page, reset
                // the pointer that is returned.
                pCandidate = nullptr;
                break;
            }
            else
                nPageIndex --;
        }
    }

    return pCandidate;
}

SdPage* DocumentHelper::AddMasterPage (
    SdDrawDocument& rTargetDocument,
    SdPage const * pMasterPage)
{
    rtl::Reference<SdPage> pClonedMasterPage;

    if (pMasterPage!=nullptr)
    {
        try
        {
            // Duplicate the master page.
            pClonedMasterPage = static_cast<SdPage*>(pMasterPage->CloneSdrPage(rTargetDocument).get());

            // Copy the necessary styles.
            SdDrawDocument& rSourceDocument(static_cast< SdDrawDocument& >(pMasterPage->getSdrModelFromSdrPage()));
            ProvideStyles(rSourceDocument, rTargetDocument, pClonedMasterPage.get());

            // Copy the precious flag.
            pClonedMasterPage->SetPrecious(pMasterPage->IsPrecious());

            // Now that the styles are available we can insert the cloned
            // master page.
            rTargetDocument.InsertMasterPage (pClonedMasterPage.get());
        }
        catch(const uno::Exception&)
        {
            DBG_UNHANDLED_EXCEPTION("sd");
            pClonedMasterPage = nullptr;
        }
        catch(const ::std::exception& e)
        {
            pClonedMasterPage = nullptr;
            SAL_WARN("sd", "caught general exception " << e.what());
        }
        catch(...)
        {
            pClonedMasterPage = nullptr;
            SAL_WARN("sd", "caught general exception");
        }
    }

    return pClonedMasterPage.get();
}

void DocumentHelper::ProvideStyles (
    SdDrawDocument const & rSourceDocument,
    SdDrawDocument& rTargetDocument,
    SdPage const * pPage)
{
    // Get the layout name of the given page.
    OUString sLayoutName (pPage->GetLayoutName());
    sal_Int32 nIndex = sLayoutName.indexOf(SD_LT_SEPARATOR);
    if( nIndex != -1 )
        sLayoutName = sLayoutName.copy(0, nIndex);

    // Copy the style sheet from source to target document.
    SdStyleSheetPool* pSourceStyleSheetPool =
        static_cast<SdStyleSheetPool*>(rSourceDocument.GetStyleSheetPool());
    SdStyleSheetPool* pTargetStyleSheetPool =
        static_cast<SdStyleSheetPool*>(rTargetDocument.GetStyleSheetPool());
    StyleSheetCopyResultVector aCreatedStyles;
    pTargetStyleSheetPool->CopyLayoutSheets (
        sLayoutName,
        *pSourceStyleSheetPool,
        aCreatedStyles);

    // Add an undo action for the copied style sheets.
    if( !aCreatedStyles.empty() )
    {
        SfxUndoManager* pUndoManager = rTargetDocument.GetDocSh()->GetUndoManager();
        if (pUndoManager != nullptr)
        {
            pUndoManager->AddUndoAction (
               std::make_unique<SdMoveStyleSheetsUndoAction>(
                   rTargetDocument,
                   aCreatedStyles,
                   true));
        }
    }
}

void DocumentHelper::AssignMasterPageToPageList (
    SdDrawDocument& rTargetDocument,
    SdPage* pMasterPage,
    const std::shared_ptr<std::vector<SdPage*> >& rpPageList)
{
    if (pMasterPage == nullptr || !pMasterPage->IsMasterPage())
        return;

    // Make the layout name by stripping out the layout postfix from the
    // layout name of the given master page.
    OUString sFullLayoutName(pMasterPage->GetLayoutName());
    OUString sBaseLayoutName (sFullLayoutName);
    sal_Int32 nIndex = sBaseLayoutName.indexOf(SD_LT_SEPARATOR);
    if( nIndex != -1 )
        sBaseLayoutName = sBaseLayoutName.copy(0, nIndex);

    if (rpPageList->empty())
        return;

    // Create a second list that contains only the valid pointers to
    // pages for which an assignment is necessary.
    ::std::vector<SdPage*> aCleanedList;
    for (const auto& rpPage : *rpPageList)
    {
        OSL_ASSERT(rpPage!=nullptr && &rpPage->getSdrModelFromSdrPage() == &rTargetDocument);
        if (rpPage != nullptr && rpPage->GetLayoutName() != sFullLayoutName)
        {
            aCleanedList.push_back(rpPage);
        }
    }
    if (aCleanedList.empty() )
        return;

    ViewShellId  nViewShellId(-1);
    if (sd::ViewShell* pViewShell = rTargetDocument.GetDocSh()->GetViewShell())
        nViewShellId = pViewShell->GetViewShellBase().GetViewShellId();
    SfxUndoManager* pUndoMgr = rTargetDocument.GetDocSh()->GetUndoManager();
    if( pUndoMgr )
        pUndoMgr->EnterListAction(SdResId(STR_UNDO_SET_PRESLAYOUT), OUString(), 0, nViewShellId);

    SdPage* pMasterPageInDocument = ProvideMasterPage(rTargetDocument,pMasterPage,rpPageList);
    if (pMasterPageInDocument == nullptr)
        return;

    // Assign the master pages to the given list of pages.
    for (const auto& rpPage : aCleanedList)
    {
        AssignMasterPageToPage (
            pMasterPageInDocument,
            sBaseLayoutName,
            rpPage);
    }

    if( pUndoMgr )
        pUndoMgr->LeaveListAction();
}

SdPage* DocumentHelper::AddMasterPage (
    SdDrawDocument& rTargetDocument,
    SdPage const * pMasterPage,
    sal_uInt16 nInsertionIndex)
{
    rtl::Reference<SdPage> pClonedMasterPage;

    if (pMasterPage!=nullptr)
    {
        // Duplicate the master page.
        pClonedMasterPage = static_cast<SdPage*>(pMasterPage->CloneSdrPage(rTargetDocument).get());

        // Copy the precious flag.
        pClonedMasterPage->SetPrecious(pMasterPage->IsPrecious());

        // Copy the necessary styles.
        SdDrawDocument& rSourceDocument(static_cast< SdDrawDocument& >(pMasterPage->getSdrModelFromSdrPage()));
        ProvideStyles(rSourceDocument, rTargetDocument, pClonedMasterPage.get());

        // Now that the styles are available we can insert the cloned
        // master page.
        rTargetDocument.InsertMasterPage (pClonedMasterPage.get(), nInsertionIndex);

        // Adapt the size of the new master page to that of the pages in
        // the document.
        Size aNewSize (rTargetDocument.GetSdPage(0, pMasterPage->GetPageKind())->GetSize());
        ::tools::Rectangle aBorders (
            pClonedMasterPage->GetLeftBorder(),
            pClonedMasterPage->GetUpperBorder(),
            pClonedMasterPage->GetRightBorder(),
            pClonedMasterPage->GetLowerBorder());
        pClonedMasterPage->ScaleObjects(aNewSize, aBorders, true);
        pClonedMasterPage->SetSize(aNewSize);
        pClonedMasterPage->CreateTitleAndLayout(true);
    }

    return pClonedMasterPage.get();
}

/** In here we have to handle three cases:
    1. pPage is a normal slide.  We can use SetMasterPage to assign the
    master pages to it.
    2. pPage is a master page that is used by at least one slide.  We can
    assign the master page to these slides.
    3. pPage is a master page that is currently not used by any slide.
    We can delete that page and add copies of the given master pages
    instead.

    For points 2 and 3 where one master page A is assigned to another B we have
    to keep in mind that the master page that page A has already been
    inserted into the target document.
*/
void DocumentHelper::AssignMasterPageToPage (
    SdPage const * pMasterPage,
    std::u16string_view rsBaseLayoutName,
    SdPage* pPage)
{
    // Leave early when the parameters are invalid.
    if (pPage == nullptr || pMasterPage == nullptr)
        return;

    SdDrawDocument& rDocument(dynamic_cast< SdDrawDocument& >(pPage->getSdrModelFromSdrPage()));

    if ( ! pPage->IsMasterPage())
    {
        // 1. Remove the background object (so that, if it exists, does
        // not override the new master page) and assign the master page to
        // the regular slide.
        rDocument.GetDocSh()->GetUndoManager()->AddUndoAction(
            std::make_unique<SdBackgroundObjUndoAction>(
                rDocument, *pPage, pPage->getSdrPageProperties().GetItemSet()),
                true);
        pPage->getSdrPageProperties().PutItem(XFillStyleItem(drawing::FillStyle_NONE));

        rDocument.SetMasterPage (
            (pPage->GetPageNum()-1)/2,
            rsBaseLayoutName,
            &rDocument,
            false,
            false);
    }
    else
    {
        // Find first slide that uses the master page.
        SdPage* pSlide = nullptr;
        sal_uInt16 nPageCount = rDocument.GetSdPageCount(PageKind::Standard);
        for (sal_uInt16 nPage=0; nPage<nPageCount&&pSlide==nullptr; nPage++)
        {
            SdrPage* pCandidate = rDocument.GetSdPage(nPage,PageKind::Standard);
            if (pCandidate != nullptr
                && pCandidate->TRG_HasMasterPage()
                && &(pCandidate->TRG_GetMasterPage()) == pPage)
            {
                pSlide = static_cast<SdPage*>(pCandidate);
            }
        }

        if (pSlide != nullptr)
        {
            // 2. Assign the given master pages to the first slide that was
            // found above that uses the master page.
            rDocument.SetMasterPage (
                (pSlide->GetPageNum()-1)/2,
                rsBaseLayoutName,
                &rDocument,
                false,
                false);
        }
        else
        {
            // 3. Replace the master page A by a copy of the given master
            // page B.
            rDocument.RemoveUnnecessaryMasterPages (
                pPage);
        }
    }
}

SdPage* DocumentHelper::ProvideMasterPage (
    SdDrawDocument& rTargetDocument,
    SdPage* pMasterPage,
    const std::shared_ptr<std::vector<SdPage*> >& rpPageList)
{
    // Make sure that both the master page and its notes master exist
    // in the source document.  If one is missing then return without
    // making any changes.
    if (pMasterPage == nullptr)
    {
        // The caller should make sure that the master page is valid.
        OSL_ASSERT(pMasterPage != nullptr);
        return nullptr;
    }
    SdDrawDocument& rSourceDocument(static_cast< SdDrawDocument& >(pMasterPage->getSdrModelFromSdrPage()));
    SdPage* pNotesMasterPage = static_cast<SdPage*>(
        rSourceDocument.GetMasterPage(pMasterPage->GetPageNum()+1));
    if (pNotesMasterPage == nullptr)
    {
        // The model is not in a valid state.  Maybe a new master page
        // is being (not finished yet) created?  Return without making
        // any changes.
        return nullptr;
    }

    SdPage* pMasterPageInDocument = nullptr;
    // Search for a master page with the same name as the given one in
    // the target document.
    const OUString sMasterPageLayoutName (pMasterPage->GetLayoutName());
    for (sal_uInt16 nIndex=0,nCount=rTargetDocument.GetMasterPageCount(); nIndex<nCount; ++nIndex)
    {
        SdPage* pCandidate = static_cast<SdPage*>(rTargetDocument.GetMasterPage(nIndex));
        if (pCandidate && sMasterPageLayoutName == pCandidate->GetLayoutName())
        {
            // The requested master page does already exist in the
            // target document, return it.
            return pCandidate;
        }
    }

    // The given master page does not already belong to the target
    // document so we have to create copies and insert them into the
    // target document.

    // Determine the position where the new master pages are inserted.
    // By default they are inserted at the end.  When we assign to a
    // master page then insert after the last of the (selected) pages.
    sal_uInt16 nInsertionIndex = rTargetDocument.GetMasterPageCount();
    if (rpPageList->front()->IsMasterPage())
    {
        nInsertionIndex = rpPageList->back()->GetPageNum();
    }

    // Clone the master page.
    if (&pMasterPage->getSdrModelFromSdrPage() != &rTargetDocument)
    {
        pMasterPageInDocument = AddMasterPage (rTargetDocument, pMasterPage, nInsertionIndex);
        if( rTargetDocument.IsUndoEnabled() )
                rTargetDocument.AddUndo(
                    rTargetDocument.GetSdrUndoFactory().CreateUndoNewPage(*pMasterPageInDocument));
    }
    else
        pMasterPageInDocument = pMasterPage;

    // Clone the notes master.
    if (&pNotesMasterPage->getSdrModelFromSdrPage() != &rTargetDocument)
    {
        SdPage* pClonedNotesMasterPage
            = AddMasterPage (rTargetDocument, pNotesMasterPage, nInsertionIndex+1);
        if( rTargetDocument.IsUndoEnabled() )
            rTargetDocument.AddUndo(
                rTargetDocument.GetSdrUndoFactory().CreateUndoNewPage(*pClonedNotesMasterPage));
    }

    return pMasterPageInDocument;
}

} // end of namespace sd::sidebar

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