/**
 * 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.idl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.objectweb.fractal.adl.ADLException;
import org.objectweb.fractal.adl.CompilerError;
import org.objectweb.fractal.adl.Definition;
import org.objectweb.fractal.adl.Loader;
import org.objectweb.fractal.adl.Node;
import org.objectweb.fractal.adl.error.GenericErrors;
import org.objectweb.fractal.adl.types.TypeInterface;
import org.ow2.mind.NodeContainerDecoration;
import org.ow2.mind.adl.ast.DefinitionReference;
import org.ow2.mind.idl.IDLLoader;
import org.ow2.mind.idl.ast.IDL;
import org.ow2.mind.idl.ast.InterfaceDefinition;

public class InterfaceDefinitionDecorationHelper {

  public static final String RESOLVED_INTERFACE_DEFINITION_DECORATION = "resolved-interface-definition";

  /**
   * Sets the resolved {@link InterfaceDefinition} corresponding to the
   * signature of the given {@link TypeInterface}. The decoration that is
   * actually attached to the given <code>itf</code> node is an instance of
   * {@link InterfaceDefinitionDecoration}. This imply that, if the
   * <code>itf</code> is serialized, only the
   * {@link InterfaceDefinition#getName() name} of the interface definition is
   * serialized (and not the interface definition AST itself).
   * 
   * @param itf a typed interface.
   * @param resolvedItfDef the interface definition corresponding to the
   *          signature of given interface.
   * @see #getResolvedInterfaceDefinition(DefinitionReference, Loader, Map)
   */
  public static void setResolvedInterfaceDefinition(final TypeInterface itf,
      final InterfaceDefinition resolvedItfDef) {
    itf.astSetDecoration(RESOLVED_INTERFACE_DEFINITION_DECORATION,
        new InterfaceDefinitionDecoration(resolvedItfDef));
  }

  /**
   * Retrieve the {@link InterfaceDefinition} corresponding to the signature of
   * the given {@link TypeInterface}. Returns <code>null</code>, if the given
   * <code>itf</code> node has no
   * {@link #RESOLVED_INTERFACE_DEFINITION_DECORATION} decoration. If the
   * decoration attached to the given <code>itf</code> node contains only the
   * interface definition name and not the definition AST (i.e. the
   * <code>itf</code> node has been de-serialized), the given
   * <code>loaderItf</code> is used to load the corresponding interface
   * definition.
   * 
   * @param itf a typed interface.
   * @param loaderItf a {@link IDLLoader} interface that is used if only the
   *          name of the interface definition is attached (and not the
   *          interface definition AST itself). May be <code>null</code>.
   * @param context additional parameters. Used only if only the name of the
   *          interface definition is attached (and not the interface definition
   *          AST itself). May be <code>null</code>.
   * @return the {@link InterfaceDefinition} corresponding to the signature of
   *         the given {@link TypeInterface} or <code>null</code>, if the given
   *         <code>itf</code> node has no
   *         {@link #RESOLVED_INTERFACE_DEFINITION_DECORATION} decoration or if
   *         only the name of the interface definition is attached and
   *         <code>loaderItf</code> is <code>null</code>.
   * @throws ADLException if an error occurs while
   *           {@link IDLLoader#load(String, Map) loading} the interface
   *           definition using the given <code>loaderItf</code>.
   */
  public static InterfaceDefinition getResolvedInterfaceDefinition(
      final TypeInterface itf, final IDLLoader loaderItf,
      final Map<Object, Object> context) throws ADLException {
    final InterfaceDefinitionDecoration definitionDecoration = (InterfaceDefinitionDecoration) itf
        .astGetDecoration(RESOLVED_INTERFACE_DEFINITION_DECORATION);
    return getDefinition(definitionDecoration, loaderItf, context);
  }

  public static final String USED_INTERFACE_DEFINITION_DECORATION = "used-interface-definition";

  public static void addUsedInterfaceDefinition(final Definition definition,
      final InterfaceDefinition usedInterfaceDefinition) {
    InterfaceDefinitionDecorationMap decoration = getUsedInterfaceDefinitionDecoration(definition);
    if (decoration == null) {
      decoration = new InterfaceDefinitionDecorationMap();
      definition.astSetDecoration(USED_INTERFACE_DEFINITION_DECORATION,
          decoration);
    }
    final String name = usedInterfaceDefinition.getName();
    if (!decoration.containsKey(name)) {
      decoration.put(name, new InterfaceDefinitionDecoration(
          usedInterfaceDefinition));
    }
  }

  public static Set<InterfaceDefinition> getUsedInterfaceDefinitions(
      final Definition definition, final IDLLoader loaderItf,
      final Map<Object, Object> context) throws ADLException {
    final InterfaceDefinitionDecorationMap decoration = getUsedInterfaceDefinitionDecoration(definition);
    final HashSet<InterfaceDefinition> result = new HashSet<InterfaceDefinition>();
    if (decoration != null) {
      for (final InterfaceDefinitionDecoration itfDefDeco : decoration.values()) {
        result.add(getDefinition(itfDefDeco, loaderItf, context));
      }
    }
    return result;
  }

  private static InterfaceDefinitionDecorationMap getUsedInterfaceDefinitionDecoration(
      final Definition definition) {
    return (InterfaceDefinitionDecorationMap) definition
        .astGetDecoration(USED_INTERFACE_DEFINITION_DECORATION);
  }

  protected static InterfaceDefinition getDefinition(
      final InterfaceDefinitionDecoration definitionDecoration,
      final IDLLoader loaderItf, final Map<Object, Object> context)
      throws ADLException {
    if (definitionDecoration == null) {
      return null;
    } else {
      InterfaceDefinition definition = definitionDecoration
          .getInterfaceDefinition();
      if (definition == null && loaderItf != null) {
        final IDL idl = loaderItf.load(definitionDecoration.getInterfaceName(),
            context);
        if (!(idl instanceof InterfaceDefinition)) {
          throw new CompilerError(GenericErrors.INTERNAL_ERROR,
              "Referenced IDL is not an interface definition");
        }
        definition = (InterfaceDefinition) idl;
        definitionDecoration.setDefinition(definition);
      }
      return definition;
    }
  }

  public static final class InterfaceDefinitionDecoration
      implements
        NodeContainerDecoration,
        Serializable {

    private transient InterfaceDefinition itfDefinition;
    private final String                  itfName;

    /**
     * Default constructor.
     * 
     * @param definition the attached definition.
     */
    public InterfaceDefinitionDecoration(final InterfaceDefinition definition) {
      if (definition == null)
        throw new IllegalArgumentException("definition can't be null");
      if (definition.getName() == null)
        throw new IllegalArgumentException("definition's name can't be null");

      this.itfDefinition = definition;
      itfName = definition.getName();
    }

    /**
     * @return the attached interface definition or <code>null</code> if this
     *         object do not contains an interface definition.
     */
    public InterfaceDefinition getInterfaceDefinition() {
      return itfDefinition;
    }

    /**
     * Sets the interface definition AST corresponding to this decoration.
     * 
     * @param itfDefinition the interface definition AST corresponding to this
     *          decoration.
     * @throws IllegalArgumentException if the name of the given definition do
     *           not match the name contained by this decoration.
     */
    public void setDefinition(final InterfaceDefinition itfDefinition) {
      if (itfDefinition == null)
        throw new IllegalArgumentException("definition can't be null");
      if (itfDefinition.getName() == null)
        throw new IllegalArgumentException("definition's name can't be null");
      if (!itfDefinition.getName().equals(itfName))
        throw new IllegalArgumentException("Wrong definition name \"" + itfName
            + "\" expected instead of \"" + itfDefinition.getName() + "\".");

      this.itfDefinition = itfDefinition;
    }

    /**
     * @return the name of the interface definition attached with this
     *         decoration.
     */
    public String getInterfaceName() {
      return itfName;
    }

    // -------------------------------------------------------------------------
    // Implementation of the NodeContainerDecoration interface
    // -------------------------------------------------------------------------

    public Collection<Node> getNodes() {
      if (itfDefinition == null) {
        return Collections.emptyList();
      } else {
        final List<Node> l = new ArrayList<Node>();
        l.add(itfDefinition);
        return l;
      }
    }

    public void replaceNodes(final IdentityHashMap<Node, Node> replacements) {
      if (replacements.containsKey(itfDefinition)) {
        itfDefinition = (InterfaceDefinition) replacements.get(itfDefinition);
      }
    }
  }

  // -------------------------------------------------------------------------
  // Implementation of the NodeContainerDecoration interface
  // -------------------------------------------------------------------------

  public static final class InterfaceDefinitionDecorationMap
      extends
        HashMap<String, InterfaceDefinitionDecoration>
      implements
        NodeContainerDecoration {

    public Collection<Node> getNodes() {
      final Collection<Node> nodes = new ArrayList<Node>(size());
      for (final InterfaceDefinitionDecoration deco : values()) {
        if (deco.itfDefinition != null) nodes.add(deco.itfDefinition);
      }
      return nodes;
    }

    public void replaceNodes(final IdentityHashMap<Node, Node> replacements) {
      for (final Node replacement : replacements.values()) {
        final InterfaceDefinition itfDef = (InterfaceDefinition) replacement;
        final InterfaceDefinitionDecoration deco = get(itfDef.getName());
        deco.itfDefinition = itfDef;
      }
    }
  }

}
