/*
 * Copyright 2015-2016 the original author or authors.
 *
 * 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
 */

package org.junit.gen5.commons.util;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.logging.Logger;

/**
 * <h3>DISCLAIMER</h3>
 *
 * <p>These utilities are intended solely for usage within the JUnit framework
 * itself. <strong>Any usage by external parties is not supported.</strong>
 * Use at your own risk!
 *
 * @since 5.0
 */
class ClasspathScanner {

	private static final String CLASS_FILE_SUFFIX = ".class";

	private static final Logger LOG = Logger.getLogger(ClasspathScanner.class.getName());

	private final Supplier<ClassLoader> classLoaderSupplier;

	private final BiFunction<String, ClassLoader, Optional<Class<?>>> loadClass;

	ClasspathScanner(Supplier<ClassLoader> classLoaderSupplier,
			BiFunction<String, ClassLoader, Optional<Class<?>>> loadClass) {
		this.classLoaderSupplier = classLoaderSupplier;
		this.loadClass = loadClass;
	}

	boolean isPackage(String packageName) {
		String path = packagePath(packageName);
		try {
			Enumeration<URL> resource = classLoaderSupplier.get().getResources(path);
			return resource.hasMoreElements();
		}
		catch (IOException e) {
			return false;
		}
	}

	List<Class<?>> scanForClassesInPackage(String basePackageName, Predicate<Class<?>> classFilter) {
		Preconditions.notBlank(basePackageName, "basePackageName must not be blank");

		List<File> dirs = allSourceDirsForPackage(basePackageName);
		LOG.fine(() -> "Directories found: " + dirs);
		return allClassesInSourceDirs(dirs, basePackageName, classFilter);
	}

	private List<Class<?>> allClassesInSourceDirs(List<File> sourceDirs, String basePackageName,
			Predicate<Class<?>> classFilter) {
		List<Class<?>> classes = new ArrayList<>();
		for (File aSourceDir : sourceDirs) {
			classes.addAll(findClassesInSourceDirRecursively(aSourceDir, basePackageName, classFilter));
		}
		return classes;
	}

	List<Class<?>> scanForClassesInClasspathRoot(File root, Predicate<Class<?>> classFilter) {
		Preconditions.notNull(root, "root must not be null");
		Preconditions.condition(root.exists(), "root must exist");
		Preconditions.condition(root.isDirectory(), "root must be a directory");

		return findClassesInSourceDirRecursively(root, "", classFilter);
	}

	private List<File> allSourceDirsForPackage(String basePackageName) {
		try {
			ClassLoader classLoader = classLoaderSupplier.get();
			LOG.fine(() -> "ClassLoader: " + classLoader);
			String path = packagePath(basePackageName);
			Enumeration<URL> resources = classLoader.getResources(path);
			List<File> dirs = new ArrayList<>();
			while (resources.hasMoreElements()) {
				URL resource = resources.nextElement();
				dirs.add(new File(resource.getFile()));
			}
			return dirs;
		}
		catch (IOException e) {
			return Collections.emptyList();
		}
	}

	private String packagePath(String basePackageName) {
		return basePackageName.replace('.', '/');
	}

	private List<Class<?>> findClassesInSourceDirRecursively(File sourceDir, String packageName,
			Predicate<Class<?>> classFilter) {
		List<Class<?>> classesCollector = new ArrayList<>();
		collectClassesRecursively(sourceDir, packageName, classesCollector, classFilter);
		return classesCollector;
	}

	private void collectClassesRecursively(File sourceDir, String packageName, List<Class<?>> classesCollector,
			Predicate<Class<?>> classFilter) {
		LOG.finer(() -> "Searching for classes in package: " + packageName);
		File[] files = sourceDir.listFiles();
		if (files == null) {
			return;
		}
		LOG.finer(() -> "Files found: " + Arrays.toString(files));
		for (File file : files) {
			if (isClassFile(file)) {
				Optional<Class<?>> classForClassFile = loadClassForClassFile(file, packageName);
				classForClassFile.filter(classFilter).ifPresent(clazz -> classesCollector.add(clazz));
			}
			else if (file.isDirectory()) {
				collectClassesRecursively(file, appendPackageName(packageName, file.getName()), classesCollector,
					classFilter);
			}
		}
	}

	private String appendPackageName(String packageName, String subpackageName) {
		if (packageName.isEmpty())
			return subpackageName;
		else
			return packageName + "." + subpackageName;
	}

	private Optional<Class<?>> loadClassForClassFile(File file, String packageName) {
		String className = packageName + '.'
				+ file.getName().substring(0, file.getName().length() - CLASS_FILE_SUFFIX.length());
		return loadClass.apply(className, classLoaderSupplier.get());
	}

	private static boolean isClassFile(File file) {
		return file.isFile() && file.getName().endsWith(CLASS_FILE_SUFFIX);
	}

}

@@ -447,33 +447,8 @@
 st;%0A
-import java.util.Arrays;%0A
 impo
@@ -473,16 +473,16 @@
 ctions;%0A
+
 import j
@@ -667,41 +667,8 @@
 ier;
-%0Aimport java.util.logging.Logger;
 %0A%0A/*
@@ -986,96 +986,8 @@
 %22;%0A%0A
-%09private static final Logger LOG = Logger.getLogger(ClasspathScanner.class.getName());%0A%0A
 %09pri
@@ -1840,56 +1840,8 @@
 e);%0A
-%09%09LOG.fine(() -%3E %22Directories found: %22 + dirs);%0A
 %09%09re
@@ -2742,58 +2742,8 @@
 ();%0A
-%09%09%09LOG.fine(() -%3E %22ClassLoader: %22 + classLoader);%0A
 %09%09%09S
@@ -3667,79 +3667,8 @@
 ) %7B%0A
-%09%09LOG.finer(() -%3E %22Searching for classes in package: %22 + packageName);%0A
 %09%09Fi
@@ -3741,73 +3741,12 @@
 rn;%0A
+
 %09%09%7D%0A
-%09%09LOG.finer(() -%3E %22Files found: %22 + Arrays.toString(files));%0A
 %09%09fo

Remove logging from ClasspathScanner