// Copyright 2016 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://openusd.org/license.
//
// Modified by Jeremy Retailleau.

/// \file wrapPrimSpec.cpp

#include <pxr/sdf/primSpec.h>
#include <pxr/sdf/attributeSpec.h>
#include <pxr/sdf/layer.h>
#include <pxr/sdf/pyChildrenProxy.h>
#include <pxr/sdf/pySpec.h>
#include <pxr/sdf/relationshipSpec.h>
#include <pxr/sdf/variantSetSpec.h>

#include <pxr/tf/pyContainerConversions.h>
#include <pxr/tf/pyEnum.h>
#include <pxr/tf/pyPtrHelpers.h>
#include <pxr/tf/pyResultConversions.h>
#include <pxr/tf/iterator.h>
#include <pxr/tf/stringUtils.h>
#include <pxr/tf/token.h>

#include <pxr/boost/python.hpp>
#include <pxr/boost/python/stl_iterator.hpp>

using namespace pxr;

using namespace pxr::boost::python;

namespace {

////////////////////////////////////////////////////////////////////////
// Wrappers for constructors and proxy constructors

static
SdfPrimSpecHandle
_NewFromLayer(const SdfLayerHandle& parent,
          const std::string& name, SdfSpecifier spec,
          const std::string& typeName)
{
    return SdfPrimSpec::New(parent, name, spec, typeName);
}

static
SdfPrimSpecHandle
_NewTypelessFromLayer(const SdfLayerHandle& parent,
          const std::string& name, SdfSpecifier spec)
{
    return SdfPrimSpec::New(parent, name, spec);
}

static
SdfPrimSpecHandle
_NewPrim(const SdfPrimSpecHandle& parent,
         const std::string& name, SdfSpecifier spec,
         const std::string& typeName)
{
    return SdfPrimSpec::New(parent, name, spec, typeName);
}

static
SdfPrimSpecHandle
_NewTypelessPrim(const SdfPrimSpecHandle& parent,
                 const std::string& name, SdfSpecifier spec)
{
    return SdfPrimSpec::New(parent, name, spec);
}

typedef SdfPyChildrenProxy<SdfPrimSpec::NameChildrenView> NameChildrenProxy;

static
NameChildrenProxy
_WrapGetNameChildrenProxy(const SdfPrimSpec& prim)
{
    return NameChildrenProxy(prim.GetNameChildren(), "prim");
}

typedef SdfPyChildrenProxy<SdfPrimSpec::PropertySpecView> PropertiesProxy;

static
PropertiesProxy
_WrapGetPropertiesProxy(const SdfPrimSpec& prim)
{
    return PropertiesProxy(prim.GetProperties(), "property");
}

typedef SdfPyChildrenProxy<SdfVariantSetView> VariantSetProxy;

static
VariantSetProxy
_WrapGetVariantSetsProxy(const SdfPrimSpec& prim)
{
    return VariantSetProxy(prim.GetVariantSets());
}

static void
_SetSymmetryArguments(const SdfPrimSpec& self,
                      VtDictionary const &dictionary)
{
    self.GetSymmetryArguments() = dictionary;
}

static void
_SetCustomData(const SdfPrimSpec& self,
               VtDictionary const &dictionary)
{
    self.GetCustomData() = dictionary;
}

static void
_SetAssetInfo(const SdfPrimSpec& self,
              VtDictionary const &dictionary)
{
    self.GetAssetInfo() = dictionary;
}

static void
_SetRelocates(SdfPrimSpec& self, const dict &d)
{
    SdfRelocatesMap reloMap;

    auto items = d.attr("items")();
    auto end =  stl_input_iterator<tuple>();
    for (auto it = stl_input_iterator<tuple>(items); it != end; ++it) {
        tuple kv = *it;
        SdfPath key = extract<SdfPath>(kv[0]);
        SdfPath val = extract<SdfPath>(kv[1]);

        reloMap[key] = val;
    }

    self.SetRelocates(reloMap);
}

////////////////////////////////////////////////////////////////////////

static
void
_WrapSetName(SdfPrimSpec& self, const std::string& newName)
{
    // Always validate the new name from python.
    self.SetName(newName, true);
}

static
bool
_WrapCanSetName(SdfPrimSpec& self, const std::string& newName)
{
    std::string errStr;
    return self.CanSetName(newName, &errStr);
}

static
std::vector<TfToken>
_ApplyNameChildrenOrder(
    const SdfPrimSpec& self,
    const std::vector<TfToken>& names)
{
    std::vector<TfToken> result = names;
    self.ApplyNameChildrenOrder(&result);
    return result;
}

static
std::vector<TfToken>
_ApplyPropertyOrder(
    const SdfPrimSpec& self,
    const std::vector<TfToken>& names)
{
    std::vector<TfToken> result = names;
    self.ApplyPropertyOrder(&result);
    return result;
}

} // anonymous namespace 

void wrapPrimSpec()
{
    def("CreatePrimInLayer", SdfCreatePrimInLayer);
    def("JustCreatePrimInLayer", SdfJustCreatePrimInLayer);

    typedef SdfPrimSpec This;

    // Register python conversions for vector<SdfPrimSpecHandle>
    to_python_converter< SdfPrimSpecHandleVector,
                         TfPySequenceToPython<SdfPrimSpecHandleVector> >();
    TfPyContainerConversions::from_python_sequence<
        SdfPrimSpecHandleVector,
        TfPyContainerConversions::variable_capacity_policy >();

    // Register python conversions for vector<SdfPrimSpecConstHandle>
    to_python_converter< SdfPrimSpecConstHandleVector,
                         TfPySequenceToPython<SdfPrimSpecConstHandleVector> >();
    TfPyContainerConversions::from_python_sequence<
        SdfPrimSpecConstHandleVector,
        TfPyContainerConversions::variable_capacity_policy >();

    // Register python coversions for SdfVariantSets
    typedef SdfVariantSetSpecHandleMap::value_type VSSHVT;
    to_python_converter<VSSHVT,
                        TfPyContainerConversions::to_tuple<VSSHVT> >();
    to_python_converter<SdfVariantSetSpecHandleMap,
                        TfPySequenceToPython<SdfVariantSetSpecHandleMap> >();

    class_<This, SdfHandle<This>, bases<SdfSpec>, noncopyable>
        ("PrimSpec", no_init)
        .def(SdfPySpec())

        .def(SdfMakePySpecConstructor(&_NewFromLayer))
        .def(SdfMakePySpecConstructor(&_NewTypelessFromLayer))
        .def(SdfMakePySpecConstructor(&_NewPrim))
        .def(SdfMakePySpecConstructor(&_NewTypelessPrim))

        .add_property("name",
            make_function(&This::GetName,
                          return_value_policy<return_by_value>()),
            &_WrapSetName,
            "The prim's name.")

        .add_property("comment",
            &This::GetComment,
            &This::SetComment,
            "The prim's comment string.")

        .add_property("documentation",
            &This::GetDocumentation,
            &This::SetDocumentation,
            "The prim's documentation string.")

        .add_property("active",
            &This::GetActive,
            &This::SetActive,
            "Whether this prim spec is active.\n"
            "The default value is true.")

        .def("HasActive", &This::HasActive)
        .def("ClearActive", &This::ClearActive)

        .add_property("hidden",
            &This::GetHidden,
            &This::SetHidden,
            "Whether this prim spec will be hidden in browsers.\n"
            "The default value is false.")

        .add_property("kind",
            &This::GetKind,
            &This::SetKind,
            "What kind of model this prim spec represents, if any.\n"
            "The default is an empty string")

        .def("HasKind", &This::HasKind)
        .def("ClearKind", &This::ClearKind)

        .add_property("instanceable",
            &This::GetInstanceable,
            &This::SetInstanceable,
            "Whether this prim spec is flagged as instanceable.\n"
            "The default value is false.")

        .def("HasInstanceable", &This::HasInstanceable)
        .def("ClearInstanceable", &This::ClearInstanceable)

        .add_property("permission",
            &This::GetPermission,
            &This::SetPermission,
            "The prim's permission restriction.\n"
            "The default value is SdfPermissionPublic.")

        .add_property("symmetryFunction",
            &This::GetSymmetryFunction,
            &This::SetSymmetryFunction,
            "The prim's symmetry function.")

        .add_property("symmetryArguments",
            &This::GetSymmetryArguments,
            &_SetSymmetryArguments,
            "Dictionary with prim symmetry arguments.\n\n"
            "Although this property is marked read-only, you can "
            "modify the contents to add, change, and clear symmetry "
            "arguments.")

        .add_property("symmetricPeer",
            &This::GetSymmetricPeer,
            &This::SetSymmetricPeer,
            "The prims's symmetric peer.")

        .add_property("customData",
            &This::GetCustomData,
            &_SetCustomData,
            "The custom data for this prim.\n\n"
            "The default value for custom data is an empty dictionary.\n\n"
            "Custom data is for use by plugins or other non-tools supplied \n"
            "extensions that need to be able to store data attached to arbitrary\n"
            "scene objects.  Note that if the only objects you want to store data\n"
            "on are prims, using custom attributes is probably a better choice.\n"
            "But if you need to possibly store this data on attributes or \n"
            "relationships or as annotations on reference arcs, then custom data\n"
            "is an appropriate choice.")

        .add_property("assetInfo",
            &This::GetAssetInfo,
            &_SetAssetInfo,
            "Returns the asset info dictionary for this prim.\n\n"
            "The default value is an empty dictionary.\n\n"
            "The asset info dictionary is used to annotate prims representing "
            "the root-prims of assets (generally organized as models) with "
            "various data related to asset management. For example, asset "
            "name, root layer identifier, asset version etc.")

        .add_property("specifier",
            &This::GetSpecifier,
            &This::SetSpecifier,
            "The prim's specifier (SpecifierDef or SpecifierOver).\n"
            "The default value is SpecifierOver.")

        .add_property("nameRoot",
            &This::GetNameRoot,
            "The name pseudo-root of this prim.")

        .add_property("nameParent",
            &This::GetNameParent,
            "The name parent of this prim.")

        .add_property("realNameParent",
            &This::GetRealNameParent,
            "The name parent of this prim.")

        .def("GetObjectAtPath",
            &This::GetObjectAtPath,
            "GetObjectAtPath(path) -> object\n\n"
            "path: Path\n\n"
            "Returns a prim or property given its namespace path.\n\n"
            "If path is relative then it will be interpreted as relative "
            "to this prim.  If it is absolute then it will be "
            "interpreted as absolute in this prim's layer. The "
            "return type can be either PrimSpecPtr or "
            "PropertySpecPtr.")
        .def("GetPrimAtPath", &This::GetPrimAtPath)
        .def("GetPropertyAtPath", &This::GetPropertyAtPath)
        .def("RemoveProperty", &This::RemoveProperty)
        .def("GetAttributeAtPath", &This::GetAttributeAtPath)
        .def("GetRelationshipAtPath", &This::GetRelationshipAtPath)
        .def("GetVariantNames", &This::GetVariantNames)
        .def("BlockVariantSelection", &This::BlockVariantSelection)

        .add_property("variantSelections",
            &This::GetVariantSelections,
            "Dictionary whose keys are variant set names and whose values are "
            "the variants chosen for each set.\n\n"
            "Although this property is marked read-only, you can "
            "modify the contents to add, change, and clear variants.")

        .add_property("prefix",
            &This::GetPrefix,
            &This::SetPrefix,
            "The prim's prefix.")

        .add_property("prefixSubstitutions",
            &This::GetPrefixSubstitutions,
            &This::SetPrefixSubstitutions,
            "Dictionary of prefix substitutions.")

        .add_property("suffix",
            &This::GetSuffix,
            &This::SetSuffix,
            "The prim's suffix.")

        .add_property("suffixSubstitutions",
            &This::GetSuffixSubstitutions,
            &This::SetSuffixSubstitutions,
            "Dictionary of prefix substitutions.")

        .add_property("variantSetNameList",
            &This::GetVariantSetNameList,
            "A StringListEditor for the names of the variant \n"
            "sets for this prim.\n\n"
            "The list of the names of the variants sets of this prim may be\n"
            "modified with this StringListEditor.\n\n"
            "A StringListEditor may express a list either as an explicit "
            "value or as a set of list editing operations.  See StringListEditor "
            "for more information.\n\n"
            "Although this property is marked as read-only, the returned object "
            "is modifiable.")

        .add_property("variantSets",
            &_WrapGetVariantSetsProxy,
            "The VariantSetSpecs for this prim indexed by name.\n\n"
            "Although this property is marked as read-only, you can \n"
            "modify the contents to remove variant sets.  New variant sets \n"
            "are created by creating them with the prim as the owner.\n\n"
            "Although this property is marked as read-only, the returned object\n"
            "is modifiable.")

        .add_property("typeName",
            &This::GetTypeName,
            &This::SetTypeName,
            "The type of this prim.")

        .add_property("nameChildren",
            &_WrapGetNameChildrenProxy,
            "The prim name children of this prim, as an ordered "
            "dictionary.\n\n"
            "Note that although this property is described as being "
            "read-only, you can modify the contents to add, "
            "remove, or reorder children.")

        .add_property("nameChildrenOrder",
            &This::GetNameChildrenOrder,
            &This::SetNameChildrenOrder,
            "Get/set the list of child names for this prim's 'reorder "
            "nameChildren' statement.")

        .add_property("properties",
            &_WrapGetPropertiesProxy,
            "The properties of this prim, as an ordered dictionary.\n\n"
            "Note that although this property is described as being "
            "read-only, you can modify the contents to add, "
            "remove, or reorder properties.")

        .add_property("attributes",
            &This::GetAttributes,
            "The attributes of this prim, as an ordered dictionary.")

        .add_property("relationships",
            &This::GetRelationships,
            "The relationships of this prim, as an ordered dictionary.")

        .add_property("propertyOrder",
            &This::GetPropertyOrder,
            &This::SetPropertyOrder,
            "Get/set the list of property names for this prim's 'reorder "
            "properties' statement.")

        .add_property("inheritPathList",
            &This::GetInheritPathList,
            "A PathListEditor for the prim's inherit paths.\n\n"
            "The list of the inherit paths for this prim may be "
            "modified with this PathListEditor.\n\n"
            "A PathListEditor may express a list either as an explicit "
            "value or as a set of list editing operations.  See PathListEditor "
            "for more information.")

        .add_property("specializesList",
            &This::GetSpecializesList,
            "A PathListEditor for the prim's specializes.\n\n"
            "The list of the specializes for this prim may be "
            "modified with this PathListEditor.\n\n"
            "A PathListEditor may express a list either as an explicit "
            "value or as a set of list editing operations.  See PathListEditor "
            "for more information.")

        .add_property("referenceList",
            &This::GetReferenceList,
            "A ReferenceListEditor for the prim's references.\n\n"
            "The list of the references for this prim may be "
            "modified with this ReferenceListEditor.\n\n"
            "A ReferenceListEditor may express a list either as an explicit "
            "value or as a set of list editing operations.  See "
            "ReferenceListEditor for more information.")

        .add_property("payloadList",
            &This::GetPayloadList,
            "A PayloadListEditor for the prim's payloads.\n\n"
            "The list of the payloads for this prim may be "
            "modified with this PayloadListEditor.\n\n"
            "A PayloadListEditor may express a list either as an explicit "
            "value or as a set of list editing operations.  See "
            "PayloadListEditor for more information.")

        .add_property("hasReferences",
            &This::HasReferences,
            "Returns true if this prim has references set.")

        .add_property("hasPayloads",
            &This::HasPayloads,
            "Returns true if this prim has payloads set.")

        .add_property("hasInheritPaths",
            &This::HasInheritPaths,
            "Returns true if this prim has inherits set.")

        .add_property("hasSpecializes",
            &This::HasSpecializes,
            "Returns true if this prim has specializes set.")

        .add_property("relocates",
            &This::GetRelocates,
            &_SetRelocates,
            "An editing proxy for the prim's map of relocation paths.\n\n"
            "The map of source-to-target paths specifying namespace "
            "relocation may be set or cleared whole, or individual map "
            "entries may be added, removed, or edited.")

        .def("ClearReferenceList",
            &This::ClearReferenceList,
            "Clears the references for this prim.")

        .def("ClearPayloadList",
            &This::ClearPayloadList,
            "Clears the payloads for this prim.")

        .def("CanSetName", &_WrapCanSetName)

        .def("ApplyNameChildrenOrder", &_ApplyNameChildrenOrder,
             return_value_policy<TfPySequenceToList>())
        .def("ApplyPropertyOrder", &_ApplyPropertyOrder,
             return_value_policy<TfPySequenceToList>())

        .setattr("ActiveKey", SdfFieldKeys->Active)
        .setattr("AnyTypeToken", SdfTokens->AnyTypeToken)
        .setattr("CommentKey", SdfFieldKeys->Comment)
        .setattr("CustomDataKey", SdfFieldKeys->CustomData)
        .setattr("DisplayName", SdfFieldKeys->DisplayName)
        .setattr("DocumentationKey", SdfFieldKeys->Documentation)
        .setattr("HiddenKey", SdfFieldKeys->Hidden)
        .setattr("InheritPathsKey", SdfFieldKeys->InheritPaths)
        .setattr("KindKey", SdfFieldKeys->Kind)
        .setattr("PrimOrderKey", SdfFieldKeys->PrimOrder)
        .setattr("PayloadKey", SdfFieldKeys->Payload)
        .setattr("PermissionKey", SdfFieldKeys->Permission)
        .setattr("PrefixKey", SdfFieldKeys->Prefix)
        .setattr("PrefixSubstitutionsKey", SdfFieldKeys->PrefixSubstitutions)
        .setattr("PropertyOrderKey", SdfFieldKeys->PropertyOrder)
        .setattr("ReferencesKey", SdfFieldKeys->References)
        .setattr("RelocatesKey", SdfFieldKeys->Relocates)
        .setattr("SpecializesKey", SdfFieldKeys->Specializes)
        .setattr("SpecifierKey", SdfFieldKeys->Specifier)
        .setattr("SymmetricPeerKey", SdfFieldKeys->SymmetricPeer)
        .setattr("SymmetryArgumentsKey", SdfFieldKeys->SymmetryArguments)
        .setattr("SymmetryFunctionKey", SdfFieldKeys->SymmetryFunction)
        .setattr("TypeNameKey", SdfFieldKeys->TypeName)
        .setattr("VariantSelectionKey", SdfFieldKeys->VariantSelection)
        .setattr("VariantSetNamesKey", SdfFieldKeys->VariantSetNames)
        ;
}
