//
// 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 "pxr/usd/pcp/instanceKey.h"

#include "pxr/usd/pcp/composeSite.h"
#include "pxr/usd/pcp/diagnostic.h"
#include "pxr/usd/pcp/instancing.h"

#include "pxr/base/tracelite/trace.h"

#include <boost/foreach.hpp>

struct PcpInstanceKey::_Collector
{
    bool Visit(const PcpNodeRef& node, bool nodeIsInstanceable)
    {
        if (nodeIsInstanceable) {
            instancingArcs.push_back(_Arc(node));
            // We can stop immediately if we know there is no payload
            // arc in the node graph -- but otherwise we must continue,
            // since payload arcs can be optionally included, and
            // therefore affect instance sharing.
            if (not indexHasPayload) {
                return false;
            }
        }
        return true;
    }

    std::vector<_Arc> instancingArcs;
    bool indexHasPayload;
};

PcpInstanceKey::PcpInstanceKey()
    : _hash(0)
{
}

PcpInstanceKey::PcpInstanceKey(const PcpPrimIndex& primIndex)
    : _hash(0)
{
    TRACE_FUNCTION();

    // Instance keys only apply to instanceable prim indexes.
    if (not primIndex.IsInstanceable()) {
        return;
    }

    // Collect all composition arcs that contribute to the instance key.
    _Collector collector;
    collector.indexHasPayload = primIndex.HasPayload();
    Pcp_TraverseInstanceableStrongToWeak(primIndex, &collector);
    _arcs.swap(collector.instancingArcs);

    // Collect all authored variant selections in strong-to-weak order.
    SdfVariantSelectionMap variantSelection;
    TF_FOR_ALL(nodeIt, primIndex.GetNodeRange()) {
        if (nodeIt->CanContributeSpecs()) {
            PcpComposeSiteVariantSelections(
                nodeIt->GetSite(), &variantSelection);
        }
    }
    _variantSelection.assign(variantSelection.begin(), variantSelection.end());
    
    BOOST_FOREACH(const _Arc& arc, _arcs) {
        boost::hash_combine(_hash, arc.GetHash());
    }
    BOOST_FOREACH(const _VariantSelection& vsel, _variantSelection) {
        boost::hash_combine(_hash, vsel);
    }
}

bool 
PcpInstanceKey::operator==(const PcpInstanceKey& rhs) const
{
    return _variantSelection == rhs._variantSelection and
        _arcs == rhs._arcs;
}

bool 
PcpInstanceKey::operator!=(const PcpInstanceKey& rhs) const
{
    return not (*this == rhs);
}

std::string 
PcpInstanceKey::GetString() const
{
    std::string s;
    s += "Arcs:\n";
    if (_arcs.empty()) {
        s += "  (none)\n";
    }
    else {
        BOOST_FOREACH(const _Arc& arc, _arcs) {
            s += TfStringPrintf("  %s%s : %s\n",
                TfEnum::GetDisplayName(arc._arcType).c_str(),
                (arc._timeOffset.IsIdentity() ? 
                    "" :
                    TfStringPrintf(" (offset: %f scale: %f)",
                        arc._timeOffset.GetOffset(),
                        arc._timeOffset.GetScale()).c_str()),
                Pcp_FormatSite(arc._sourceSite).c_str());
        }
    }

    s += "Variant selections:\n";
    if (_variantSelection.empty()) {
        s += "  (none)";
    }
    else {
        BOOST_FOREACH(const _VariantSelection& vsel, _variantSelection) {
            s += TfStringPrintf("  %s = %s\n", 
                vsel.first.c_str(), vsel.second.c_str());
        }
        // Kill the last newline.
        s.erase(s.length() - 1);
    }

    return s;
}
