package com.github.sormuras.bach.project;

import java.io.StringReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;

/** An external module locator tries to link a module name to a remote location. */
@FunctionalInterface
public interface ExternalModuleLocator {

  String locate(String module);

  default Optional<String> find(String module) {
    return Optional.ofNullable(locate(module));
  }

  default String caption() {
    return getClass().getSimpleName();
  }

  record SingleExternalModuleLocator(String module, String uri) implements ExternalModuleLocator {
    @Override
    public String caption() {
      return "SingleExternalModuleLocator: " + module + " -> " + uri;
    }

    @Override
    public String locate(String module) {
      return module().equals(module) ? uri : null;
    }
  }

  record MultiExternalModuleLocator(Map<String, String> map) implements ExternalModuleLocator {
    @Override
    public String caption() {
      return "MultiExternalModuleLocator for %d module%s"
          .formatted(map.size(), map.size() == 1 ? "" : "s");
    }

    @Override
    public String locate(String module) {
      return map.get(module);
    }
  }

  class SormurasBachExternalModulesProperties implements ExternalModuleLocator {

    public static ExternalModuleLocator of(String library, String version, String... classifiers) {
      var url = new StringBuilder();
      url.append("https://github.com/sormuras/bach-external-modules/raw/main/properties");
      url.append('/').append(library);
      url.append('/').append(library).append('@').append(version);
      for (var classifier : classifiers) url.append('-').append(classifier);
      url.append("-modules.properties");
      return new SormurasBachExternalModulesProperties(url.toString());
    }

    private final String url;
    private volatile Properties properties;

    public SormurasBachExternalModulesProperties(String url) {
      this.url = url;
      this.properties = null;
    }

    @Override
    public String locate(String module) {
      if (properties == null) {
        try (var in = new URL(url).openStream()) {
          var text = new String(in.readAllBytes(), StandardCharsets.UTF_8);
          var temp = new Properties();
          temp.load(new StringReader(text));
          properties = temp; // last threads wins...
        } catch (Exception exception) {
          throw new RuntimeException(exception);
        }
      }
      return properties.getProperty(module);
    }
  }
}

@@ -205,16 +205,42 @@
 perties;
+%0Aimport java.util.TreeMap;
 %0A%0A/** An
@@ -515,16 +515,84 @@
 );%0A  %7D%0A%0A
+  default Map%3CString, String%3E findAll() %7B%0A    return Map.of();%0A  %7D%0A%0A
   defaul
@@ -984,24 +984,122 @@
  null;%0A    %7D
+%0A%0A    @Override%0A    public Map%3CString, String%3E findAll() %7B%0A      return Map.of(module, uri);%0A    %7D
 %0A  %7D%0A%0A  reco
@@ -1442,32 +1442,114 @@
 t(module);%0A    %7D
+%0A%0A    @Override%0A    public Map%3CString, String%3E findAll() %7B%0A      return map;%0A    %7D
 %0A  %7D%0A%0A  class So
@@ -2380,36 +2380,147 @@
  String 
-locate(String module
+caption() %7B%0A      return %22SormurasBachExternalModulesProperties -%3E %22 + url;%0A    %7D%0A%0A    private void ensurePropertiesAreInitialized(
 ) %7B%0A    
@@ -2540,17 +2540,17 @@
 ies 
-=
+!
 = null) 
 %7B%0A  
@@ -2545,20 +2545,24 @@
 = null) 
-%7B%0A  
+return;%0A
       tr
@@ -2606,26 +2606,24 @@
 ) %7B%0A        
- 
- 
 var text = n
@@ -2680,26 +2680,24 @@
 8);%0A        
-  
 var temp = n
@@ -2721,18 +2721,16 @@
         
- 
- 
 temp.loa
@@ -2756,18 +2756,16 @@
 text));%0A
-  
         
@@ -2813,18 +2813,16 @@
 .%0A      
- 
- 
 %7D catch 
@@ -2841,26 +2841,24 @@
 xception) %7B%0A
-  
         thro
@@ -2900,65 +2900,425 @@
     
+  %7D%0A
     %7D%0A
+%0A
     
-  %7D%0A      return properties.getProperty(module)
+@Override%0A    public String locate(String module) %7B%0A      ensurePropertiesAreInitialized();%0A      return properties.getProperty(module);%0A    %7D%0A%0A    @Override%0A    public Map%3CString, String%3E findAll() %7B%0A      ensurePropertiesAreInitialized();%0A      var map = new TreeMap%3CString, String%3E();%0A      properties.stringPropertyNames().forEach(key -%3E map.put(key, properties.getProperty(key)));%0A      return map
 ;%0A  

Add best-effort find all module-location pairs accessor