/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.tools.perflib.heap.analysis;

import com.android.annotations.NonNull;
import com.android.tools.perflib.heap.Instance;
import com.android.tools.perflib.heap.NonRecursiveVisitor;
import com.android.tools.perflib.heap.RootObj;
import com.android.tools.perflib.heap.Snapshot;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;

import java.util.List;

import gnu.trove.TLongHashSet;

public class TopologicalSort {
    @NonNull
    public static List<Instance> compute(@NonNull Iterable<RootObj> roots) {
        TopologicalSortVisitor visitor = new TopologicalSortVisitor();
        visitor.doVisit(roots);
        List<Instance> instances = visitor.getPreorderedInstances();

        // We add the special sentinel node as the single root of the object graph, to ensure the
        // dominator algorithm terminates when having to choose between two GC roots.
        Snapshot.SENTINEL_ROOT.setTopologicalOrder(0);

        // Set localIDs in the range 1..keys.size(). This simplifies the algorithm and data
        // structures for dominator computation.
        int currentIndex = 0;
        for (Instance node : instances) {
            node.setTopologicalOrder(++currentIndex);
        }

        return instances;
    }

    /**
     * Topological sort visitor computing a post-order traversal of the graph.
     *
     * We use the classic iterative three-color marking algorithm in order to correctly compute the
     * finishing time for each node. Nodes in decreasing order of their finishing time satisfy the
     * topological order property, i.e. any node appears before its successors.
     */
    private static class TopologicalSortVisitor extends NonRecursiveVisitor {

        // Marks nodes that have been fully visited and popped off the stack.
        private final TLongHashSet mVisited = new TLongHashSet();

        private final List<Instance> mPostorder = Lists.newArrayList();

        @Override
        public void visitLater(Instance parent, @NonNull Instance child) {
            if (!mSeen.contains(child.getId())) {
                mStack.push(child);
            }
        }

        @Override
        public void doVisit(Iterable<? extends Instance> startNodes) {
            // root nodes are instances that share the same id as the node they point to.
            // This means that we cannot mark them as visited here or they would be marking
            // the actual root instance
            // TODO RootObj should not be Instance objects

            for (Instance node : startNodes) {
                node.accept(this);
            }
            while (!mStack.isEmpty()) {
                Instance node = mStack.peek();
                if (mSeen.add(node.getId())) {
                    node.accept(this);
                } else {
                    mStack.pop();
                    if (mVisited.add(node.getId())) {
                        mPostorder.add(node);
                    }
                }
            }
        }

        List<Instance> getPreorderedInstances() {
            return Lists.reverse(mPostorder);
        }
    }
}
