/*
 * Copyright 2016 DiffPlug
 *
 * 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 com.diffplug.gradle.spotless;

import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.Dependency;

import com.diffplug.common.base.Preconditions;
import com.diffplug.common.base.StringPrinter;
import com.diffplug.common.collect.ImmutableList;
import com.diffplug.spotless.Provisioner;

/** Gradle integration for Provisioner. */
public class GradleProvisioner {
	private GradleProvisioner() {}

	public static Provisioner fromProject(Project project) {
		return project.getPlugins().apply(SpotlessPlugin.class).getExtension().registerDependenciesTask.rootProvisioner;
	}

	/** The provisioner used for the root project. */
	static class RootProvisioner implements Provisioner {
		private final Project rootProject;
		private final Map<Request, Set<File>> cache = new HashMap<>();

		RootProvisioner(Project rootProject) {
			Preconditions.checkArgument(rootProject == rootProject.getRootProject());
			this.rootProject = rootProject;
		}

		@Override
		public Set<File> provisionWithTransitives(boolean withTransitives, Collection<String> mavenCoordinates) {
			Request req = new Request(withTransitives, mavenCoordinates);
			Set<File> result = cache.get(req);
			if (result != null) {
				return result;
			} else {
				result = GradleProvisioner.fromRootBuildscript(rootProject).provisionWithTransitives(req.withTransitives, req.mavenCoords);
				cache.put(req, result);
				return result;
			}
		}
	}

	static Provisioner fromRootBuildscript(Project project) {
		Objects.requireNonNull(project);
		return (withTransitives, mavenCoords) -> {
			try {
				Dependency[] deps = mavenCoords.stream()
						.map(project.getBuildscript().getDependencies()::create)
						.toArray(Dependency[]::new);

				// #372 workaround: Accessing rootProject.configurations from multiple projects is not thread-safe
				ConfigurationContainer configContainer;
				synchronized (project.getRootProject()) {
					configContainer = project.getRootProject().getBuildscript().getConfigurations();
				}
				Configuration config = configContainer.detachedConfiguration(deps);
				config.setDescription(mavenCoords.toString());
				config.setTransitive(withTransitives);
				return config.resolve();
			} catch (Exception e) {
				logger.log(Level.SEVERE,
						StringPrinter.buildStringFromLines("You probably need to add a repository containing the '" + mavenCoords + "' artifact in the 'build.gradle' of your root project.",
								"E.g.: 'buildscript { repositories { mavenCentral() }}'",
								"Note that included buildscripts (using 'apply from') do not share their buildscript repositories with the underlying project.",
								"You have to specify the missing repository explicitly in the buildscript of the root project."),
						e);
				throw e;
			}
		};
	}

	private static final Logger logger = Logger.getLogger(GradleProvisioner.class.getName());

	/** Models a request to the provisioner. */
	private static class Request {
		final boolean withTransitives;
		final ImmutableList<String> mavenCoords;

		public Request(boolean withTransitives, Collection<String> mavenCoords) {
			this.withTransitives = withTransitives;
			this.mavenCoords = ImmutableList.copyOf(mavenCoords);
		}

		@Override
		public int hashCode() {
			return withTransitives ? mavenCoords.hashCode() : ~mavenCoords.hashCode();
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			} else if (obj instanceof Request) {
				Request o = (Request) obj;
				return o.withTransitives == withTransitives && o.mavenCoords.equals(mavenCoords);
			} else {
				return false;
			}
		}

		@Override
		public String toString() {
			String coords = mavenCoords.toString();
			StringBuilder builder = new StringBuilder();
			builder.append(coords.substring(1, coords.length() - 1)); // strip off []
			if (withTransitives) {
				builder.append(" with transitives");
			} else {
				builder.append(" no transitives");
			}
			return builder.toString();
		}
	}
}

@@ -918,64 +918,8 @@
 on;%0A
-import org.gradle.api.artifacts.ConfigurationContainer;%0A
 impo
@@ -1010,55 +1010,8 @@
 ns;%0A
-import com.diffplug.common.base.StringPrinter;%0A
 impo
@@ -1266,16 +1266,106 @@
 ject) %7B%0A
+%09%09// TODO: this method is not necessary - we could remove it entirely for a small speedup%0A
 %09%09return
@@ -2609,217 +2609,28 @@
 %09%09%09%09
-// #372 workaround: Accessing rootProject.configurations from multiple projects is not thread-safe%0A%09%09%09%09ConfigurationContainer configContainer;%0A%09%09%09%09synchronized (project.getRootProject()) %7B%0A%09%09%09%09%09configContainer
+Configuration config
  = p
@@ -2693,58 +2693,8 @@
 ns()
-;%0A%09%09%09%09%7D%0A%09%09%09%09Configuration config = configContainer
 .det
@@ -2884,16 +2884,23 @@
 ger.log(
+%0A%09%09%09%09%09%09
 Level.SE
@@ -2915,43 +2915,8 @@
 %09%09%09%09
-StringPrinter.buildStringFromLines(
 %22You
@@ -3031,34 +3031,37 @@
 ur root project.
-%22,
+%5Cn%22 +
 %0A%09%09%09%09%09%09%09%09%22E.g.: 
@@ -3115,251 +3115,8 @@
 '%22,%0A
-%09%09%09%09%09%09%09%09%22Note that included buildscripts (using 'apply from') do not share their buildscript repositories with the underlying project.%22,%0A%09%09%09%09%09%09%09%09%22You have to specify the missing repository explicitly in the buildscript of the root project.%22),%0A
 %09%09%09%09

Remove the #372 workaround, since we don't need it anymore.