/*
 * 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.io.IOException;
import java.util.Arrays;
import java.util.Locale;
import java.util.SortedSet;
import java.util.TreeSet;

import org.junit.Assert;
import org.junit.Test;

import com.diffplug.common.base.Errors;
import com.diffplug.common.base.StringPrinter;

public class GradleIncrementalResolutionTest extends GradleIntegrationTest {
	@Test
	public void failureDoesntTriggerAll() throws IOException {
		setFile("build.gradle").toLines(
				"plugins {",
				"    id 'com.diffplug.gradle.spotless'",
				"}",
				"spotless {",
				"    format 'misc', {",
				"        target '*.md'",
				"        custom 'lowercase', { str ->",
				"            String result = str.toLowerCase(Locale.ROOT)",
				"            println(\"<${result.trim()}>\")",
				"            return result",
				"        }",
				"        bumpThisNumberIfACustomStepChanges(1)",
				"    }",
				"}");
		// test our harness (build makes things lower case)
		writeState("ABC");
		assertState("ABC");
		writeState("aBc");
		assertState("aBc");
		// check will run against all three the first time (and second and third)
		checkRanAgainst("abc");
		checkRanAgainst("abc");
		checkRanAgainst("abc");
		// apply will run against all three the first time
		applyRanAgainst("abc");
		// the second time, it will only run on the file that was changes
		applyRanAgainst("b");
		// and nobody the last time
		applyRanAgainst("");

		// if we change just one file
		writeState("Abc");
		// then check runs against just the changed file
		// and also (because #144) the last files to be changed
		checkRanAgainst("a", "b");
		// even after failing, still just the one
		checkRanAgainst("a", "b");
		// and so does apply
		applyRanAgainst("a", "b");
		applyRanAgainst("a");
		// until the issue has been fixed
		applyRanAgainst("");
	}

	private String filename(String name) {
		return name.toLowerCase(Locale.ROOT) + ".md";
	}

	private void writeState(String state) throws IOException {
		for (char c : state.toCharArray()) {
			String letter = new String(new char[]{c});
			boolean exists = new File(rootFolder(), filename(letter)).exists();
			boolean needsChanging = exists && !read(filename(letter)).trim().equals(letter);
			if (!exists || needsChanging) {
				setFile(filename(letter)).toContent(letter);
			}
		}
	}

	private void assertState(String state) throws IOException {
		for (char c : state.toCharArray()) {
			String letter = new String(new char[]{c});
			if (Character.isLowerCase(c)) {
				Assert.assertEquals(letter.toLowerCase(Locale.ROOT), read(filename(letter)).trim());
			} else {
				Assert.assertEquals(letter.toUpperCase(Locale.ROOT), read(filename(letter)).trim());
			}
		}
	}

	private void applyRanAgainst(String... ranAgainst) throws IOException {
		taskRanAgainst("spotlessApply", ranAgainst);
	}

	private void checkRanAgainst(String... ranAgainst) throws IOException {
		taskRanAgainst("spotlessCheck", ranAgainst);
	}

	private void taskRanAgainst(String task, String... ranAgainst) throws IOException {
		pauseForFilesystem();
		String console = StringPrinter.buildString(Errors.rethrow().wrap(printer -> {
			boolean expectFailure = task.equals("spotlessCheck") && !isClean();
			if (expectFailure) {
				gradleRunner().withArguments(task).forwardStdOutput(printer.toWriter()).buildAndFail();
			} else {
				gradleRunner().withArguments(task).forwardStdOutput(printer.toWriter()).build();
			}
		}));
		SortedSet<String> added = new TreeSet<>();
		for (String line : console.split("\n")) {
			String trimmed = line.trim();
			if (trimmed.startsWith("<") && trimmed.endsWith(">")) {
				added.add(trimmed.substring(1, trimmed.length() - 1));
			}
		}
		Assert.assertEquals(concat(Arrays.asList(ranAgainst)), concat(added));
	}

	private String concat(Iterable<String> iterable) {
		StringBuilder result = new StringBuilder();
		for (String item : iterable) {
			result.append(item);
		}
		return result.toString();
	}

	private boolean isClean() throws IOException {
		for (File file : rootFolder().listFiles()) {
			if (file.isFile() && file.getName().length() == 4 && file.getName().endsWith(".md")) {
				String content = read(file.getName());
				if (!content.toLowerCase(Locale.ROOT).equals(content)) {
					return false;
				}
			}
		}
		return true;
	}
}

@@ -1721,30 +1721,116 @@
 time
- (and second and third
+.%0A%09%09// Subsequent runs will only run the formatter on the bad file (in order to generate the failure message
 )%0A%09%09
@@ -1864,35 +1864,33 @@
 heckRanAgainst(%22
-abc
+b
 %22);%0A%09%09checkRanAg
@@ -1892,31 +1892,30 @@
 RanAgainst(%22
-abc
+b
 %22);%0A
+%0A
 %09%09// apply w
@@ -1922,44 +1922,66 @@
 ill 
-run against all three the first time
+simply copy outputs the first time: no formatters executed
 %0A%09%09a
@@ -1992,27 +1992,24 @@
 RanAgainst(%22
-abc
 %22);%0A%09%09// the
@@ -2066,17 +2066,26 @@
 s change
-s
+d by apply
 %0A%09%09apply
@@ -2263,66 +2263,8 @@
 ile%0A
-%09%09// and also (because #144) the last files to be changed%0A
 %09%09ch
@@ -2272,37 +2272,32 @@
 ckRanAgainst(%22a%22
-, %22b%22
 );%0A%09%09// even aft
@@ -2310,28 +2310,8 @@
 ling
-, still just the one
 %0A%09%09c
@@ -2324,29 +2324,24 @@
 nAgainst(%22a%22
-, %22b%22
 );%0A%09%09// and 
@@ -2376,16 +2376,8 @@
 nst(
-%22a%22, %22b%22
 );%0A%09

Fix test for changed behaviour with incremental resolution