/*******************************************************************************
 * Copyright (c) 2006, 2011 Wind River Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *     Ericsson 		  - Modified for additional features in DSF Reference implementation
 *     Dobrin Alexiev (Texas Instruments) - added helpers for recursive data model contexts (bug 240208)
 *******************************************************************************/
package org.eclipse.cdt.dsf.datamodel;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.cdt.dsf.concurrent.ThreadSafe;


/**
 * Holder for utility static methods for manipulating IDMContext objects.
 * 
 * @since 1.0
 */
public class DMContexts {

    /**
     * Convenience constant.
     */
    public static final IDMContext[] EMPTY_CONTEXTS_ARRAY = new IDMContext[0];

    /**
     * Finds a data model context of given type among ancestors of the 
     * specified context.  The returned ancestor is the one closest to the
     * specified context, in terms of depth.
     * 
     * Note that for efficiency, this method does not re-use getAllAncestorsOfType()
     * to avoid the unnecessary creation of an array.
     * 
     * @param ctx DMC to search.
     * @param ancestorType Class type of the desired DMC ancestor.
     * @return Returns the ancestor if found, null otherwise.
     */
    @ThreadSafe
    @SuppressWarnings("unchecked")
    public static <V extends IDMContext> V getAncestorOfType(IDMContext ctx, Class<V> ancestorType) {
    	if(ctx == null)
    		return null;
    	// Check the first context here for efficiency
        if (ancestorType.isAssignableFrom(ctx.getClass())) {
            return (V)ctx;
        }
        
        // Use a LinkedHashSet to avoid duplicates and preserver insertion-order
    	Set<IDMContext> nodes = new LinkedHashSet<IDMContext>();
        nodes.addAll(Arrays.asList(ctx.getParents()));
        while (nodes.isEmpty() == false) {
        	Set<IDMContext> parents = nodes;
        	nodes = new LinkedHashSet<IDMContext>();
        	for (IDMContext parent : parents) {
                if (ancestorType.isAssignableFrom(parent.getClass())) {
                	return (V)parent;
                }

                nodes.addAll(Arrays.asList(parent.getParents()));
        	}
        }
        
        return null;
    }

    /**
     * Finds the top most ancestor of the specified type.  
     * It assumes only one immediate parent of the give type exists. 
     * The search is done until there is no more immediate parents of the given type.
     * The method returns the last one found.    
     * 
     * @param <V>
     * @param ctx DMC to search.
     * @param ancestorType Class type of the desired DMC ancestor.
     * @return Returns the ancestor if found, null otherwise.
     * @since 2.2
     */
    @ThreadSafe
    @SuppressWarnings("unchecked")
    public static <V extends IDMContext> V getTopMostAncestorOfType(IDMContext ctx, Class<V> ancestorType) {
    	if(ctx == null)
    		return null;
    	
    	V topMostAncestor = null;
        boolean hasAncestorOfType = false;
        IDMContext current = ctx;
        do {
        	hasAncestorOfType = false;
        	IDMContext[] parents = current.getParents();
        	for( IDMContext parent : parents) {
                if (ancestorType.isAssignableFrom(parent.getClass())) {
                	hasAncestorOfType = true;
                	topMostAncestor = (V) parent;
                	current = parent;
                }
        	}
        	
        } while( hasAncestorOfType);
        return topMostAncestor;
    }

    /**
     * Finds the immediate parent of the specified type if exists.  
     * 
     * @param ctx DMC to search.
     * @param ancestorType Class type of the desired DMC ancestor.
     * @return Returns the ancestor if found, null otherwise.
     * @since 2.2
     */
    @ThreadSafe
    @SuppressWarnings("unchecked")
    public static <V extends IDMContext> V getParentOfType(IDMContext ctx, Class<V> ancestorType) {
    	if(ctx == null)
    		return null;

    	for( IDMContext parent : ctx.getParents())
            if (ancestorType.isAssignableFrom(parent.getClass())) 
            	return (V)parent;

    	return null;
    }
    
    /**
     * Finds all data model contexts of given type among ancestors of the 
     * specified context.  Ancestors are returned in order of closest to farthest,
     * in terms of depth.
     * @param ctx DMC to search.
     * @param ancestorType Class type of the desired DMC ancestor.
     * @return Returns all ancestors found, null if none.
     * @since 1.1
     */
    @ThreadSafe
    @SuppressWarnings("unchecked")
    public static <V extends IDMContext> V[] getAllAncestorsOfType(IDMContext ctx, Class<V> ancestorType) {
    	if(ctx == null)
    		return null;
    	
        // Use a LinkedHashSet to avoid duplicates and preserver insertion-order
    	Set<V> requestedAncestors = new LinkedHashSet<V>();
    	Set<IDMContext> nodes = new LinkedHashSet<IDMContext>();
        nodes.add(ctx);
        while (nodes.isEmpty() == false) {
        	Set<IDMContext> parents = nodes;
        	nodes = new LinkedHashSet<IDMContext>();
        	for (IDMContext parent : parents) {
                if (ancestorType.isAssignableFrom(parent.getClass())) {
                	requestedAncestors.add((V)parent);
                }

                nodes.addAll(Arrays.asList(parent.getParents()));
        	}
        }
        
        if (requestedAncestors.isEmpty()) return null;
        else {
        	V[] v = (V[])Array.newInstance(ancestorType, 0);
        	return requestedAncestors.toArray(v);
        }
    }
    
    /**
     * Checks all ancestors for a given context to see if the given 
     * potentialAncestor is in fact an ancestor.
     * @param dmc DM Contexts who's ancestors to check.
     * @param potentialAncestor Ancestor context to look for.
     * @return true if a match is found.
     */
    @ThreadSafe
    public static boolean isAncestorOf(IDMContext dmc, IDMContext potentialAncestor) {
        // Check the direct parents for a match.
        for (IDMContext parentDmc : dmc.getParents()) {
            if (potentialAncestor.equals(parentDmc)) {
                return true;
            }
        }

        // Recursively check the parents' parents for a match.
        for (IDMContext parentDmc : dmc.getParents()) {
            if (isAncestorOf(parentDmc, potentialAncestor)) {
                return true;
            }
        }
        
        // No match.
        return false;
    }

    /**
     * Traverses all the parents of a context and converts the whole
     * into a list.
     */
    @ThreadSafe
    public static List<IDMContext> toList(IDMContext dmc) {
        /*
         * This method is implemented recursively, which is not necessarily
         * the most efficient way to do this.
         */
        List<IDMContext> list = new ArrayList<IDMContext>();
        list.add(dmc);

        for (IDMContext parentDmc : dmc.getParents()) {
            list.addAll(toList(parentDmc));
        }
        return list;
    }
}
