//
// Copyright 2016 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
//    names, trademarks, service marks, or product names of the Licensor
//    and its affiliates, except as required to comply with Section 4(c) of
//    the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#ifndef PXR_USD_USD_NOTICE_H
#define PXR_USD_USD_NOTICE_H

#include "pxr/pxr.h"
#include "pxr/usd/usd/api.h"
#include "pxr/usd/usd/common.h"
#include "pxr/usd/usd/object.h"

#include "pxr/usd/sdf/changeList.h"
#include "pxr/usd/sdf/path.h"
#include "pxr/base/tf/notice.h"

PXR_NAMESPACE_OPEN_SCOPE


/// \class UsdNotice
///
/// Container class for Usd notices
///
class UsdNotice {
public:

    /// Base class for UsdStage notices.
    class StageNotice : public TfNotice {
    public:
        USD_API
        StageNotice(const UsdStageWeakPtr &stage);
        USD_API
        virtual ~StageNotice();

        /// Return the stage associated with this notice.
        const UsdStageWeakPtr &GetStage() const { return _stage; }

    private:
        UsdStageWeakPtr _stage;
    };

    /// \class StageContentsChanged
    ///
    /// Ultra-conservative notice sent when the given UsdStage's contents
    /// have changed in any way.  This notice is sent when \em any authoring
    /// is performed in any of the stage's participatory layers, in the
    /// thread performing the authoring, \em after the affected UsdStage
    /// has reconfigured itself in response to the authored changes.
    ///
    /// Receipt of this notice should cause clients to disregard any cached
    /// values for properties or metadata.  It does not \em necessarily imply
    /// invalidation of UsdPrim s.
    ///
    class StageContentsChanged : public StageNotice {
    public:
        explicit StageContentsChanged(const UsdStageWeakPtr& stage)
            : StageNotice(stage) {}
        USD_API virtual ~StageContentsChanged();
    };

    /// \class ObjectsChanged
    ///
    /// Notice sent in response to authored changes that affect UsdObjects.
    ///
    /// The kinds of object changes are divided into two categories: "resync"
    /// and "changed-info".  "Resyncs" are potentially structural changes that
    /// invalidate entire subtrees of UsdObjects (including prims and
    /// properties).  For example, if the path "/foo" is resynced, then all
    /// subpaths like "/foo/bar" and "/foo/bar.baz" may be arbitrarily changed.
    /// In contrast, "changed-info" means that a nonstructural change has
    /// occurred, like an attribute value change or a value change to a metadata
    /// field not related to composition.
    ///
    /// When a prim is resynced, say "/foo/bar", it might have been created or
    /// destroyed.  In that case "/foo"'s list of children will have changed,
    /// but we *do not* consider "/foo" to be resynced.  If we did, it would
    /// mean clients would have to consider all of "/foo/bar"'s siblings (and
    /// their descendants) to be resynced which might be egregious
    /// overinvalidation.
    ///
    /// This notice provides API for two client use-cases.  Clients interested
    /// in testing whether specific objects are affected by the changes should
    /// use the AffectedObject() method (and the ResyncedObject() and
    /// ChangedInfoOnly() methods).  Clients that wish to reason about all
    /// changes as a whole should use the GetResyncedPaths() and
    /// GetChangedInfoOnlyPaths() methods.
    ///
    class ObjectsChanged : public StageNotice {
        using _PathsToChangesMap = 
            std::map<SdfPath, std::vector<const SdfChangeList::Entry*>>;

        friend class UsdStage;
        ObjectsChanged(const UsdStageWeakPtr &stage,
                       const _PathsToChangesMap *resyncChanges,
                       const _PathsToChangesMap *infoChanges)
            : StageNotice(stage)
            , _resyncChanges(resyncChanges)
            , _infoChanges(infoChanges) {}
    public:
        USD_API virtual ~ObjectsChanged();

        /// Return true if \p obj was possibly affected by the layer changes
        /// that generated this notice.  This is the case if either the object
        /// is subject to a resync or has changed info.  Equivalent to:
        /// \code
        /// ResyncedObject(obj) or ChangedInfoOnly(obj)
        /// \endcode
        bool AffectedObject(const UsdObject &obj) const {
            return ResyncedObject(obj) || ChangedInfoOnly(obj);
        }

        /// Return true if \p obj was resynced by the layer changes that
        /// generated this notice.  This is the case if the object's path or an
        /// ancestor path is present in GetResyncedPrimPaths().
        USD_API bool ResyncedObject(const UsdObject &obj) const;

        /// Return true if \p obj was changed but not resynced by the layer
        /// changes that generated this notice.
        USD_API bool ChangedInfoOnly(const UsdObject &obj) const;

        /// \class PathRange
        /// An iterable range of paths to objects that have changed.
        ///
        /// Users may use this object in range-based for loops, or use the
        /// iterators to access additional information about each changed
        /// object.
        class PathRange
        {
        public:
            /// \class iterator
            class iterator : public boost::iterator_adaptor<
                iterator,                           // crtp base,
                _PathsToChangesMap::const_iterator, // base iterator
                const SdfPath&                      // value type
            >
            {
            public:
                iterator() 
                    : iterator_adaptor_(base_type()) {}

                /// Return the set of changed fields in layers that affected 
                /// the object at the path specified by this iterator.  See
                /// UsdNotice::ObjectsChanged::GetChangedFields for more
                /// details.
                USD_API TfTokenVector GetChangedFields() const;

                /// Return true if the object at the path specified by this
                /// iterator has any changed fields, false otherwise.  See
                /// UsdNotice::ObjectsChanged::HasChangedFields for more
                /// details.
                USD_API bool HasChangedFields() const;

            private:
                friend class PathRange;
                friend class boost::iterator_core_access;

                iterator(base_type baseIter) 
                    : iterator_adaptor_(baseIter) {}
                inline reference dereference() const { 
                    return base()->first;
                }
            };

            using const_iterator = iterator;

            PathRange() : _changes(nullptr) { }

            /// Explicit conversion to SdfPathVector for convenience
            explicit operator SdfPathVector() const {
                return SdfPathVector(begin(), end());
            }
        
            /// Return true if this range contains any paths, false otherwise.
            bool empty() const { 
                return !_changes || _changes->empty(); 
            }

            /// Return the number of paths in this range.
            size_t size() const { 
                return _changes ? _changes->size() : 0; 
            }

            /// Return iterator to the start of this range.
            iterator begin() const { 
                return iterator(_changes->cbegin()); 
            }

            /// Return iterator to the start of this range.
            const_iterator cbegin() const { 
                return iterator(_changes->cbegin()); 
            }

            /// Return the end iterator for this range.
            iterator end() const { 
                return iterator(_changes->cend()); 
            }

            /// Return the end iterator for this range.
            const_iterator cend() const { 
                return iterator(_changes->cend()); 
            }

            /// Return an iterator to the specified \p path in this range if
            /// it exists, or end() if it does not.  This is potentially more
            /// efficient than std::find(begin(), end()).
            const_iterator find(const SdfPath& path) const {
                return const_iterator(_changes->find(path));
            }

        private:
            friend class ObjectsChanged;
            explicit PathRange(const _PathsToChangesMap* changes)
                : _changes(changes)
            { }

            const _PathsToChangesMap* _changes;
        };

        /// Return the set of paths that are resynced in lexicographical order.
        /// Resyncs imply entire subtree invalidation of all descendant prims
        /// and properties, so this set is minimal regarding ancestors and
        /// descendants.  For example, if the path '/foo' appears in this set,
        /// the entire subtree at '/foo' is resynced so the path '/foo/bar' will
        /// not appear, but it should be considered resynced.
        USD_API PathRange GetResyncedPaths() const;

        /// Return the set of paths that have only info changes (those that do
        /// not affect the structure of cached UsdPrims on a UsdStage) in
        /// lexicographical order.  Info changes do not imply entire subtree
        /// invalidation, so this set is not minimal regarding ancestors and
        /// descendants, as opposed to GetResyncedPaths().  For example, both
        /// the paths '/foo' and '/foo/bar' may appear in this set.
        USD_API PathRange GetChangedInfoOnlyPaths() const;

        /// Return the set of changed fields in layers that affected \p obj. 
        ///
        /// This set will be empty for objects whose paths are not in
        /// GetResyncedPaths() or GetChangedInfoOnlyPaths().  
        ///
        /// If a field is present in this set, it does not necessarily mean the 
        /// composed value of that field on \p obj has changed.  For example, if
        /// a metadata value on \p obj is overridden in a stronger layer and
        /// is changed in a weaker layer, that field will appear in this set.
        /// However, since the value in the stronger layer did not change,
        /// the composed value returned by GetMetadata() will not have changed.
        USD_API TfTokenVector GetChangedFields(const UsdObject &obj) const;

        /// \overload
        USD_API TfTokenVector GetChangedFields(const SdfPath &path) const;

        /// Return true if there are any changed fields that affected \p obj,
        /// false otherwise.  See GetChangedFields for more details.
        USD_API bool HasChangedFields(const UsdObject &obj) const;
        
        /// \overload
        USD_API bool HasChangedFields(const SdfPath &path) const;

    private:
        const _PathsToChangesMap *_resyncChanges;
        const _PathsToChangesMap *_infoChanges;
    };

    /// \class StageEditTargetChanged
    ///
    /// Notice sent when a stage's EditTarget has changed.  Sent in the
    /// thread that changed the target.
    ///
    class StageEditTargetChanged : public StageNotice {
    public:
        explicit StageEditTargetChanged(const UsdStageWeakPtr &stage)
            : StageNotice(stage) {}
        USD_API virtual ~StageEditTargetChanged();
    };

    /// \class LayerMutingChanged
    ///
    /// Notice sent after a set of layers have been newly muted or unmuted.
    /// Note this does not necessarily mean the specified layers are currently 
    /// loaded.
    ///
    /// LayerMutingChanged notice is sent before any UsdNotice::ObjectsChanged 
    /// or UsdNotice::StageContentsChanged notices are sent resulting from
    /// muting or unmuting of layers.
    /// 
    /// Note that LayerMutingChanged notice is sent even if the
    /// muting/unmuting layer does not belong to the current stage, or is a 
    /// layer that does belong to the current stage but is not yet loaded 
    /// because it is behind an unloaded payload or unselected variant.
    class LayerMutingChanged : public StageNotice {
    public:
        explicit LayerMutingChanged(const UsdStageWeakPtr &stage, 
                const std::vector<std::string>& mutedLayers, 
                const std::vector<std::string>& unmutedLayers)
            : StageNotice(stage),
             _mutedLayers(mutedLayers),
             _unMutedLayers(unmutedLayers) {}

        USD_API virtual ~LayerMutingChanged();

        /// Returns the identifier of the layers that were muted.
        ///
        /// The stage's resolver context must be bound when looking up
        /// layers using the returned identifiers to ensure the same layers
        /// that would be used by the stage are found.
        const std::vector<std::string>& GetMutedLayers() const {
            return _mutedLayers;
        }

        /// Returns the identifier of the layers that were unmuted.
        ///
        /// The stage's resolver context must be bound when looking up
        /// layers using the returned identifiers to ensure the same layers
        /// that would be used by the stage are found.
        const std::vector<std::string>& GetUnmutedLayers() const {
            return _unMutedLayers;
        }

    private:
        const std::vector<std::string>& _mutedLayers;
        const std::vector<std::string>& _unMutedLayers;
    };

};


PXR_NAMESPACE_CLOSE_SCOPE

#endif // PXR_USD_USD_NOTICE_H
