package com.github.jleyba.dossier;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterables.all;
import static com.google.common.collect.Iterables.isEmpty;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Lists.newArrayList;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;

import java.io.File;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * Utilities for working with {@link Path paths}.
 */
class Paths {
  private Paths() {}

  /**
   * Computes the relative path {@code from} one path {@code to} another. The
   * origin path is assumed to be a file.
   */
  static Path getRelativePath(Path from, Path to) {
    from = from.toAbsolutePath().getParent();
    to = to.toAbsolutePath();

    Path root = getCommonPrefix(ImmutableSet.<Path>of(from, to));
    Path pathToRoot = from.relativize(root);
    Path pathFromRoot = root.relativize(to);

    return pathToRoot.resolve(pathFromRoot).normalize();
  }

  /**
   * Returns the {@link Path} that represents the longest common prefix for the provided
   * {@code paths}. All paths will be resolved and normalized relative to the current working
   * directory before computing a common prefix.
   */
  static Path getCommonPrefix(Iterable<Path> paths) {
    Path pwd = new File(".").getAbsoluteFile().toPath();
    return getCommonPrefix(pwd, paths);
  }

  /**
   * Returns the {@link Path} that represents the longest common prefix for the provided
   * {@code paths}. All paths will be resolved and normalized relative to the given {@code root}
   * directory before computing a common prefix.
   *
   * <p>If all of the provided {@code paths} do not designate
   */
  static Path getCommonPrefix(Path root, Iterable<Path> paths) {
    if (isEmpty(paths)) {
      return root;
    }
    root = root.toFile().getAbsoluteFile().toPath();
    paths = transform(paths, normalizeRelativeTo(root));

    Path prefix = root.getRoot();
    Path shortest = Ordering.from(length()).min(paths);
    Iterator<Path> parts = shortest.iterator();
    while (parts.hasNext()) {
      Path possiblePrefix = prefix.resolve(parts.next());
      if (all(paths, startsWith(possiblePrefix))) {
        prefix = possiblePrefix;
      } else {
        break;
      }
    }
    return prefix;
  }

  /**
   * Expands the given directory path, collecting all of its descendant files that are accepted
   * by the filter.
   *
   * @param dir The directory to expand.
   * @param filter The filter to apply to the directory entries.
   * @return All of the files located under the directory accepted by the filter.
   * @throws IOException If an I/O error occurs.
   */
  static List<Path> expandDir(Path dir, DirectoryStream.Filter<Path> filter) throws IOException {
    checkArgument(Files.isDirectory(dir), "%s is not a directory", dir);
    List<Path> paths = new LinkedList<>();
    try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, filter)) {
      for (Path path : stream) {
        if (Files.isDirectory(path)) {
          paths.addAll(expandDir(path, filter));
        } else {
          paths.add(path);
        }
      }
    }
    return paths;
  }

  private static Predicate<Path> startsWith(final Path root) {
    return new Predicate<Path>() {
      @Override
      public boolean apply(Path input) {
        return input.startsWith(root);
      }
    };
  }

  private static Function<Path, Path> normalizeRelativeTo(final Path root) {
    return new Function<Path, Path>() {
      @Override
      public Path apply(Path input) {
        return root.resolve(input).normalize();
      }
    };
  }

  private static Comparator<Path> length() {
    return new Comparator<Path>() {
      @Override
      public int compare(Path o1, Path o2) {
        return Integer.compare(o1.getNameCount(), o2.getNameCount());
      }
    };
  }
}

@@ -273,68 +273,8 @@
 orm;
-%0Aimport static com.google.common.collect.Lists.newArrayList;
 %0A%0Aim
@@ -403,92 +403,8 @@
 et;%0A
-import com.google.common.collect.Iterables;%0Aimport com.google.common.collect.Lists;%0A
 impo
@@ -447,29 +447,8 @@
 g;%0A%0A
-import java.io.File;%0A
 impo
@@ -513,41 +513,8 @@
 am;%0A
-import java.nio.file.FileSystem;%0A
 impo
@@ -631,35 +631,8 @@
 or;%0A
-import java.util.Iterator;%0A
 impo
@@ -1571,33 +1571,55 @@
 d = 
-new 
 File
+Systems.getDefault().getPath
 (%22
-.
 %22).
-ge
 t
+o
 Absolute
 File
@@ -1610,33 +1610,24 @@
 ).toAbsolute
-File().to
 Path();%0A    
@@ -2119,35 +2119,16 @@
 t.to
-File().getAbsoluteFile().to
+Absolute
 Path
@@ -2287,30 +2287,24 @@
     
-Iterator%3C
+for (
 Path
-%3E
  part
-s =
+ :
  sho
@@ -2312,47 +2312,8 @@
 test
-.iterator();%0A    while (parts.hasNext()
 ) %7B%0A
@@ -2363,16 +2363,8 @@
 part
-s.next()
 );%0A 

Clean up path handling code.