/*
 * Copyright 2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.gradle.api.internal;

import org.gradle.api.GradleException;
import org.gradle.api.UncheckedIOException;
import org.gradle.util.ClasspathUtil;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.regex.Pattern;

public abstract class AbstractClassPathProvider implements ClassPathProvider, GradleDistributionLocator {
    private final List<Pattern> all = Arrays.asList(Pattern.compile(".+"));
    private final Map<String, List<Pattern>> classPaths = new HashMap<String, List<Pattern>>();
    private final Scanner runtimeLibs;
    private final Scanner pluginLibs;
    private final Scanner coreImplLibs;
    private final File gradleHome;

    protected AbstractClassPathProvider() {
        File codeSource = getClasspathForClass(DefaultClassPathProvider.class);
        if (codeSource.isFile()) {
            // Loaded from a JAR - assume we're running from the distribution
            gradleHome = codeSource.getParentFile().getParentFile();
            runtimeLibs = new DirScanner(new File(gradleHome + "/lib"));
            pluginLibs = new DirScanner(new File(gradleHome + "/lib/plugins"));
            coreImplLibs = new DirScanner(new File(gradleHome + "/lib/core-impl"));
        } else {
            // Loaded from a classes dir - assume we're running from the ide or tests
            gradleHome = null;
            runtimeLibs = new ClassPathScanner(codeSource);
            pluginLibs = runtimeLibs;
            coreImplLibs = runtimeLibs;
        }
    }

    public File getGradleHome() {
        return gradleHome;
    }

    protected void add(String name, List<Pattern> patterns) {
        classPaths.put(name, patterns);
    }

    protected static List<Pattern> toPatterns(String... patternStrings) {
        List<Pattern> patterns = new ArrayList<Pattern>();
        for (String patternString : patternStrings) {
            patterns.add(Pattern.compile(patternString + "-.+"));
        }
        return patterns;
    }

    public Set<File> findClassPath(String name) {
        Set<File> matches = new LinkedHashSet<File>();
        if (name.equals("GRADLE_RUNTIME")) {
            runtimeLibs.find(all, matches);
            return matches;
        }
        if (name.equals("GRADLE_PLUGINS")) {
            pluginLibs.find(all, matches);
            return matches;
        }
        if (name.equals("GRADLE_CORE_IMPL")) {
            coreImplLibs.find(all, matches);
            return matches;
        }
        List<Pattern> classPathPatterns = classPaths.get(name);
        if (classPathPatterns != null) {
            runtimeLibs.find(classPathPatterns, matches);
            pluginLibs.find(classPathPatterns, matches);
            coreImplLibs.find(classPathPatterns, matches);
            return matches;
        }
        return null;
    }

    public static File getClasspathForClass(Class<?> targetClass) {
        URI location;
        try {
            location = targetClass.getProtectionDomain().getCodeSource().getLocation().toURI();
        } catch (URISyntaxException e) {
            throw new UncheckedIOException(e);
        }
        if (!location.getScheme().equals("file")) {
            throw new GradleException(String.format("Cannot determine Gradle home using codebase '%s'.", location));
        }
        return new File(location.getPath());
    }

    private static boolean matches(Iterable<Pattern> patterns, String name) {
        for (Pattern pattern : patterns) {
            if (pattern.matcher(name).matches()) {
                return true;
            }
        }
        return false;
    }

    private interface Scanner {
        void find(Iterable<Pattern> patterns, Collection<File> into);
    }

    private static class DirScanner implements Scanner {
        private final File dir;

        private DirScanner(File dir) {
            this.dir = dir;
        }

        public void find(Iterable<Pattern> patterns, Collection<File> into) {
            for (File file : dir.listFiles()) {
                if (matches(patterns, file.getName())) {
                    into.add(file);
                }
            }
        }
    }

    // This is used when running from the IDE or junit tests
    private static class ClassPathScanner implements Scanner {
        private final File classesDir;
        private final Collection<URL> classpath;

        private ClassPathScanner(File classesDir) {
            this.classesDir = classesDir;
            this.classpath = ClasspathUtil.getClasspath(getClass().getClassLoader());
        }

        public void find(Iterable<Pattern> patterns, Collection<File> into) {
            if (matches(patterns, "gradle-core-version.jar")) {
                into.add(classesDir);
            }
            for (URL url : classpath) {
                if (url.getProtocol().equals("file")) {
                    try {
                        File file = new File(url.toURI());
                        if (matches(patterns, file.getName())) {
                            into.add(file);
                        }
                    } catch (URISyntaxException e) {
                        throw new UncheckedIOException(e);
                    }
                }
            }
        }
    }
}
