package test.base;

import static org.junit.jupiter.api.Assertions.assertLinesMatch;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

class FilesTests {

  record AnyPathMatcher(PathMatcher... matchers) implements Predicate<Path> {

    public static AnyPathMatcher of(String... patterns) {
      return of(Arrays.stream(patterns));
    }

    public static AnyPathMatcher ofFiles(String named, String... patterns) {
      return of(Arrays.stream(patterns).map(string -> string + '/' + named));
    }

    public static AnyPathMatcher of(Stream<String> patterns) {
      return new AnyPathMatcher(
          patterns
              .map(pattern -> pattern.indexOf(':') > 0 ? pattern : "glob:" + pattern)
              .map(syntaxAndPattern -> FileSystems.getDefault().getPathMatcher(syntaxAndPattern))
              .toArray(PathMatcher[]::new));
    }

    @Override
    public boolean test(Path path) {
      for (var matcher : matchers) if (matcher.matches(path)) return true;
      return false;
    }
  }

  @Nested
  class PathMatcherTests {

    @SuppressWarnings("unused")
    static List<Path> paths() {
      return List.of(
          // configuration module
          Path.of(".bach/bach.info/module-info.java"),
          // main module
          Path.of("com.github.sormuras.bach/main/java/module-info.java"),
          // test modules
          Path.of("com.github.sormuras.bach/test/java-module/module-info.java"),
          Path.of("test.base/test/java/module-info.java"),
          Path.of("test.integration/test/java/module-info.java"),
          Path.of("test.projects/test/java/module-info.java"),
          // sample project modules
          Path.of("test.projects/JigsawQuickStartGreetings/com.greetings/module-info.java"),
          Path.of("test.projects/JigsawQuickStartWorld/com.greetings/module-info.java"),
          Path.of("test.projects/JigsawQuickStartWorld/org.astro/module-info.java"),
          Path.of("test.projects/Simple/.bach/bach.info/module-info.java"),
          Path.of("test.projects/Simple/module-info.java"),
          Path.of("test.projects/Simplicissimus/module-info.java"));
    }

    @ParameterizedTest
    @MethodSource("paths")
    void isRegularFile(Path path) {
      assertTrue(Files.isRegularFile(path), path.toString());
    }

    @ParameterizedTest
    @MethodSource("paths")
    void matchesGlobAny(Path path) {
      var matcher = path.getFileSystem().getPathMatcher("glob:**/*");
      assertTrue(matcher.matches(path));
    }

    @ParameterizedTest
    @MethodSource("paths")
    void matchesRegexAny(Path path) {
      var matcher = path.getFileSystem().getPathMatcher("regex:.*");
      assertTrue(matcher.matches(path));
    }

    @Test
    void findTestModulesOfBachUsingSingleGlobPattern() {
      var matcher = FileSystems.getDefault().getPathMatcher("glob:**/test/**/module-info.java");
      assertTestModulesOfBach(matcher::matches);
    }

    @Test
    void findTestModulesOfBachUsingMultiPathMatcher() {
      var matcher = AnyPathMatcher.ofFiles("module-info.java", "*/test/java", "*/test/java-module");
      assertTestModulesOfBach(matcher);
    }

    void assertTestModulesOfBach(Predicate<Path> predicate) {
      assertLinesMatch(
          """
          com.github.sormuras.bach/test/java-module/module-info.java
          test.base/test/java/module-info.java
          test.integration/test/java/module-info.java
          test.projects/test/java/module-info.java
          """
              .lines(),
          paths().stream().filter(predicate).map(Path::toString).map(s -> s.replace('\\', '/')));
    }
  }
}

@@ -722,32 +722,44 @@
       return of(
+%22glob%22, %22%22, 
 Arrays.stream(pa
@@ -823,21 +823,24 @@
 (String 
-n
+fileN
 ame
-d
 , String
@@ -875,66 +875,54 @@
  of(
-Arrays.stream(patterns).map(string -%3E string + '/' + named
+%22glob%22, '/' + fileName, Arrays.stream(patterns
 ));%0A
@@ -964,16 +964,46 @@
 cher of(
+String syntax, String suffix, 
 Stream%3CS
@@ -1144,25 +1144,115 @@
 n : 
-%22glob:%22 + pattern
+syntax + ':' + pattern)%0A              .map(pattern -%3E pattern.endsWith(suffix) ? pattern : pattern + suffix
 )%0A  
@@ -2904,17 +2904,23 @@
 hesGlobA
-n
+rbitrar
 y(Path p
@@ -3116,17 +3116,23 @@
 esRegexA
-n
+rbitrar
 y(Path p
@@ -3247,32 +3247,253 @@
 (path));%0A    %7D%0A%0A
+    @ParameterizedTest%0A    @MethodSource(%22paths%22)%0A    void matchesGlobExactly(Path path) %7B%0A      var matcher = path.getFileSystem().getPathMatcher(%22glob:%22 + slashed(path));%0A      assertTrue(matcher.matches(path));%0A    %7D%0A%0A
     @Test%0A    vo
@@ -4322,34 +4322,105 @@
 map(
-Path::toString).map(s -%3E s
+FilesTests::slashed));%0A    %7D%0A  %7D%0A%0A  static String slashed(Path path) %7B%0A    return path.toString()
 .rep
@@ -4434,24 +4434,16 @@
 %5C', '/')
-));%0A    %7D
+;
 %0A  %7D%0A%7D%0A

Improve AnyPathMatcher's implementation