/*
 * 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.spotless;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import org.junit.Assert;
import org.junit.ComparisonFailure;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;

import com.diffplug.common.io.Resources;

public class ResourceHarness {
	/**
	 * On OS X, the temp folder is a symlink,
	 * and some of gradle's stuff breaks symlinks.
	 * By only accessing it through the {@link #rootFolder()}
	 * and {@link #newFile()} apis, we can guarantee there
	 * will be no symlink problems.
	 */
	@Rule
	public TemporaryFolder folderDontUseDirectly = new TemporaryFolder();

	/** Log nontruncated diff in case of a comparison failure to ease test development.*/
	@Rule
	public TestWatcher logComparisonFailureDiff = new TestWatcher() {
		private static final String COMPARISON_SEPARATOR = "------------------------------------";

		@Override
		protected void failed(Throwable e, Description description) {
			if (e instanceof ComparisonFailure) {
				ComparisonFailure failure = (ComparisonFailure) e;
				String msg = "";
				msg += String.format("Output:  %n%1$s%n%2$s%n%1$s%n", COMPARISON_SEPARATOR, failure.getActual());
				msg += String.format("Expected:%n%1$s%n%2$s%n%1$s%n", COMPARISON_SEPARATOR, failure.getExpected());
				logFailure(msg, description);
			}

		}

		private void logFailure(String message, Description description) {
			Logger log = Logger.getLogger(description.getClassName());
			log.warning(String.format("Step '%s' failed.%n%s", description.getDisplayName(), message));
		}

	};

	/** Returns the root folder (canonicalized to fix OS X issue) */
	protected File rootFolder() throws IOException {
		return folderDontUseDirectly.getRoot().getCanonicalFile();
	}

	/** Returns a new child of the root folder. */
	protected File newFile(String subpath) throws IOException {
		return new File(rootFolder(), subpath);
	}

	/** Returns a random child of the root folder. */
	protected File newFile() throws IOException {
		return folderDontUseDirectly.newFile().getCanonicalFile();
	}

	/** Writes the given content to the given path. */
	protected File write(String path, String... lines) throws IOException {
		return write(path, LineEnding.UNIX, lines);
	}

	protected File write(String path, LineEnding ending, String... lines) throws IOException {
		return write(path, ending, StandardCharsets.UTF_8, lines);
	}

	protected File write(String path, LineEnding ending, Charset encoding, String... lines) throws IOException {
		String content = Arrays.stream(lines).collect(Collectors.joining(ending.str())) + ending.str();
		Path target = newFile(path).toPath();
		Files.createDirectories(target.getParent());
		Files.write(target, content.getBytes(encoding));
		return target.toFile();
	}

	protected String read(Path path) throws IOException {
		return read(path, LineEnding.UNIX);
	}

	protected String read(String path) throws IOException {
		return read(path, LineEnding.UNIX);
	}

	protected String read(String path, LineEnding ending) throws IOException {
		return read(path, ending, StandardCharsets.UTF_8);
	}

	protected String read(Path path, LineEnding ending) throws IOException {
		return read(path, ending, StandardCharsets.UTF_8);
	}

	protected String read(String path, LineEnding ending, Charset encoding) throws IOException {
		Path target = newFile(path).toPath();
		return read(target, ending, encoding);
	}

	protected String read(Path path, LineEnding ending, Charset encoding) throws IOException {
		String content = new String(Files.readAllBytes(path), encoding);
		String allUnixNewline = LineEnding.toUnix(content);
		return allUnixNewline.replace("\n", ending.str());
	}

	protected void replace(String path, String toReplace, String replaceWith) throws IOException {
		String before = read(path);
		String after = before.replace(toReplace, replaceWith);
		if (before.equals(after)) {
			throw new IllegalArgumentException("Replace was ineffective! '" + toReplace + "' was not found in " + path);
		}
		write(path, after);
	}

	/** Returns the contents of the given file from the src/test/resources directory. */
	protected static String getTestResource(String filename) throws IOException {
		URL url = ResourceHarness.class.getResource("/" + filename);
		if (url == null) {
			throw new IllegalArgumentException("No such resource " + filename);
		}
		return Resources.toString(url, StandardCharsets.UTF_8);
	}

	/** Returns Files (in a temporary folder) which has the contents of the given file from the src/test/resources directory. */
	protected List<File> createTestFiles(String... filenames) throws IOException {
		List<File> files = new ArrayList<>(filenames.length);
		for (String filename : filenames) {
			files.add(createTestFile(filename));
		}
		return files;
	}

	/** Returns a File (in a temporary folder) which has the contents of the given file from the src/test/resources directory. */
	protected File createTestFile(String filename) throws IOException {
		int lastSlash = filename.lastIndexOf('/');
		String name = lastSlash >= 0 ? filename.substring(lastSlash) : filename;
		File file = newFile(name);
		file.getParentFile().mkdirs();
		Files.write(file.toPath(), getTestResource(filename).getBytes(StandardCharsets.UTF_8));
		return file;
	}

	/** Returns a File (in a temporary folder) which has the given contents. */
	protected File createTestFile(String filename, String content) throws IOException {
		File file = newFile(filename);
		file.getParentFile().mkdirs();
		Files.write(file.toPath(), content.getBytes(StandardCharsets.UTF_8));
		return file;
	}

	/** Asserts that the given resource from the src/test/resources directory has the same content as the given file. */
	protected void assertFileContent(String expectedContent, File actual) throws IOException {
		// This line thing is necessary for the tests to pass when Windows git screws up the line-endings
		String actualContent = new String(Files.readAllBytes(actual.toPath()), StandardCharsets.UTF_8);
		Assert.assertEquals(expectedContent, actualContent);
	}

	/** Reads the given resource from "before", applies the step, and makes sure the result is "after". */
	protected void assertOnResources(FormatterStep step, String unformattedPath, String expectedPath) throws Throwable {
		assertOnResources(rawUnix -> step.format(rawUnix, new File("")), unformattedPath, expectedPath);
	}

	/** Reads the given resource from "before", applies the step, and makes sure the result is "after". */
	protected void assertOnResources(FormatterFunc step, String unformattedPath, String expectedPath) throws Throwable {
		String unformatted = LineEnding.toUnix(getTestResource(unformattedPath)); // unix-ified input
		String formatted = step.apply(unformatted);
		// no windows newlines
		Assert.assertEquals(-1, formatted.indexOf('\r'));

		// unix-ify the test resource output in case git screwed it up
		String expected = LineEnding.toUnix(getTestResource(expectedPath)); // unix-ified output
		Assert.assertEquals(expected, formatted);
	}
}
