/*
 * Copyright 2017 the original author or authors.
 *
 * 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 gradle_clojure.plugin.internal;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.inject.Inject;

import org.projectodd.shimdandy.ClojureRuntimeShim;

public class ClojureWorker implements Runnable {
  private static ClojureRuntime existingRuntime;

  private final String namespace;
  private final String function;
  private final Object[] args;

  @Inject
  public ClojureWorker(String namespace, String function, Object[] args) {
    this.namespace = namespace;
    this.function = function;
    this.args = args;
  }

  @Override
  public void run() {
    ClojureRuntime runtime = ClojureRuntime.get(existingRuntime);
    ClojureRuntimeShim shim = runtime.getShim();

    // (require namespace)
    shim.require(namespace);

    // (apply namespace/function args)
    Object sym = shim.invoke("clojure.core/symbol", namespace, function);
    Object var = shim.invoke("clojure.core/find-var", sym);
    shim.invoke("clojure.core/apply", var, args);

    // save for possible reuse next time
    existingRuntime = runtime;
  }

  private static class ClojureRuntime implements AutoCloseable {
    private final URLClassLoader loader;
    private final ClojureRuntimeShim shim;
    private final Set<Path> classFiles;

    private ClojureRuntime(URLClassLoader loader, ClojureRuntimeShim shim, Set<Path> classFiles) {
      this.loader = loader;
      this.shim = shim;
      this.classFiles = classFiles;
    }

    public ClojureRuntimeShim getShim() {
      return shim;
    }

    @Override
    public void close() {
      try {
        shim.close();
        loader.close();
      } catch (IOException e) {
        // don't care
      }
    }

    public static ClojureRuntime get(ClojureRuntime runtime) {
      String[] classpathElements = System.getProperty("shim.classpath").split(File.pathSeparator);
      Set<Path> classFiles = Arrays.stream(classpathElements)
          .map(Paths::get)
          .filter(Files::isDirectory)
          .flatMap(safe(Files::walk))
          .filter(path -> path.getFileName().toString().endsWith(".class"))
          .collect(Collectors.toSet());

      if (runtime == null || !classFiles.containsAll(runtime.classFiles)) {
        System.out.println("Creating fresh Clojure runtime.");
        if (runtime != null) {
          runtime.close();
        }

        URL[] classpathUrls = Arrays.stream(classpathElements)
            .map(Paths::get)
            .map(Path::toUri)
            .map(safe(URI::toURL))
            .toArray(size -> new URL[size]);

        URLClassLoader loader = new ClojureWorkerClassLoader(classpathUrls, ClojureWorker.class.getClassLoader());
        ClojureRuntimeShim shim = ClojureRuntimeShim.newRuntime(loader, "gradle-clojure");
        return new ClojureRuntime(loader, shim, classFiles);
      } else {
        System.out.println("Reusing existing Clojure runtime.");
        return runtime;
      }
    }

    private static <T, R> Function<T, R> safe(FunctionThrows<T, R> fun) {
      return fun;
    }

    private static interface FunctionThrows<T, R> extends Function<T, R> {
      @Override
      default R apply(T arg) {
        try {
          return applyThrows(arg);
        } catch (Exception e) {
          sneakyThrows(e);
          return null;
        }
      }

      R applyThrows(T arg) throws Exception;
    }

    @SuppressWarnings("unchecked")
    private static <T extends Throwable> void sneakyThrows(Throwable t) throws T {
      throw (T) t;
    }
  }
}

@@ -1111,58 +1111,8 @@
 e %7B%0A
-  private static ClojureRuntime existingRuntime;%0A%0A
   pr
@@ -1414,24 +1414,29 @@
 run() %7B%0A    
+try (
 ClojureRunti
@@ -1471,26 +1471,15 @@
 get(
-existingRuntime);%0A
+)) %7B%0A  
     
@@ -1524,24 +1524,26 @@
 him();%0A%0A    
+ 
+ 
 // (require 
@@ -1553,16 +1553,18 @@
 espace)%0A
+  
     shim
@@ -1585,24 +1585,26 @@
 pace);%0A%0A    
+ 
+ 
 // (apply na
@@ -1626,16 +1626,18 @@
 n args)%0A
+  
     Obje
@@ -1702,24 +1702,26 @@
 ction);%0A    
+  
 Object var =
@@ -1764,24 +1764,26 @@
 , sym);%0A    
+  
 shim.invoke(
@@ -1820,80 +1820,13 @@
 s);%0A
-%0A
     
-// save for possible reuse next time%0A    existingRuntime = runtime;
+%7D
 %0A  %7D
@@ -1979,48 +1979,8 @@
 him;
-%0A    private final Set%3CPath%3E classFiles;
 %0A%0A  
@@ -2054,30 +2054,8 @@
 shim
-, Set%3CPath%3E classFiles
 ) %7B%0A
@@ -2110,44 +2110,8 @@
 im;%0A
-      this.classFiles = classFiles;%0A
     
@@ -2385,38 +2385,16 @@
 ime get(
-ClojureRuntime runtime
 ) %7B%0A    
@@ -2492,500 +2492,8 @@
 r);%0A
-      Set%3CPath%3E classFiles = Arrays.stream(classpathElements)%0A          .map(Paths::get)%0A          .filter(Files::isDirectory)%0A          .flatMap(safe(Files::walk))%0A          .filter(path -%3E path.getFileName().toString().endsWith(%22.class%22))%0A          .collect(Collectors.toSet());%0A%0A      if (runtime == null %7C%7C !classFiles.containsAll(runtime.classFiles)) %7B%0A        System.out.println(%22Creating fresh Clojure runtime.%22);%0A        if (runtime != null) %7B%0A          runtime.close();%0A        %7D%0A%0A  
     
@@ -2551,34 +2551,32 @@
 ents)%0A          
- 
- 
 .map(Paths::get)
@@ -2576,18 +2576,16 @@
 s::get)%0A
-  
         
@@ -2604,18 +2604,16 @@
 :toUri)%0A
-  
         
@@ -2647,18 +2647,16 @@
         
-  
 .toArray
@@ -2677,26 +2677,24 @@
 RL%5Bsize%5D);%0A%0A
-  
       URLCla
@@ -2796,26 +2796,24 @@
 r());%0A      
- 
- 
 ClojureRunti
@@ -2885,26 +2885,24 @@
 re%22);%0A      
-  
 return new C
@@ -2931,134 +2931,10 @@
 shim
-, classFiles);%0A      %7D else %7B%0A        System.out.println(%22Reusing existing Clojure runtime.%22);%0A        return runtime;%0A      %7D
+);
 %0A   

Stop reusing Clojure runtimes in workers