package jp.ac.osaka_u.ist.sel.similarity.commons;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;

/**
 * Search files recursively.
 * 
 * Hint:
 * http://sattontanabe.blog86.fc2.com/blog-entry-55.html
 * 
 * @author ysk-ssk
 */
public class FileSearch {

	public static final int TYPE_FILE_OR_DIR = 1;
	public static final int TYPE_FILE = 2;
	public static final int TYPE_DIR = 3;

    /* TreeSet for sorting */
    private TreeSet<File> _set = new TreeSet<File>();
    
    /* hash for duplicated directories */
    private Set<String> _finishedSet = new HashSet<String>();

	/**
	 * Search target files from directoryPath recursively,
	 * and return a file object list.
	 * 
	 * Ex. 
	 * File[] files =listFiles("C:/filelist/", "*.java");
	 * This shows that search filelist dir recursively and
	 * get files which has a '.java' ext.
	 * 
	 * @param directoryPath, target dir path
	 * @param fileName target files which can include wild cards. 
	 * @return matched file object list
	 * @throws IOException
	 */
	public File[] listFiles(String directoryPath, String fileName) throws IOException {
		// convert '*' to regular expressions
		if (fileName != null) {
			fileName = fileName.replace(".", "\\.");
			fileName = fileName.replace("*", ".*");
		}
		Pattern pattern = Pattern.compile(fileName,Pattern.CASE_INSENSITIVE);
		listFiles(directoryPath, pattern, TYPE_FILE, true, 0);
		return _set.toArray(new File[_set.size()]);
	}

	/**
	 * Search target files from directoryPath recursively,
	 * and return a file object list.
	 * 
	 * Ex. 
	 * File[] files =listFiles("C:/filelist/", "*.java");
	 * This shows that search filelist dir recursively and
	 * get files which has a '.java' ext.
	 * 
	 * You can add constraints for pasted days from last modified.
	 * 
	 * Ex.
	 * File[] files = 
	 *         listFiles("C:/filelist/", "*.java",TYPE_FILE, true, 2);
	 * 
	 * @param directoryPath, target dir path
	 * @param fileName target file regular expressions which can include wild cards. 
	 * @param type 
	 *                TYPE_FILE_OR_DIR 
	 *                TYPE_FILE
	 *                TYPE_DIR
	 * @param isRecursive true if search recursively
	 * @param period pasted days from last modified.
	 *                0:non constraints
	 *                greater than 1:later files
	 *                less than -1:older files
	 * @return matched file object list
	 * @throws IOException
	 */
	public File[] listFiles(String directoryPath, 
			String fileName, int type, 
			boolean isRecursive, int period) throws IOException {
		if (fileName != null) {
			fileName = fileName.replace(".", "\\.");
			fileName = fileName.replace("*", ".*");
		}
		Pattern pattern = Pattern.compile(fileName,Pattern.CASE_INSENSITIVE);
		listFiles(directoryPath,pattern,type,isRecursive,period);
		return _set.toArray(new File[_set.size()]);
	}

	private void listFiles(String directoryPath, 
			Pattern fileNamePattern, int type, 
			boolean isRecursive, int period) throws IOException {
		File dir = new File(directoryPath);
        if (!dir.exists() || !dir.canRead()) {
            throw new IllegalArgumentException
            ("Denoted path [" + dir.getPath() + "] is not accessible.");
        }
		if (!dir.isDirectory()) {
			throw new IllegalArgumentException
			("Denoted path [" + dir.getPath() + "] is not a directory.");
		}
        String canonicalPath = dir.getCanonicalPath();
        if (_finishedSet.contains(canonicalPath)) {
            // has already listed.
            return;
        }
        _finishedSet.add(canonicalPath);
		
		File[] files = dir.listFiles();
		// if can not find the target path
		if (files == null) {
			throw new IllegalArgumentException
			("Denoted path [" + dir.getAbsolutePath() + 
			"] is not found.");
		}
		for (int i = 0; i < files.length; i++) {
			File file = files[i];
			addFile(type, fileNamePattern, _set, file, period);
			// if the target is a directory, get files recursively
			if (isRecursive && file.isDirectory()) {
				// get files recursively
				listFiles(file.getPath(), fileNamePattern, type, isRecursive, period);
			}
		}
		return;
	}

	/**
	 * if the target is a symbolic link, return true
	 * 
	 * @param currentDirectory
	 * @param file
	 * @return
	 * @throws IOException
	 *
	private boolean isSymbolicLink(File currentDirectory, File file) throws IOException {
		// detect if the target is really in the current directory or not
		// get the current directory canonical path
		String currentCanonicalPath = currentDirectory.getCanonicalPath();
		// get the canonical parent of the target file
		String canonicalPath = file.getCanonicalPath();
		File canonicalFile = new File(canonicalPath);
		File parentDirectory = canonicalFile.getParentFile();
		String parentCanonicalPath = parentDirectory.getCanonicalPath();
		if (currentCanonicalPath == null) {
			throw new IllegalArgumentException
			("Canonical path of denoted path [" + currentDirectory.getAbsolutePath() + 
			"] is not found.");
		}
		if (!currentCanonicalPath.equals(parentCanonicalPath)) {
			return true;
		}
		// detect if the target canonical name is the same or not
		String originalName = file.getName();
		String canonicalName = canonicalFile.getName();
		if (canonicalName == null) {
			throw new IllegalArgumentException
			("Canonical path of denoted path [" + file.getAbsolutePath() + 
			"] is not found.");
		}
		if (!canonicalName.equals(originalName)) {
			return true;
		}
		return false;
	}
	*/

	private void addFile(int type, Pattern pattern, TreeSet<File> set,
			File file,int period) throws IOException {
		switch (type) {
		case TYPE_FILE:
			if (!file.isFile()) {
				return;
			}
			break;
		case TYPE_DIR:
			if (!file.isDirectory()) {
				return;
			}
			break;
		}
		if (pattern != null && !pattern.matcher(file.getName()).matches()) {
			return;
		}
		// if constraints for modified time exist
		if (period != 0) {
			Date lastModifiedDate = new Date(file.lastModified());
			String lastModifiedDateStr = new SimpleDateFormat("yyyyMMdd")
			.format(lastModifiedDate);

			// get the number of days.
			long oneDayTime = 24L * 60L * 60L * 1000L; 
			long periodTime = oneDayTime * Math.abs(period);
			Date designatedDate = 
				new Date(System.currentTimeMillis() - periodTime);
			String designatedDateStr = new SimpleDateFormat("yyyyMMdd")
			.format(designatedDate);
			if (period > 0) {
				if (lastModifiedDateStr.compareTo(designatedDateStr) < 0) {
					return;
				}
			} else {
				if (lastModifiedDateStr.compareTo(designatedDateStr) > 0) {
					return;
				}
			}
		}
		set.add(file);

	}

	/**
	 * If you want to find new files, use this method.
	 * Ex.
	 *  FileSearch search = new FileSearch();
	 *  File[] f1 = search.listFiles(C:/filelist/", "*.java");
	 *  search.clear();
	 *  File[] f2 = search.listFiles("C:/filelist/", "*.jsp"); 
	 */
	public void clear(){
		_set.clear();
		_finishedSet.clear();
	}
}
