/**
 * Copyright (c) 2006 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 * 
 * Contributors: 
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.codegen.merge.java.facade.ast;

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

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;

import org.eclipse.emf.codegen.merge.java.facade.JMember;
import org.eclipse.emf.codegen.merge.java.facade.JNode;

/**
 * Each <code>ASTJMember</code> has a reference to {@link BodyDeclaration} as a wrapped object.
 * 
 * @param <T> wrapped body declaration type
 * 
 * @since 2.2.0
 */
public abstract class ASTJMember<T extends BodyDeclaration> extends ASTJNode<T> implements JMember
{
  /**
   * Cached value of member comment.
   * @see #getComment()
   * @see #setComment(String)
   */
  protected String comment = UNITIALIZED_STRING;

  /**
   * @param bodyDeclaration
   */
  public ASTJMember(T bodyDeclaration)
  {
    super(bodyDeclaration);
  }
  
  @Override
  public void dispose()
  {
    comment = null;
    super.dispose();
  }
  
  /**
   * Returns original flags of the member.
   * 
   * @see org.eclipse.emf.codegen.merge.java.facade.AbstractJNode#getFlags()
   */
  @Override
  public int getFlags()
  {
    return getASTNode().getModifiers();
  }
  
  /**
   * Sets the flags of the member.
   * <p>
   * Note that <code>getFlags()</code> will not return the new value.
   * 
   * @see org.eclipse.emf.codegen.merge.java.facade.ast.ASTJNode#setFlags(int)
   */
  @Override
  public void setFlags(int flags)
  {
    BodyDeclaration bodyDeclaration = getASTNode();
    ListRewrite listRewrite = rewriter.getListRewrite(bodyDeclaration, bodyDeclaration.getModifiersProperty());

    // remove all existing modifiers
    @SuppressWarnings("unchecked") 
    List<IExtendedModifier> existingModifiers = listRewrite.getRewrittenList();
    for (IExtendedModifier modifier : existingModifiers)
    {
      if (modifier.isModifier())
      {
        listRewrite.remove((ASTNode)modifier, null);
      }
    }
    
    // create new modifiers and add to rewrite
    @SuppressWarnings("unchecked")    
    List<Modifier> newModifiers = bodyDeclaration.getAST().newModifiers(flags);
    for (Modifier modifier : newModifiers)
    {
      listRewrite.insertLast(modifier, null);
    }    
  }
  
  public String getComment()
  {
    if (comment == UNITIALIZED_STRING)
    {
      comment = getFacadeHelper().toString(getASTNode().getJavadoc());
    }
    return comment;
  }

  public void setComment(String comment)
  {
    this.comment = comment;
    setTrackedNodeProperty(getASTNode(), comment, getASTNode().getJavadocProperty(), ASTNode.JAVADOC);
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.emf.codegen.merge.java.facade.ast.ASTJNode#remove(org.eclipse.emf.codegen.merge.java.facade.ast.ASTJNode)
   */
  @Override
  public boolean remove(ASTJNode<?> node)
  {
    if (node.getParent() != this)
    {
      return false;
    }
    
    if (node instanceof ASTJAnnotation)
    {
      remove(node, getASTNode().getModifiersProperty());
      return true;
    }
    
    return false;
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.emf.codegen.merge.java.facade.ast.ASTJNode#insertSibling(org.eclipse.emf.codegen.merge.java.facade.ast.ASTJNode, org.eclipse.emf.codegen.merge.java.facade.ast.ASTJNode, boolean)
   */
  @Override
  public boolean insertSibling(ASTJNode<?> node, ASTJNode<?> newSibling, boolean before)
  {
    if (newSibling.getParent() != null || node.getParent() != this)
    {
      return false;
    }
    
    if (newSibling instanceof ASTJAnnotation)
    {
      if (node instanceof ASTJAnnotation)
      {
        insert(newSibling, getASTNode().getModifiersProperty(), node, before);
      }
      else
      {
        insertLastAnnotation((ASTJAnnotation)newSibling);
      }
      return true;
    }
    
    return false;
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.emf.codegen.merge.java.facade.ast.ASTJNode#addChild(org.eclipse.emf.codegen.merge.java.facade.ast.ASTJNode)
   */
  @Override
  public boolean addChild(ASTJNode<?> child)
  {
    if (child.getParent() != null)
    {
      return false;
    }
    
    if (child instanceof ASTJAnnotation)
    {
      insertLastAnnotation((ASTJAnnotation)child);
      return true;
    }
    return false;
  }
  
  /* (non-Javadoc)
   * @see org.eclipse.emf.codegen.merge.java.facade.AbstractJNode#getChildren()
   */
  @Override
  public List<JNode> getChildren()
  {
    if (!isDisposed())
    {
      List<JNode> children = new ArrayList<JNode>();
      children.addAll(getAnnotationList());
      if (!children.isEmpty())
      {
        return Collections.unmodifiableList(children);
      }
    }
    return Collections.emptyList();
  }

  protected List<JNode> getAnnotationList()
  {
    List<JNode> annotations = new ArrayList<JNode>();
    ListRewrite listRewrite = rewriter.getListRewrite(getASTNode(), getASTNode().getModifiersProperty());
    
    @SuppressWarnings("unchecked")
    List<IExtendedModifier> modifiers = listRewrite.getRewrittenList();
    
    for (IExtendedModifier extendedModifier : modifiers)
    {
      if (extendedModifier.isAnnotation())
      {
        JNode annotation = getFacadeHelper().convertToNode(extendedModifier);
        // specify the exact parent of annotations
        ((ASTJNode<?>)annotation).setParent(this);
        annotations.add(annotation);
      }
    }
    return annotations;    
  }
  
  /**
   * Insert annotation after the last annotation of the given body declaration.
   * <p>
   * Note that modifiers and annotations can alternate in the modifiers list.
   * This method inserts the new annotation after the last existing annotation.
   * 
   * @param annotation
   */
  protected void insertLastAnnotation(ASTJAnnotation annotation)
  {
    List<JNode> annotations = getAnnotationList();
    if (annotations.size() > 0)
    {
      ASTJNode<?> lastAnnotation = (ASTJNode<?>)annotations.get(annotations.size() - 1);
      insert(annotation, getASTNode().getModifiersProperty(), lastAnnotation, false);
    }
    else
    {
      insertFirst(annotation, getASTNode().getModifiersProperty());
    }
  }
}
