/* -*- 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 <frame.hxx>
#include <hintids.hxx>
#include <hints.hxx>
#include <swcache.hxx>
#include <swfntcch.hxx>
#include <tools/debug.hxx>
#include <sal/log.hxx>
#include <algorithm>
 
namespace sw
{
    bool ListenerEntry::GetInfo(SfxPoolItem& rInfo) const
        { return m_pToTell == nullptr || m_pToTell->GetInfo( rInfo ); }
    void ListenerEntry::Modify(const SfxPoolItem *const pOldValue,
                               const SfxPoolItem *const pNewValue)
    {
        SwClientNotify(*GetRegisteredIn(), sw::LegacyModifyHint(pOldValue, pNewValue));
    }
    void ListenerEntry::SwClientNotify(const SwModify& rModify, const SfxHint& rHint)
    {
        if (auto pLegacyHint = dynamic_cast<const sw::LegacyModifyHint*>(&rHint))
        {
            if (pLegacyHint->m_pNew && pLegacyHint->m_pNew->Which() == RES_OBJECTDYING)
            {
                auto pModifyChanged = CheckRegistration(pLegacyHint->m_pOld);
                if (pModifyChanged)
                    m_pToTell->SwClientNotify(rModify, *pModifyChanged);
            }
            else if (m_pToTell)
                m_pToTell->SwClientNotifyCall(rModify, rHint);
        }
        else if (m_pToTell)
            m_pToTell->SwClientNotifyCall(rModify, rHint);
    }
}
 
sw::LegacyModifyHint::~LegacyModifyHint() {}
sw::ModifyChangedHint::~ModifyChangedHint() {}
 
SwClient::SwClient(SwClient&& o) noexcept
    : m_pRegisteredIn(nullptr)
{
    if(o.m_pRegisteredIn)
    {
        o.m_pRegisteredIn->Add(this);
        o.EndListeningAll();
    }
}
 
SwClient::~SwClient()
{
    if(GetRegisteredIn())
        DBG_TESTSOLARMUTEX();
    OSL_ENSURE( !m_pRegisteredIn || m_pRegisteredIn->HasWriterListeners(), "SwModify still known, but Client already disconnected!" );
    if( m_pRegisteredIn && m_pRegisteredIn->HasWriterListeners() )
        m_pRegisteredIn->Remove( this );
}
 
std::unique_ptr<sw::ModifyChangedHint> SwClient::CheckRegistration( const SfxPoolItem* pOld )
{
    DBG_TESTSOLARMUTEX();
    // this method only handles notification about dying SwModify objects
    if( !pOld || pOld->Which() != RES_OBJECTDYING )
        return nullptr;
 
    const SwPtrMsgPoolItem* pDead = static_cast<const SwPtrMsgPoolItem*>(pOld);
    if(!pDead || pDead->pObject != m_pRegisteredIn)
    {
        // we should only care received death notes from objects we are following
        return nullptr;
    }
    // I've got a notification from the object I know
    SwModify* pAbove = m_pRegisteredIn->GetRegisteredIn();
    if(pAbove)
    {
        // if the dying object itself was listening at an SwModify, I take over
        // adding myself to pAbove will automatically remove me from my current pRegisteredIn
        pAbove->Add(this);
    }
    else
    {
        // destroy connection
        EndListeningAll();
    }
    return std::unique_ptr<sw::ModifyChangedHint>(new sw::ModifyChangedHint(pAbove));
}
 
void SwClient::SwClientNotify(const SwModify&, const SfxHint& rHint)
{
    if (auto pLegacyHint = dynamic_cast<const sw::LegacyModifyHint*>(&rHint))
    {
        Modify(pLegacyHint->m_pOld, pLegacyHint->m_pNew);
    }
};
 
void SwClient::StartListeningToSameModifyAs(const SwClient& other)
{
    if(other.m_pRegisteredIn)
        other.m_pRegisteredIn->Add(this);
    else
        EndListeningAll();
}
 
void SwClient::EndListeningAll()
{
    if(m_pRegisteredIn)
        m_pRegisteredIn->Remove(this);
}
 
void SwClient::Modify(SfxPoolItem const*const pOldValue, SfxPoolItem const*const /*pNewValue*/)
{
    CheckRegistration( pOldValue );
}
 
void SwModify::SetInDocDTOR()
{
    // If the document gets destroyed anyway, just tell clients to
    // forget me so that they don't try to get removed from my list
    // later when they also get destroyed
    SwIterator<SwClient,SwModify> aIter(*this);
    for(SwClient* pClient = aIter.First(); pClient; pClient = aIter.Next())
        pClient->m_pRegisteredIn = nullptr;
    m_pWriterListeners = nullptr;
}
 
SwModify::~SwModify()
{
    DBG_TESTSOLARMUTEX();
    OSL_ENSURE( !IsModifyLocked(), "Modify destroyed but locked." );
 
    if ( IsInCache() )
        SwFrame::GetCache().Delete( this );
 
    if ( IsInSwFntCache() )
        pSwFontCache->Delete( this );
 
    // notify all clients that they shall remove themselves
    SwPtrMsgPoolItem aDyObject( RES_OBJECTDYING, this );
    NotifyClients( &aDyObject, &aDyObject );
 
    // remove all clients that have not done themselves
    // mba: possibly a hotfix for forgotten base class calls?!
    while( m_pWriterListeners )
        static_cast<SwClient*>(m_pWriterListeners)->CheckRegistration( &aDyObject );
}
 
void SwModify::NotifyClients( const SfxPoolItem* pOldValue, const SfxPoolItem* pNewValue )
{
    DBG_TESTSOLARMUTEX();
    if ( IsInCache() || IsInSwFntCache() )
    {
        const sal_uInt16 nWhich = pOldValue ? pOldValue->Which() :
                                        pNewValue ? pNewValue->Which() : 0;
        CheckCaching( nWhich );
    }
 
    if ( !m_pWriterListeners || IsModifyLocked() )
        return;
 
    LockModify();
 
    // mba: WTF?!
    if( !pOldValue )
    {
        m_bLockClientList = true;
    }
    else
    {
        switch( pOldValue->Which() )
        {
        case RES_OBJECTDYING:
        case RES_REMOVE_UNO_OBJECT:
            m_bLockClientList = static_cast<const SwPtrMsgPoolItem*>(pOldValue)->pObject != this;
            break;
 
        default:
            m_bLockClientList = true;
        }
    }
 
    ModifyBroadcast( pOldValue, pNewValue );
    m_bLockClientList = false;
    UnlockModify();
}
 
bool SwModify::GetInfo( SfxPoolItem& rInfo ) const
{
    if(!m_pWriterListeners)
        return true;
    SwIterator<SwClient,SwModify> aIter(*this);
    for(SwClient* pClient = aIter.First(); pClient; pClient = aIter.Next())
        if(!pClient->GetInfo( rInfo ))
            return false;
    return true;
}
 
void SwModify::Add( SwClient* pDepend )
{
    DBG_TESTSOLARMUTEX();
    OSL_ENSURE( !m_bLockClientList, "Client inserted while in Modify" );
 
    if(pDepend->m_pRegisteredIn != this )
    {
#if OSL_DEBUG_LEVEL > 0
        if(sw::ClientIteratorBase::s_pClientIters)
        {
            for(auto& rIter : sw::ClientIteratorBase::s_pClientIters->GetRingContainer())
            {
                SAL_WARN_IF(&rIter.m_rRoot == m_pWriterListeners, "sw.core", "a " << typeid(*pDepend).name() << " client added as listener to a " << typeid(*this).name() << " during client iteration.");
            }
        }
#endif
        // deregister new client in case it is already registered elsewhere
        if( pDepend->m_pRegisteredIn != nullptr )
            pDepend->m_pRegisteredIn->Remove( pDepend );
 
        if( !m_pWriterListeners )
        {
            // first client added
            m_pWriterListeners = pDepend;
            m_pWriterListeners->m_pLeft = nullptr;
            m_pWriterListeners->m_pRight = nullptr;
        }
        else
        {
            // append client
            pDepend->m_pRight = m_pWriterListeners->m_pRight;
            m_pWriterListeners->m_pRight = pDepend;
            pDepend->m_pLeft = m_pWriterListeners;
            if( pDepend->m_pRight )
                pDepend->m_pRight->m_pLeft = pDepend;
        }
 
        // connect client to me
        pDepend->m_pRegisteredIn = this;
    }
}
 
SwClient* SwModify::Remove( SwClient* pDepend )
{
    DBG_TESTSOLARMUTEX();
    assert(pDepend->m_pRegisteredIn == this);
 
    // SwClient is my listener
    // remove it from my list
    ::sw::WriterListener* pR = pDepend->m_pRight;
    ::sw::WriterListener* pL = pDepend->m_pLeft;
    if( m_pWriterListeners == pDepend )
        m_pWriterListeners = pL ? pL : pR;
 
    if( pL )
        pL->m_pRight = pR;
    if( pR )
        pR->m_pLeft = pL;
 
    // update ClientIterators
    if(sw::ClientIteratorBase::s_pClientIters)
    {
        for(auto& rIter : sw::ClientIteratorBase::s_pClientIters->GetRingContainer())
        {
            if (&rIter.m_rRoot == this &&
                (rIter.m_pCurrent == pDepend || rIter.m_pPosition == pDepend))
            {
                // if object being removed is the current or next object in an
                // iterator, advance this iterator
                rIter.m_pPosition = static_cast<SwClient*>(pR);
            }
        }
    }
    pDepend->m_pLeft = nullptr;
    pDepend->m_pRight = nullptr;
    pDepend->m_pRegisteredIn = nullptr;
    return pDepend;
}
 
void SwModify::CheckCaching( const sal_uInt16 nWhich )
{
    if( isCHRATR( nWhich ) )
    {
        SetInSwFntCache( false );
    }
    else
    {
        switch( nWhich )
        {
        case RES_OBJECTDYING:
        case RES_FMT_CHG:
        case RES_ATTRSET_CHG:
            SetInSwFntCache( false );
            SAL_FALLTHROUGH;
        case RES_UL_SPACE:
        case RES_LR_SPACE:
        case RES_BOX:
        case RES_SHADOW:
        case RES_FRM_SIZE:
        case RES_KEEP:
        case RES_BREAK:
            if( IsInCache() )
            {
                SwFrame::GetCache().Delete( this );
                SetInCache( false );
            }
            break;
        }
    }
}
 
sw::WriterMultiListener::WriterMultiListener(SwClient& rToTell)
    : m_rToTell(rToTell)
{}
 
sw::WriterMultiListener::~WriterMultiListener()
{}
 
void sw::WriterMultiListener::StartListening(SwModify* pDepend)
{
    EndListening(nullptr);
    m_vDepends.emplace_back(ListenerEntry(&m_rToTell, pDepend));
}
 
 
bool sw::WriterMultiListener::IsListeningTo(const SwModify* const pBroadcaster)
{
    return std::any_of(m_vDepends.begin(), m_vDepends.end(),
        [&pBroadcaster](const ListenerEntry& aListener)
        {
            return aListener.GetRegisteredIn() == pBroadcaster;
        });
}
 
void sw::WriterMultiListener::EndListening(SwModify* pBroadcaster)
{
    m_vDepends.erase(
        std::remove_if( m_vDepends.begin(), m_vDepends.end(),
            [&pBroadcaster](const ListenerEntry& aListener)
            {
                return aListener.GetRegisteredIn() == nullptr || aListener.GetRegisteredIn() == pBroadcaster;
            }),
        m_vDepends.end());
}
 
void sw::WriterMultiListener::EndListeningAll()
{
    m_vDepends.clear();
}
 
sw::ClientIteratorBase* sw::ClientIteratorBase::s_pClientIters = nullptr;
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V560 A part of conditional expression is always false: !pDead.