package com.github.tornaia.lsr.matcher;

import com.google.common.collect.Lists;
import org.apache.commons.lang.ArrayUtils;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static org.junit.Assert.*;

/**
 * Assertion for recursively testing directories.
 */
public class DirectoryMatcher {

    private DirectoryMatcher() {
    }

    /**
     * Asserts that two directories are recursively equal. If they are not, an {@link AssertionError} is thrown with the
     * given message.<br/>
     * There will be a binary comparison of all files under expected with all files under actual. File attributes will
     * not be considered.<br/>
     * Missing or additional files are considered an error.<br/>
     *
     * @param expected Path expected directory
     * @param actual   Path actual directory
     */
    public static void assertPathEqualsRecursively(final Path expected, final Path actual) {
        assertNotNull(expected);
        assertNotNull(actual);
        final Path absoluteExpected = expected.toAbsolutePath();
        final Path absoluteActual = actual.toAbsolutePath();
        try {
            Files.walkFileTree(expected, new FileVisitor<Path>() {

                @Override
                public FileVisitResult preVisitDirectory(Path expectedDir, BasicFileAttributes attrs) throws IOException {
                    Path relativeExpectedDir = absoluteExpected.relativize(expectedDir.toAbsolutePath());
                    Path actualDir = absoluteActual.resolve(relativeExpectedDir);

                    if (!Files.exists(actualDir)) {
                        fail(String.format("Directory \'%s\' missing from target \'%s\'.", expectedDir.getFileName(), actualDir.getFileName()));
                    }

                    assertEquals(String.format("Directory size of \'%s\' and \'%s\' differ. ", relativeExpectedDir, actualDir), expectedDir.toFile().list().length, actualDir.toFile().list().length);

                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult visitFile(Path expectedFile, BasicFileAttributes attrs) throws IOException {
                    Path relativeExpectedFile = absoluteExpected.relativize(expectedFile.toAbsolutePath());
                    Path actualFile = absoluteActual.resolve(relativeExpectedFile);

                    if (!Files.exists(actualFile)) {
                        fail(String.format("File \'%s\' missing in target.", expectedFile.getFileName()));
                    }

                    String expectedFileAbsolutePath = expectedFile.toFile().getAbsolutePath();
                    String actualFileAbsolutePath = actualFile.toFile().getAbsolutePath();
                    byte[] expectedFileContent = filterCarriageReturn(Files.readAllBytes(expectedFile));
                    byte[] actualFileContent = filterCarriageReturn(Files.readAllBytes(actualFile));
                    long expectedFileSize = expectedFileContent.length;
                    long actualFileSize = actualFileContent.length;

                    assertEquals(String.format("File size of \'%s\' and \'%s\' differ. ", expectedFileAbsolutePath, actualFileAbsolutePath), expectedFileSize, actualFileSize);
                    assertArrayEquals(String.format("File content of \'%s\' and \'%s\' differ. ", expectedFileAbsolutePath, actualFileAbsolutePath), expectedFileContent, actualFileContent);
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                    fail(exc.getMessage());
                    return FileVisitResult.TERMINATE;
                }

                @Override
                public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                    return FileVisitResult.CONTINUE;
                }

            });
        } catch (IOException e) {
            fail(e.getMessage());
        }
    }

    private static byte[] filterCarriageReturn(byte[] bytes) {
        List<Byte> expectedFileContentAsByteList = Lists.newArrayList(ArrayUtils.toObject(bytes));
        List<Byte> filteredFileContentAsByteList = expectedFileContentAsByteList.stream().filter(b -> b != 13).collect(Collectors.toList());
        return ArrayUtils.toPrimitive(filteredFileContentAsByteList.toArray(new Byte[0]));
    }
}
@@ -114,24 +114,45 @@
 rrayUtils;%0A%0A
+import java.io.File;%0A
 import java.
@@ -1556,24 +1556,22 @@
 ry(Path 
-expected
+actual
 Dir, Bas
@@ -1690,24 +1690,22 @@
 ativize(
-expected
+actual
 Dir.toAb
@@ -1744,22 +1744,24 @@
    Path 
-actual
+expected
 Dir = ab
@@ -1842,22 +1842,24 @@
 .exists(
-actual
+expected
 Dir)) %7B%0A
@@ -1916,34 +1916,42 @@
 ory 
-%5C'%25s%5C' missing from target
+not found. Expected %5C'%25s%5C', actual
  %5C'%25
@@ -1973,106 +1973,343 @@
 Dir.
-ge
 t
+o
 File
-Name(), actualDir.getFileName()));%0A                    %7D%0A%0A                    assertEquals(
+().getAbsolutePath(), actualDir.toFile().getAbsolutePath()));%0A                    %7D%0A%0A                    String expectedAbsPath = expectedDir.toFile().getAbsolutePath();%0A                    String actualAbsPath = absoluteExpected.resolve(relativeExpectedDir).toFile().getAbsolutePath();%0A                    String errorMessage = 
 Stri
@@ -2338,126 +2338,190 @@
 ize 
-of
+mismatch. Expected
  %5C'%25s%5C'
+,
  a
-nd
+ctual
  %5C'%25s%5C'
- differ. %22, relativeExpectedDir, actualDir), expectedDir.toFile().list().length, actualDir.toFile(
+%22, expectedDir, actualAbsPath);%0A                    assertEquals(errorMessage, new File(expectedAbsPath).list().length, new File(actualAbsPath
 ).li

Improve output on error