/*
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You 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 org.apache.flex.compiler.internal.definitions.references;

import static org.apache.flex.abc.ABCConstants.CONSTANT_Multiname;
import static org.apache.flex.abc.ABCConstants.CONSTANT_Qname;

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

import org.apache.flex.abc.semantics.Name;
import org.apache.flex.abc.semantics.Namespace;
import org.apache.flex.abc.semantics.Nsset;
import org.apache.flex.compiler.common.DependencyType;
import org.apache.flex.compiler.definitions.IDefinition;
import org.apache.flex.compiler.definitions.INamespaceDefinition;
import org.apache.flex.compiler.definitions.references.IReference;
import org.apache.flex.compiler.definitions.references.IResolvedQualifiersReference;
import org.apache.flex.compiler.internal.definitions.AmbiguousDefinition;
import org.apache.flex.compiler.internal.definitions.NamespaceDefinition;
import org.apache.flex.compiler.internal.projects.CompilerProject;
import org.apache.flex.compiler.internal.scopes.ASScope;
import org.apache.flex.compiler.projects.ICompilerProject;
import org.apache.flex.compiler.units.ICompilationUnit;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;

/**
 * Implementation of {@link IReference} representing a reference where all the
 * qualifiers have been resolved to namespace definitions.
 */
public class ResolvedQualifiersReference implements IResolvedQualifiersReference
{
    /**
     * Constructor.
     */
    public ResolvedQualifiersReference(ImmutableSet<INamespaceDefinition> qualifiers, String baseName)
    {
        assert baseName != null;
        this.qualifiers = qualifiers;
        this.baseName = baseName;
    }

    private final ImmutableSet<INamespaceDefinition> qualifiers;

    private final String baseName;

    @Override
    public String getName()
    {
        return baseName;
    }

    @Override
    public IDefinition resolve(ICompilerProject project, ASScope scope,
                               DependencyType dependencyType,
                               boolean canEscapeWith)
    {
        if (qualifiers.size() == 1)
        {
            INamespaceDefinition qualifier = Iterables.getOnlyElement(qualifiers);
            return scope.findPropertyQualified(project, qualifier, getName(), dependencyType, canEscapeWith);
        }

        return ((CompilerProject)project).getCacheForScope(scope).findPropertyMultiname(this, dependencyType);
    }

    @Override
    public String getDisplayString()
    {
        // Look through the multiname set for a package name.
        // Although most names in SWCs seem to have only
        // one namespace in their namespace set, interfaces seem
        // to have true multinames with multiple namespaces.
        // For code mode purposes, just look for the package namespace.
        String packageName = null;
        for (INamespaceDefinition namespace : qualifiers)
        {
            if (namespace.isPublicOrInternalNamespace())
            {
                packageName = namespace.getURI();
                if (packageName.length() > 0)
                    break;
            }
        }

        return packageName != null && packageName.length() > 0 ?
                packageName + '.' + baseName :
                baseName;
    }

    @Override
    public Name getMName(ICompilerProject project, ASScope scope)
    {
        return getMName();
    }

    private IDefinition resolveAmbiguities(ICompilerProject project, List<IDefinition> defs)
    {
        switch (defs.size())
        {
            case 0:
            {
                return null;
            }
            case 1:
            {
                assert Iterables.getOnlyElement(defs).isInProject(project);
                return Iterables.getOnlyElement(defs);
            }
            default:
            {
                IDefinition d = AmbiguousDefinition.resolveAmbiguities(project, defs);
                if (d == null)
                    return AmbiguousDefinition.get();
                return d;
            }
        }
    }

    @Override
    public IDefinition resolve(ICompilerProject project, ICompilationUnit referencingCompilationUnit, DependencyType dt)
    {
        CompilerProject compilerProject = (CompilerProject)project;
        ArrayList<IDefinition> defs = new ArrayList<IDefinition>(2);
        compilerProject.getScope().findDefinitionByName(referencingCompilationUnit, defs, baseName, qualifiers, dt);
        return resolveAmbiguities(project, defs);
    }

    @Override
    public IDefinition resolve(ICompilerProject project)
    {
        CompilerProject compilerProject = (CompilerProject)project;
        ArrayList<IDefinition> defs = new ArrayList<IDefinition>(2);
        compilerProject.getScope().findDefinitionByName(defs, baseName, qualifiers);
        return resolveAmbiguities(project, defs);
    }

    @Override
    public Name getMName()
    {
        ArrayList<Namespace> ns_set = new ArrayList<Namespace>(qualifiers.size());
        for (INamespaceDefinition namespace : qualifiers)
            ns_set.add(((NamespaceDefinition)namespace).getAETNamespace());
        int nameType = ns_set.size() == 1 ? CONSTANT_Qname : CONSTANT_Multiname;
        return new Name(nameType, new Nsset(ns_set), baseName);
    }

    @Override
    public ImmutableSet<INamespaceDefinition> getQualifiers()
    {
        return qualifiers;
    }

    @Override
    public boolean matches(IResolvedQualifiersReference toMatch)
    {
        if (!baseName.equals(toMatch.getName()))
            return false;
        for (INamespaceDefinition qualifierToMatch : toMatch.getQualifiers())
        {
            if (qualifiers.contains(qualifierToMatch))
                return true;
        }
        return false;
    }
}
