/*******************************************************************************
 * Copyright (c) 2008 Institute for Software, HSR Hochschule fuer Technik  
 * Rapperswil, University of applied sciences and others
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Eclipse Public License v1.0 
 * which accompanies this distribution, and is available at 
 * http://www.eclipse.org/legal/epl-v10.html  
 *  
 * Contributors: 
 * Institute for Software - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.core.dom.rewrite.changegenerator;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTInitializer;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.internal.core.dom.rewrite.ASTModification;
import org.eclipse.cdt.internal.core.dom.rewrite.ASTModification.ModificationKind;
import org.eclipse.cdt.internal.core.dom.rewrite.astwriter.ContainerNode;

public class ASTModificationHelper {

	private final ModificationScopeStack modificationStore;

	public ASTModificationHelper(ModificationScopeStack stack) {
		this.modificationStore = stack;
	}

	
	public <T extends IASTNode> T[] createModifiedChildArray(IASTNode parent, T[] unmodifiedChildren, Class<T> clazz){
		ArrayList<T> modifiedChildren = new ArrayList<T>(Arrays.asList(unmodifiedChildren));
		for(T currentChild : unmodifiedChildren){
			for(ASTModification childModification : modificationsForNode(currentChild)){
				try{
					final T newNode = cast(childModification.getNewNode(), clazz);
					switch(childModification.getKind()){
					case REPLACE:
						if (newNode != null) {
							modifiedChildren.add(modifiedChildren.indexOf(childModification.getTargetNode()), newNode);
						}
						modifiedChildren.remove(childModification.getTargetNode());
						break;
					case INSERT_BEFORE:
					case APPEND_CHILD:
						throw new UnhandledASTModificationException(childModification);
					}
				}catch(ClassCastException e){
					throw new UnhandledASTModificationException(childModification);
				}
			} 
		}

		for(ASTModification parentModification : modificationsForNode(parent)){
			switch(parentModification.getKind()){
			case APPEND_CHILD:
				IASTNode newNode = parentModification.getNewNode();
				T appendedTNode = cast(newNode, clazz);
				if (appendedTNode != null) {
					modifiedChildren.add(appendedTNode);
				}
				else if (newNode instanceof ContainerNode){
					ContainerNode nodeContainer = (ContainerNode) newNode;
					for(IASTNode currentNode : nodeContainer.getNodes()){
						T tnode= cast(currentNode, clazz);
						if(tnode != null){
							modifiedChildren.add(tnode);
						}
					}
				}
				break;
			
			case INSERT_BEFORE:
				T insertedTNode = cast(parentModification.getNewNode(), clazz);	
				
				int targetNodeIndex = modifiedChildren.indexOf(parentModification.getTargetNode());
				if(targetNodeIndex >= 0){
					modifiedChildren.add(targetNodeIndex, insertedTNode);
				}
				break;	
				
			case REPLACE:
			}
		}
		
		return modifiedChildren.toArray(newArrayInstance(clazz, modifiedChildren.size()));
	}


	@SuppressWarnings("unchecked")
	private <T> T[] newArrayInstance(Class<T> clazz, int size) {
		return (T[]) Array.newInstance(clazz, size);
	}

	@SuppressWarnings("unchecked")
	private <T> T cast(IASTNode node, Class<T> clazz) {
		if (clazz.isInstance(node)){
			return (T) node;
		}
		return null;
	}


	public List<ASTModification> modificationsForNode(
			IASTNode targetNode) {
		List<ASTModification> modificationsForNode;
		if(modificationStore.getModifiedNodes().contains(targetNode)){
			modificationsForNode = modificationStore.getModificationsForNode(targetNode);
		}
		else{
			modificationsForNode = Collections.emptyList();
		}
			return modificationsForNode;
		
	}
	
	
	public IASTInitializer getInitializer(IASTDeclarator decl) {
		IASTInitializer initializer = decl.getInitializer();
		
		if(initializer != null){
			for(ASTModification childModification : modificationsForNode(initializer)){
				switch(childModification.getKind()){
				case REPLACE:
					if(childModification.getNewNode() instanceof IASTInitializer){
						return (IASTInitializer)childModification.getNewNode();
					} else if (childModification.getNewNode() == null) {
						return null;
					}
					throw new UnhandledASTModificationException(childModification);
					
					
				case INSERT_BEFORE:
					throw new UnhandledASTModificationException(childModification);
					
				case APPEND_CHILD:
					throw new UnhandledASTModificationException(childModification);
				}
			}
		}
		else
		{
			for(ASTModification parentModification : modificationsForNode(decl)){
				if(parentModification.getKind() == ModificationKind.APPEND_CHILD){
					IASTNode newNode = parentModification.getNewNode();
					if(newNode instanceof IASTInitializer){
						return (IASTInitializer) newNode;
					}
				}
			}
		}
		return initializer;
	}


	@SuppressWarnings("unchecked")
	public <T extends IASTNode> T getNodeAfterReplacement(T replacedNode) {
		List<ASTModification> modifications = modificationsForNode(replacedNode);
		for(ASTModification currentModification : modifications){
			try{
				if(currentModification.getKind() == ModificationKind.REPLACE){
					return (T) currentModification.getNewNode();
				}
			}
			catch(ClassCastException e){
				throw new UnhandledASTModificationException(currentModification);
			}
		}
		return replacedNode;
	}
}
