/*******************************************************************************
 * Copyright (c) 2007, 2010 David Green 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:
 *     David Green - initial API and implementation
 *******************************************************************************/

package org.eclipse.mylyn.wikitext.tests;

import static com.google.common.base.Preconditions.checkNotNull;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.mylyn.internal.wikitext.ui.WikiTextUiPlugin;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;

/**
 * A utility for visiting Mylyn classes available on the classpath.
 * 
 * @author David Green
 */
public class ClassTraversal {
	private static final Pattern BUNDLE_RESOURCE_35 = Pattern.compile("(\\d+)\\..*");;

	public void visitClasses(Visitor visitor) {
		visitClasses(ClassTraversal.class, visitor);
	}

	private void visitClasses(Class<ClassTraversal> classOnClasspath, Visitor visitor) {
		ClassLoader loader = classOnClasspath.getClassLoader();
		String resourceOfClass = classOnClasspath.getCanonicalName().replace('.', '/') + ".class";
		Enumeration<URL> resources;
		try {
			resources = loader.getResources(resourceOfClass);
		} catch (IOException e) {
			throw new IllegalStateException(e);
		}
		while (resources.hasMoreElements()) {
			URL url = resources.nextElement();
			String protocol = url.getProtocol();
			if (protocol.equals("file")) {
				String file = url.getFile();
				try {
					file = URLDecoder.decode(file.substring(0, file.indexOf(resourceOfClass)), "utf-8");
				} catch (UnsupportedEncodingException e) {
					throw new IllegalStateException(e);
				}
				visitClasses(loader, new File(file), new File(file), visitor);

			} else if (protocol.equals("bundleresource")) {
				String host = url.getHost();
				// bug 266767 see http://dev.eclipse.org/mhonarc/lists/equinox-dev/msg05209.html
				Matcher bundle35Matcher = BUNDLE_RESOURCE_35.matcher(host);
				if (bundle35Matcher.matches()) {
					host = bundle35Matcher.group(1);
				}
				long bundleId = Long.parseLong(host);
				Bundle bundle = getBundle(bundleId);
				if (bundle == null) {
					throw new IllegalStateException("Cannot get bundle " + bundleId);
				}

				String path = url.getFile();
				path = path.substring(0, path.indexOf(resourceOfClass));

				visitClasses(bundle, path, visitor);
			} else {
				throw new IllegalStateException("Unimplemented protocol: " + protocol);
			}
		}
	}

	private Bundle getBundle(long bundleId) {
		return checkNotNull(FrameworkUtil.getBundle(WikiTextUiPlugin.class), "Cannot determine bundle").getBundleContext()
				.getBundle(bundleId);
	}

	private void visitClasses(ClassLoader loader, File root, File file, Visitor visitor) {
		File[] files = file.listFiles();
		if (files != null) {
			for (File child : files) {
				if (child.isDirectory()) {
					visitClasses(loader, root, child, visitor);
				} else {
					String path = child.getPath();
					if (path.endsWith(".class")) {
						String fqn = path.substring(root.getPath().length() + 1, path.length() - ".class".length());
						fqn = fqn.replace('/', '.').replace('\\', '.');
						Class<?> clazz;
						try {
							clazz = Class.forName(fqn, true, loader);
						} catch (LinkageError e) {
							// see bug 255568 comment 11
							// can't load the class, so skip it.
							continue;
						} catch (Exception e) {
							// can't load the class, so skip it.
							continue;
						}
						visitor.visit(clazz);
					}
				}
			}
		}
	}

	private void visitClasses(Bundle bundle, String path, Visitor visitor) {
		Enumeration<URL> entries = bundle.findEntries(path, "*.class", true);
		while (entries.hasMoreElements()) {
			URL element = entries.nextElement();
			String filePath = element.getFile();
			if (filePath.indexOf("org/eclipse/mylyn") != -1) {
				filePath = filePath.substring(filePath.indexOf("org/eclipse/mylyn"));
			} else {
				continue;
			}
			String fqn = filePath.substring(0, filePath.length() - ".class".length()).replace('/', '.');
			Class<?> clazz;
			try {
				clazz = bundle.loadClass(fqn);
			} catch (Exception e) {
				// can't laod the class, so skip it.
				continue;
			}
			visitor.visit(clazz);
		}
	}

	public interface Visitor {
		public void visit(Class<?> clazz);
	}
}
