/*******************************************************************************
 * 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.eclipse.jdt.internal.core.ClassFile;
import org.eclipse.ajdt.core.codeconversion.AspectsConvertingParser;
import org.eclipse.ajdt.core.javaelements.AJCodeElement;
import org.eclipse.ajdt.core.javaelements.AJCompilationUnit;
import org.eclipse.ajdt.core.javaelements.AJCompilationUnitManager;
import org.eclipse.ajdt.core.javaelements.AJInjarElement;
import org.eclipse.ajdt.core.javaelements.AspectElement;
import org.eclipse.ajdt.core.javaelements.BinaryAspectElement;
import org.eclipse.ajdt.internal.core.AJWorkingCopyOwner;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IOpenable;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.internal.core.CompilationUnit;
import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner;
import org.eclipse.jdt.internal.core.JavaElement;
import org.eclipse.jdt.internal.core.JavaModelManager;
import org.eclipse.jdt.internal.core.PackageFragment;

public class AspectJCore {

    public static IJavaElement create(IFile file) {
        if ("aj".equals(file.getFileExtension())) {
            return AJCompilationUnitManager.INSTANCE.getAJCompilationUnit(file);
        }
        return JavaModelManager.create(file, null/*unknown java project*/);
    }

    
	/**
	 * 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) {
		    int occurrenceIndex = codeElementHandle.lastIndexOf(JavaElement.JEM_COUNT);
		    if (Character.isDigit(codeElementHandle.charAt(occurrenceIndex+1))) {
		        int occurrence = Integer.parseInt(codeElementHandle.substring(occurrenceIndex+1));
		        String cname = codeElementHandle.substring(0, li);
		        return new AJCodeElement(parent, cname, occurrence);
		    }
            codeElementHandle = codeElementHandle.substring(0, li);
		}
		// no occurrance count
		return new AJCodeElement(parent, codeElementHandle);
	}
	
	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);
		}

		AJMementoTokenizer 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) 
					|| (token.charAt(0) == JavaElement.JEM_CLASSFILE)) {
			    
				int index;
				if (token.charAt(0) == AspectElement.JEM_ASPECT_CU) {
				    index = handleIdentifier.indexOf(AspectElement.JEM_ASPECT_CU);
				} else if (token.charAt(0) == JavaElement.JEM_COMPILATIONUNIT) {
				    index = handleIdentifier.indexOf(JavaElement.JEM_COMPILATIONUNIT);
				} else { // JEM_CLASSFILE 
				    index = handleIdentifier.indexOf(JavaElement.JEM_CLASSFILE);
				}
				
				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);
                        }
                        int ind5 = cuName.indexOf(AspectElement.JEM_IMPORTDECLARATION);
                        if (ind5 != -1) {
                            cuName = cuName.substring(0, ind5);
                        }
                        int ind6 = cuName.indexOf(AspectElement.JEM_PACKAGEDECLARATION);
                        if (ind6 != -1) {
                            cuName = cuName.substring(0, ind6);
                        }
						if (CoreUtils.ASPECTJ_SOURCE_ONLY_FILTER.accept(cuName)) {
						    // no need to use a cuprovider because we know this
						    // is an AJCompilationUnit
							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 ind7 = aspectName.indexOf(AspectElement.JEM_DECLARE);
								if (ind7 != -1) {
									aspectName = aspectName.substring(0, ind7);
									identifierIsAspect = false;
								}
								int ind8 = aspectName.indexOf(AspectElement.JEM_ADVICE);
								if (ind8 != -1) {
									aspectName = aspectName.substring(0, ind8);
									identifierIsAspect = false;
								}
								int ind9 = aspectName.indexOf(AspectElement.JEM_ITD);
								if (ind9 != -1) {
									aspectName = aspectName.substring(0, ind9);
									identifierIsAspect = false;
								}
								int ind10 = aspectName.indexOf(AspectElement.JEM_ASPECT_TYPE);
								if (ind10 != -1) {
									aspectName = aspectName.substring(0, ind10);
									identifierIsAspect = false;
								}
								int ind11 = aspectName.indexOf(AspectElement.JEM_TYPE);
								if (ind11 != -1) {
									aspectName = aspectName.substring(0, ind11);
									identifierIsAspect = false;
								}
								int ind12 = aspectName.indexOf(AspectElement.JEM_FIELD);
								if (ind12 != -1) {
									aspectName = aspectName.substring(0, ind12);
									identifierIsAspect = false;
								}
								int ind13 = aspectName.indexOf(AspectElement.JEM_METHOD);
								if (ind13 != -1) {
									aspectName = aspectName.substring(0, ind13);
									identifierIsAspect = false;
								}
								int ind14 = aspectName.indexOf(AspectElement.JEM_POINTCUT);
								if (ind14 != -1) {
									aspectName = aspectName.substring(0, ind14);
									identifierIsAspect = false;
								}
								IOpenable openable;
								if (cuName.endsWith(".class")) {
								    openable = pf.getClassFile(cuName);
								} else {
								    openable = pf.getCompilationUnit(cuName);
								}
								List l;
								if(aspectsInJavaFiles.get(openable) instanceof List) {
									l = (List)aspectsInJavaFiles.get(openable);
								} else {
									l = new ArrayList();
									aspectsInJavaFiles.put(openable, l);
								}
								JavaElement aspectEl = null;
								for (Iterator iter = l.iterator(); iter.hasNext();) {
									AspectElement element = (AspectElement) iter.next();
									if(element.getElementName().equals(aspectName)) {
										aspectEl = element;
									}								
								}
								if(aspectEl == null) {
								    if (openable instanceof ClassFile) {
								        ClassFile cOpenable = (ClassFile) openable;
								        aspectEl = new BinaryAspectElement((JavaElement) cOpenable, aspectName);						
                                    } else {
                                        aspectEl = new AspectElement((JavaElement) openable, aspectName);						
                                    }
									l.add(aspectEl);
								}
                                int afterAspectIndex = index3 + aspectName.length() + 1;

								
								if (identifierIsAspect) {
								    return aspectEl;
								} else {
    								memento.setIndexTo(afterAspectIndex);
    								return aspectEl.getHandleFromMemento(memento.nextToken(), memento, owner);
								}
							}
						}
					}
				}
			}
		}
		// XXX can we get here???
		if (isCodeElement) {
			// an injar aspect with no parent
			return new AJInjarElement(codeElementHandle);
		}
		return JavaCore.create(handleIdentifier);
	}
	
	    /**
     * Converts a handle signifying Java class to a handle signifying an
     * aspect element.
     * 
     * This method is necessary because JavaCore does not create
     * AspectElements when it is building structure using the {@link AspectsConvertingParser}
     * 
     * Note that this changes the top level class to being an aspect and keeps 
     * all others the same.  This may not work in all situations (eg- an inner aspect)
     * 
     * @param classHandle
     * @return converts the handle to using {@link AspectElement#JEM_ASPECT_CU} and 
     * {@link AspectElement#JEM_ASPECT_TYPE}
     */
    public static String convertToAspectHandle(String classHandle, IJavaElement elt) {
       String aspectHandle = classHandle.replaceFirst("\\" +
               Character.toString(JavaElement.JEM_TYPE), 
               Character.toString(AspectElement.JEM_ASPECT_TYPE));

       if (CoreUtils.ASPECTJ_SOURCE_ONLY_FILTER.accept(elt.getResource().getName())) {
           aspectHandle = aspectHandle.replace(JavaElement.JEM_COMPILATIONUNIT, 
                   AspectElement.JEM_ASPECT_CU);
       }
       return aspectHandle;
    }
	
	
	public static String convertToJavaCUHandle(String aspectHandle, IJavaElement elt) {
	    String javaHandle = aspectHandle;
	    if (elt != null) {
            IResource resource = elt.getResource();
            if (resource != null) {
                if (CoreUtils.ASPECTJ_SOURCE_ONLY_FILTER.accept(resource.getName())) {
                    javaHandle = javaHandle.replaceFirst("\\" + AspectElement.JEM_ASPECT_CU, 
                            Character.toString(JavaElement.JEM_COMPILATIONUNIT));
                }
            }
	    }
        return javaHandle;
    }
	
}

