package jycessing;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.python.core.Py;
import org.python.core.PyObject;
import org.python.core.PyStringMap;
import org.python.google.common.base.Joiner;
import org.python.util.InteractiveConsole;

/**
 * {@link LibraryImporter} adds the add_library function to processing.py.
 * 
 * <p>add_library causes the given Processing library (all of its jar files and
 * library subdirectories potentially containing native code) to be added to the
 * Jython sys.path, and generates import statements bringing all top-level
 * classes available in the main library jar file into the sketch's namespace.
 * 
 * @author feinberg
 */
class LibraryImporter {
  private final File libdir;
  private final InteractiveConsole interp;
  private final Set<String> loadedLibs = new HashSet<String>();

  private boolean didAddLibraries = false;

  public LibraryImporter(File libdir, InteractiveConsole interp) {
    this.libdir = libdir;
    this.interp = interp;
  }

  public void initialize() {
    final PyStringMap builtins = (PyStringMap)interp.getSystemState().getBuiltins();
    builtins.__setitem__("add_library", new PyObject() {
      @Override
      public PyObject __call__(final PyObject[] args, final String[] kws) {
        addLibrary(args[0].asString());
        return Py.None;
      }
    });
  }

  private void appendToSysPath(final File file) {
    interp.exec(String.format("sys.path.append(\"%s\")\n", file.getAbsolutePath()));
    if (file.isDirectory()) {
      for (final File f : file.listFiles()) {
        if (f.isDirectory() || f.getName().endsWith(".jar")) {
          appendToSysPath(f);
        }
      }
    }
  }

  protected void addLibrary(final String libName) {
    didAddLibraries = true;

    if (loadedLibs.contains(libName)) {
      return;
    }
    loadedLibs.add(libName);

    final File libClassDir =
        new File(String.format("%s/%s/library", libdir.getAbsolutePath(), libName));

    appendToSysPath(libClassDir);

    final String jarPath = String.format("%s/%s.jar", libClassDir.getAbsolutePath(), libName);

    try {
      ZipFile file = new ZipFile(jarPath);
      Enumeration<? extends ZipEntry> entries = file.entries();
      while (entries.hasMoreElements()) {
        final ZipEntry entry = entries.nextElement();
        if (entry.isDirectory()) {
          continue;
        }
        final String name = entry.getName();
        if (!name.endsWith(".class")) {
          continue;
        }
        int slash = name.lastIndexOf('/');
        if (slash == -1) {
          continue;
        }
        if (name.contains("$")) {
          continue;
        }
        final String[] path = name.split("/");
        final String className = path[path.length - 1].replace(".class", "");
        final String packageName =
            Joiner.on(".").join(Arrays.asList(path).subList(0, path.length - 1));
        interp.exec(String.format("from %s import %s", packageName, className));
      }
      file.close();
    } catch (IOException e) {
      throw new RuntimeException("While trying to add " + libName + " library:", e);
    }
  }

  public boolean didAddLibraries() {
    return didAddLibraries;
  }
}
@@ -227,16 +227,51 @@
 pFile;%0A%0A
+import jycessing.mode.PythonMode;%0A%0A
 import o
@@ -897,16 +897,181 @@
 orter %7B%0A
+  private static void log(final String msg) %7B%0A    if (PythonMode.VERBOSE) %7B%0A      System.err.println(LibraryImporter.class.getSimpleName() + %22: %22 + msg);%0A    %7D%0A  %7D%0A%0A
   privat
@@ -1268,16 +1268,22 @@
 mporter(
+final 
 File lib
@@ -1286,16 +1286,22 @@
  libdir,
+ final
  Interac
@@ -1638,24 +1638,77 @@
 ng%5B%5D kws) %7B%0A
+        log(%22Adding library %22 + args%5B0%5D.asString());%0A
         addL
@@ -1830,36 +1830,63 @@
 file) %7B%0A    
+f
 in
-terp.exec(
+al String appendStatement =%0A        
 String.forma
@@ -1939,16 +1939,75 @@
 ePath())
+;%0A    log(appendStatement);%0A    interp.exec(appendStatement
 );%0A    i
@@ -2632,16 +2632,22 @@
 %7B%0A      
+final 
 ZipFile 
@@ -2681,16 +2681,22 @@
 ;%0A      
+final 
 Enumerat
@@ -2987,24 +2987,70 @@
 .class%22)) %7B%0A
+          log(%22Rejecting non-class %22 + name);%0A
           co
@@ -3075,16 +3075,22 @@
         
+final 
 int slas
@@ -3139,24 +3139,60 @@
 sh == -1) %7B%0A
+          log(%22Rejecting %22 + name);%0A
           co
@@ -3239,24 +3239,60 @@
 ins(%22$%22)) %7B%0A
+          log(%22Rejecting %22 + name);%0A
           co
@@ -3551,24 +3551,25 @@
  - 1));%0A
+%0A
         
 interp.e
@@ -3560,28 +3560,47 @@
         
+f
 in
-terp.exec(
+al String importStatement = 
 String.f
@@ -3649,16 +3649,83 @@
 assName)
+;%0A        log(importStatement);%0A        interp.exec(importStatement
 );%0A     
@@ -3760,16 +3760,22 @@
  catch (
+final 
 IOExcept
@@ -3949,8 +3949,9 @@
 s;%0A  %7D%0A%7D
+%0A

Add logging to LibraryImporter.