/* -*- 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 "RecentlyUsedMasterPages.hxx"
#include "MasterPageContainerProviders.hxx"
#include <MasterPageObserver.hxx>
#include "MasterPageDescriptor.hxx"
#include <tools/ConfigurationAccess.hxx>

#include <algorithm>
#include <memory>
#include <vector>

#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/container/XNameContainer.hpp>
#include <com/sun/star/lang/XSingleServiceFactory.hpp>
#include <osl/doublecheckedlocking.h>
#include <osl/getglobalmutex.hxx>

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

namespace {

OUString GetPathToImpressConfigurationRoot()
{
    return u"/org.openoffice.Office.Impress/"_ustr;
}
OUString GetPathToSetNode()
{
    return u"MultiPaneGUI/ToolPanel/RecentlyUsedMasterPages"_ustr;
}

} // end of anonymous namespace

namespace sd::sidebar {

RecentlyUsedMasterPages* RecentlyUsedMasterPages::mpInstance = nullptr;

RecentlyUsedMasterPages&  RecentlyUsedMasterPages::Instance()
{
    if (mpInstance == nullptr)
    {
        ::osl::GetGlobalMutex aMutexFunctor;
        ::osl::MutexGuard aGuard (aMutexFunctor());
        if (mpInstance == nullptr)
        {
            RecentlyUsedMasterPages* pInstance = new RecentlyUsedMasterPages();
            pInstance->LateInit();
            SdGlobalResourceContainer::Instance().AddResource (
                ::std::unique_ptr<SdGlobalResource>(pInstance));
            OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
            mpInstance = pInstance;
        }
    }
    else {
        OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
    }

    return *mpInstance;
}

constexpr size_t gnMaxListSize(8);

RecentlyUsedMasterPages::RecentlyUsedMasterPages()
    : mpContainer(std::make_shared<MasterPageContainer>())
{
}

void RecentlyUsedMasterPages::ImplDestroy()
{
    Link<MasterPageContainerChangeEvent&,void> aLink (LINK(this,RecentlyUsedMasterPages,MasterPageContainerChangeListener));
    mpContainer->RemoveChangeListener(aLink);

    MasterPageObserver::Instance().RemoveEventListener(
        LINK(this,RecentlyUsedMasterPages,MasterPageChangeListener));
}

RecentlyUsedMasterPages::~RecentlyUsedMasterPages()
{
    suppress_fun_call_w_exception(ImplDestroy());
}

void RecentlyUsedMasterPages::LateInit()
{
    Link<MasterPageContainerChangeEvent&,void> aLink (LINK(this,RecentlyUsedMasterPages,MasterPageContainerChangeListener));
    mpContainer->AddChangeListener(aLink);

    LoadPersistentValues ();
    MasterPageObserver::Instance().AddEventListener(
        LINK(this,RecentlyUsedMasterPages,MasterPageChangeListener));
}

void RecentlyUsedMasterPages::LoadPersistentValues()
{
    try
    {
        sdtools::ConfigurationAccess aConfiguration (
            GetPathToImpressConfigurationRoot(),
            sdtools::ConfigurationAccess::READ_ONLY);
        Reference<container::XNameAccess> xSet (
            aConfiguration.GetConfigurationNode(GetPathToSetNode()),
            UNO_QUERY);
        if ( ! xSet.is())
            return;

        static constexpr OUStringLiteral sURLMemberName(u"URL");
        static constexpr OUStringLiteral sNameMemberName(u"Name");
        OUString sURL;
        OUString sName;

        // Read the names and URLs of the master pages.
        const Sequence<OUString> aKeys (xSet->getElementNames());
        mvMasterPages.clear();
        mvMasterPages.reserve(aKeys.getLength());
        for (const auto& rKey : aKeys)
        {
            Reference<container::XNameAccess> xSetItem (
                xSet->getByName(rKey), UNO_QUERY);
            if (xSetItem.is())
            {
                Any aURL (xSetItem->getByName(sURLMemberName));
                Any aName (xSetItem->getByName(sNameMemberName));
                aURL >>= sURL;
                aName >>= sName;
                SharedMasterPageDescriptor pDescriptor = std::make_shared<MasterPageDescriptor>(
                    MasterPageContainer::TEMPLATE,
                    -1,
                    sURL,
                    OUString(),
                    sName,
                    false,
                    std::make_shared<TemplatePageObjectProvider>(sURL),
                    std::make_shared<TemplatePreviewProvider>(sURL));
                // For user supplied templates we use a different
                // preview provider: The preview in the document shows
                // not only shapes on the master page but also shapes on
                // the foreground.  This is misleading and therefore
                // these previews are discarded and created directly
                // from the page objects.
                if (pDescriptor->GetURLClassification() == MasterPageDescriptor::URLCLASS_USER)
                    pDescriptor->mpPreviewProvider = std::make_shared<PagePreviewProvider>();
                MasterPageContainer::Token aToken (mpContainer->PutMasterPage(pDescriptor));
                mvMasterPages.emplace_back(aToken,sURL,sName);
            }
        }

        ResolveList();
    }
    catch (Exception&)
    {
        // Ignore exception.
    }
}

void RecentlyUsedMasterPages::SavePersistentValues()
{
    try
    {
        sdtools::ConfigurationAccess aConfiguration (
            GetPathToImpressConfigurationRoot(),
            sdtools::ConfigurationAccess::READ_WRITE);
        Reference<container::XNameContainer> xSet (
            aConfiguration.GetConfigurationNode(GetPathToSetNode()),
            UNO_QUERY);
        if ( ! xSet.is())
            return;

        // Clear the set.
        const Sequence<OUString> aKeys (xSet->getElementNames());
        for (const auto& rKey : aKeys)
            xSet->removeByName (rKey);

        // Fill it with the URLs of this object.
        static constexpr OUStringLiteral sURLMemberName(u"URL");
        static constexpr OUStringLiteral sNameMemberName(u"Name");
        Any aValue;
        Reference<lang::XSingleServiceFactory> xChildFactory (
            xSet, UNO_QUERY);
        if ( ! xChildFactory.is())
            return;
        sal_Int32 nIndex(0);
        for (const auto& rDescriptor : mvMasterPages)
        {
            // Create new child.
            OUString sKey = "index_" + OUString::number(nIndex);
            Reference<container::XNameReplace> xChild(
                xChildFactory->createInstance(), UNO_QUERY);
            if (xChild.is())
            {
                xSet->insertByName (sKey, Any(xChild));

                aValue <<= rDescriptor.msURL;
                xChild->replaceByName (sURLMemberName, aValue);

                aValue <<= rDescriptor.msName;
                xChild->replaceByName (sNameMemberName, aValue);
            }
            ++nIndex;
        }

        // Write the data back to disk.
        aConfiguration.CommitChanges();
    }
    catch (Exception&)
    {
        // Ignore exception.
    }
}

void RecentlyUsedMasterPages::AddEventListener (const Link<LinkParamNone*,void>& rEventListener)
{
    if (::std::find (
        maListeners.begin(),
        maListeners.end(),
        rEventListener) == maListeners.end())
    {
        maListeners.push_back (rEventListener);
    }
}

void RecentlyUsedMasterPages::RemoveEventListener (const Link<LinkParamNone*,void>& rEventListener)
{
    maListeners.erase (
        ::std::find (
            maListeners.begin(),
            maListeners.end(),
            rEventListener));
}

int RecentlyUsedMasterPages::GetMasterPageCount() const
{
    return mvMasterPages.size();
}

MasterPageContainer::Token RecentlyUsedMasterPages::GetTokenForIndex (sal_uInt32 nIndex) const
{
    if(nIndex<mvMasterPages.size())
        return mvMasterPages[nIndex].maToken;
    else
        return MasterPageContainer::NIL_TOKEN;
}

void RecentlyUsedMasterPages::SendEvent()
{
    for (const auto& aLink : maListeners)
    {
        aLink.Call(nullptr);
    }
}

IMPL_LINK(RecentlyUsedMasterPages, MasterPageChangeListener,
    MasterPageObserverEvent&, rEvent, void)
{
    switch (rEvent.meType)
    {
        case MasterPageObserverEvent::ET_MASTER_PAGE_ADDED:
        case MasterPageObserverEvent::ET_MASTER_PAGE_EXISTS:
            AddMasterPage(
                mpContainer->GetTokenForStyleName(rEvent.mrMasterPageName));
            break;

        case MasterPageObserverEvent::ET_MASTER_PAGE_REMOVED:
            // Do not change the list of recently master pages (the deleted
            // page was recently used) but tell the listeners.  They may want
            // to update their lists.
            SendEvent();
            break;
    }
}

IMPL_LINK(RecentlyUsedMasterPages, MasterPageContainerChangeListener,
    MasterPageContainerChangeEvent&, rEvent, void)
{
    switch (rEvent.meEventType)
    {
        case MasterPageContainerChangeEvent::EventType::CHILD_ADDED:
        case MasterPageContainerChangeEvent::EventType::CHILD_REMOVED:
        case MasterPageContainerChangeEvent::EventType::INDEX_CHANGED:
            ResolveList();
            break;

        default:
            // Ignored.
            break;
    }
}

void RecentlyUsedMasterPages::AddMasterPage (
    MasterPageContainer::Token aToken)
{
    // For the page to be inserted the token has to be valid and the page
    // has to have a valid URL.  This excludes master pages that do not come
    // from template files.
    if (aToken == MasterPageContainer::NIL_TOKEN
        || mpContainer->GetURLForToken(aToken).isEmpty())
        return;

    MasterPageList::iterator aIterator (
        ::std::find_if(mvMasterPages.begin(),mvMasterPages.end(),
            Descriptor::TokenComparator(aToken)));
    if (aIterator != mvMasterPages.end())
    {
        // When an entry for the given token already exists then remove
        // it now and insert it later at the head of the list.
        mvMasterPages.erase (aIterator);
    }

    mvMasterPages.insert(mvMasterPages.begin(),
        Descriptor(
            aToken,
            mpContainer->GetURLForToken(aToken),
            mpContainer->GetStyleNameForToken(aToken)));

    // Shorten list to maximal size.
    while (mvMasterPages.size() > gnMaxListSize)
    {
        mvMasterPages.pop_back ();
    }

    SavePersistentValues ();
    SendEvent();
}

void RecentlyUsedMasterPages::ResolveList()
{
    bool bNotify (false);

    for (auto& rDescriptor : mvMasterPages)
    {
        if (rDescriptor.maToken == MasterPageContainer::NIL_TOKEN)
        {
            MasterPageContainer::Token aToken (mpContainer->GetTokenForURL(rDescriptor.msURL));
            rDescriptor.maToken = aToken;
            if (aToken != MasterPageContainer::NIL_TOKEN)
                bNotify = true;
        }
        else
        {
            if ( ! mpContainer->HasToken(rDescriptor.maToken))
            {
                rDescriptor.maToken = MasterPageContainer::NIL_TOKEN;
                bNotify = true;
            }
        }
    }

    if (bNotify)
        SendEvent();
}

} // end of namespace sd::sidebar

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