//PRET-Extractor
//Copyright (c) 2013 Tetsuya Kanda
//
//http://sel.ist.osaka-u.ac.jp/pret/
//
//Permission is hereby granted, free of charge, to any person obtaining
//a copy of this software and associated documentation files (the
//"Software"), to deal in the Software without restriction, including
//without limitation the rights to use, copy, modify, merge, publish,
//distribute, sublicense, and/or sell copies of the Software, and to
//permit persons to whom the Software is furnished to do so, subject to
//the following conditions:
//
//The above copyright notice and this permission notice shall be
//included in all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
//MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
//LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
//OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
//WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package jp.ac.osaka_u.ist.sel.pret.engine;

import gnu.trove.iterator.TIntIterator;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.TObjectCharMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.set.TIntSet;
import gnu.trove.set.hash.THashSet;

import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.List;

import jp.ac.osaka_u.ist.sel.pret.OptionContainer;
import jp.ac.osaka_u.ist.sel.pret.engine.data.DirectoryInfo;
import jp.ac.osaka_u.ist.sel.pret.engine.data.FileInfo;

/**
 * data container
 * 
 * @author t-kanda
 * 
 */
public class Target implements Serializable {

	private static final long serialVersionUID = 6981720218469796287L;
	private int nextFileId = 1;
	private int nextProjectId = 1;
	protected TIntObjectMap<FileInfo> files;
	private TIntObjectMap<DirectoryInfo> projects;
	private URI tmp;
	private TObjectCharMap<String> types;

	/**
	 * set up
	 * 
	 * @param options
	 */
	public Target(OptionContainer options) {
		files = new TIntObjectHashMap<>();
		projects = new TIntObjectHashMap<>();
		types = options.types();
		tmp = options.tmp().toUri();
	}

	private Collection<FileInfo> afterReadProject(TIntObjectMap<FileInfo> newFiles, DirectoryInfo d) {
		files.putAll(newFiles);
		projects.put(nextProjectId, d);
		nextProjectId++;
		nextFileId += newFiles.size();
		return newFiles.valueCollection();
	}

	/**
	 * read file or directory
	 * 
	 * @param root
	 *            project path
	 * @return newly added file information
	 */
	public Collection<FileInfo> readProject(Path root) {
		Path projectTmp = Paths.get(Paths.get(tmp).toString(), Integer.toString(nextProjectId));
		try {
			Files.createDirectory(projectTmp);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			// e.printStackTrace();
		}
		FileInfoMaker targets = new FileInfoMaker(nextProjectId, nextFileId, projectTmp);

		DirectoryInfo d = read(root, targets, null);
		TIntObjectMap<FileInfo> newFiles = targets.make();

		if (d == null) {
			d = new DirectoryInfo(root, nextProjectId, null);
			for (TIntIterator it = newFiles.keySet().iterator(); it.hasNext();) {
				d.addFile(it.next());
			}
		}

		return afterReadProject(newFiles, d);
	}

	/**
	 * picking up
	 * 
	 * @param target
	 *            target file or directory
	 * @param targets
	 *            reader
	 * @param parent
	 *            parent directory
	 * @param projectTmp
	 *            tmp directory
	 * @return
	 */
	private DirectoryInfo read(Path target, FileInfoMaker targets, DirectoryInfo parent) {
		if (Files.isDirectory(target)) {
			// read children if the target is a directory
			DirectoryInfo dir = new DirectoryInfo(target, nextProjectId, parent);
			try (DirectoryStream<Path> dirs = Files.newDirectoryStream(target)){
				for (Path p : dirs) {
					DirectoryInfo child = read(p, targets, dir);
					if (child != null) {
						dir.addDirectory(child);
					}
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return dir;
		} else if (Files.isRegularFile(target)) {
			// get infromation if the target is a file
			String type = type(target.getFileName());
			if (types.containsKey(type)) {
				targets.add(target, parent, types.get(type));
			}
		}
		return null;
	}

	/**
	 * read files in the list file
	 * 
	 * @param list
	 *            path to the list file
	 * @return newly added file information
	 */
	public Collection<FileInfo> readProjectByList(Path list) {
		Path projectTmp = Paths.get(tmp.toString(), Integer.toString(nextProjectId));
		FileInfoMaker targets = new FileInfoMaker(nextProjectId, nextFileId, projectTmp);

		if (Files.exists(list)) {
			try {
				List<String> files = Files.readAllLines(list, Charset.defaultCharset());
				for (String line : files) {
					String type = type(line);
					if (types.containsKey(type)) {
						targets.add(Paths.get(line), null, types.get(type));
					}
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		TIntObjectMap<FileInfo> newFiles = targets.make();

		return afterReadProject(newFiles, null);
	}

	/**
	 * get extention
	 * 
	 * @param path
	 *            file
	 * @return extention or ""
	 */
	private String type(Path path) {
		return type(path.getFileName().toString());
	}

	private String type(String fileName) {
		int point = fileName.lastIndexOf('.');
		return point < 0 ? "" : fileName.toLowerCase().substring(fileName.lastIndexOf('.') + 1);
	}

	/**
	 * @param fileId
	 *            file ID
	 * @return@file information
	 */
	public FileInfo getFile(int fileId) {
		return files.get(fileId);
	}

	/**
	 * get file set
	 * 
	 * @return set of file information
	 */
	public Collection<FileInfo> getCopyOfFileSet() {
		return new THashSet<FileInfo>(files.valueCollection());
	}

	public int numberOfFiles() {
		return nextFileId - 1;
	}

	public TIntSet getFileIDsInProject(int pid) {
		return projects.get(pid).allFiles();
	}

	public int numberOfProject() {
		return nextProjectId - 1;
	}

	public String getProjectName(int pid) {
		return projects.get(pid).getName();
	}

	public URI getProjectPath(int pid) {
		return projects.get(pid).getPath();
	}
}
