/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.reflection.inspect;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import net.ranides.assira.io.DirectoryHelper;
import net.ranides.assira.io.PathConvertException;
import net.ranides.assira.io.PathHelper;
import net.ranides.assira.text.StringEncoder;
import net.ranides.assira.text.StringUtils;
import net.ranides.assira.text.TextEncoding;
import net.ranides.assira.trace.AdvLogger;
import net.ranides.assira.trace.LoggerUtils;

/**
 *
 * @author ranides
 */
@SuppressWarnings("PMD.UseSingleton")
public final class PackageInspector {

    private static final AdvLogger LOGGER = LoggerUtils.getLogger();

    private final ClassLoader loader;
    private final String packageName;
    private final String packagePath;
    private final Set<Class<?>> target;
    
    private PackageInspector(String packageName, ClassLoader loader) {
        this.loader = loader;
        this.packageName = packageName;
        this.packagePath = packageName.replace('.', '/');
        this.target = new HashSet<>();
    }
    
    public static Collection<Class<?>> scan(String packageName) throws IOException {
        return scan(packageName, Thread.currentThread().getContextClassLoader());
    }
    
    public static Collection<Class<?>> scan(String packageName, ClassLoader loader) throws IOException {
        return new PackageInspector(packageName, loader).scan();
    }

    private Collection<Class<?>> scan() throws IOException {
        Enumeration<URL> resources = loader.getResources(packagePath);
        if (resources == null) {
            return target;
        }

        while (resources.hasMoreElements()) {
            String path = StringEncoder.decodeURL(resources.nextElement().getPath(), TextEncoding.NIO_DEFAULT);
            File file = new File(path);
            if(file.isDirectory()) {
                getFromDirectory(file);
            } else {
                getFromJARFile( StringUtils.between(path, ":", "!") );
            }
        }

        return target;
    }

    private void getFromDirectory(File directory) {
        for(File file : DirectoryHelper.find(directory, "*.class")) {
            try {
                String cname = packageName + '.' + path2name(PathHelper.asRelative(directory, file));
                Class<?> clazz = Class.forName(cname, false, loader);
                target.add(clazz);
            } catch(PathConvertException | ClassNotFoundException cause) {
                LOGGER.warn(cause);
            }
        }
    }

    private void getFromJARFile(String jar) {
        try {
            JarInputStream jarFile = new JarInputStream(new FileInputStream(jar));
            while(true) {
                try {
                    JarEntry entry = jarFile.getNextJarEntry();
                    if (entry == null) {
                        break;
                    }
                    String entryName = entry.getName();
                    if( entryName.startsWith(packagePath) && entryName.endsWith(".class")) {
                        String cname = path2name(entryName);
                        Class<?> clazz = Class.forName(cname, false, loader);
                        target.add(clazz);
                    }
                } catch(ClassNotFoundException cause) {
                    LOGGER.warn(cause);
                }
            }
        } catch(IOException cause) {
            LOGGER.warn(cause);
        }
    }

    private static String path2name(String path) {
        // usuwa rozszerzenie ".class" oraz zamienia separatory katalogów na kropki
        return path.substring(0, path.length()- 6).replace('/', '.').replace('\\', '.');
    }

}
