/**
 * Copyright (C) 2010 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.idl.ast;

import static org.ow2.mind.CommonASTHelper.newNode;
import static org.ow2.mind.PathHelper.isRelative;

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

import org.objectweb.fractal.adl.ADLException;
import org.objectweb.fractal.adl.Node;
import org.objectweb.fractal.adl.NodeFactory;
import org.ow2.mind.NodeContainerDecoration;
import org.ow2.mind.idl.IDLLoader;

public final class IDLASTHelper {
  private IDLASTHelper() {
  }

  // ---------------------------------------------------------------------------
  // InterfaceDefinition helper methods
  // ---------------------------------------------------------------------------

  /**
   * Create a new {@link InterfaceDefinition} node using the given
   * {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param name the name of the {@link InterfaceDefinition}.
   * @return a new {@link InterfaceDefinition} node.
   */
  public static InterfaceDefinition newInterfaceDefinitionNode(
      final NodeFactory nodeFactory, final String name) {
    final InterfaceDefinition node = newNode(nodeFactory, "itf",
        InterfaceDefinition.class, TypeCollectionContainer.class,
        IncludeContainer.class);
    setKindDecorations(node);
    node.setName(name);
    return node;
  }

  /**
   * Create a new {@link Method} node using the given {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param name the name of the {@link Method}.
   * @return a new {@link Method} node.
   */
  public static Method newMethodNode(final NodeFactory nodeFactory,
      final String name) {
    final Method node = newNode(nodeFactory, "method", Method.class);
    setKindDecorations(node);
    node.setName(name);
    return node;
  }

  /**
   * Create a new {@link Parameter} node using the given {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param name the name of the {@link Parameter}.
   * @return a new {@link Parameter} node.
   */
  public static Parameter newParameterNode(final NodeFactory nodeFactory,
      final String name) {
    final Parameter node = newNode(nodeFactory, "parameter", Parameter.class);
    setKindDecorations(node);
    node.setName(name);
    return node;
  }

  // ---------------------------------------------------------------------------
  // IDT helper methods
  // ---------------------------------------------------------------------------

  /**
   * Create a new {@link SharedTypeDefinition} node using the given
   * {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param name the name of the {@link SharedTypeDefinition}.
   * @return a new {@link SharedTypeDefinition} node.
   */
  public static SharedTypeDefinition newIDTNode(final NodeFactory nodeFactory,
      final String name) {
    final SharedTypeDefinition node = newNode(nodeFactory, "idt",
        SharedTypeDefinition.class, IncludeContainer.class);
    setKindDecorations(node);
    node.setName(name);
    return node;
  }

  // ---------------------------------------------------------------------------
  // Enum helper methods
  // ---------------------------------------------------------------------------

  /**
   * Create a new {@link EnumDefinition} node using the given
   * {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param name the name of the {@link EnumDefinition}.
   * @return a new {@link EnumDefinition} node.
   */
  public static EnumDefinition newEnumDefinitionNode(
      final NodeFactory nodeFactory, final String name) {
    final EnumDefinition node = newNode(nodeFactory, "type",
        EnumDefinition.class);
    setKindDecorations(node);
    node.setName(name);
    return node;
  }

  /**
   * Create a new {@link EnumReference} node using the given {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param name the name of the {@link EnumReference}.
   * @return a new {@link EnumReference} node.
   */
  public static EnumReference newEnumReferenceNode(
      final NodeFactory nodeFactory, final String name) {
    final EnumReference node = newNode(nodeFactory, "type", EnumReference.class);
    setKindDecorations(node);
    node.setName(name);
    return node;
  }

  /**
   * Create a new {@link EnumMember} node using the given {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param name the name of the {@link EnumMember}.
   * @return a new {@link EnumMember} node.
   */
  public static EnumMember newEnumMemberNode(final NodeFactory nodeFactory,
      final String name) {
    final EnumMember node = newNode(nodeFactory, "enumMember", EnumMember.class);
    setKindDecorations(node);
    node.setName(name);
    return node;
  }

  // ---------------------------------------------------------------------------
  // Struct/Union helper methods
  // ---------------------------------------------------------------------------

  /**
   * Create a new {@link StructDefinition} node using the given
   * {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param name the name of the {@link StructDefinition}.
   * @return a new {@link StructDefinition} node.
   */
  public static StructDefinition newStructDefinitionNode(
      final NodeFactory nodeFactory, final String name) {
    final StructDefinition node = newNode(nodeFactory, "type",
        StructDefinition.class);
    setKindDecorations(node);
    node.setName(name);
    return node;
  }

  /**
   * Create a new {@link StructReference} node using the given
   * {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param name the name of the {@link StructReference}.
   * @return a new {@link StructReference} node.
   */
  public static StructReference newStructReferenceNode(
      final NodeFactory nodeFactory, final String name) {
    final StructReference node = newNode(nodeFactory, "type",
        StructReference.class);
    setKindDecorations(node);
    node.setName(name);
    return node;
  }

  /**
   * Create a new {@link UnionDefinition} node using the given
   * {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param name the name of the {@link UnionDefinition}.
   * @return a new {@link UnionDefinition} node.
   */
  public static UnionDefinition newUnionDefinitionNode(
      final NodeFactory nodeFactory, final String name) {
    final UnionDefinition node = newNode(nodeFactory, "type",
        UnionDefinition.class);
    setKindDecorations(node);
    node.setName(name);
    return node;
  }

  /**
   * Create a new {@link UnionReference} node using the given
   * {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param name the name of the {@link UnionDefinition}.
   * @return a new {@link UnionReference} node.
   */
  public static UnionReference newUnionReferenceNode(
      final NodeFactory nodeFactory, final String name) {
    final UnionReference node = newNode(nodeFactory, "type",
        UnionReference.class);
    setKindDecorations(node);
    node.setName(name);
    return node;
  }

  /**
   * Create a new {@link Member} node using the given {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param name the name of the {@link Member}.
   * @return a new {@link Member} node.
   */
  public static Member newMemberNode(final NodeFactory nodeFactory,
      final String name) {
    final Member node = newNode(nodeFactory, "member", Member.class);
    setKindDecorations(node);
    node.setName(name);
    return node;
  }

  // ---------------------------------------------------------------------------
  // Type helper methods
  // ---------------------------------------------------------------------------

  /**
   * Create a new {@link PrimitiveType} node using the given {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param name the name of the {@link PrimitiveType}.
   * @return a new {@link PrimitiveType} node.
   */
  public static PrimitiveType newPrimitiveTypeNode(
      final NodeFactory nodeFactory, final String name) {
    final PrimitiveType node = newNode(nodeFactory, "type", PrimitiveType.class);
    setKindDecorations(node);
    node.setName(name);
    return node;
  }

  /**
   * Create a new {@link ArrayOf} node using the given {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param size the size of the array. May be null;
   * @return a new {@link ArrayOf} node.
   */
  public static ArrayOf newArrayOfNode(final NodeFactory nodeFactory,
      final ConstantExpression size) {
    final ArrayOf node = newNode(nodeFactory, "type", ArrayOf.class);
    setKindDecorations(node);
    node.setConstantExpression(size);
    return node;
  }

  /**
   * Create a new {@link PointerOf} node using the given {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @return a new {@link PointerOf} node.
   */
  public static PointerOf newPointerOfNode(final NodeFactory nodeFactory) {
    final PointerOf node = newNode(nodeFactory, "type", PointerOf.class);
    setKindDecorations(node);
    return node;
  }

  // ---------------------------------------------------------------------------
  // Constant Expression helper methods
  // ---------------------------------------------------------------------------

  /**
   * Create a new {@link BinaryOperation} node using the given
   * {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param operation the operation of the {@link BinaryOperation}.
   * @return a new {@link BinaryOperation} node.
   */
  public static BinaryOperation newBinaryOperationNode(
      final NodeFactory nodeFactory, final String operation) {
    final BinaryOperation node = newNode(nodeFactory, "constantExpression",
        BinaryOperation.class);
    setKindDecorations(node);
    node.setOperation(operation);
    return node;
  }

  /**
   * Create a new {@link UnaryOperation} node using the given
   * {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param operation the operation of the {@link UnaryOperation}.
   * @return a new {@link UnaryOperation} node.
   */
  public static UnaryOperation newUnaryOperationNode(
      final NodeFactory nodeFactory, final String operation) {
    final UnaryOperation node = newNode(nodeFactory, "constantExpression",
        UnaryOperation.class);
    setKindDecorations(node);
    node.setOperation(operation);
    return node;
  }

  /**
   * Create a new {@link CastOperation} node using the given {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param type the type of the {@link CastOperation}.
   * @return a new {@link CastOperation} node.
   */
  public static CastOperation newCastOperationNode(
      final NodeFactory nodeFactory, final Type type) {
    final CastOperation node = newNode(nodeFactory, "constantExpression",
        CastOperation.class);
    setKindDecorations(node);
    node.setType(type);
    return node;
  }

  /**
   * Create a new {@link Literal} node using the given {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param expr the expression of the {@link Literal}.
   * @return a new {@link Literal} node.
   */
  public static Literal newLiteralNode(final NodeFactory nodeFactory,
      final String expr) {
    final Literal node = newNode(nodeFactory, "constantExpression",
        Literal.class);
    setKindDecorations(node);
    node.setExpr(expr);
    return node;
  }

  // ---------------------------------------------------------------------------
  // Include helper methods
  // ---------------------------------------------------------------------------

  /**
   * Create a new {@link Include} node using the given {@link NodeFactory}
   * 
   * @param nodeFactory the {@link NodeFactory} to use to create the node.
   * @param path the path of the {@link Include}.
   * @return a new {@link Include} node.
   */
  public static Include newIncludeNode(final NodeFactory nodeFactory,
      final String path) {
    final Include node = newNode(nodeFactory, "include", Include.class);
    setKindDecorations(node);
    node.setPath(path);
    return node;
  }

  public static enum IncludeDelimiter {
    QUOTE, LT_GT;
  }

  public static String getIncludedPath(final Include include) {
    return include.getPath().substring(1, include.getPath().length() - 1);
  }

  public static IncludeDelimiter getIncludeDelimiter(final Include include) {
    if (include.getPath().startsWith("\"")) {
      return IncludeDelimiter.QUOTE;
    } else {
      if (!include.getPath().startsWith("<")) {
        throw new IllegalArgumentException("Invalid include path: "
            + include.getPath() + " Must start with '\"' of '<'");
      }
      return IncludeDelimiter.LT_GT;
    }
  }

  public static void setIncludePath(final Include include, final String path,
      final IncludeDelimiter delimiter) {
    switch (delimiter) {
      case QUOTE :
        include.setPath("\"" + path + "\"");
        break;
      case LT_GT :
        include.setPath("<" + path + ">");
        break;
    }
  }

  public static void setIncludePathPreserveDelimiter(final Include include,
      final String path) {
    setIncludePath(include, path, getIncludeDelimiter(include));
  }

  public static final String INCLUDED_IDL_DECORATION_NAME = "included-idl";

  public static void setIncludedIDL(final Include include, final IDL idl) {
    ReferencedIDLDecoration decoration = getIncludedIDLDecoration(include);
    if (decoration == null) {
      decoration = new ReferencedIDLDecoration(idl);
      include.astSetDecoration(INCLUDED_IDL_DECORATION_NAME, decoration);
    } else {
      decoration.setIDL(idl);
    }
  }

  public static IDL getIncludedIDL(final Include include,
      final IDLLoader loaderItf, final Map<Object, Object> context)
      throws ADLException {
    final ReferencedIDLDecoration decoration = getIncludedIDLDecoration(include);
    if (decoration == null) return null;
    if (decoration.getIDL() == null && loaderItf != null) {
      decoration.setIDL(loaderItf.load(decoration.getPath(), context));
    }
    return decoration.getIDL();
  }

  // ---------------------------------------------------------------------------
  // Kind decoration helper methods
  // ---------------------------------------------------------------------------

  public static final String KIND_DECORATION = "kind";

  public static void setKindDecorations(final Node node) {
    if (node instanceof InterfaceDefinition)
      node.astSetDecoration(KIND_DECORATION, "interface");
    else if (node instanceof EnumDefinition)
      node.astSetDecoration(KIND_DECORATION, "enum");
    else if (node instanceof EnumReference)
      node.astSetDecoration(KIND_DECORATION, "enumRef");
    else if (node instanceof StructDefinition)
      node.astSetDecoration(KIND_DECORATION, "struct");
    else if (node instanceof StructReference)
      node.astSetDecoration(KIND_DECORATION, "structRef");
    else if (node instanceof UnionDefinition)
      node.astSetDecoration(KIND_DECORATION, "union");
    else if (node instanceof UnionReference)
      node.astSetDecoration(KIND_DECORATION, "unionRef");
    else if (node instanceof TypeDefinition)
      node.astSetDecoration(KIND_DECORATION, "typedef");
    else if (node instanceof TypeDefReference)
      node.astSetDecoration(KIND_DECORATION, "typedefRef");
    else if (node instanceof PrimitiveType)
      node.astSetDecoration(KIND_DECORATION, "primitiveType");
    else if (node instanceof ArrayOf)
      node.astSetDecoration(KIND_DECORATION, "arrayOf");
    else if (node instanceof PointerOf)
      node.astSetDecoration(KIND_DECORATION, "pointerOf");
    else if (node instanceof ConstantDefinition)
      node.astSetDecoration(KIND_DECORATION, "constDef");
    else if (node instanceof BinaryOperation)
      node.astSetDecoration(KIND_DECORATION, "binaryOperation");
    else if (node instanceof UnaryOperation)
      node.astSetDecoration(KIND_DECORATION, "unaryOperation");
    else if (node instanceof CastOperation)
      node.astSetDecoration(KIND_DECORATION, "castOperation");
    else if (node instanceof Literal)
      node.astSetDecoration(KIND_DECORATION, "literal");

    for (final String type : node.astGetNodeTypes()) {
      for (final Node n : node.astGetNodes(type)) {
        if (n != null) setKindDecorations(n);
      }
    }
  }

  private static ReferencedIDLDecoration getIncludedIDLDecoration(
      final Include include) {
    return (ReferencedIDLDecoration) include
        .astGetDecoration(INCLUDED_IDL_DECORATION_NAME);
  }

  public static final class ReferencedIDLDecoration
      implements
        NodeContainerDecoration,
        Serializable {
    private transient IDL idl;
    private final String  path;

    public ReferencedIDLDecoration(final IDL idl) {
      if (idl == null) throw new IllegalArgumentException("idl can't be null");
      if (idl.getName() == null)
        throw new IllegalArgumentException("idl's name can't be null");
      if (!(idl instanceof InterfaceDefinition) && !(idl instanceof Header)
          && isRelative(idl.getName()))
        throw new IllegalArgumentException("idl's name can't be relative");

      this.idl = idl;
      this.path = idl.getName();
    }

    public IDL getIDL() {
      return idl;
    }

    public void setIDL(final IDL idl) {
      if (idl == null) throw new IllegalArgumentException("idl can't be null");
      if (idl.getName() == null)
        throw new IllegalArgumentException("idl's name can't be null");
      if (!idl.getName().equals(path))
        throw new IllegalArgumentException("Wrong idl path \"" + path
            + "\" expected instead of \"" + idl.getName() + "\".");

      this.idl = idl;
    }

    public String getPath() {
      return path;
    }

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

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

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

  public static final String REFERENCED_INTERFACE_DECORATION = "referenced-interfaces";

  public static void addReferencedInterface(final IDL idl,
      final InterfaceDefinition itf) {
    getDecoration(idl).addReferencedInterface(new ReferencedIDLDecoration(itf));
  }

  public static Collection<InterfaceDefinition> getReferencedInterfaces(
      final IDL idl, final IDLLoader loaderItf,
      final Map<Object, Object> context) throws ADLException {

    final ReferencedInterfaceDecoration decoration = getDecoration(idl);
    if (decoration == null) return Collections.emptyList();
    final Collection<ReferencedIDLDecoration> referencedInterfaces = decoration
        .getReferencedInterfaces();
    final List<InterfaceDefinition> result = new ArrayList<InterfaceDefinition>(
        referencedInterfaces.size());
    for (final ReferencedIDLDecoration referencedInterface : referencedInterfaces) {
      if (referencedInterface.getIDL() == null && loaderItf != null) {
        referencedInterface.setIDL(loaderItf.load(
            referencedInterface.getPath(), context));
      }
      result.add((InterfaceDefinition) referencedInterface.getIDL());
    }
    return result;
  }

  private static ReferencedInterfaceDecoration getDecoration(final IDL idl) {
    ReferencedInterfaceDecoration decoration = (ReferencedInterfaceDecoration) idl
        .astGetDecoration(REFERENCED_INTERFACE_DECORATION);
    if (decoration == null) {
      decoration = new ReferencedInterfaceDecoration();
      idl.astSetDecoration(REFERENCED_INTERFACE_DECORATION, decoration);
    }
    return decoration;
  }

  public static class ReferencedInterfaceDecoration
      implements
        NodeContainerDecoration,
        Serializable {

    Map<String, ReferencedIDLDecoration> itfs = new HashMap<String, ReferencedIDLDecoration>();

    void addReferencedInterface(final ReferencedIDLDecoration itf) {
      itfs.put(itf.getPath(), itf);
    }

    public Collection<ReferencedIDLDecoration> getReferencedInterfaces() {
      return itfs.values();
    }

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

    public Collection<Node> getNodes() {
      if (itfs.isEmpty()) {
        return Collections.emptyList();
      } else {
        final List<Node> l = new ArrayList<Node>();
        for (final ReferencedIDLDecoration itfDef : itfs.values()) {
          l.addAll(itfDef.getNodes());
        }
        return l;
      }
    }

    public void replaceNodes(final IdentityHashMap<Node, Node> replacements) {
      for (final Map.Entry<Node, Node> replacement : replacements.entrySet()) {
        final ReferencedIDLDecoration deco = itfs
            .get(((InterfaceDefinition) replacement.getKey()).getName());
        deco.setIDL((IDL) replacement.getValue());
      }
    }
  }

}
