/******************************************************************************* 
 * Copyright (c) 2007 Red Hat, Inc. 
 * Distributed under license by Red Hat, Inc. All rights reserved. 
 * This program is 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: 
 * Red Hat, Inc. - initial API and implementation 
 ******************************************************************************/ 
package org.jboss.tools.seam.internal.core.scanner.lib;

import java.io.ByteArrayInputStream;
import java.io.IOException;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IParent;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
import org.jboss.tools.common.model.XModel;
import org.jboss.tools.common.model.XModelObject;
import org.jboss.tools.common.model.filesystems.impl.FileSystemsImpl;
import org.jboss.tools.common.model.plugin.ModelPlugin;
import org.jboss.tools.common.model.util.EclipseResourceUtil;
import org.jboss.tools.common.model.util.XModelObjectUtil;
import org.jboss.tools.jst.web.model.helpers.InnerModelHelper;
import org.jboss.tools.seam.core.ISeamProject;
import org.jboss.tools.seam.core.SeamCoreMessages;
import org.jboss.tools.seam.internal.core.SeamNamespace;
import org.jboss.tools.seam.internal.core.scanner.IFileScanner;
import org.jboss.tools.seam.internal.core.scanner.LoadedDeclarations;
import org.jboss.tools.seam.internal.core.scanner.ScannerException;
import org.jboss.tools.seam.internal.core.scanner.java.SeamAnnotations;
import org.jboss.tools.seam.internal.core.scanner.xml.PropertiesScanner;
import org.jboss.tools.seam.internal.core.scanner.xml.XMLScanner;

/**
 * @author Viacheslav Kabanovich
 */
public class LibraryScanner implements IFileScanner {
	ClassPath classPath = null;
	
	//Now it is absolute file on disk
	IPath sourcePath = null;
	
	public LibraryScanner() {}
	
	public void setClassPath(ClassPath classPath) {
		this.classPath = classPath;
	}

	public boolean isRelevant(IFile f) {
		if(EclipseResourceUtil.isJar(f.getName())) return true;
		return false;
	}

	XModelObject o = null;
	XModel model = null;
	
	public boolean isLikelyComponentSource(IFile f) {
		cleanState();
		boolean isComponent = false;
		try {
			model = InnerModelHelper.createXModel(f.getProject());
			if(model != null) {
				o = EclipseResourceUtil.getObjectByResource(model, f);
				if(o != null && o.getModelEntity().getName().equals("FileSystemJar")) {
					((FileSystemsImpl)o.getModel().getByPath("FileSystems")).updateOverlapped(); //$NON-NLS-1$
					o = EclipseResourceUtil.getObjectByResource(f);
					if(o != null && o.getModelEntity().getName().equals("FileSystemJar")) {
						isComponent =  isLikelyComponentSource(o);
					}
				}
			}
		} finally {	
			if(!isComponent) {
				cleanState();
			}
		}
		return isComponent;
	}

	private void cleanState() {
		model = null;
		o = null;
	}

	public LoadedDeclarations parse(IFile f, ISeamProject sp) throws ScannerException {
		LoadedDeclarations decls = null;
		if(o!=null) {
			decls = parse(o, f.getFullPath(), sp);
		}
		return decls;
	}

	public static final boolean isLikelyComponentSource(XModelObject o) {
		return o.getChildByPath("seam.properties") != null 
			|| o.getChildByPath("META-INF/seam.properties") != null
			|| o.getChildByPath("META-INF/components.xml") != null;
	}

	public LoadedDeclarations parse(XModelObject o, IPath path, ISeamProject sp) throws ScannerException {
		sourcePath = path;
		XModelObject seamProperties = o.getChildByPath("META-INF/seam.properties"); //$NON-NLS-1$
		if(seamProperties == null) seamProperties = o.getChildByPath("seam.properties"); //$NON-NLS-1$
		XModelObject componentsXML = o.getChildByPath("META-INF/components.xml"); //$NON-NLS-1$
		if(componentsXML == null && seamProperties == null) return null;
		
		LoadedDeclarations ds = new LoadedDeclarations();

		try {
			processJavaClasses(o, ds);
		} catch (JavaModelException e) {
			throw new ScannerException(SeamCoreMessages.LIBRARY_SCANNER_CANNOT_PROCESS_JAVA_CLASSES, e);
		} catch (ClassFormatException e) {
			throw new ScannerException(SeamCoreMessages.LIBRARY_SCANNER_CANNOT_PROCESS_JAVA_CLASSES, e);
		} catch (IOException e) {
			throw new ScannerException(SeamCoreMessages.LIBRARY_SCANNER_CANNOT_PROCESS_JAVA_CLASSES, e);
		}
		
		if(componentsXML != null) {
			LoadedDeclarations ds1 = new XMLScanner().parse(componentsXML, path, sp);
			if(ds1 != null) ds.add(ds1);
		}
		if(seamProperties != null) {
			PropertiesScanner scanner = new PropertiesScanner();
			LoadedDeclarations ds1 = scanner.parse(seamProperties, path);
			if(ds1 != null) ds.add(ds1);
		}		
		
		return ds;
	}
	
	protected void processJavaClasses(XModelObject o, LoadedDeclarations ds) throws JavaModelException, ClassFormatException, IOException {
		IJavaProject javaProject = JavaCore.create(classPath.getProject().getProject());
		String location = o.getAttributeValue("location"); //$NON-NLS-1$
		location = XModelObjectUtil.expand(location, o.getModel(), null);
		
		IFile[] fs = ModelPlugin.getWorkspace().getRoot().findFilesForLocation(new Path(location));
		IPackageFragmentRoot root = null;
		if(fs != null) { 
			for (int i = 0; i < fs.length && root == null; i++) {
				root = javaProject.findPackageFragmentRoot(fs[i].getFullPath());
			}
			if(root == null) {
				root = javaProject.findPackageFragmentRoot(new Path(location));
			}
			if(root != null) {
				process(root, ds);
			}				
		} 
	}
	
	protected void process(IParent element, LoadedDeclarations ds) throws JavaModelException, ClassFormatException, IOException {
		if(element == null) return;
		IJavaElement[] es = element.getChildren();
		String prefix = null;
		for (int i = 0; i < es.length; i++) {
			if(es[i] instanceof IClassFile) {
				IClassFile typeRoot = (IClassFile)es[i];
				if(es[i].getElementName().equals("package-info.class")) {
					prefix = processPackageInfo(typeRoot, ds);
					break;
				}
			}
		}
		for (int i = 0; i < es.length; i++) {
			if(es[i] instanceof IPackageFragment) {
				process((IPackageFragment)es[i], ds);
			} else if(es[i] instanceof IClassFile) {
				IClassFile typeRoot = (IClassFile)es[i];
				if(es[i].getElementName().equals("package-info.class")) {
					continue;
				} else {
					processWithClassReader(typeRoot, ds, prefix);
				}
			}
		}
	}
	
	void processWithClassReader(IClassFile typeRoot, LoadedDeclarations ds, String prefix) throws JavaModelException, ClassFormatException, IOException {
		IType type = typeRoot.getType();
		
		ClassFileReader reader = getReader(type, typeRoot);

		if(reader == null) return;
		LoadedDeclarations ds1 = null;

		//TODO use prefix
		TypeScanner scanner = new TypeScanner();
		if(!scanner.isLikelyComponentSource(reader)) return;
		ds1 = scanner.parse(type, reader, sourcePath);

		if(ds1 != null) {
			ds.add(ds1);
		}
	}

	String processPackageInfo(IClassFile typeRoot, LoadedDeclarations ds) throws JavaModelException, ClassFormatException, IOException {
		IType type = typeRoot.getType();
		
		ClassFileReader reader = getReader(type, typeRoot);
		
		if(reader == null) return null;
		IBinaryAnnotation[] as = reader.getAnnotations();
		IBinaryAnnotation namespaceAnnotation = getNamespaceAnnotation(as);
		if(namespaceAnnotation == null) return null;
		String uri = TypeScanner.getValue(namespaceAnnotation, "value");
		String prefix = TypeScanner.getValue(namespaceAnnotation, "prefix");
		if(uri == null) return null;
		
		String className = type.getFullyQualifiedName();
		int i = className.indexOf(".package-info");
		if(i < 0) return null;
		String packageName = className.substring(0, i);

		SeamNamespace n = new SeamNamespace();
		n.setSourcePath(sourcePath);
		n.setURI(uri);
		n.setPackage(packageName);

		ds.getNamespaces().add(n);

		return prefix;
	}

	static IBinaryAnnotation getNamespaceAnnotation(IBinaryAnnotation[] as) {
		if(as != null) {
			for (int i = 0; i < as.length; i++) {
				String type = TypeScanner.getTypeName(as[i]);
				if(type != null && type.equals(SeamAnnotations.NAMESPACE_ANNOTATION_TYPE)) {
					return as[i];
				}
			}
		}
		return null;
	}

	private ClassFileReader getReader(IType type, IClassFile typeRoot) throws JavaModelException, ClassFormatException, IOException {
		String className = type.getFullyQualifiedName();
		ClassFileReader newReader = null;
		byte[] bs = null;
		
			bs = typeRoot.getBytes();
			return ClassFileReader.read(new ByteArrayInputStream(bs), className, false);
	}

}
