void NodeCapacitySubspace::GetTargetNodesForReplicas(
    TempSolution const& tempSolution,
    std::vector<PlacementReplica const*> const& replicas,
    NodeSet & candidateNodes,
    bool useNodeBufferCapacity,
    NodeToConstraintDiagnosticsDataMapSPtr const nodeToConstraintDiagnosticsDataMapSPtr /* = nullptr */) const
{
    ASSERT_IF(replicas.size() == 0, "Replica empty.");
    NodeEntry const* currentNode = replicas.size() == 1 ? tempSolution.GetReplicaLocation(replicas[0]) : nullptr;

    //Choose the function here to avoid the runtime speeed hit of boolean checks for every node in the filter
    auto FilterCandidateNodesForCapacityConstraintsWithOrWithoutDiagnostics = (nodeToConstraintDiagnosticsDataMapSPtr == nullptr) ?
            static_cast<std::function<bool(NodeEntry const *)>>(
                [&](NodeEntry const *node) -> bool
                {
                    return FilterCandidateNodesForCapacityConstraints(node, tempSolution, replicas, useNodeBufferCapacity, false);
                }
            ):
            static_cast<std::function<bool(NodeEntry const *)>>(
                [&](NodeEntry const *node) -> bool
                {
                    NodeCapacityViolationDiagnosticsDataSPtr constraintDiagnosticsDataSPtr =
                        make_shared<NodeCapacityViolationDiagnosticsData>(L"", 0, 0, 0, 0, 0, 0, false);

                    return FilterCandidateNodesForCapacityConstraints(node,
                        tempSolution,
                        replicas,
                        useNodeBufferCapacity,
                        constraintDiagnosticsDataSPtr, false) ? true :
                            [&node, &nodeToConstraintDiagnosticsDataMapSPtr, &constraintDiagnosticsDataSPtr]() -> bool
                            {
                                nodeToConstraintDiagnosticsDataMapSPtr->insert(move(std::pair<Federation::NodeId, NodeCapacityViolationDiagnosticsDataSPtr>(node->NodeId, move(constraintDiagnosticsDataSPtr))));  // with diagnostics
                                return false;
                            }();
                }
            );

    candidateNodes.Filter([&](NodeEntry const *node) -> bool
    {
        if (!node->HasCapacity)
        {
            return true;
        }
        else if (node == currentNode)
        {
            auto itNode = nodeExtraLoads_.find(currentNode);
            if (itNode != nodeExtraLoads_.end())
            {
                auto itReplica = itNode->second.find(replicas[0]);
                if (itReplica != itNode->second.end())
                {
                    // if the replica is one of invalid replicas on this node
                    return false;
                }
            }

            return true;
        }
        else if (replicas[0]->Partition->Service->Application
                 && replicas[0]->Partition->Service->Application->HasReservation)
        {
            // If replica was already on this node, it will be allowed to return because it will be evaluated in the previous
            // else-if branch above. Here we check if application was on this node, and we don't allow new replicas from that app.
            // This won't happen during search for upgrade (replicas.size() > 1), so we're safe checking replicas[0]
            auto const& nodeIt = nodeExtraApplications_.find(node);
            if (   nodeIt != nodeExtraApplications_.end()
                && nodeIt->second.find(replicas[0]->Partition->Service->Application) != nodeIt->second.end())
            {
                // We are moving the entire application from this node, so it's not allowed as target. If we had allowed it, and if 
                // replica load was below reservation then there is no other mechanism for ensuring that application will be moved.
                return false;
            }
            return FilterCandidateNodesForCapacityConstraintsWithOrWithoutDiagnostics(node);
        }
        else if (replicas[0]->Partition->Service->ServicePackage)
        {
            // This won't happen during search for upgrade (replicas.size() > 1), so we're safe checking replicas[0]
            auto const& nodeIt = nodeExtraServicePackages_.find(node);
            if (   nodeIt != nodeExtraServicePackages_.end()
                && nodeIt->second.find(replicas[0]->Partition->Service->ServicePackage) != nodeIt->second.end())
            {
                // We are moving the entire service package from this node, so it's not allowed as target. If we had allowed it, and if 
                // replica load was below reservation then there is no other mechanism for ensuring that application will be moved.
                return false;
            }
            return FilterCandidateNodesForCapacityConstraintsWithOrWithoutDiagnostics(node);
        }
        else
        {
            return FilterCandidateNodesForCapacityConstraintsWithOrWithoutDiagnostics(node);
        }
    });
}
void NodeCapacitySubspace::PromoteSecondaryForPartitions(TempSolution const& tempSolution, std::vector<PartitionEntry const*> const& partitions, NodeSet & candidateNodes) const
{

    size_t globalMetricCount = tempSolution.OriginalPlacement->GlobalMetricCount;
    size_t totalMetricCount = tempSolution.OriginalPlacement->TotalMetricCount;
    ASSERT_IFNOT(totalMetricCount >= globalMetricCount, "Invalid metric count");
    size_t globalMetricStartIndex = totalMetricCount - globalMetricCount;

    bool applyOnlyMoveInChanges = tempSolution.OriginalPlacement->Settings.PreventTransientOvercommit;

    candidateNodes.Filter([&](NodeEntry const *node) -> bool
    {
        if (!node->IsUp)
        {
            return false;
        }
        if (!node->HasCapacity)
        {
            return true;
        }
        else
        {
            LoadEntry const& originalLoads = node->Loads;
            LoadEntry const& shouldDisappearLoads = node->ShouldDisappearLoads;
            LoadEntry const& baseLoads = tempSolution.BaseSolution.NodeChanges[node];
            LoadEntry const& tempLoads = applyOnlyMoveInChanges ? tempSolution.NodeMovingInChanges[node] : tempSolution.NodeChanges[node];

            ASSERT_IFNOT(totalMetricCount == originalLoads.Length && totalMetricCount == tempLoads.Length &&
                totalMetricCount == baseLoads.Length, "Invalid temp loads or base loads");

            bool ret = true;
            LoadEntry swapLoadDiffEntry(globalMetricCount);

            // We rely on static subspace to filter out nodes without secondaries, so we assume that there are replicas on the node.
            std::map<ApplicationEntry const*, LoadEntry> appLoadOnNode;

            for (auto pIt = partitions.begin(); pIt != partitions.end(); ++pIt)
            {
                auto partition = *pIt;
                ServiceEntry const* replicaService = partition->Service;
                ApplicationEntry const* application = replicaService->Application;
                bool hasReservation = application && application->HasReservation;

                std::map<ApplicationEntry const*, LoadEntry>::iterator appLoadOnNodeIt;

                if (hasReservation)
                {
                    appLoadOnNodeIt = appLoadOnNode.find(application);
                    if (appLoadOnNodeIt == appLoadOnNode.end())
                    {
                        appLoadOnNodeIt = appLoadOnNode.insert(make_pair(application, ((NodeMetrics const&)tempSolution.ApplicationNodeLoad[application])[node])).first;
                    }
                }

                LoadEntry const& primaryEntry = partition->GetReplicaEntry(ReplicaRole::Primary);
                LoadEntry const& secondaryEntry = partition->GetReplicaEntry(ReplicaRole::Secondary, true, node->NodeId);

                for (size_t metricIndex = 0; metricIndex < replicaService->MetricCount; metricIndex++)
                {
                    ASSERT_IFNOT(metricIndex < secondaryEntry.Length,
                        "Metric index should be < replica entry size: {0} {1}",
                        metricIndex,
                        secondaryEntry.Length);

                    int64 swapLoadDiffValue = primaryEntry.Values[metricIndex] - secondaryEntry.Values[metricIndex];

                    size_t globalMetricIndex = replicaService->GlobalMetricIndices[metricIndex];
                    size_t capacityIndex = globalMetricIndex - globalMetricStartIndex;

                    if (hasReservation)
                    {
                        // There are already replicas on the node: this replica may fit into reservation that already exists
                        auto applicationLoadOnNode =  appLoadOnNodeIt->second.Values[capacityIndex];
                        appLoadOnNodeIt->second.AddLoad(capacityIndex, swapLoadDiffValue);
                        auto applicationReservation = application->Reservation.Values[capacityIndex];
                        auto remainingReservation = applicationLoadOnNode < applicationReservation ? applicationReservation - applicationLoadOnNode : 0;
                        swapLoadDiffValue = swapLoadDiffValue < remainingReservation ? 0 : swapLoadDiffValue - remainingReservation;
                    }

                    swapLoadDiffEntry.AddLoad(capacityIndex, swapLoadDiffValue);
                }
            }

            for (size_t i = 0; i < globalMetricCount; ++i)
            {
                int64 partitionsSwapDiffValue = swapLoadDiffEntry.Values[i];
                int64 capacity = node->TotalCapacities.Values[i];

                if (partitionsSwapDiffValue <= 0 || capacity < 0)
                {
                    continue;
                }

                size_t totalIndex = globalMetricStartIndex + i;

                int64 totalLoadWithPartitionsSwap = originalLoads.Values[totalIndex] +
                    tempSolution.GetApplicationReservedLoad(node, i) +
                    shouldDisappearLoads.Values[totalIndex] +
                    tempLoads.Values[totalIndex] +
                    partitionsSwapDiffValue;

                if (totalLoadWithPartitionsSwap > capacity)
                {
                    ret = false;
                    break;
                }
            }

            return ret;
        }
    });
}