package edu.tamu.tcat.hathitrust.htrc.features.simple.impl;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

import edu.tamu.tcat.hathitrust.HathiTrustClientException;
import edu.tamu.tcat.hathitrust.htrc.features.simple.ExtractedFeatures;
import edu.tamu.tcat.hathitrust.htrc.features.simple.ExtractedFeaturesProvider;

public class DefaultExtractedFeaturesProvider implements ExtractedFeaturesProvider, AutoCloseable
{
   private static final Logger debug = Logger.getLogger(DefaultExtractedFeaturesProvider.class.getName());
   private static final String TYPE_BASIC = "basic";
   private static final String TYPE_ADVANCED = "advanced";

   private final AtomicBoolean isDisposed = new AtomicBoolean(false);
   private final ConcurrentHashMap<String, DefaultExtractedFeatures> cache;
   private final Path root;

   private final ExecutorService exec;

   public DefaultExtractedFeaturesProvider(Path root)
   {
      this.root = root;
      cache = new ConcurrentHashMap<>();
      exec = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("def ext feat %1$d").build());
   }

   @Override
   public String toString()
   {
      return "provider[" + root + "]";
   }

   @Override
   public void close() throws Exception
   {
      debug.fine("Closing provider");
      // prevent any new cache entries from being created
      isDisposed.set(true);

      exec.shutdownNow();

      cache.forEachValue(1, ef ->
      {
         try
         {
            ef.close();
         }
         catch (Exception e)
         {
            debug.log(Level.SEVERE, "Error disposing [" + ef + "]", e);
         }
      });
      if (!cache.isEmpty())
      {
         debug.log(Level.SEVERE, "Provider had " + cache.size() + " dangling cache entries");
         cache.clear();
      }
   }

   public void closed(DefaultExtractedFeatures ch)
   {
      if (isDisposed.get())
         return;

      String vid = ch.getVolumeId();
      DefaultExtractedFeatures ef = cache.remove(vid);
      if (ef == null)
      {
         debug.log(Level.WARNING, "Extracted features closing but not in cache [" + vid + "]");
         return;
      }
   }

   /**
    * Given the volume-id, type (currently "basic" or "advanced"), and internal root path,
    * provide a {@link Path} to the requested .json.bz2 file if it exists.
    * 
    * @param htrcVolumeId
    * @param type
    * @return The requested path, or {@code null} if the file does not exist.
    * @throws HathiTrustClientException
    * 
    * @deprecated Use proper PairTree implementation
    */
   @Deprecated
   private Path getArchivePath(String htrcVolumeId, String type) throws HathiTrustClientException
   {
      // Volume-id looks like "xxx.123456" and needs to be split into
      // "xxx", "12", "34", "56", "xxx.123456.basic.json.bz2" parts

      String[] strs = htrcVolumeId.split(Pattern.quote("."));
      String src = strs[0];
      String tail = strs[1];

      List<String> pairs = new ArrayList<>();
      for (int i = 0; i < tail.length(); i += 2)
      {
         pairs.add(tail.substring(i, i + 2));
      }

      Path p = root.resolve(type).resolve(src).resolve("pairtree_root");
      for (String part : pairs)
         p = p.resolve(part);

      p = p.resolve(tail);
      Path file = p.resolve(htrcVolumeId + "." + type + ".json.bz2");
      if (!Files.exists(file))
         return null;

      return file;
   }

   @Override
   public ExtractedFeatures getExtractedFeatures(String htrcVolumeId) throws HathiTrustClientException
   {
      if (isDisposed.get())
         throw new IllegalStateException("Provider is disposed");

      Path basic = getArchivePath(htrcVolumeId, TYPE_BASIC);
      Path advanced = getArchivePath(htrcVolumeId, TYPE_ADVANCED);

      // These are cheap enough to create and destroy if already in the cache
      DefaultExtractedFeatures ef = new DefaultExtractedFeatures(this, htrcVolumeId, basic, advanced);
      DefaultExtractedFeatures old = cache.putIfAbsent(htrcVolumeId, ef);
      if (old != null)
         // return 'old', don't bother calling "ef.close()"
         return old;

      ef.load(exec);
      return ef;
   }
}

@@ -113,59 +113,8 @@
 th;%0A
-import java.util.ArrayList;%0Aimport java.util.List;%0A
 impo
@@ -156,16 +156,16 @@
 ashMap;%0A
+
 import j
@@ -358,40 +358,8 @@
 ger;
-%0Aimport java.util.regex.Pattern;
 %0A%0Aim
@@ -547,24 +547,24 @@
 edFeatures;%0A
-
 import edu.t
@@ -630,16 +630,56 @@
 rovider;
+%0Aimport edu.tamu.tcat.pairtree.Pairtree;
 %0A%0Apublic
@@ -2861,84 +2861,9 @@
    *
- %0A    * @deprecated Use proper PairTree implementation%0A    */%0A   @Deprecated
+/
 %0A   
@@ -3104,27 +3104,30 @@
 rts%0A
+      
 %0A      
-String%5B%5D str
+int sepPo
 s = 
@@ -3143,249 +3143,320 @@
 eId.
-split(Pattern.quote(%22.%22));%0A      String src = strs%5B0%5D;%0A      String tail = strs%5B1%5D;%0A
+indexOf('.');%0A      if (sepPos %3C 0)%0A         throw new IllegalArgumentException(%22Parameter does not 'look like' a volume id %5B%22+htrcVolumeId+%22%5D%22);%0A      
 %0A      
-List%3C
 String
-%3E pairs = new ArrayList%3C%3E();%0A      for (int i = 0; i %3C tail.length(); i += 2)
+ src = htrcVolumeId.substring(0, sepPos);%0A      String objId = htrcVolumeId.substring(sepPos+1);
 %0A      
-%7B
 %0A      
-   pairs.add(tail.substring(i, i + 2));%0A      %7D
+Path ppath = Pairtree.toPPath(objId);
 %0A%0A  
@@ -3464,17 +3464,20 @@
    Path 
-p
+file
  = root.
@@ -3493,21 +3493,67 @@
 ype)
-.resolve(src)
+%0A                      .resolve(src)%0A                      
 .res
@@ -3577,42 +3577,19 @@
 ot%22)
-;
 %0A      
-for (String part : pairs)%0A
+    
     
@@ -3589,29 +3589,27 @@
             
-p = p
+   
 .resolve(par
@@ -3610,26 +3610,36 @@
 ve(p
-art);%0A
+path)
 %0A      
-p = p
+                
 .res
@@ -3647,34 +3647,37 @@
 lve(
-tail);
+objId)
 %0A      
-Path file = p
+                
 .res

Use Pairtree lib to properly resolve volume paths