//
// 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_SCHEMA_BASE_H
#define PXR_USD_USD_SCHEMA_BASE_H

#include "pxr/pxr.h"
#include "pxr/usd/usd/api.h"
#include "pxr/usd/usd/prim.h"
#include "pxr/usd/usd/attribute.h"
#include "pxr/usd/usd/relationship.h"
#include "pxr/usd/usd/references.h"

PXR_NAMESPACE_OPEN_SCOPE


/// \class UsdSchemaBase
///
/// The base class for all schema types in Usd.
/// 
/// Schema objects hold a ::UsdPrim internally and provide a layer of specific
/// named API atop the underlying scene graph.
///
/// Schema objects are polymorphic but they are intended to be created as
/// automatic local variables, so they may be passed and returned by-value.
/// This leaves them subject to <a
/// href="http://en.wikipedia.org/wiki/Object_slicing">slicing</a>.  This means
/// that if one passes a <tt>SpecificSchema</tt> instance to a function that
/// takes a UsdSchemaBase \e by-value, all the polymorphic behavior specific to
/// <tt>SpecificSchema</tt> is lost.
///
/// To avoid slicing, it is encouraged that functions taking schema object
/// arguments take them by <tt>const &</tt> if const access is sufficient,
/// otherwise by non-const pointer.
///
class UsdSchemaBase {
public:
    /// Compile time constant representing what kind of schema this class is.
    ///
    /// \sa UsdSchemaType in usd/common.h
    static const UsdSchemaType schemaType = UsdSchemaType::AbstractBase;

    /// Returns whether or not this class corresponds to a concrete instantiable
    /// prim type in scene description.  If this is true,
    /// GetStaticPrimDefinition() will return a valid prim definition with
    /// a non-empty typeName.
    bool IsConcrete() const {
        return _GetSchemaType() == UsdSchemaType::ConcreteTyped;
    }

    /// Returns whether or not this class inherits from UsdTyped. Types which
    /// inherit from UsdTyped can impart a typename on a UsdPrim.
    bool IsTyped() const {
        return _GetSchemaType() == UsdSchemaType::ConcreteTyped
            || _GetSchemaType() == UsdSchemaType::AbstractTyped;
    }

    /// Returns whether this is an API schema or not.
    bool IsAPISchema() const {
        return _GetSchemaType() == UsdSchemaType::NonAppliedAPI
            || _GetSchemaType() == UsdSchemaType::SingleApplyAPI
            || _GetSchemaType() == UsdSchemaType::MultipleApplyAPI;
    }

    /// Returns whether this is an applied API schema or not. If this returns
    /// true this class will have an Apply() method
    bool IsAppliedAPISchema() const {
        return _GetSchemaType() == UsdSchemaType::SingleApplyAPI
            || _GetSchemaType() == UsdSchemaType::MultipleApplyAPI;
    }

    /// Returns whether this is an applied API schema or not. If this returns
    /// true the constructor, Get and Apply methods of this class will take
    /// in the name of the API schema instance.
    bool IsMultipleApplyAPISchema() const {
        return _GetSchemaType() == UsdSchemaType::MultipleApplyAPI;
    }

    UsdSchemaType GetSchemaType() const {
        return _GetSchemaType();
    }

    /// Construct and store \p prim as the held prim.
    USD_API
    explicit UsdSchemaBase(const UsdPrim& prim = UsdPrim());

    /// Construct and store for the same prim held by \p otherSchema
    USD_API
    explicit UsdSchemaBase(const UsdSchemaBase& otherSchema);

    /// Destructor.
    USD_API
    virtual ~UsdSchemaBase();

    /// \name Held prim access.
    //@{

    /// Return this schema object's held prim.
    UsdPrim GetPrim() const { return UsdPrim(_primData, _proxyPrimPath); }

    /// Shorthand for GetPrim()->GetPath().
    SdfPath GetPath() const { 
        if (!_proxyPrimPath.IsEmpty()) {
            return _proxyPrimPath;
        }
        else if (Usd_PrimDataConstPtr p = get_pointer(_primData)) {
            return p->GetPath();
        }
        return SdfPath::EmptyPath();
    }

    //@}

    /// \name PrimDefinition access.
    //@{

    /// Return the prim definition associated with this schema instance if one
    /// exists, otherwise return null.  This does not use the held prim's type.
    /// To get the held prim instance's definition, use
    /// UsdPrim::GetPrimDefinition().  \sa UsdPrim::GetPrimDefinition()
    USD_API
    const UsdPrimDefinition *GetSchemaClassPrimDefinition() const;

    //@}

    static const TfTokenVector &
    GetSchemaAttributeNames(bool includeInherited=true)
    {
        /* This only exists for consistency */
        static TfTokenVector names;
        return names;
    }

    /// Return true if this schema object is compatible with its held prim,
    /// false otherwise.  For untyped schemas return true if the held prim is
    /// not expired, otherwise return false.  For typed schemas return true if
    /// the held prim is not expired and its type is the schema's type or a
    /// subtype of the schema's type.  Otherwise return false.  This method
    /// invokes polymorphic behavior.
    /// 
    /// \sa UsdSchemaBase::_IsCompatible()
    USD_API
    explicit operator bool() const {
        return _primData && _IsCompatible();
    }

protected:
    /// Returns the type of schema this class is.
    ///
    /// \sa UsdSchemaBase::schemaType
    virtual UsdSchemaType _GetSchemaType() const {
        return schemaType;
    }
    // Helper for subclasses to get the TfType for this schema object's dynamic
    // C++ type.
    const TfType &_GetType() const {
        return _GetTfType();
    }

    USD_API
    UsdAttribute _CreateAttr(TfToken const &attrName,
                             SdfValueTypeName const & typeName,
                             bool custom, SdfVariability variability,
                             VtValue const &defaultValue, 
                             bool writeSparsely) const;
    
    /// Subclasses may override _IsCompatible to do specific compatibility
    /// checking with the given prim, such as type compatibility or value
    /// compatibility.  This check is performed when clients invoke the
    /// explicit bool operator.
    USD_API
    virtual bool _IsCompatible() const;

private:
    // Subclasses should not override _GetTfType.  It is implemented by the
    // schema class code generator.
    USD_API
    virtual const TfType &_GetTfType() const;

    // The held prim and proxy prim path.
    Usd_PrimDataHandle _primData;
    SdfPath _proxyPrimPath;
};


PXR_NAMESPACE_CLOSE_SCOPE

#endif //PXR_USD_USD_SCHEMA_BASE_H
