// Path: src/io/lacuna/bifurcan/IDurableCollection.java
// interface Fingerprint extends Comparable<Fingerprint> {
//   String ALGORITHM = "SHA-512";
//   int HASH_BYTES = 32;
// 
//   byte[] binary();
// 
//   default String toHexString() {
//     return Bytes.toHexString(ByteBuffer.wrap(binary()));
//   }
// 
//   default int compareTo(Fingerprint o) {
//     return Bytes.compareBuffers(
//         ByteBuffer.wrap(binary()),
//         ByteBuffer.wrap(o.binary())
//     );
//   }
// }
// 
// Path: src/io/lacuna/bifurcan/IDurableCollection.java
// interface Root {
//   void close();
// 
//   Path path();
// 
//   DurableInput.Pool bytes();
// 
//   Fingerprint fingerprint();
// 
//   DurableInput cached(DurableInput in);
// 
//   IMap<Fingerprint, Fingerprint> redirects();
// 
//   ISet<Fingerprint> dependencies();
// 
//   default DirectedAcyclicGraph<Fingerprint, Void> dependencyGraph() {
//     Function<Root, Iterable<Root>> deps = r -> () -> r.dependencies().stream().map(r::open).iterator();
//     DirectedAcyclicGraph<Fingerprint, Void> result = new DirectedAcyclicGraph<Fingerprint, Void>().linear();
//     for (Root r : Graphs.bfsVertices(this, deps)) {
//       deps.apply(r).forEach(d -> result.link(r.fingerprint(), d.fingerprint()));
//     }
//     return result.forked();
//   }
// 
//   Root open(Fingerprint dependency);
// 
//   default <T extends IDurableCollection> T decode(IDurableEncoding encoding) {
//     return (T) decodeCollection(encoding, this, bytes());
//   }
// }
// 
// Path: src/io/lacuna/bifurcan/utils/Functions.java
// public class Functions {
// 
//   private static class MemoizedFunction<U, V> implements Function<U, V> {
// 
//     private final LinearMap<U, V> cache = new LinearMap<>();
//     private final Function<U, V> f;
// 
//     MemoizedFunction(Function<U, V> f) {
//       this.f = f;
//     }
// 
//     @Override
//     public V apply(U u) {
//       return cache.getOrCreate(u, () -> f.apply(u));
//     }
//   }
// 
//   public static <U, V> Function<U, V> memoize(Function<U, V> f) {
//     if (f instanceof MemoizedFunction) {
//       return f;
//     } else {
//       return new MemoizedFunction<>(f);
//     }
//   }
// }


import io.lacuna.bifurcan.*;
import io.lacuna.bifurcan.IDurableCollection.Fingerprint;
import io.lacuna.bifurcan.IDurableCollection.Root;
import io.lacuna.bifurcan.durable.io.*;
import io.lacuna.bifurcan.utils.Functions;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
    LinearList<DurableInput> bufs = new LinearList<>();
    long offset = 0;
    while (offset < size) {
      long len = Math.min(Integer.MAX_VALUE, size - offset);
      bufs.addLast(map(file, offset, len));
      offset += len;
    }
    return DurableInput.from(bufs);
  }

  public static DurableInput cachedInput(DurableInput in) {
    ByteBuffer bytes = Bytes.allocate((int) in.size());
    in.duplicate().read(bytes);
    return new BufferInput((ByteBuffer) bytes.flip());
  }

  public static Root open(Path directory, Fingerprint fingerprint) {
    return open(directory.resolve(fingerprint.toHexString() + ".bfn"));
  }

  public static Root open(Path path) {
    AtomicReference<Function<Lookup, Root>> fn = new AtomicReference<>();
    fn.set(Functions.memoize(l -> open(
        path.getParent().resolve(l.fingerprint.toHexString() + ".bfn"),
        l.redirects,
        fn.get()
    )));
    return open(path, Map.empty(), fn.get());
  }

// Path: src/io/lacuna/bifurcan/IDurableCollection.java
// interface Fingerprint extends Comparable<Fingerprint> {
//   String ALGORITHM = "SHA-512";
//   int HASH_BYTES = 32;
// 
//   byte[] binary();
// 
//   default String toHexString() {
//     return Bytes.toHexString(ByteBuffer.wrap(binary()));
//   }
// 
//   default int compareTo(Fingerprint o) {
//     return Bytes.compareBuffers(
//         ByteBuffer.wrap(binary()),
//         ByteBuffer.wrap(o.binary())
//     );
//   }
// }
// 
// Path: src/io/lacuna/bifurcan/IDurableCollection.java
// interface Root {
//   void close();
// 
//   Path path();
// 
//   DurableInput.Pool bytes();
// 
//   Fingerprint fingerprint();
// 
//   DurableInput cached(DurableInput in);
// 
//   IMap<Fingerprint, Fingerprint> redirects();
// 
//   ISet<Fingerprint> dependencies();
// 
//   default DirectedAcyclicGraph<Fingerprint, Void> dependencyGraph() {
//     Function<Root, Iterable<Root>> deps = r -> () -> r.dependencies().stream().map(r::open).iterator();
//     DirectedAcyclicGraph<Fingerprint, Void> result = new DirectedAcyclicGraph<Fingerprint, Void>().linear();
//     for (Root r : Graphs.bfsVertices(this, deps)) {
//       deps.apply(r).forEach(d -> result.link(r.fingerprint(), d.fingerprint()));
//     }
//     return result.forked();
//   }
// 
//   Root open(Fingerprint dependency);
// 
//   default <T extends IDurableCollection> T decode(IDurableEncoding encoding) {
//     return (T) decodeCollection(encoding, this, bytes());
//   }
// }
// 
// Path: src/io/lacuna/bifurcan/utils/Functions.java
// public class Functions {
// 
//   private static class MemoizedFunction<U, V> implements Function<U, V> {
// 
//     private final LinearMap<U, V> cache = new LinearMap<>();
//     private final Function<U, V> f;
// 
//     MemoizedFunction(Function<U, V> f) {
//       this.f = f;
//     }
// 
//     @Override
//     public V apply(U u) {
//       return cache.getOrCreate(u, () -> f.apply(u));
//     }
//   }
// 
//   public static <U, V> Function<U, V> memoize(Function<U, V> f) {
//     if (f instanceof MemoizedFunction) {
//       return f;
//     } else {
//       return new MemoizedFunction<>(f);
//     }
//   }
// }


// Path: src/io/lacuna/bifurcan/durable/Roots.java
import io.lacuna.bifurcan.*;
import io.lacuna.bifurcan.IDurableCollection.Fingerprint;
import io.lacuna.bifurcan.IDurableCollection.Root;
import io.lacuna.bifurcan.durable.io.*;
import io.lacuna.bifurcan.utils.Functions;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;

    LinearList<DurableInput> bufs = new LinearList<>();
    long offset = 0;
    while (offset < size) {
      long len = Math.min(Integer.MAX_VALUE, size - offset);
      bufs.addLast(map(file, offset, len));
      offset += len;
    }
    return DurableInput.from(bufs);
  }

  public static DurableInput cachedInput(DurableInput in) {
    ByteBuffer bytes = Bytes.allocate((int) in.size());
    in.duplicate().read(bytes);
    return new BufferInput((ByteBuffer) bytes.flip());
  }

  public static Root open(Path directory, Fingerprint fingerprint) {
    return open(directory.resolve(fingerprint.toHexString() + ".bfn"));
  }

  public static Root open(Path path) {
    AtomicReference<Function<Lookup, Root>> fn = new AtomicReference<>();
    fn.set(Functions.memoize(l -> open(
        path.getParent().resolve(l.fingerprint.toHexString() + ".bfn"),
        l.redirects,
        fn.get()
    )));
    return open(path, Map.empty(), fn.get());
  }

  private static Root open(Path path, IMap<Fingerprint, Fingerprint> parentRebases, Function<Lookup, Root> roots) {