/*--------------------------------------------------------------*
  Copyright (C) 2006-2015 OpenSim Ltd.

  This file is distributed WITHOUT ANY WARRANTY. See the file
  'License' for details on this and other legal matters.
*--------------------------------------------------------------*/

package org.omnetpp.ned.core;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import org.eclipse.core.resources.IProject;
import org.omnetpp.ned.model.ex.CompoundModuleElementEx;
import org.omnetpp.ned.model.ex.SubmoduleElementEx;
import org.omnetpp.ned.model.interfaces.INedTypeInfo;
import org.omnetpp.ned.model.interfaces.INedTypeLookupContext;
import org.omnetpp.ned.model.interfaces.INedTypeResolver;
import org.omnetpp.ned.model.interfaces.ISubmoduleOrConnection;


/**
 * Traverses NED module type usage hierarchy.
 * @author Andras
 */
public class NedTreeTraversal {
    private INedTypeResolver resolver;
    private IModuleTreeVisitor visitor;
    private IProject contextProject;  // for "like" type lookups
    private Stack<INedTypeInfo> visitedTypes = new Stack<INedTypeInfo>(); // cycle detection

    /**
     * Constructor
     */
    public NedTreeTraversal(INedTypeResolver resolver, IModuleTreeVisitor visitor, IProject contextProject) {
        this.resolver = resolver;
        this.visitor = visitor;
        this.contextProject = contextProject;
    }

    /**
     * Traverse the module usage hierarchy, and call methods for the visitor.
     */
    public void traverse(String fullyQualifiedModuleTypeName) {
        INedTypeInfo moduleType = resolver.getToplevelOrInnerNedType(fullyQualifiedModuleTypeName, contextProject);
        if (moduleType==null)
            visitor.unresolvedType(null, fullyQualifiedModuleTypeName);
        else
            traverse(moduleType);
    }

    /**
     * Traverse the module usage hierarchy, and call methods for the visitor.
     */
    public void traverse(INedTypeInfo moduleType) {
        visitedTypes.clear();
        doTraverse(null, moduleType);
    }

    /**
     * Traverse the module usage hierarchy, and call methods for the visitor.
     */
    public void traverse(ISubmoduleOrConnection element) {
        visitedTypes.clear();
        INedTypeInfo elementType = resolveEffectiveType(element, element.getEnclosingLookupContext());
        if (elementType != null)
            doTraverse(element, elementType);
    }

    protected void doTraverse(ISubmoduleOrConnection element, INedTypeInfo effectiveTypeInfo) {
        // enter module
        visitedTypes.push(effectiveTypeInfo);
        boolean recurse = visitor.enter(element, effectiveTypeInfo);

        // traverse submodules
        if (recurse && effectiveTypeInfo.getNedElement() instanceof CompoundModuleElementEx) {
            CompoundModuleElementEx compoundModule = (CompoundModuleElementEx)effectiveTypeInfo.getNedElement();

            List<SubmoduleElementEx> submodules = compoundModule.getSubmodules();
            ArrayList<ISubmoduleOrConnection> elements = new ArrayList<ISubmoduleOrConnection>();
            elements.addAll(submodules);
            elements.addAll(compoundModule.getSrcConnections());

            for (SubmoduleElementEx submodule : submodules)
                elements.addAll(compoundModule.getSrcConnectionsFor(submodule.getName()));

            for (ISubmoduleOrConnection childElement : elements) {
                // dig out type info (NED declaration)
                INedTypeInfo typeInfo = resolveEffectiveType(childElement, compoundModule);

                // recursive call
                if (typeInfo != null) {
                    if (visitedTypes.contains(typeInfo)) // cycle detection
                        visitor.recursiveType(childElement, typeInfo);
                    else
                        doTraverse(childElement, typeInfo);
                }
            }
        }

        // leave module
        visitor.leave();
        visitedTypes.pop();
    }

    /**
     * Return the actual type of a submodule or connection. If it is a concrete type,
     * it is looked up in the compound module as context (using imports, inner types, etc).
     * If it is a parametric type ("<param> like IInterface"), we obtain the value for
     * the parameter using visitor.resolveLikeType(), and choose a module with that
     * name from the ones that implement the given interface.
     *
     * The return value may be null ("could not be resolved"); in that case this method
     * already calls visitor.unresolvedType().
     */
    protected INedTypeInfo resolveEffectiveType(ISubmoduleOrConnection element, INedTypeLookupContext compoundModule) {
        INedTypeInfo result;
        if (!element.isParametricType()) {
            result = element.getNedTypeInfo();  // note: this resolves connections with getType()==null as well (to ned.IdealChannel)
            if (result == null)
                visitor.unresolvedType(element, element.getType());
        }
        else
        {
            // resolve "like" type
            INedTypeInfo interfaceType = resolver.lookupNedType(element.getLikeType(), element.getEnclosingLookupContext());
            if (interfaceType == null) {
                visitor.unresolvedType(element, element.getLikeType()); // undefined interface
                result = null;
            }
            else {
                String effectiveTypeName = visitor.resolveLikeType(element);  // value of the "like" parameter
                if (effectiveTypeName == null) {
                    result = interfaceType; // no actual type given, return the interface
                }
                else {
                    // effectiveTypeName is likely unqualified -- look it up according to the "like" type name resolution rules
                    result = resolver.lookupLikeType(effectiveTypeName, interfaceType, contextProject); // actual type
                    if (result == null)
                        visitor.unresolvedType(element, effectiveTypeName); // no such type
                }
            }
        }
        return result;
    }

}
