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

#include <svl/itempool.hxx>
#include <svl/itemset.hxx>
#include <comphelper/profilezone.hxx>
#include <comphelper/sequence.hxx>
#include <cppuhelper/supportsservice.hxx>

#include <svx/svdmodel.hxx>
#include "UnoNameItemTable.hxx"
#include <vcl/svapp.hxx>

#include <svx/unoapi.hxx>
#include <memory>

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

namespace
{
    // We need to override operator== here and specifically bypass the assert
    // in SfxPoolItem::operator== in order to make the GetItemSurrogates call
    // and comparing it's results in SvxUnoNameItemTable::hasByName safe.
    class SampleItem : public NameOrIndex
    {
    public:
        DECLARE_ITEM_TYPE_FUNCTION(SampleItem)
        SampleItem(sal_uInt16 nWhich, const OUString& rName) : NameOrIndex(TypedWhichId<NameOrIndex>(nWhich), rName) {}

        bool operator==(const SfxPoolItem& rCmp) const override
        {
            assert(dynamic_cast<const NameOrIndex*>(&rCmp) && "comparing different pool item subclasses");
            auto const & rOther = static_cast<const NameOrIndex&>(rCmp);
            return GetName() == rOther.GetName() && GetPalIndex() == rOther.GetPalIndex();
        }
    };

}


SvxUnoNameItemTable::SvxUnoNameItemTable( SdrModel* pModel, sal_uInt16 nWhich, sal_uInt8 nMemberId ) noexcept
: mpModel( pModel ),
  mpModelPool( pModel ? &pModel->GetItemPool() : nullptr ),
  mnWhich( nWhich ), mnMemberId( nMemberId )
{
    if( pModel )
        StartListening( *pModel );
}

SvxUnoNameItemTable::~SvxUnoNameItemTable() noexcept
{
    SolarMutexGuard aGuard;

    if( mpModel )
        EndListening( *mpModel );
    dispose();
}

bool SvxUnoNameItemTable::isValid( const NameOrIndex* pItem ) const
{
    return pItem && !pItem->GetName().isEmpty();
}

void SvxUnoNameItemTable::dispose()
{
    maItemSetVector.clear();
}

void SvxUnoNameItemTable::Notify( SfxBroadcaster&, const SfxHint& rHint ) noexcept
{
    if (rHint.GetId() != SfxHintId::ThisIsAnSdrHint)
        return;
    const SdrHint* pSdrHint = static_cast<const SdrHint*>(&rHint);
    if( SdrHintKind::ModelCleared == pSdrHint->GetKind() )
        dispose();
}

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

void SvxUnoNameItemTable::ImplInsertByName( const OUString& aName, const uno::Any& aElement )
{
    maItemSetVector.push_back( std::make_unique< SfxItemSet >( *mpModelPool, mnWhich, mnWhich ) );

    std::unique_ptr<NameOrIndex> xNewItem(createItem());
    xNewItem->SetName(aName);
    xNewItem->PutValue(aElement, mnMemberId);
    xNewItem->SetWhich(mnWhich);
    maItemSetVector.back()->Put(std::move(xNewItem));
}

// XNameContainer
void SAL_CALL SvxUnoNameItemTable::insertByName( const OUString& aApiName, const uno::Any& aElement )
{
    SolarMutexGuard aGuard;
    comphelper::ProfileZone aZone("SvxUnoNameItemTable::insertByName");

    if( hasByName( aApiName ) )
        throw container::ElementExistException();

    OUString aName = SvxUnogetInternalNameForItem(mnWhich, aApiName);

    ImplInsertByName( aName, aElement );
}

void SAL_CALL SvxUnoNameItemTable::cancel()
{
    SolarMutexGuard aGuard;
    // drop all items that are owned by this service and not the document
    // (i.e. they are unused)
    dispose();
}

void SAL_CALL SvxUnoNameItemTable::removeByName( const OUString& aApiName )
{
    SolarMutexGuard aGuard;
    comphelper::ProfileZone aZone("SvxUnoNameItemTable::removeByName");

    OUString sName = SvxUnogetInternalNameForItem(mnWhich, aApiName);

    auto aIter = std::find_if(maItemSetVector.begin(), maItemSetVector.end(),
        [&](const std::unique_ptr<SfxItemSet>& rpItem) {
            const NameOrIndex *pItem = static_cast<const NameOrIndex *>(&(rpItem->Get( mnWhich ) ));
            return sName == pItem->GetName();
        });
    if (aIter != maItemSetVector.end())
    {
        maItemSetVector.erase( aIter );
        return;
    }

    if (!hasByName(sName))
        throw container::NoSuchElementException();
}

// XNameReplace
void SAL_CALL SvxUnoNameItemTable::replaceByName( const OUString& aApiName, const uno::Any& aElement )
{
    SolarMutexGuard aGuard;

    OUString aName = SvxUnogetInternalNameForItem(mnWhich, aApiName);

    auto aIter = std::find_if(maItemSetVector.begin(), maItemSetVector.end(),
        [&](const std::unique_ptr<SfxItemSet>& rpItem) {
            const NameOrIndex *pItem = static_cast<const NameOrIndex *>(&(rpItem->Get( mnWhich ) ));
            return aName == pItem->GetName();
        });
    if (aIter != maItemSetVector.end())
    {
        std::unique_ptr<NameOrIndex> xNewItem(createItem());
        xNewItem->SetName(aName);
        if (!xNewItem->PutValue(aElement, mnMemberId) || !isValid(xNewItem.get()))
            throw lang::IllegalArgumentException();
        (*aIter)->Put(std::move(xNewItem));
        return;
    }

    // if it is not in our own sets, modify the pool!
    bool bFound = false;

    if (mpModelPool)
    {
        SampleItem aSample(mnWhich, aName);

        for (const SfxPoolItem* pNameOrIndex : mpModelPool->GetItemSurrogates(mnWhich))
            if (aSample == *pNameOrIndex)
                if (isValid(static_cast<const NameOrIndex*>(pNameOrIndex)))
                {
                    const_cast<SfxPoolItem*>(pNameOrIndex)->PutValue( aElement, mnMemberId );
                    bFound = true;
                }
    }

    if( !bFound )
        throw container::NoSuchElementException();

    ImplInsertByName( aName, aElement );

    if( !hasByName( aName ) )
        throw container::NoSuchElementException();
}

// XNameAccess
uno::Any SAL_CALL SvxUnoNameItemTable::getByName( const OUString& aApiName )
{
    SolarMutexGuard aGuard;
    comphelper::ProfileZone aZone("SvxUnoNameItemTable::getByName");

    OUString aName = SvxUnogetInternalNameForItem(mnWhich, aApiName);

    if (mpModelPool && !aName.isEmpty())
    {
        SampleItem aSample(mnWhich, aName);

        for (const SfxPoolItem* pFindItem : mpModelPool->GetItemSurrogates(mnWhich))
            if (aSample == *pFindItem)
                if (isValid(static_cast<const NameOrIndex*>(pFindItem)))
                {
                    uno::Any aAny;
                    pFindItem->QueryValue( aAny, mnMemberId );
                    return aAny;
                }
    }

    throw container::NoSuchElementException();
}

uno::Sequence< OUString > SAL_CALL SvxUnoNameItemTable::getElementNames(  )
{
    SolarMutexGuard aGuard;

    std::set< OUString > aNameSet;


    if (mpModelPool)
    {
        for (const SfxPoolItem* pItem : mpModelPool->GetItemSurrogates(mnWhich))
        {
            const NameOrIndex *pNameOrIndex = static_cast<const NameOrIndex*>(pItem);

            if( !isValid( pNameOrIndex ) )
                continue;

            OUString aApiName = SvxUnogetApiNameForItem(mnWhich, pNameOrIndex->GetName());
            aNameSet.insert(aApiName);
        }
    }

    return comphelper::containerToSequence(aNameSet);
}

sal_Bool SAL_CALL SvxUnoNameItemTable::hasByName( const OUString& aApiName )
{
    SolarMutexGuard aGuard;

    OUString aName = SvxUnogetInternalNameForItem(mnWhich, aApiName);

    if (aName.isEmpty())
        return false;

    if (!mpModelPool)
        return false;

    SampleItem aSample(mnWhich, aName);

    for (const SfxPoolItem* pFindItem : mpModelPool->GetItemSurrogates(mnWhich))
        if (aSample == *pFindItem)
            if (isValid(static_cast<const NameOrIndex*>(pFindItem)))
                return true;
    return false;
}

sal_Bool SAL_CALL SvxUnoNameItemTable::hasElements(  )
{
    SolarMutexGuard aGuard;

    if (mpModelPool)
    {
        for (const SfxPoolItem* pItem : mpModelPool->GetItemSurrogates(mnWhich))
        {
            const NameOrIndex *pNameOrIndex = static_cast<const NameOrIndex*>(pItem);

            if( isValid( pNameOrIndex ) )
                return true;
        }
    }

    return false;
}

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