//
// 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.
//
#include "usdMaya/registryHelper.h"
#include "usdMaya/debugCodes.h"

#include "pxr/base/plug/plugin.h"
#include "pxr/base/plug/registry.h"

#include "pxr/base/tf/staticTokens.h"
#include "pxr/base/tf/stl.h"

#include <maya/MGlobal.h>

TF_DEFINE_PRIVATE_TOKENS(_tokens, 
    (mayaPlugin)
    (providesTranslator)
);

template <typename T>
bool
_GetData(const JsValue& any, T* val)
{
    if (not any.Is<T>()) {
        TF_CODING_ERROR("bad plugInfo.json");
        return false;
    }

    *val = any.Get<T>();
    return true;
}

template <typename T>
bool
_GetData(const JsValue& any, std::vector<T>* val)
{
    if (not any.IsArrayOf<T>()) {
        TF_CODING_ERROR("bad plugInfo.json");
        return false;
    }

    *val = any.GetArrayOf<T>();
    return true;
}

static bool
_ReadNestedDict(
        const JsObject& data,
        const std::vector<TfToken>& keys,
        JsObject* dict)
{
    JsObject currDict = data;
    TF_FOR_ALL(iter, keys) {
        const TfToken& currKey = *iter;
        JsValue any;
        if (not TfMapLookup(currDict, currKey, &any)) {
            return false;
        }

        if (not any.IsObject()) {
            TF_CODING_ERROR("bad plugInfo data.");
            return false;
        }
        currDict = any.GetObject();
    }
    *dict = currDict;
    return true;
}

static bool
_ProvidesForType(
        const PlugPluginPtr& plug,
        const std::vector<TfToken>& scope,
        const std::string& typeName,
        std::string* mayaPluginName)
{

    JsObject metadata = plug->GetMetadata();
    JsObject mayaTranslatorMetadata;
    if (not _ReadNestedDict(metadata, scope, &mayaTranslatorMetadata)) {
        return false;
    }

    JsValue any;
    if (not TfMapLookup(mayaTranslatorMetadata, _tokens->providesTranslator, &any)) {
        return false;
    }
    std::vector<std::string> usdTypes;
    if (not _GetData(any, &usdTypes)) {
        return false;
    }

    bool provides = std::find(
            usdTypes.begin(), usdTypes.end(), 
            typeName) != usdTypes.end();
    if (provides) {
        if (TfMapLookup(mayaTranslatorMetadata, _tokens->mayaPlugin, &any)) {
            return _GetData(any, mayaPluginName);
        }
    }

    return provides;
}

/* static */
std::string
_PluginDictScopeToDebugString(
        const std::vector<TfToken>& scope)
{
    std::vector<std::string> s;
    TF_FOR_ALL(iter, scope) {
        s.push_back(iter->GetString());
    }
    return TfStringJoin(s, "/");
}

/* static */
void
PxrUsdMaya_RegistryHelper::FindAndLoadMayaPlug(
        const std::vector<TfToken>& scope,
        const std::string& value)
{
    std::string mayaPlugin;
    PlugPluginPtrVector plugins = PlugRegistry::GetInstance().GetAllPlugins();
    TF_FOR_ALL(plugIter, plugins) {
        PlugPluginPtr plug = *plugIter;
        if (_ProvidesForType(plug, scope, value, &mayaPlugin)) {
            if (not mayaPlugin.empty()) {
                TF_DEBUG(PXRUSDMAYA_REGISTRY).Msg(
                        "Found usdMaya plugin %s:  %s = %s.  Loading maya plugin %s.\n", 
                        plug->GetName().c_str(),
                        _PluginDictScopeToDebugString(scope).c_str(),
                        value.c_str(),
                        mayaPlugin.c_str());
                std::string loadPluginCmd = TfStringPrintf(
                        "loadPlugin -quiet %s", mayaPlugin.c_str());
                if (not MGlobal::executeCommand(loadPluginCmd.c_str())) {
                    TF_CODING_ERROR("Unable to load mayaplugin %s\n",
                            mayaPlugin.c_str());
                }
            }
            else {
                TF_DEBUG(PXRUSDMAYA_REGISTRY).Msg(
                        "Found usdMaya plugin %s: %s = %s.  No maya plugin.\n", 
                        plug->GetName().c_str(),
                        _PluginDictScopeToDebugString(scope).c_str(),
                        value.c_str());
            }
            break;
        }
    }
}
