package de.sormuras.bach;

import java.lang.module.ModuleDescriptor.Version;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/*BODY*/
/** Build project. */
public /*STATIC*/ class ProjectBuilder {

  /** Supported properties. */
  public enum Property {
    /** Name of the project. */
    NAME("project"),

    /** Version of the project, consumable by {@link Version#parse(String)}. */
    VERSION("0");

    public final String key;
    public final String defaultValue;

    Property(String defaultValue) {
      this.key = name().toLowerCase();
      this.defaultValue = defaultValue;
    }
  }

  /** Create default project scanning the passed base directory. */
  public static Project build(Path base) {
    if (!Files.isDirectory(base)) {
      throw new IllegalArgumentException("Expected a directory but got: " + base);
    }
    return new Scanner(base).project();
  }

  static class Scanner {

    private final Path base;
    private final Properties properties;

    Scanner(Path base) {
      this.base = base;
      this.properties = Util.load(new Properties(), base.resolve(".bach").resolve(".properties"));
    }

    String get(Property property) {
      return get(property, property.defaultValue);
    }

    String get(Property property, String defaultValue) {
      return System.getProperty(property.key, properties.getProperty(property.key, defaultValue));
    }

    List<Project.ModuleUnit> units(Path src, String realm) {
      var units = new ArrayList<Project.ModuleUnit>();
      for (var module : Util.list(src, Files::isDirectory)) {
        var path = module.resolve(realm);
        if (Files.notExists(path)) {
          continue;
        }
        // jigsaw
        if (Files.isDirectory(path.resolve("java"))) {
          var info = Project.ModuleInfo.of(path.resolve("java").resolve("module-info.java"));
          var sources = List.of(Project.Source.of(path.resolve("java")));
          var resources = Util.findExistingDirectories(List.of(path.resolve("resources")));
          var mavenPom = path.resolve("maven").resolve("pom.xml");
          units.add(new Project.ModuleUnit(info, sources, resources, mavenPom));
          continue;
        }
        // multi-release
        if (!Util.list(path, "java-*").isEmpty()) {
          Project.ModuleInfo info = null;
          var sources = new ArrayList<Project.Source>();
          for (int feature = 7; feature <= Runtime.version().feature(); feature++) {
            var sourced = path.resolve("java-" + feature);
            if (Files.notExists(sourced)) {
              continue;
            }
            sources.add(Project.Source.of(sourced, feature));
            var infoPath = sourced.resolve("module-info.java");
            if (info == null && Util.isModuleInfo(infoPath)) { // select first
              info = Project.ModuleInfo.of(infoPath);
            }
          }
          var resources = Util.findExistingDirectories(List.of(path.resolve("resources")));
          var mavenPom = path.resolve("maven").resolve("pom.xml");
          units.add(new Project.ModuleUnit(info, sources, resources, mavenPom));
          continue;
        }
        throw new IllegalStateException("Failed to scan module: " + module);
      }
      return units;
    }

    Project.Realm realm(String name, Project.Realm... realms) {
      var units = units(base.resolve("src"), name);
      return Project.Realm.of(name, units, realms);
    }

    Project project() {
      var main = realm("main");
      var test = realm("test", main);
      return new Project(
          base,
          base.resolve("bin"),
          get(Property.NAME, Util.findFileName(base).orElse(Property.NAME.defaultValue)),
          Version.parse(get(Property.VERSION)),
          new Project.Library(base.resolve("lib")),
          List.of(main, test));
    }
  }
}

@@ -485,18 +485,88 @@
 ION(%220%22)
+,%0A%0A    /** Directory that contains all modules. */%0A    SRC_PATH(%22src%22)
 ;%0A
-
 %0A    pub
@@ -686,16 +686,34 @@
  name().
+replace('_', '-').
 toLowerC
@@ -1556,24 +1556,396 @@
 e));%0A    %7D%0A%0A
+    Project.ModuleInfo info(Path path) %7B%0A      for (var directory : List.of(%22java%22, %22module%22)) %7B%0A        var info = path.resolve(directory).resolve(%22module-info.java%22);%0A        if (Util.isModuleInfo(info)) %7B%0A          return Project.ModuleInfo.of(info);%0A        %7D%0A      %7D%0A      throw new IllegalArgumentException(%22Couldn't find module-info.java file in: %22 + path);%0A    %7D%0A%0A
     List%3CPro
@@ -2317,78 +2317,17 @@
 o = 
-Project.ModuleInfo.of(path.resolve(%22java%22).resolve(%22module-info.java%22)
+info(path
 );%0A 
@@ -3839,21 +3839,38 @@
 resolve(
-%22src%22
+get(Property.SRC_PATH)
 ), name)

Make module directory configurable