/* -*- 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 .
 */

#pragma once

#include <config_options.h>
#include <rtl/ref.hxx>
#include <svl/poolitem.hxx>
#include <svl/svldllapi.h>
#include <svl/typedwhich.hxx>
#include <svl/whichranges.hxx>
#include <memory>
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <salhelper/simplereferenceobject.hxx>
#include <svl/SfxBroadcaster.hxx>

// flag definitions to be used for _nItemInfoFlags
// in SfxItemInfo
#define SFX_ITEMINFOFLAG_NONE               0x0000

// Defines if this Item needs to be registered at the pool
// to make it accessible for the GetItemSurrogates call. It
// will not be included when this flag is not set, but also
// needs no registration. There are SAL_INFO calls in the
// GetItemSurrogates impl that will mention that
#define SFX_ITEMINFOFLAG_SUPPORT_SURROGATE  0x0001

class SfxItemSet;

class SVL_DLLPUBLIC ItemInfo
{
    sal_uInt16          m_nWhich;
    sal_uInt16          m_nSlotID;
    sal_uInt16          m_nItemInfoFlags;

public:
    ItemInfo(sal_uInt16 nWhich, sal_uInt16 nSlotID, sal_uInt16 nItemInfoFlags)
    : m_nWhich(nWhich), m_nSlotID(nSlotID), m_nItemInfoFlags(nItemInfoFlags) {}
    ItemInfo(const ItemInfo& rIemInfo) = default;
    virtual ~ItemInfo() = default;

    sal_uInt16 getWhich() const { return m_nWhich; }
    virtual const SfxPoolItem* getItem() const = 0;
    sal_uInt16 getSlotID() const { return m_nSlotID; }
    sal_uInt16 getItemInfoFlags() const { return m_nItemInfoFlags; }
};

class SVL_DLLPUBLIC ItemInfoStatic final : public ItemInfo
{
    friend class ItemInfoPackage;
    void setItem(SfxPoolItem* pItem)
    {
        if (nullptr != pItem)
            pItem->setStaticDefault();
        m_pItem.reset(pItem);
    }

    std::unique_ptr<const SfxPoolItem> m_pItem;

public:
    ItemInfoStatic(sal_uInt16 nWhich, SfxPoolItem* pItem, sal_uInt16 nSlotID, sal_uInt16 nItemInfoFlags)
    : ItemInfo(nWhich, nSlotID, nItemInfoFlags)
    , m_pItem(pItem) { if(nullptr != pItem) pItem->setStaticDefault(); }

    virtual const SfxPoolItem* getItem() const override { return m_pItem.get(); }
};

class SVL_DLLPUBLIC ItemInfoDynamic final : public ItemInfo
{
    std::unique_ptr<const SfxPoolItem> m_pItem;

public:
    ItemInfoDynamic(const ItemInfo& rItemInfo, SfxPoolItem* pItem)
    : ItemInfo(rItemInfo)
    , m_pItem(pItem) { if(nullptr != pItem) pItem->setDynamicDefault(); }

    virtual const SfxPoolItem* getItem() const override { return m_pItem.get(); }
};

class UNLESS_MERGELIBS(SVL_DLLPUBLIC) ItemInfoUser final : public ItemInfo
{
    const SfxPoolItem* m_pItem;

public:
    ItemInfoUser(const ItemInfo& rItemInfo, const SfxItemPool& rItemPool, const SfxPoolItem& rItem, bool bPassingOwnership = false);
    virtual ~ItemInfoUser();

    virtual const SfxPoolItem* getItem() const override { return m_pItem; }
};

typedef std::unordered_map<sal_uInt16, sal_uInt16> SlotIDToWhichIDMap;

class SVL_DLLPUBLIC ItemInfoPackage
{
protected:
    // this is needed for on-demand creation of static entries in constructors
    // derived from ItemInfoPackage or implementations of ::getItemInfo(). This
    // takes ownership of the item
    static void setItemAtItemInfoStatic(SfxPoolItem* pItem, ItemInfoStatic& rItemInfo) { rItemInfo.setItem(pItem); }

private:
    // mechanism for buffered SlotIDToWhichIDMap
    virtual const ItemInfoStatic& getItemInfoStatic(size_t nIndex) const = 0;
    mutable SlotIDToWhichIDMap maSlotIDToWhichIDMap;

public:
    ItemInfoPackage() = default;
    virtual ~ItemInfoPackage() = default;

    virtual size_t size() const = 0;
    virtual const ItemInfo& getItemInfo(size_t nIndex, SfxItemPool& rPool) = 0;
    virtual const ItemInfo& getExistingItemInfo(size_t /*nIndex*/);
    const SlotIDToWhichIDMap& getSlotIDToWhichIDMap() const;
};

typedef std::unordered_set<SfxItemSet*> registeredSfxItemSets;
class SfxPoolItemHolder;
typedef std::unordered_set<SfxPoolItemHolder*> registeredSfxPoolItemHolders;
typedef std::vector<const SfxPoolItem*> ItemSurrogates;
typedef std::unordered_map<sal_uInt16, const ItemInfo*> userItemInfos;
typedef std::vector<const ItemInfo*> itemInfoVector;
typedef std::unordered_map<const SfxPoolItem*, sal_uInt32> NameOrIndexContent;
typedef std::unordered_map<SfxItemType, NameOrIndexContent> registeredNameOrIndex;

/** Base class for providers of defaults of SfxPoolItems.
 *
 * The derived classes hold the concrete (const) instances which are referenced in several places
 * (usually within a single document).
 * This helps to lower the amount of calls to lifecycle methods, speeds up comparisons within a document
 * and facilitates loading and saving of attributes.
 */
class SVL_DLLPUBLIC SfxItemPool : public salhelper::SimpleReferenceObject
{
    friend class SfxItemSet;
    friend class SfxPoolItemHolder;
    friend class SfxAllItemSet;

    // allow ItemSetTooling to access
    friend SfxPoolItem const* implCreateItemEntry(const SfxItemPool&, SfxPoolItem const*, bool);
    friend void implCleanupItemEntry(SfxPoolItem const*);

    SfxBroadcaster                  aBC;
    OUString                        aName;
    SfxItemPool*                    mpMaster;
    rtl::Reference<SfxItemPool>     mpSecondary;
    mutable WhichRangesContainer    maPoolRanges;
    sal_uInt16                      mnStart;
    sal_uInt16                      mnEnd;
    MapUnit                         eDefMetric;

    registeredSfxItemSets maRegisteredSfxItemSets;
    registeredSfxPoolItemHolders maRegisteredSfxPoolItemHolders;

    // place to register all NameOrIndex Items that are either set in an
    // SfxItemSet or SfxPolItemHolder for this Model/Pool
    registeredNameOrIndex maRegisteredNameOrIndex;

    bool mbShutdownHintSent;

    itemInfoVector maItemInfos;
    userItemInfos maUserItemInfos;
    const SlotIDToWhichIDMap* mpSlotIDToWhichIDMap;

public:
    void registerItemInfoPackage(
        ItemInfoPackage& rPackage,
        const std::function<SfxPoolItem*(sal_uInt16)>& rCallback = std::function<SfxPoolItem*(sal_uInt16)>());
protected:
    const ItemInfo* impCheckItemInfoForClone(const ItemInfo* pInfo);
    void impClearUserDefault(const userItemInfos::iterator& rHit);
    void impCreateUserDefault(const SfxPoolItem& rItem);
private:
    void cleanupItemInfos();

private:
    sal_uInt16 GetIndex_Impl(sal_uInt16 nWhich) const
    {
        if (IsInRange(nWhich))
            return nWhich - mnStart;
        assert(false && "missing bounds check before use");
        return 0;
    }
    sal_uInt16 GetSize_Impl() const { return mnEnd - mnStart + 1; }
    SfxItemPool* getTargetPool(sal_uInt16 nWhich) const;

    // moved to private: use the access methods, e.g. NeedsSurrogateSupport
    SVL_DLLPRIVATE bool CheckItemInfoFlag(sal_uInt16 nWhich, sal_uInt16 nMask) const;
    SVL_DLLPRIVATE bool CheckItemInfoFlag_Impl(sal_uInt16 nPos, sal_uInt16 nMask) const
        { return maItemInfos[nPos]->getItemInfoFlags() & nMask; }

    void registerItemSet(SfxItemSet& rSet);
    void unregisterItemSet(SfxItemSet& rSet);

    void registerPoolItemHolder(SfxPoolItemHolder& rHolder);
    void unregisterPoolItemHolder(SfxPoolItemHolder& rHolder);

    void registerNameOrIndex(const SfxPoolItem& rItem);
    void unregisterNameOrIndex(const SfxPoolItem& rItem);

public:
    // for default SfxItemSet::CTOR, set default WhichRanges
    const WhichRangesContainer& GetMergedIdRanges() const;

protected:
    static inline void              AddRef(const SfxPoolItem& rItem);
    static inline sal_uInt32        ReleaseRef(const SfxPoolItem& rItem, sal_uInt32 n = 1);

public:
    SfxItemPool(const SfxItemPool &rPool);
    SfxItemPool(const OUString &rName);
    virtual ~SfxItemPool();

    SfxBroadcaster&                 BC();

    // UserDefaults: Every PoolDefault can be 'overloaded' with a user-defined
    // default. This is then owned by the pool. The read access is limited
    // to check the UserDefaults, so it *will* return nullptr if none is set
    void                            SetUserDefaultItem( const SfxPoolItem& );
    const SfxPoolItem*              GetUserDefaultItem( sal_uInt16 nWhich ) const;
    template<class T> const T*      GetUserDefaultItem( TypedWhichId<T> nWhich ) const
    { return static_cast<const T*>(GetUserDefaultItem(sal_uInt16(nWhich))); }
    void                            ResetUserDefaultItem( sal_uInt16 nWhich );

    // PoolDefaults: Owned by the pool. The read access will only return
    // nullptr if the WhichID asked for is not in the range of the pool,
    // making the request invalid.
    const SfxPoolItem *             GetPoolDefaultItem(sal_uInt16 nWhich) const;
    template<class T> const T*      GetPoolDefaultItem( TypedWhichId<T> nWhich ) const
    { return static_cast<const T*>(GetPoolDefaultItem(sal_uInt16(nWhich))); }

    // UserOrPoolDefaults: Combination of UserDefaults and PoolDefaults.
    // UserDefaults will be preferred. If none is set for that WhichID,
    // the PoolDefault will be returned.
    // Note that read access will return a reference, but this will lead
    // to an asserted error when the given WhichID is not in the range of
    // the pool.
    const SfxPoolItem&              GetUserOrPoolDefaultItem( sal_uInt16 nWhich ) const;
    template<class T> const T&      GetUserOrPoolDefaultItem( TypedWhichId<T> nWhich ) const
    { return static_cast<const T&>(GetUserOrPoolDefaultItem(sal_uInt16(nWhich))); }

    virtual MapUnit                 GetMetric( sal_uInt16 nWhich ) const;
    void                            SetDefaultMetric( MapUnit eNewMetric );
    MapUnit GetDefaultMetric() const { return eDefMetric; }

    /** Request string representation of pool items.

        This virtual function produces a string representation
        from the respective SfxItemPool subclass' known SfxPoolItems.

        Subclasses, please override this method, and handle
        SfxPoolItems that don't return useful/complete information on
        SfxPoolItem::GetPresentation()

        This baseclass yields the unmodified string representation of
        rItem.

        @param[in] rItem
        SfxPoolItem to query the string representation of

        @param[in] ePresent
        requested kind of representation - see SfxItemPresentation

        @param[in] eMetric
        requested unit of measure of the representation

        @param[out] rText
        string representation of 'rItem'

        @return true if it has a valid string representation
    */
    virtual bool                    GetPresentation( const SfxPoolItem& rItem,
                                                     MapUnit ePresentationMetric,
                                                     OUString& rText,
                                                     const IntlWrapper& rIntlWrapper ) const;
    virtual rtl::Reference<SfxItemPool> Clone() const;
    const OUString& GetName() const { return aName; }

public:
    // SurrogateData callback helper for iterateItemSurrogates
    class SurrogateData
    {
    public:
        virtual ~SurrogateData() = default;
        SurrogateData(const SurrogateData&) = default;
        SurrogateData() = default;

        // read-access to Item
        virtual const SfxPoolItem& getItem() const = 0;

        // write-access when Item needs to be modified
        virtual const SfxPoolItem* setItem(std::unique_ptr<SfxPoolItem>) = 0;
    };

    // Iterate using a lambda/callback with read/write access to registered SfxPoolItems.
    // If you use this (look for current usages) inside the callback you may
    //   return true; // to continue callback (like 'continue')
    //   return false; // to end callbacks (like 'break')
    void iterateItemSurrogates(
        sal_uInt16 nWhich,
        const std::function<bool(SfxItemPool::SurrogateData& rData)>& rItemCallback) const;

    // Read-only access to registered SfxPoolItems
    // NOTE: In *no* case use const_cast and change those Items (!)
    // Read commit text for more information
    ItemSurrogates GetItemSurrogates(sal_uInt16 nWhich) const;

    // special version for read-only itemSurrogates for NameOrIndex Items
    ItemSurrogates GetItemSurrogatesForItem(const SfxPoolItem& rItem) const;
    ItemSurrogates GetItemSurrogatesForItem(SfxItemType eItemType) const;

    sal_uInt16 GetFirstWhich() const { return mnStart; }
    sal_uInt16 GetLastWhich() const { return mnEnd; }
    bool IsInRange( sal_uInt16 nWhich ) const { return nWhich >= mnStart && nWhich <= mnEnd; }

    void                            SetSecondaryPool( SfxItemPool *pPool );
    SfxItemPool* GetSecondaryPool() const { return mpSecondary.get(); }
    /* get the last pool by following the GetSecondaryPool chain */
    SfxItemPool*                    GetLastPoolInChain();
    SfxItemPool* GetMasterPool() const { return mpMaster; }
    void sendShutdownHint();

    // syntactical sugar: direct call to not have to use the flag define
    // and make the intention clearer
    bool NeedsSurrogateSupport(sal_uInt16 nWhich) const
        { return CheckItemInfoFlag(nWhich, SFX_ITEMINFOFLAG_SUPPORT_SURROGATE); }

    // tries to translate back from SlotID to WhichID.
    // If none is defined, return nSlot.
    // If nSlot is not a SlotID, return nSlot.
    sal_uInt16 GetWhichIDFromSlotID(sal_uInt16 nSlot, bool bDeep = true) const;
    template<class T> TypedWhichId<T> GetWhichIDFromSlotID(TypedWhichId<T> nSlot, bool bDeep = true) const
     { return TypedWhichId<T>(GetWhichIDFromSlotID(sal_uInt16(nSlot), bDeep)); }

    // get SlotID that may be registered in the SfxItemInfo for
    // the given WhichID.
    // If none is defined, return nWhich.
    // If nWhich is not a WhichID, return nWhich.
    sal_uInt16 GetSlotId( sal_uInt16 nWhich ) const;

    // same as GetWhichIDFromSlotID, but returns 0 in error cases, so:
    // If none is defined, return 0.
    // If nSlot is not a SlotID, return 0.
    sal_uInt16 GetTrueWhichIDFromSlotID( sal_uInt16 nSlot, bool bDeep = true ) const;

    // same as GetSlotId, but returns 0 in error cases, so:
    // If none is defined, return 0.
    // If nWhich is not a WhichID, return 0.
    sal_uInt16 GetTrueSlotId( sal_uInt16 nWhich ) const;

    static bool IsWhich(sal_uInt16 nId) { return nId && nId <= SFX_WHICH_MAX; }
    static bool IsSlot(sal_uInt16 nId) { return nId && nId > SFX_WHICH_MAX; }

private:
    const SfxItemPool& operator=(const SfxItemPool &) = delete;

     //IDs below or equal are Which IDs, IDs above slot IDs
    static const sal_uInt16 SFX_WHICH_MAX = 4999;
};

// only the pool may manipulate the reference counts
inline void SfxItemPool::AddRef(const SfxPoolItem& rItem)
{
    rItem.AddRef();
}

// only the pool may manipulate the reference counts
inline sal_uInt32 SfxItemPool::ReleaseRef(const SfxPoolItem& rItem, sal_uInt32 n)
{
    return rItem.ReleaseRef(n);
}

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