/*******************************************************************************
 * Copyright (c) 2005 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *     Matt Chapman - initial version
 *******************************************************************************/
package org.eclipse.ajdt.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.aspectj.asm.IProgramElement;
import org.eclipse.ajdt.core.javaelements.AJCodeElement;
import org.eclipse.ajdt.core.javaelements.AJCompilationUnit;
import org.eclipse.ajdt.core.javaelements.AJInjarElement;
import org.eclipse.ajdt.core.javaelements.AspectElement;
import org.eclipse.ajdt.core.javaelements.AspectElementInfo;
import org.eclipse.ajdt.core.javaelements.MockAdviceElement;
import org.eclipse.ajdt.core.javaelements.MockAspectElement;
import org.eclipse.ajdt.core.javaelements.MockDeclareElement;
import org.eclipse.ajdt.core.javaelements.MockIntertypeElement;
import org.eclipse.ajdt.core.javaelements.MockPointcutElement;
import org.eclipse.ajdt.core.javaelements.MockSourceMethod;
import org.eclipse.ajdt.internal.core.AJWorkingCopyOwner;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner;
import org.eclipse.jdt.internal.core.JavaElement;
import org.eclipse.jdt.internal.core.PackageFragment;
import org.eclipse.jdt.internal.core.util.MementoTokenizer;

public class AspectJCore {

	/**
	 * Returns the Java model element corresponding to the given handle
	 * identifier generated by <code>IJavaElement.getHandleIdentifier()</code>,
	 * or <code>null</code> if unable to create the associated element.
	 * 
	 * @param handleIdentifier
	 *            the given handle identifier
	 * @return the Java element corresponding to the handle identifier
	 */
	public static IJavaElement create(String handleIdentifier) {
		return create(handleIdentifier, AJWorkingCopyOwner.INSTANCE);
	}

	private static int indexOfIgnoringEscapes(String str, char ch) {
		boolean prevEscape = false;
		for (int i = 0; i < str.length(); i++) {
			char c = str.charAt(i);
			if (c == JavaElement.JEM_ESCAPE) {
				prevEscape = true;
			} else {
				if ((c == ch) && (!prevEscape)) {
					return i;
				}
				prevEscape = false;
			}
		}
		return -1;
	}

	private static IJavaElement getCodeElement(String codeElementHandle, JavaElement parent) {
		int li = indexOfIgnoringEscapes(
				codeElementHandle,
				JavaElement.JEM_COUNT);
		if (li != -1) {
			String cname = codeElementHandle
					.substring(0, li);
			String rest = codeElementHandle
					.substring(li + 1);
			li = indexOfIgnoringEscapes(rest,
					JavaElement.JEM_COUNT);
			if (li != -1) {
				String lineStr = rest.substring(0,li);
				try {
					int line = new Integer(lineStr)
							.intValue();
					return new AJCodeElement(
							parent, line, cname);
				} catch (NumberFormatException e) {
				}
			}
		} else {
			// no line number
			return new AJCodeElement(parent, 0, codeElementHandle);
		}
		return null;
	}
	
	public static IJavaElement create(String handleIdentifier,
			WorkingCopyOwner owner) {
		
		if (handleIdentifier == null) {
			return null;
		}
		Map aspectsInJavaFiles = new HashMap();
		
		boolean isCodeElement = false;
		String codeElementHandle = ""; //$NON-NLS-1$

		int codeElementDelimPos = indexOfIgnoringEscapes(handleIdentifier,
				AspectElement.JEM_CODEELEMENT);
		if (codeElementDelimPos != -1) {
			isCodeElement = true;
			codeElementHandle = handleIdentifier
					.substring(codeElementDelimPos + 1);
			handleIdentifier = handleIdentifier.substring(0,
					codeElementDelimPos);
		}

		MementoTokenizer memento = new AJMementoTokenizer(handleIdentifier);
		while (memento.hasMoreTokens()) {
			String token = memento.nextToken();
			if ((token.charAt(0) == AspectElement.JEM_ASPECT_CU)
					|| (token.charAt(0) == JavaElement.JEM_COMPILATIONUNIT)) {
				int index1 = handleIdentifier
						.indexOf(JavaElement.JEM_COMPILATIONUNIT);
				int index2 = handleIdentifier
					.indexOf(AspectElement.JEM_ASPECT_CU);
				int index = Math.max(index1,index2);
				if (index != -1) {
					IJavaElement je = JavaCore.create(handleIdentifier
							.substring(0, index));
					if (je instanceof PackageFragment) {
						PackageFragment pf = (PackageFragment) je;
						String cuName = handleIdentifier.substring(index + 1);
						int ind1 = cuName.indexOf(JavaElement.JEM_TYPE);
						if (ind1 != -1) {
							cuName = cuName.substring(0, ind1);
						}						
						int ind2 = cuName.indexOf(AspectElement.JEM_ASPECT_TYPE);
						if (ind2 != -1) {
							cuName = cuName.substring(0, ind2);
						}
						int ind3 = cuName.indexOf(AspectElement.JEM_ITD);
						if (ind3 != -1) {
							cuName = cuName.substring(0, ind3);
						}
						int ind4 = cuName.indexOf(AspectElement.JEM_DECLARE);
						if (ind4 != -1) {
							cuName = cuName.substring(0, ind4);
						}
						if (CoreUtils.ASPECTJ_SOURCE_ONLY_FILTER.accept(cuName)) {
							JavaElement cu = new AJCompilationUnit(pf, cuName,
									owner);
							token = memento.nextToken();
							if (!memento.hasMoreTokens()) {
								return cu;
							}
							IJavaElement restEl = cu.getHandleFromMemento(
									memento.nextToken(), memento, owner);
							if (restEl != null) {
								if (isCodeElement) {
									// there was an AJCodeElement at the end of
									// the handle
									IJavaElement codeEl = getCodeElement(codeElementHandle,(JavaElement) restEl);
									if (codeEl != null) {
										return codeEl;
									}
								}
								return restEl;
							}
						} else {
							// Use the default working copy owner for Java elements
							IJavaElement restEl = pf.getHandleFromMemento(
									token, memento, DefaultWorkingCopyOwner.PRIMARY);
							if (restEl != null) {
								if (isCodeElement) {
									// there was an AJCodeElement at the end of
									// the handle
									IJavaElement codeEl = getCodeElement(codeElementHandle,(JavaElement) restEl);
									if (codeEl != null) {
										return codeEl;
									}
								}
								return restEl;
							} else if (ind2 != -1) { // An aspect in a .java file...
								int index3 = handleIdentifier
								.indexOf(AspectElement.JEM_ASPECT_TYPE);
								String aspectName = handleIdentifier.substring(index3 + 1);
								boolean identifierIsAspect = true;
								int ind5b = aspectName.indexOf(AspectElement.JEM_DECLARE);
								if (ind5b != -1) {
									aspectName = aspectName.substring(0, ind5b);
									identifierIsAspect = false;
								}
								int ind6 = aspectName.indexOf(AspectElement.JEM_ADVICE);
								if (ind6 != -1) {
									aspectName = aspectName.substring(0, ind6);
									identifierIsAspect = false;
								}
								int ind7 = aspectName.indexOf(AspectElement.JEM_ITD);
								if (ind7 != -1) {
									aspectName = aspectName.substring(0, ind7);
									identifierIsAspect = false;
								}
								int ind8 = aspectName.indexOf(AspectElement.JEM_ASPECT_TYPE);
								if (ind8 != -1) {
									aspectName = aspectName.substring(0, ind8);
									identifierIsAspect = false;
								}
								int ind9 = aspectName.indexOf(AspectElement.JEM_TYPE);
								if (ind9 != -1) {
									aspectName = aspectName.substring(0, ind9);
									identifierIsAspect = false;
								}
								int ind10 = aspectName.indexOf(AspectElement.JEM_FIELD);
								if (ind10 != -1) {
									aspectName = aspectName.substring(0, ind10);
									identifierIsAspect = false;
								}
								int ind11 = aspectName.indexOf(AspectElement.JEM_METHOD);
								if (ind11 != -1) {
									aspectName = aspectName.substring(0, ind11);
									identifierIsAspect = false;
								}
								int ind12 = aspectName.indexOf(AspectElement.JEM_POINTCUT);
								if (ind12 != -1) {
									aspectName = aspectName.substring(0, ind12);
									identifierIsAspect = false;
								}
								ICompilationUnit unit = pf.getCompilationUnit(cuName);
								List l;
								if(aspectsInJavaFiles.get(unit) instanceof List) {
									l = (List)aspectsInJavaFiles.get(unit);
								} else {
									l = new ArrayList();
									aspectsInJavaFiles.put(unit, l);
								}
								AspectElement aspectEl = null;
								for (Iterator iter = l.iterator(); iter.hasNext();) {
									AspectElement element = (AspectElement) iter.next();
									if(element.getElementName().equals(aspectName)) {
										aspectEl = element;
									}								
								}
								if(aspectEl == null) {
									AspectElementInfo info = new AspectElementInfo();
									info.setAJKind(IProgramElement.Kind.ASPECT);
									info.setSourceRangeStart(0);
									aspectEl = new MockAspectElement((JavaElement)unit, aspectName, info);						
									l.add(aspectEl);
//									((CompilationUnitElementInfo)((CompilationUnit)unit).getElementInfo()).addChild(aspectEl);
								}
								if (identifierIsAspect) {
									return aspectEl;
								}
								IJavaElement mockEl;
								char[] possibleDelimiters = new char[] {
										AspectElement.JEM_ADVICE,
										AspectElement.JEM_DECLARE,
										AspectElement.JEM_ITD,
										AspectElement.JEM_METHOD,
										AspectElement.JEM_POINTCUT
								};
								for (int i = 0; i < possibleDelimiters.length; i++) {
									mockEl = getMockElement(possibleDelimiters[i], handleIdentifier, aspectEl); 
									if(mockEl != null) {
										return mockEl;
									}										
								}							
								return null;								
							}
						}
					}
				}
			}
		}
		if (isCodeElement) {
			// an injar aspect with no parent
			return new AJInjarElement(codeElementHandle);
		}
		return JavaCore.create(handleIdentifier);
	}

	private static IJavaElement getMockElement(char delimiterChar, String handleIdentifier, AspectElement aspectEl) {
		String mockElementHandle;
		int mockElementDelimPos = indexOfIgnoringEscapes(handleIdentifier,
				delimiterChar);
		if (mockElementDelimPos != -1) {
			mockElementHandle = handleIdentifier
				.substring(mockElementDelimPos + 1);
			int li = indexOfIgnoringEscapes(mockElementHandle, AspectElement.JEM_EXTRA_INFO);
			if (li != -1) {
				String cname = mockElementHandle.substring(0, li);
				String rest = mockElementHandle.substring(li + 1);
				String[] extraInfo = rest.split(Character.toString(AspectElement.JEM_EXTRA_INFO));
				try {
					int offset = new Integer(extraInfo[0]).intValue();
					switch (delimiterChar) {
						case AspectElement.JEM_ADVICE:
							String[] split = cname.split(Character.toString(AspectElement.JEM_ADVICE)); //$NON-NLS-1$
							String[] parameterTypes = new String[split.length - 1];
							for (int i = 0; i < parameterTypes.length; i++) {
								parameterTypes[i] = split[i + 1];
							}
							return new MockAdviceElement(aspectEl, offset, split[0], parameterTypes);
						case AspectElement.JEM_DECLARE:
							cname = cname.replaceAll("\\\\", "");  //$NON-NLS-1$//$NON-NLS-2$
							return new MockDeclareElement(aspectEl, offset, cname);
						case AspectElement.JEM_ITD:
							String kind = extraInfo.length > 1 ? extraInfo[1] : null;
							String accessibility = extraInfo.length > 2 ? extraInfo[2] : null;
							split = cname.split("\\" + Character.toString(AspectElement.JEM_ITD)); //$NON-NLS-1$
							parameterTypes = new String[split.length - 1];
							for (int i = 0; i < parameterTypes.length; i++) {
								parameterTypes[i] = split[i + 1];
							}
							return new MockIntertypeElement(aspectEl, offset, split[0], parameterTypes, kind, accessibility);
						case AspectElement.JEM_METHOD:
							accessibility = extraInfo.length > 1 ? extraInfo[1] : null;
							split = cname.split(Character.toString(AspectElement.JEM_METHOD)); //$NON-NLS-1$
							parameterTypes = new String[split.length - 1];
							for (int i = 0; i < parameterTypes.length; i++) {
								parameterTypes[i] = split[i + 1];
							}						
							return new MockSourceMethod(aspectEl, split[0], parameterTypes, offset, accessibility);
						case AspectElement.JEM_POINTCUT:
							accessibility = extraInfo.length > 1 ? extraInfo[1] : null;
							split = cname.split("\\" + Character.toString(AspectElement.JEM_POINTCUT)); //$NON-NLS-1$
							parameterTypes = new String[split.length - 1];
							for (int i = 0; i < parameterTypes.length; i++) {
								parameterTypes[i] = split[i + 1];
							}						
							return new MockPointcutElement(aspectEl, split[0], parameterTypes, offset, accessibility);
							
						default:
							AJLog.log("AspectJCore unable to deserialize: " + mockElementHandle); //$NON-NLS-1$
					}
				} catch (NumberFormatException e) {
				}
			}
		}
		return null;
	}

}

class AJMementoTokenizer extends MementoTokenizer {
	private static final String COUNT = Character
			.toString(JavaElement.JEM_COUNT);

	private static final String JAVAPROJECT = Character
			.toString(JavaElement.JEM_JAVAPROJECT);

	private static final String PACKAGEFRAGMENTROOT = Character
			.toString(JavaElement.JEM_PACKAGEFRAGMENTROOT);

	private static final String PACKAGEFRAGMENT = Character
			.toString(JavaElement.JEM_PACKAGEFRAGMENT);

	private static final String FIELD = Character
			.toString(JavaElement.JEM_FIELD);

	private static final String METHOD = Character
			.toString(JavaElement.JEM_METHOD);

	private static final String INITIALIZER = Character
			.toString(JavaElement.JEM_INITIALIZER);

	private static final String COMPILATIONUNIT = Character
			.toString(JavaElement.JEM_COMPILATIONUNIT);

	private static final String CLASSFILE = Character
			.toString(JavaElement.JEM_CLASSFILE);

	private static final String TYPE = Character.toString(JavaElement.JEM_TYPE);

	private static final String PACKAGEDECLARATION = Character
			.toString(JavaElement.JEM_PACKAGEDECLARATION);

	private static final String IMPORTDECLARATION = Character
			.toString(JavaElement.JEM_IMPORTDECLARATION);

	private static final String LOCALVARIABLE = Character
			.toString(JavaElement.JEM_LOCALVARIABLE);

	// begin AspectJ change
	private static final String ASPECT_CU = Character
			.toString(AspectElement.JEM_ASPECT_CU);

	private static final String TYPE_PARAMETER = Character
			.toString(AspectElement.JEM_TYPE_PARAMETER);

	private static final String ADVICE = Character
			.toString(AspectElement.JEM_ADVICE);

	private static final String ASPECT_TYPE = Character
			.toString(AspectElement.JEM_ASPECT_TYPE);

	private static final String CODEELEMENT = Character
			.toString(AspectElement.JEM_CODEELEMENT);

	private static final String ITD = Character
			.toString(AspectElement.JEM_ITD);

	private static final String DECLARE = Character
		.toString(AspectElement.JEM_DECLARE);

	private static final String POINTCUT = Character
		.toString(AspectElement.JEM_POINTCUT);
	// end AspectJ change

	private final char[] memento;

	private final int length;

	private int index = 0;

	public AJMementoTokenizer(String memento) {
		super(memento);
		this.memento = memento.toCharArray();
		this.length = this.memento.length;
	}

	public boolean hasMoreTokens() {
		return this.index < this.length;
	}

	public String nextToken() {
		int start = this.index;
		StringBuffer buffer = null;
		switch (this.memento[this.index++]) {
		case JavaElement.JEM_ESCAPE:
			buffer = new StringBuffer();
			buffer.append(this.memento[this.index]);
			start = ++this.index;
			break;
		case JavaElement.JEM_COUNT:
			return COUNT;
		case JavaElement.JEM_JAVAPROJECT:
			return JAVAPROJECT;
		case JavaElement.JEM_PACKAGEFRAGMENTROOT:
			return PACKAGEFRAGMENTROOT;
		case JavaElement.JEM_PACKAGEFRAGMENT:
			return PACKAGEFRAGMENT;
		case JavaElement.JEM_FIELD:
			return FIELD;
		case JavaElement.JEM_METHOD:
			return METHOD;
		case JavaElement.JEM_INITIALIZER:
			return INITIALIZER;
		case JavaElement.JEM_COMPILATIONUNIT:
			return COMPILATIONUNIT;
		case JavaElement.JEM_CLASSFILE:
			return CLASSFILE;
		case JavaElement.JEM_TYPE:
			return TYPE;
		case JavaElement.JEM_PACKAGEDECLARATION:
			return PACKAGEDECLARATION;
		case JavaElement.JEM_IMPORTDECLARATION:
			return IMPORTDECLARATION;
		case JavaElement.JEM_LOCALVARIABLE:
			return LOCALVARIABLE;
		// begin AspectJ change
		case AspectElement.JEM_ASPECT_CU:
			return ASPECT_CU;
		case AspectElement.JEM_TYPE_PARAMETER:
			return TYPE_PARAMETER;
		case AspectElement.JEM_ADVICE:
			return ADVICE;
		case AspectElement.JEM_ASPECT_TYPE:
			return ASPECT_TYPE;
		case AspectElement.JEM_CODEELEMENT:
			return CODEELEMENT;
		case AspectElement.JEM_ITD:
			return ITD;
		case AspectElement.JEM_DECLARE:
			return DECLARE;
		case AspectElement.JEM_POINTCUT:
			return POINTCUT;
		// end AspectJ change
		}
		loop: while (this.index < this.length) {
			switch (this.memento[this.index]) {
			case JavaElement.JEM_ESCAPE:
				if (buffer == null)
					buffer = new StringBuffer();
				buffer.append(this.memento, start, this.index - start);
				start = ++this.index;
				break;
			case JavaElement.JEM_COUNT:
			case JavaElement.JEM_JAVAPROJECT:
			case JavaElement.JEM_PACKAGEFRAGMENTROOT:
			case JavaElement.JEM_PACKAGEFRAGMENT:
			case JavaElement.JEM_FIELD:
			case JavaElement.JEM_METHOD:
			case JavaElement.JEM_INITIALIZER:
			case JavaElement.JEM_COMPILATIONUNIT:
			case JavaElement.JEM_CLASSFILE:
			case JavaElement.JEM_TYPE:
			case JavaElement.JEM_PACKAGEDECLARATION:
			case JavaElement.JEM_IMPORTDECLARATION:
			case JavaElement.JEM_LOCALVARIABLE:
			// begin AspectJ change
			case AspectElement.JEM_ASPECT_CU:
			case AspectElement.JEM_TYPE_PARAMETER:
			case AspectElement.JEM_ADVICE:
			case AspectElement.JEM_ASPECT_TYPE:
			case AspectElement.JEM_CODEELEMENT:
			case AspectElement.JEM_ITD:
			case AspectElement.JEM_DECLARE:
			case AspectElement.JEM_POINTCUT:
			// end AspectJ change
				break loop;
			}
			this.index++;
		}
		if (buffer != null) {
			buffer.append(this.memento, start, this.index - start);
			return buffer.toString();
		}
		return new String(this.memento, start, this.index - start);		
	}
}