/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office 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 <sal/config.h>

#include <sdr/properties/attributeproperties.hxx>
#include <tools/debug.hxx>
#include <svl/itemset.hxx>
#include <svl/style.hxx>
#include <svl/whiter.hxx>
#include <svl/poolitem.hxx>
#include <svx/svdobj.hxx>
#include <svx/fillbitmaplink.hxx>
#include <svx/xbtmpit.hxx>
#include <svx/xlndsit.hxx>
#include <svx/xlnstit.hxx>
#include <svx/xlnedit.hxx>
#include <svx/xflgrit.hxx>
#include <svx/xflftrit.hxx>
#include <svx/xflhtit.hxx>
#include <svx/svdmodel.hxx>
#include <svx/svdpage.hxx>
#include <svx/diagram/DiagramHelper_svx.hxx>
#include <osl/diagnose.h>

namespace sdr::properties
{
        void AttributeProperties::ImpSetParentAtSfxItemSet(bool bDontRemoveHardAttr)
        {
            if(HasSfxItemSet() && mpStyleSheet)
            {
                // Delete hard attributes where items are set in the style sheet
                if(!bDontRemoveHardAttr)
                {
                    const SfxItemSet& rStyle = mpStyleSheet->GetItemSet();
                    SfxWhichIter aIter(rStyle);
                    sal_uInt16 nWhich = aIter.FirstWhich();

                    while(nWhich)
                    {
                        if(SfxItemState::SET == aIter.GetItemState())
                        {
                            moItemSet->ClearItem(nWhich);
                        }

                        nWhich = aIter.NextWhich();
                    }
                }

                // set new stylesheet as parent
                moItemSet->SetParent(&mpStyleSheet->GetItemSet());
            }
            else
            {
                OSL_ENSURE(false, "ImpSetParentAtSfxItemSet called without SfxItemSet/SfxStyleSheet (!)");
            }
        }

        void AttributeProperties::ImpAddStyleSheet(SfxStyleSheet* pNewStyleSheet, bool bDontRemoveHardAttr)
        {
            // test if old StyleSheet is cleared, else it would be lost
            // after this method -> memory leak (!)
            DBG_ASSERT(!mpStyleSheet, "Old style sheet not deleted before setting new one (!)");

            if(!pNewStyleSheet)
                return;

            // local remember
            mpStyleSheet = pNewStyleSheet;

            if(HasSfxItemSet())
            {
                // register as listener
                StartListening(*pNewStyleSheet->GetPool());
                StartListening(*pNewStyleSheet);

                // only apply the following when we have an SfxItemSet already, else
                if(GetStyleSheet())
                {
                    ImpSetParentAtSfxItemSet(bDontRemoveHardAttr);
                }
            }
        }

        void AttributeProperties::ImpRemoveStyleSheet()
        {
            // Check type since it is destroyed when the type is deleted
            if(GetStyleSheet() && mpStyleSheet)
            {
                EndListening(*mpStyleSheet);
                if (auto const pool = mpStyleSheet->GetPool()) { // TTTT
                    EndListening(*pool);
                }

                // reset parent of ItemSet
                if(HasSfxItemSet())
                {
                    moItemSet->SetParent(nullptr);
                }

                SdrObject& rObj = GetSdrObject();
                rObj.SetBoundRectDirty();
                rObj.SetBoundAndSnapRectsDirty(/*bNotMyself*/true);
            }

            mpStyleSheet = nullptr;
        }

        // create a new itemset
        SfxItemSet AttributeProperties::CreateObjectSpecificItemSet(SfxItemPool&)
        {
            assert(false && "this class is effectively abstract, should only be instantiating subclasses");
            abort();
        }

        AttributeProperties::AttributeProperties(SdrObject& rObj)
        :   DefaultProperties(rObj),
            mpStyleSheet(nullptr)
        {
            // Do nothing else, esp. do *not* try to get and set
            // a default SfxStyle sheet. Nothing is allowed to be done
            // that may lead to calls to virtual functions like
            // CreateObjectSpecificItemSet - these would go *wrong*.
            // Thus the rest is lazy-init from here.
        }

        AttributeProperties::AttributeProperties(const AttributeProperties& rProps, SdrObject& rObj)
        :   DefaultProperties(rProps, rObj),
            mpStyleSheet(nullptr)
        {
            const bool bModelChange(&rObj.getSdrModelFromSdrObject() != &rProps.GetSdrObject().getSdrModelFromSdrObject());

            if (bModelChange)
            {
                // use default; will be corrected/handled in call to
                // postProcessAfterCloneToDifferentModel below. Thus,
                // also do not yet call ImpAddStyleSheet here
                mpStyleSheet = GetSdrObject().getSdrModelFromSdrObject().GetDefaultStyleSheet();
            }
            else
            {
                // copy existing one
                mpStyleSheet = rProps.GetStyleSheet();

                // add StyleSheet && establish the parent-links in the SfxItemSet
                ImpAddStyleSheet(mpStyleSheet, true);
            }
        }

        void AttributeProperties::postProcessAfterCloneToDifferentModel(const SdrObject& rSrcObj)
        {
            SdrModel& rDstModel(GetSdrObject().getSdrModelFromSdrObject());
            const bool bModelChange(&rSrcObj.getSdrModelFromSdrObject() != &rDstModel);

            if (!bModelChange)
                return;

            // guarantee SfxItemSet existence
            DefaultProperties::GetObjectItemSet();
            const sdr::properties::BaseProperties& rSource(rSrcObj.GetProperties());
            SfxStyleSheet* pNewStyleSheet(nullptr);

            if (rDstModel.GetStyleSheetPool())
            {
                SfxStyleSheet* pSourceStyleSheet(rSource.GetStyleSheet());

                if (pSourceStyleSheet)
                {
                    // If we have a TargetStyleSheetPool, search for the used StyleSheet
                    // in the target SdrModel using the Name from the original StyleSheet
                    // in the source-SdrModel.
                    // That will/should work always since Paste from another model will
                    // in a 1st step copy non-existing StyleSheets to destination model
                    pNewStyleSheet = dynamic_cast< SfxStyleSheet* >(
                        rDstModel.GetStyleSheetPool()->Find(
                            pSourceStyleSheet->GetName(),
                            pSourceStyleSheet->GetFamily()));
                }
            }

            // fallback: every SdrObject *should* have a StyleSheet, use
            // default from the model
            if(!pNewStyleSheet)
                pNewStyleSheet = rDstModel.GetDefaultStyleSheet();

            if(!pNewStyleSheet)
                // may still have no StyleSheet, was so before, too, but
                // should not happen since a DefaultStyleSheet should
                // always be there
                return;

            // add StyleSheet && establish the parent-links in the SfxItemSet
            if (GetStyleSheet() != pNewStyleSheet)
            {
                ImpRemoveStyleSheet();
                ImpAddStyleSheet(pNewStyleSheet, true);
            }

            if (HasSfxItemSet())
            {
                // here we have a model change and a StyleSheet set,. but that
                // StyleSheet was found by looking for name & type. It may
                // *not* be identical to the StyleSheet from the Source.

                // What to do? We could...
                // (a) the Style-friendly way:
                //   - check if StyleSheet is identical
                //   - if not, create a new in-between StyleSheet hosting missing Items
                //   - if yes, use, done.
                // (b) the pragmatic/practical way:
                // - Just hard-attribute the Items that are detected as different
                //   similar to what BurnInStyleSheetAttributes does/did, but only
                //   for differing Items here
                //
                // (b)) is the most simple for now, but will lead to more hard
                // attributes. The other possibilities will be better in the
                // long term. For now, I will use (b).
                SfxWhichIter aIter(rSource.GetObjectItemSet());

                for (sal_uInt16 nWhich = aIter.FirstWhich(); nWhich; nWhich = aIter.NextWhich())
                {
                    const SfxPoolItem& rOld(rSource.GetItem(nWhich));
                    const SfxPoolItem& rNew(GetItem(nWhich));

                    if (!SfxPoolItem::areSame(rOld, rNew))
                        SetObjectItemDirect(rOld);
                }
            }
        }

        AttributeProperties::~AttributeProperties()
        {
            ImpRemoveStyleSheet();
        }

        std::unique_ptr<BaseProperties> AttributeProperties::implCloneProperties(SdrObject&) const
        {
            assert(false && "this class is effectively abstract, should only be instantiating subclasses");
            abort();
        }

        const SfxItemSet& AttributeProperties::GetObjectItemSet() const
        {
            // remember if we had a SfxItemSet already
            const bool bHadSfxItemSet(HasSfxItemSet());

            // call parent - this will guarantee SfxItemSet existence
            DefaultProperties::GetObjectItemSet();

            if(!bHadSfxItemSet)
            {
                // need to take care for SfxStyleSheet for newly
                // created SfxItemSet
                if(nullptr == mpStyleSheet)
                {
                    // Set missing defaults without removal of hard attributes.
                    // This is more complicated historically than I first thought:
                    // Originally for GetDefaultStyleSheetForSdrGrafObjAndSdrOle2Obj
                    // SetStyleSheet(..., false) was used, while for GetDefaultStyleSheet
                    // SetStyleSheet(..., true) was used. Thus, for SdrGrafObj and SdrOle2Obj
                    // bDontRemoveHardAttr == false -> *do* delete hard attributes was used.
                    // This was probably not done by purpose, adding the method
                    // GetDefaultStyleSheetForSdrGrafObjAndSdrOle2Obj additionally to
                    // GetDefaultStyleSheet was an enhancement to allow for SdrGrafObj/SdrOle2Obj
                    // with full AttributeSet (adding e.g. FillAttributes). To stay as compatible
                    // as possible these SdrObjects got a new default-StyleSheet.
                    // There is no reason to delete the HardAttributes and it anyways has only
                    // AFAIK effects on a single Item - the SdrTextHorzAdjustItem. To get things
                    // unified I will stay with not deleting the HardAttributes and adapt the
                    // UnitTests in CppunitTest_sd_import_tests accordingly.
                    const_cast< AttributeProperties* >(this)->applyDefaultStyleSheetFromSdrModel();
                }
                else
                {
                    // Late-Init of setting parent to SfxStyleSheet after
                    // it's creation. Can only happen from copy-constructor
                    // (where creation of SfxItemSet is avoided due to the
                    // problem with constructors and virtual functions in C++),
                    // thus DontRemoveHardAttr is not needed.
                    const_cast< AttributeProperties* >(this)->SetStyleSheet(
                        mpStyleSheet, true, true);
                }
            }

            return *moItemSet;
        }

        void AttributeProperties::ItemSetChanged(std::span< const SfxPoolItem* const > aChangedItems, sal_uInt16 /*nDeletedWhich*/, bool /*bAdjustTextFrameWidthAndHeight*/)
        {
            // own modifications
            SdrObject& rObj = GetSdrObject();

            rObj.SetBoundRectDirty();
            rObj.SetBoundAndSnapRectsDirty(/*bNotMyself*/true);
            rObj.SetChanged();

            if (!rObj.getDiagramDataModelID().isEmpty() && !rObj.getSdrModelFromSdrObject().isInImportExport())
            {
                const std::shared_ptr< svx::diagram::DiagramHelper_svx >& pDiagramHelper(rObj.getDiagramHelper());
                if (pDiagramHelper)
                    pDiagramHelper->ItemSetInformationChange(aChangedItems);
            }
        }

        void AttributeProperties::ItemChange(const sal_uInt16 nWhich, const SfxPoolItem* pNewItem)
        {
            if(pNewItem)
            {
                std::unique_ptr<SfxPoolItem> pResultItem;
                SdrModel& rModel(GetSdrObject().getSdrModelFromSdrObject());

                switch( nWhich )
                {
                    case XATTR_FILLBITMAP:
                    {
                        pResultItem = static_cast<const XFillBitmapItem*>(pNewItem)->checkForUniqueItem( rModel );
                        break;
                    }
                    case XATTR_LINEDASH:
                    {
                        pResultItem = static_cast<const XLineDashItem*>(pNewItem)->checkForUniqueItem( rModel );
                        break;
                    }
                    case XATTR_LINESTART:
                    {
                        pResultItem = static_cast<const XLineStartItem*>(pNewItem)->checkForUniqueItem( rModel );
                        break;
                    }
                    case XATTR_LINEEND:
                    {
                        pResultItem = static_cast<const XLineEndItem*>(pNewItem)->checkForUniqueItem( rModel );
                        break;
                    }
                    case XATTR_FILLGRADIENT:
                    {
                        pResultItem = static_cast<const XFillGradientItem*>(pNewItem)->checkForUniqueItem( rModel );
                        break;
                    }
                    case XATTR_FILLFLOATTRANSPARENCE:
                    {
                        // #85953# allow all kinds of XFillFloatTransparenceItem to be set
                        pResultItem = static_cast<const XFillFloatTransparenceItem*>(pNewItem)->checkForUniqueItem( rModel );
                        break;
                    }
                    case XATTR_FILLHATCH:
                    {
                        pResultItem = static_cast<const XFillHatchItem*>(pNewItem)->checkForUniqueItem( rModel );
                        break;
                    }
                }

                // guarantee SfxItemSet existence
                GetObjectItemSet();

                if(pResultItem)
                {
                    // force ItemSet
                    moItemSet->Put(std::move(pResultItem));
                }
                else
                {
                    moItemSet->Put(*pNewItem);
                }
            }
            else
            {
                // clear item if ItemSet exists
                if(HasSfxItemSet())
                {
                    moItemSet->ClearItem(nWhich);
                }
            }

            // route a deferred remote fill bitmap appearing/changing/clearing on
            // this object to the model's link tracker, so the link is registered
            // as the item is set rather than by a later pool scan
            if (nWhich == XATTR_FILLBITMAP)
            {
                SdrModel& rFillModel(GetSdrObject().getSdrModelFromSdrObject());
                if (sdr::FillBitmapLinkTracker* pTracker = rFillModel.GetFillBitmapLinkTracker())
                {
                    OUString aURL;
                    if (pNewItem)
                        aURL = getDeferredOriginURL(static_cast<const XFillBitmapItem&>(*pNewItem));
                    pTracker->onFillBitmapURLChanged(GetSdrObject(), aURL);
                }
            }
        }

        void AttributeProperties::SetStyleSheet(SfxStyleSheet* pNewStyleSheet, bool bDontRemoveHardAttr,
                bool /*bBroadcast*/, bool /*bAdjustTextFrameWidthAndHeight*/)
        {
            // Make sure we have a SfxItemSet. We are deliberately bypassing our
            // own AttributeProperties::GetObjectItemSet here, because we don't want to set a default stylesheet,
            // and then immediately remove it, which is costly.
            DefaultProperties::GetObjectItemSet();

            ImpRemoveStyleSheet();
            ImpAddStyleSheet(pNewStyleSheet, bDontRemoveHardAttr);

            SdrObject& rObj = GetSdrObject();
            rObj.SetBoundRectDirty();
            rObj.SetBoundAndSnapRectsDirty(true);
        }

        SfxStyleSheet* AttributeProperties::GetStyleSheet() const
        {
            return mpStyleSheet;
        }

        void AttributeProperties::ForceStyleToHardAttributes()
        {
            if(!GetStyleSheet() || mpStyleSheet == nullptr)
                return;

            // guarantee SfxItemSet existence
            GetObjectItemSet();

            // prepare copied, new itemset, but WITHOUT parent
            SfxItemSet aDestItemSet(*moItemSet);
            aDestItemSet.SetParent(nullptr);

            // prepare forgetting the current stylesheet like in RemoveStyleSheet()
            EndListening(*mpStyleSheet);
            EndListening(*mpStyleSheet->GetPool());

            // prepare the iter; use the mpObjectItemSet which may have less
            // WhichIDs than the style.
            SfxWhichIter aIter(aDestItemSet);
            sal_uInt16 nWhich(aIter.FirstWhich());
            const SfxPoolItem *pItem = nullptr;

            // now set all hard attributes of the current at the new itemset
            while(nWhich)
            {
                // #i61284# use mpItemSet with parents, makes things easier and reduces to
                // one loop
                if(SfxItemState::SET == moItemSet->GetItemState(nWhich, true, &pItem))
                {
                    aDestItemSet.Put(*pItem);
                }

                nWhich = aIter.NextWhich();
            }

            // replace itemsets
            moItemSet.emplace(std::move(aDestItemSet));

            // set necessary changes like in RemoveStyleSheet()
            GetSdrObject().SetBoundRectDirty();
            GetSdrObject().SetBoundAndSnapRectsDirty(/*bNotMyself*/true);

            mpStyleSheet = nullptr;
        }

        void AttributeProperties::Notify(SfxBroadcaster& rBC, const SfxHint& rHint)
        {
            bool bHintUsed(false);

            SfxHintId id = rHint.GetId();
            if (id == SfxHintId::StyleSheetChanged
                || id == SfxHintId::StyleSheetErased
                || id == SfxHintId::StyleSheetModified
                || id == SfxHintId::StyleSheetInDestruction
                || id == SfxHintId::StyleSheetModifiedExtended)
            {
                const SfxStyleSheetHint* pStyleHint = static_cast<const SfxStyleSheetHint*>(&rHint);

                if(pStyleHint->GetStyleSheet() == GetStyleSheet())
                {
                    SdrObject& rObj = GetSdrObject();
                    //SdrPage* pPage = rObj.GetPage();

                    switch(id)
                    {
                        case SfxHintId::StyleSheetModified        :
                        case SfxHintId::StyleSheetModifiedExtended:
                        case SfxHintId::StyleSheetChanged         :
                        {
                            // notify change
                            break;
                        }
                        case SfxHintId::StyleSheetErased          :
                        case SfxHintId::StyleSheetInDestruction   :
                        {
                            // Style needs to be exchanged
                            SfxStyleSheet* pNewStSh = nullptr;
                            SdrModel& rModel(rObj.getSdrModelFromSdrObject());

                            // Do nothing if object is in destruction, else a StyleSheet may be found from
                            // a StyleSheetPool which is just being deleted itself. and thus it would be fatal
                            // to register as listener to that new StyleSheet.
                            if(!rObj.IsInDestruction())
                            {
                                if(SfxStyleSheet* pStyleSheet = GetStyleSheet())
                                {
                                    pNewStSh = static_cast<SfxStyleSheet*>(rModel.GetStyleSheetPool()->Find(
                                        pStyleSheet->GetParent(), pStyleSheet->GetFamily()));
                                }

                                if(!pNewStSh)
                                {
                                    pNewStSh = rModel.GetDefaultStyleSheet();
                                }
                            }

                            // remove used style, it's erased or in destruction
                            ImpRemoveStyleSheet();

                            if(pNewStSh)
                            {
                                ImpAddStyleSheet(pNewStSh, true);
                            }

                            break;
                        }
                        default: break;
                    }

                    // Get old BoundRect. Do this after the style change is handled
                    // in the ItemSet parts because GetBoundRect() may calculate a new
                    tools::Rectangle aBoundRect = rObj.GetLastBoundRect();

                    rObj.SetBoundAndSnapRectsDirty(/*bNotMyself*/true);

                    // tell the object about the change
                    rObj.SetChanged();
                    rObj.BroadcastObjectChange();

                    //if(pPage && pPage->IsInserted())
                    //{
                    //  rObj.BroadcastObjectChange();
                    //}

                    rObj.SendUserCall(SdrUserCallType::ChangeAttr, aBoundRect);

                    bHintUsed = true;
                }
            }
            if(!bHintUsed)
            {
                // forward to SdrObject ATM. Not sure if this will be necessary
                // in the future.
                GetSdrObject().Notify(rBC, rHint);
            }
        }

        bool AttributeProperties::isUsedByModel() const
        {
            const SdrObject& rObj(GetSdrObject());
            if (rObj.IsInserted())
            {
                const SdrPage* const pPage(rObj.getSdrPageFromSdrObject());
                if (pPage && pPage->IsInserted())
                    return true;
            }
            return false;
        }

        void AttributeProperties::applyDefaultStyleSheetFromSdrModel()
        {
            SfxStyleSheet* pDefaultStyleSheet(GetSdrObject().getSdrModelFromSdrObject().GetDefaultStyleSheet());

            // tdf#118139 Only do this when StyleSheet really differs. It may e.g.
            // be the case that nullptr == pDefaultStyleSheet and there is none set yet,
            // so indeed no need to set it (needed for some strange old MSWord2003
            // documents with CustomShape-'Group' and added Text-Frames, see task description)
            if(pDefaultStyleSheet != GetStyleSheet())
            {
                // do not delete hard attributes when setting dsefault Style
                SetStyleSheet(pDefaultStyleSheet, true, true);
            }
        }

} // end of namespace

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