package edu.stanford.nlp.io;

import edu.stanford.nlp.util.Pair;

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Implements a file filter that examines a number in a filename to
 * determine acceptance.  This is useful for wanting to process ranges
 * of numbered files in collections where each file has some name, part
 * of which is alphabetic and constant, and part of which is numeric.
 * The test is evaluated based on the rightmost natural number found in
 * the filename string.  (It only looks in the final filename, not in other
 * components of the path.)  Number ranges are inclusive.
 * <p>
 * This filter can select multiple discontinuous ranges based on a format
 * similar to page selection ranges in various formatting software, such as
 * "34,52-65,67,93-95".  The constructor takes a String of this sort and
 * deconstructs it into a list of ranges.  The accepted syntax is:
 * <p>
 * ranges = range <br>
 * ranges = range "," ranges <br>
 * range = integer <br>
 * range = integer "-" integer
 * <p>
 * Whitespace will be ignored.  If the filter constructor is passed anything
 * that is not a list of numeric ranges of this sort, including being passed
 * an empty String, then an {@code IllegalArgumentException} will be
 * thrown.
 *
 * @author Christopher Manning
 * @version 2003/03/31
 */
public class NumberRangesFileFilter implements FileFilter {

  private List<Pair<Integer,Integer>> ranges = new ArrayList<>();
  private boolean recursively;


  /**
   * Sets up a NumberRangesFileFilter by specifying the ranges of numbers
   * to accept, and whether to also traverse
   * folders for recursive search.
   *
   * @param ranges  The ranges of numbers to accept (see class documentation)
   * @param recurse Whether to go into subfolders
   * @throws IllegalArgumentException If the String ranges does not
   *                                  contain a suitable ranges format
   */
  public NumberRangesFileFilter(String ranges, boolean recurse) {
    recursively = recurse;
    try {
      String[] ra = ranges.split(",");
      for (String range : ra) {
        String[] one = range.split("-");
        if (one.length > 2) {
          throw new IllegalArgumentException("Constructor argument not valid list of number ranges (too many hyphens): ");
        } else {
          int low = Integer.parseInt(one[0].trim());
          int high;
          if (one.length == 2) {
            high = Integer.parseInt(one[1].trim());
          } else {
            high = low;
          }
          Pair<Integer, Integer> p = new Pair<>(Integer.valueOf(low), Integer.valueOf(high));
          this.ranges.add(p);
        }
      }
    } catch (Exception e) {
      throw new IllegalArgumentException("Constructor argument not valid list of number ranges: " + ranges, e);
    }
  }


  /**
   * Checks whether a file satisfies the number range selection filter.
   * The test is evaluated based on the rightmost natural number found in
   * the filename string (proper, not including directories in a path).
   *
   * @param file The file
   * @return true If the file is within the ranges filtered for
   */
  public boolean accept(File file) {
    if (file.isDirectory()) {
      return recursively;
    } else {
      String filename = file.getName();
      return accept(filename);
    }
  }


  /**
   * Checks whether a String satisfies the number range selection filter.
   * The test is evaluated based on the rightmost natural number found in
   * the String.   Note that this is just evaluated on the String as given.
   * It is not trying to interpret it as a filename and to decide whether
   * the file exists, is a directory or anything like that.
   *
   * @param str The String to check for a number in
   * @return true If the String is within the ranges filtered for
   */
  public boolean accept(String str) {
    int k = str.length() - 1;
    char c = str.charAt(k);
    while (k >= 0 && !Character.isDigit(c)) {
      k--;
      if (k >= 0) {
        c = str.charAt(k);
      }
    }
    if (k < 0) {
      return false;
    }
    int j = k;
    c = str.charAt(j);
    while (j >= 0 && Character.isDigit(c)) {
      j--;
      if (j >= 0) {
        c = str.charAt(j);
      }
    }
    j++;
    k++;
    String theNumber = str.substring(j, k);
    int number = Integer.parseInt(theNumber);
    for (Pair<Integer,Integer> p : ranges) {
      int low = p.first().intValue();
      int high = p.second().intValue();
      if (number >= low && number <= high) {
        return true;
      }
    }
    return false;
  }


  @Override
  public String toString() {
    StringBuilder sb;
    if (recursively) {
      sb = new StringBuilder("recursively ");
    } else {
      sb = new StringBuilder();
    }
    for (Iterator<Pair<Integer,Integer>> it = ranges.iterator(); it.hasNext(); ) {
      Pair<Integer,Integer> p = it.next();
      int low = p.first().intValue();
      int high = p.second().intValue();
      if (low == high) {
        sb.append(low);
      } else {
        sb.append(low);
        sb.append('-');
        sb.append(high);
      }
      if (it.hasNext()) {
        sb.append(',');
      }
    }
    return sb.toString();
  }

}
