/**
 * Copyright (C) 2009 STMicroelectronics
 *
 * This file is part of "Mind Compiler" is free software: you can redistribute 
 * it and/or modify it under the terms of the GNU Lesser General Public License 
 * as published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contact: mind@ow2.org
 *
 * Authors: Matthieu Leclercq
 * Contributors: 
 */

package org.ow2.mind.adl.generic;

import static org.objectweb.fractal.adl.NodeUtil.castNodeError;
import static org.objectweb.fractal.adl.types.TypeInterfaceUtil.isClient;
import static org.objectweb.fractal.adl.types.TypeInterfaceUtil.isServer;
import static org.ow2.mind.adl.ast.ASTHelper.isAbstract;
import static org.ow2.mind.adl.ast.ASTHelper.isType;
import static org.ow2.mind.adl.ast.ASTHelper.isUnresolvedDefinitionNode;
import static org.ow2.mind.adl.generic.ast.GenericASTHelper.setUsedTypeParameter;

import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;

import org.objectweb.fractal.adl.ADLException;
import org.objectweb.fractal.adl.CompilerError;
import org.objectweb.fractal.adl.ContextLocal;
import org.objectweb.fractal.adl.Definition;
import org.objectweb.fractal.adl.Node;
import org.objectweb.fractal.adl.NodeFactory;
import org.objectweb.fractal.adl.NodeUtil;
import org.objectweb.fractal.adl.error.GenericErrors;
import org.objectweb.fractal.adl.error.NodeErrorLocator;
import org.objectweb.fractal.adl.interfaces.Interface;
import org.objectweb.fractal.adl.interfaces.InterfaceContainer;
import org.objectweb.fractal.adl.types.TypeInterfaceUtil;
import org.ow2.mind.adl.ADLErrors;
import org.ow2.mind.adl.DefinitionReferenceResolver;
import org.ow2.mind.adl.DefinitionReferenceResolver.AbstractDelegatingDefinitionReferenceResolver;
import org.ow2.mind.adl.ast.ASTHelper;
import org.ow2.mind.adl.ast.DefinitionReference;
import org.ow2.mind.adl.ast.MindInterface;
import org.ow2.mind.adl.binding.BindingChecker;
import org.ow2.mind.adl.generic.ast.FormalTypeParameter;
import org.ow2.mind.adl.generic.ast.FormalTypeParameterContainer;
import org.ow2.mind.adl.generic.ast.TypeArgument;
import org.ow2.mind.adl.generic.ast.TypeArgumentContainer;
import org.ow2.mind.error.ErrorManager;

import com.google.inject.Inject;

/**
 * Delegating {@link DefinitionReferenceResolver} component that instantiates
 * generic definitions if the given {@link DefinitionReference} contains
 * {@link TypeArgument}.<br>
 * This component checks that type arguments of the definition reference match
 * correctly formal type parameters of the referenced definition and then
 * instantiates it.
 * 
 * @see TemplateInstantiator
 * @see TemplateInstantiatorImpl
 */
public class GenericDefinitionReferenceResolver
    extends
      AbstractDelegatingDefinitionReferenceResolver {

  // define a class to alias this too long generic type
  protected static final class FormalTypeParameterCache
      extends
        ContextLocal<Map<Definition, Map<String, FormalTypeParameter>>> {
  }

  protected final FormalTypeParameterCache contextualTypeParameters = new FormalTypeParameterCache();

  @Inject
  protected ErrorManager                   errorManagerItf;

  @Inject
  protected NodeFactory                    nodeFactoryItf;

  @Inject
  protected DefinitionReferenceResolver    recursiveResolverItf;

  @Inject
  protected BindingChecker                 bindingCheckerItf;

  @Inject
  protected TemplateInstantiator           templateInstantiatorItf;

  // ---------------------------------------------------------------------------
  // Implementation of the DefinitionReferenceResolver interface
  // ---------------------------------------------------------------------------

  public Definition resolve(final DefinitionReference reference,
      final Definition encapsulatingDefinition,
      final Map<Object, Object> context) throws ADLException {
    return resolve(reference, encapsulatingDefinition,
        getTypeParameters(encapsulatingDefinition, context), context);
  }

  // ---------------------------------------------------------------------------
  // Utility methods
  // ---------------------------------------------------------------------------

  protected Definition resolve(final DefinitionReference reference,
      final Definition encapsulatingDefinition,
      final Map<String, FormalTypeParameter> topLevelTypeParameters,
      final Map<Object, Object> context) throws ADLException {

    // delegate resolution of definition reference to retrieve the referenced
    // definition.
    Definition d = clientResolverItf.resolve(reference,
        encapsulatingDefinition, context);

    // Instantiate referenced definition (if it is actually a generic
    // definition).
    final TypeArgument[] typeArguments = (reference instanceof TypeArgumentContainer)
        ? ((TypeArgumentContainer) reference).getTypeArguments()
        : null;
    final FormalTypeParameter[] formalTypeParameters = (d instanceof FormalTypeParameterContainer)
        ? ((FormalTypeParameterContainer) d).getFormalTypeParameters()
        : null;

    if (formalTypeParameters == null || formalTypeParameters.length == 0) {
      if (typeArguments != null && typeArguments.length > 0) {
        if (!ASTHelper.isUnresolvedDefinitionNode(d)) {
          errorManagerItf.logError(
              ADLErrors.INVALID_REFERENCE_NO_TEMPLATE_VARIABLE, reference,
              d.getName());
          return ASTHelper.newUnresolvedDefinitionNode(nodeFactoryItf,
              reference.getName());
        } else {
          return d;
        }
      }
    } else {
      // referenced definition has type parameter

      // First, map type arguments to formal type parameters.
      // typeArgumentMap associates formal type parameter names to type
      // arguments;
      final Map<String, TypeArgument> typeArgumentMap = mapTypeArguments(
          reference, formalTypeParameters, typeArguments);

      // If mapTypeArguments returns null, the type argument list contains error
      // returns an unresolved definition node.
      if (typeArgumentMap == null) {
        return ASTHelper.newUnresolvedDefinitionNode(nodeFactoryItf,
            reference.getName());
      }

      boolean containErrors = false;

      // typeArgumentValues associates formal type parameter names to type
      // argument values (either a TypeArgument, or a referenced
      // FormalTypeParameter).
      final Map<String, Object> typeArgumentValues = new HashMap<String, Object>();

      // Second, check type argument values
      for (final FormalTypeParameter formalTypeParameter : formalTypeParameters) {
        final TypeArgument typeArgument = typeArgumentMap
            .get(formalTypeParameter.getName());
        assert typeArgument != null;
        final Definition formalTypeParamterType;
        if (formalTypeParameter.getDefinitionReference() != null) {
          formalTypeParamterType = recursiveResolverItf.resolve(
              formalTypeParameter.getDefinitionReference(), d, context);
          assert formalTypeParamterType != null;
        } else {
          // If the formal type parameter do not define a type to which it must
          // conform, do not check type compatibility. This can happen for
          // special definitions like "Factory".
          formalTypeParamterType = null;
        }

        // determine the definition of the type argument
        final Definition typeArgumentDefinition;
        if (typeArgument.getTypeParameterReference() != null) {
          // The type argument references a formal type parameter
          final String ref = typeArgument.getTypeParameterReference();

          final FormalTypeParameter referencedParameter = topLevelTypeParameters
              .get(ref);
          if (referencedParameter == null) {
            errorManagerItf.logError(ADLErrors.UNDEFINED_TEMPALTE_VARIABLE,
                typeArgument, ref);
            containErrors = true;
            continue;
          }

          setUsedTypeParameter(referencedParameter);
          typeArgumentDefinition = recursiveResolverItf.resolve(
              referencedParameter.getDefinitionReference(),
              encapsulatingDefinition, context);
          assert typeArgumentDefinition != null;

          typeArgumentValues.put(formalTypeParameter.getName(),
              referencedParameter);
        } else {
          typeArgumentValues.put(formalTypeParameter.getName(), typeArgument);
          final DefinitionReference typeArgumentDefRef = typeArgument
              .getDefinitionReference();
          if (typeArgumentDefRef != null) {
            // The type argument references a definition.
            typeArgumentDefinition = recursiveResolverItf.resolve(
                typeArgumentDefRef, encapsulatingDefinition, context);

            if (!isUnresolvedDefinitionNode(typeArgumentDefinition)
                && (isType(typeArgumentDefinition) || isAbstract(typeArgumentDefinition))
                && encapsulatingDefinition != null) {
              errorManagerItf.logError(
                  ADLErrors.INVALID_TEMPLATE_VALUE_TYPE_DEFINITON,
                  typeArgument, typeArgumentDefRef.getName());
              containErrors = true;
            }

          } else {
            // The value of the TypeArgument is ANY.
            typeArgumentDefinition = null;
          }
        }

        // check compatibility of typeArgumentDefinition against type of formal
        // type parameter (only if typeArgumentDefinition is valid)
        if (typeArgumentDefinition != null
            && isUnresolvedDefinitionNode(typeArgumentDefinition)) {
          containErrors = true;
        } else if (typeArgumentDefinition != null
            && formalTypeParamterType != null) {
          checkTypeCompatibility(formalTypeParamterType,
              typeArgumentDefinition, typeArgument);
        }
      }

      if (containErrors) {
        // the definition reference contains errors, simply return a clone of
        // the template without instantiating it
        d = NodeUtil.cloneGraph(d);
      } else {
        // instantiate template
        d = templateInstantiatorItf.instantiateTemplate(d, typeArgumentValues,
            context);
      }
      // update referenced name
      reference.setName(d.getName());

    }
    return d;
  }

  protected Definition getTypeArgumentDefinition(
      final Definition encapsulatingDefinition,
      final TypeArgument typeArgument, final Map<Object, Object> context)
      throws ADLException {
    final DefinitionReference typeArgumentDefRef = typeArgument
        .getDefinitionReference();
    if (typeArgumentDefRef != null) {
      // The type argument references a definition.
      return recursiveResolverItf.resolve(typeArgumentDefRef,
          encapsulatingDefinition, context);

    } else {
      // The value of the TypeArgument is ANY.
      return null;
    }
  }

  protected Map<String, TypeArgument> mapTypeArguments(
      final DefinitionReference reference,
      final FormalTypeParameter[] formalTypeParameters,
      final TypeArgument[] typeArguments) throws ADLException {
    if (typeArguments == null || typeArguments.length == 0) {
      if (formalTypeParameters.length > 0) {
        errorManagerItf.logError(
            ADLErrors.INVALID_REFERENCE_MISSING_TEMPLATE_VALUE, reference);
      }
      return null;
    }

    if (typeArguments[0].getTypeParameterName() == null) {
      // template values are specified by ordinal position.

      if (formalTypeParameters.length > typeArguments.length) {
        // missing template values
        errorManagerItf.logError(
            ADLErrors.INVALID_REFERENCE_MISSING_TEMPLATE_VALUE, reference);
        return null;
      }

      if (formalTypeParameters.length < typeArguments.length) {
        errorManagerItf.logError(
            ADLErrors.INVALID_REFERENCE_TOO_MANY_TEMPLATE_VALUE, reference);
        return null;
      }

      final Map<String, TypeArgument> result = new HashMap<String, TypeArgument>(
          formalTypeParameters.length);
      for (int i = 0; i < formalTypeParameters.length; i++) {
        final TypeArgument typeArgument = typeArguments[i];
        // sanity check.
        if (typeArgument.getTypeParameterName() != null) {
          throw new CompilerError(GenericErrors.INTERNAL_ERROR,
              new NodeErrorLocator(typeArgument),
              "Cannot mix ordinal and name-based template values.");
        }

        final String varName = formalTypeParameters[i].getName();
        typeArgument.setTypeParameterName(varName);
        result.put(varName, typeArgument);
      }

      return result;

    } else {
      // template values are specified by name

      final Map<String, TypeArgument> valuesByName = new HashMap<String, TypeArgument>(
          typeArguments.length);
      for (final TypeArgument value : typeArguments) {
        // sanity check.
        if (value.getTypeParameterName() == null) {
          throw new CompilerError(GenericErrors.INTERNAL_ERROR,
              new NodeErrorLocator(value),
              "Cannot mix ordinal and name-based template values.");
        }

        valuesByName.put(value.getTypeParameterName(), value);
      }

      final Map<String, TypeArgument> result = new HashMap<String, TypeArgument>();
      for (final FormalTypeParameter tmpl : formalTypeParameters) {
        final TypeArgument value = valuesByName.remove(tmpl.getName());
        if (value == null) {
          // missing template values
          errorManagerItf.logError(
              ADLErrors.INVALID_REFERENCE_MISSING_TEMPLATE_VALUE, reference,
              tmpl.getName());
          return null;
        }
        result.put(tmpl.getName(), value);
      }
      if (!valuesByName.isEmpty()) {
        // too many template values

        // get the first one
        final Map.Entry<String, TypeArgument> value = valuesByName.entrySet()
            .iterator().next();

        errorManagerItf.logError(
            ADLErrors.INVALID_REFERENCE_NO_SUCH_TEMPLATE_VARIABLE,
            value.getValue(), value.getKey());
        return null;
      }

      return result;
    }
  }

  protected void checkTypeCompatibility(final Definition templateType,
      final Definition typeValue, final Node locator) throws ADLException {

    // checks that every server interfaces of templateType is provided by
    // typeValue.
    final Map<String, MindInterface> valueServerInterfaces = new HashMap<String, MindInterface>();
    for (final Interface itf : castNodeError(typeValue,
        InterfaceContainer.class).getInterfaces()) {
      if (isServer(itf)) {
        valueServerInterfaces.put(itf.getName(),
            castNodeError(itf, MindInterface.class));
      }
    }

    for (final Interface itf : castNodeError(templateType,
        InterfaceContainer.class).getInterfaces()) {
      if (!isServer(itf)) continue;

      final MindInterface valueItf = valueServerInterfaces.get(itf.getName());
      if (valueItf == null) {
        errorManagerItf.logError(
            ADLErrors.INVALID_TEMPLATE_VALUE_MISSING_SERVER_INTERFACE, locator,
            typeValue.getName(), itf.getName());
        continue;
      }
      bindingCheckerItf.checkCompatibility(itf, valueItf, locator);
    }

    // checks that each client interface of typeValue is present in
    // templateType or is optional.
    final Map<String, MindInterface> templateClientInterfaces = new HashMap<String, MindInterface>();
    for (final Interface itf : castNodeError(templateType,
        InterfaceContainer.class).getInterfaces()) {
      if (isClient(itf)) {
        templateClientInterfaces.put(itf.getName(),
            castNodeError(itf, MindInterface.class));
      }
    }

    for (final Interface valueItf : castNodeError(typeValue,
        InterfaceContainer.class).getInterfaces()) {
      if (!isClient(valueItf)) continue;

      final MindInterface itf = templateClientInterfaces.remove(valueItf
          .getName());
      if (itf == null) {
        if (!TypeInterfaceUtil.isOptional(valueItf)) {
          errorManagerItf
              .logError(
                  ADLErrors.INVALID_TEMPLATE_VALUE_CLIENT_INTERFACE_MUST_BE_OPTIONAL,
                  locator, valueItf.getName(), new NodeErrorLocator(valueItf));
          continue;
        }
      } else {
        bindingCheckerItf.checkCompatibility(valueItf, itf, locator);
      }
    }
    if (!templateClientInterfaces.isEmpty()) {
      for (final MindInterface missingItf : templateClientInterfaces.values()) {
        errorManagerItf.logError(
            ADLErrors.INVALID_TEMPLATE_VALUE_MISSING_CLIENT_INTERFACE, locator,
            typeValue.getName(), missingItf.getName());
      }
    }
  }

  protected Map<String, FormalTypeParameter> getTypeParameters(
      final Definition d, final Map<Object, Object> context)
      throws ADLException {
    Map<Definition, Map<String, FormalTypeParameter>> typeParameters = contextualTypeParameters
        .get(context);
    if (typeParameters == null) {
      typeParameters = new IdentityHashMap<Definition, Map<String, FormalTypeParameter>>();
      contextualTypeParameters.set(context, typeParameters);
    }

    Map<String, FormalTypeParameter> result = typeParameters.get(d);

    if (result == null) {
      if (d instanceof FormalTypeParameterContainer) {
        final FormalTypeParameter[] formalTypeParameters = ((FormalTypeParameterContainer) d)
            .getFormalTypeParameters();
        if (formalTypeParameters.length > 0) {
          result = new HashMap<String, FormalTypeParameter>(
              formalTypeParameters.length);
          for (final FormalTypeParameter typeParameter : formalTypeParameters) {
            result.put(typeParameter.getName(), typeParameter);
          }
        }
      }
      typeParameters.put(d, result);
    }

    return result;
  }
}
