/*
 * Copyright (C) 2008 Google Inc.
 *
 * 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.hit;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

public class Queries {
    /*
     * NOTES:  Here's a list of the queries that can be done in hat and 
     * how you'd perform a similar query here in hit:
     *
     * hat                      hit
     * ------------------------------------------------------------------------
     * allClasses               classes
     * allClassesWithPlatform   allClasses
     * class                    findClass
     * instances                instancesOf
     * allInstances             allInstancesOf
     * object                   findObject
     * showRoots                getRoots
     * newInstances             newInstances
     *
     * reachableFrom            make a call to findObject to get the target
     *                          parent object, this will give you an Instance.
     *                          Then call visit(Set, Filter) on that to have
     *                          it build the set of objects in its subgraph.
     *
     * rootsTo                  make a call to findObject on the leaf node
     *                          in question, this will give you an Instance.
     *                          Instances have an ArrayList of all of the
     *                          parent objects that refer to it.  You can
     *                          follow those parent links until you hit an
     *                          object whose parent is null or a ThreadObj.
     *                          You've not successfully traced the paths to
     *                          the roots.
     */

    private static final String DEFAULT_PACKAGE = "<default>";

    /*
     * Produce a collection of all classes, broken down by package.
     * The keys of the resultant map iterate in sorted package order.
     * The values of the map are the classes defined in each package.
     */
    public static Map<String, Set<ClassObj>> allClasses(State state) {
        return classes(state, null);
    }

    public static Map<String, Set<ClassObj>> classes(State state, 
            String[] excludedPrefixes) {
        TreeMap<String, Set<ClassObj>> result =
        new TreeMap<String, Set<ClassObj>>();

        Set<ClassObj> classes = new TreeSet<ClassObj>();

        //  Build a set of all classes across all heaps
        for (Heap heap: state.mHeaps.values()) {
            classes.addAll(heap.mClassesById.values());
        }
        
        //  Filter it if needed
        if (excludedPrefixes != null) {
            final int N = excludedPrefixes.length;
            Iterator<ClassObj> iter = classes.iterator();
            
            while (iter.hasNext()) {
                ClassObj theClass = iter.next();
                String classPath = theClass.toString();
                
                for (int i = 0; i < N; i++) {
                    if (classPath.startsWith(excludedPrefixes[i])) {
                        iter.remove();
                        break;
                    }
                }
            }
        }
        
        //  Now that we have a final list of classes, group them by package
        for (ClassObj theClass: classes) {
            String packageName = DEFAULT_PACKAGE;
            int lastDot = theClass.mClassName.lastIndexOf('.');
            
            if (lastDot != -1) {
                packageName = theClass.mClassName.substring(0, lastDot);
            }
            
            Set<ClassObj> classSet = result.get(packageName);
            
            if (classSet == null) {
                classSet = new TreeSet<ClassObj>();
                result.put(packageName, classSet);
            }
            
            classSet.add(theClass);
        }
        
        return result;
    }
    
    /*
     * It's sorta sad that this is a pass-through call, but it seems like
     * having all of the hat-like query methods in one place is a good thing
     * even if there is duplication of effort.
     */
    public static ClassObj findClass(State state, String name) {
        return state.findClass(name);
    }

    /*
     * Return an array of instances of the given class.  This does not include
     * instances of subclasses.
     */
     public static Instance[] instancesOf(State state, String baseClassName) {
         ClassObj theClass = state.findClass(baseClassName);
         
         if (theClass == null) {
             throw new IllegalArgumentException("Class not found: "
                + baseClassName);
         }
         
         Instance[] instances = new Instance[theClass.mInstances.size()];
         
         return theClass.mInstances.toArray(instances);
     }

    /*
     * Return an array of instances of the given class.  This includes
     * instances of subclasses.
     */
    public static Instance[] allInstancesOf(State state, String baseClassName) {
        ClassObj theClass = state.findClass(baseClassName);
        
        if (theClass == null) {
            throw new IllegalArgumentException("Class not found: "
                + baseClassName);
        }

        ArrayList<ClassObj> classList = new ArrayList<ClassObj>();
        
        classList.add(theClass);
        classList.addAll(traverseSubclasses(theClass));
        
        ArrayList<Instance> instanceList = new ArrayList<Instance>();
        
        for (ClassObj someClass: classList) {
            instanceList.addAll(someClass.mInstances);
        }
        
        Instance[] result = new Instance[instanceList.size()];
        
        instanceList.toArray(result);
        
        return result;
    }
    
    private static ArrayList<ClassObj> traverseSubclasses(ClassObj base) {
        ArrayList<ClassObj> result = new ArrayList<ClassObj>();
        
        for (ClassObj subclass: base.mSubclasses) {
            result.add(subclass);
            result.addAll(traverseSubclasses(subclass));
        }
        
        return result;
    }

    /*
     * Find a reference to an object based on its id.  The id should be
     * in hexadecimal.
     */
    public static Instance findObject(State state, String id) {
        long id2 = Long.parseLong(id, 16);
        
        return state.findReference(id2);
    }

    public static Collection<RootObj> getRoots(State state) {
        HashSet<RootObj> result = new HashSet<RootObj>();
        
        for (Heap heap: state.mHeaps.values()) {
            result.addAll(heap.mRoots);
        }
        
        return result;
    }

    public static final Instance[] newInstances(State older, State newer) {
        ArrayList<Instance> resultList = new ArrayList<Instance>();
        
        for (Heap newHeap: newer.mHeaps.values()) {
            Heap oldHeap = older.getHeap(newHeap.mName);
            
            if (oldHeap == null) {
                continue;
            }
            
            for (Instance instance: newHeap.mInstances.values()) {
                Instance oldInstance = oldHeap.getInstance(instance.mId);
                
                /*
                 * If this instance wasn't in the old heap, or was there,
                 * but that ID was for an obj of a different type, then we have
                 * a newly allocated object and we should report it in the
                 * results.
                 */
                if ((oldInstance == null)
                        || (instance.mClassId != oldInstance.mClassId)) {
                    resultList.add(instance);
                }
            }
        }
        
        Instance[] resultArray = new Instance[resultList.size()];
        
        return resultList.toArray(resultArray);
    }
}
