package util.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import javax.imageio.ImageIO;

import util.simpleIO.*;
import util.*;

/**
 *	Search, copy, move, ... files.
 * 	See main for examples.
 *
 * @author java-forum + Krisi
 * // http://www.java-forum.org/allgemeines/33129-verzeichnisse-durchsuchen-bearbeiten-auslesen.html
 */

public class FilesAndFolders {

	/**
	 * Main:
	 */
//	public static void main(String[] args) {
//
//		Out.pl("Start:");
//		File dir = new File("C:/");
//		//------------------------------------
//		
//		//listDir(dir);
//		
//		ArrayList<File> list = searchFile(dir, "pagefile.sys");
//		Out.pl(list);
//		
////		Out.pl(getDirSize(dir));
//		
//		//------------------------------------
//		Out.pl("Fin.");
//	}

	/**
	 * Prints files and directories directly in "dir"
	 * @param dir
	 */
	public static void printDir(File dir) {

		File[] files;
		
		if( dir.isDirectory() )		// directory is file: put file into list.
			files = dir.listFiles();
		else{
			files = new File[1];
			files[0] = dir;
		}
		
		if (files != null) { // Erforderliche Berechtigungen etc. sind vorhanden
			for (int i = 0; i < files.length; i++) {
				System.out.print(files[i].getAbsolutePath());
				if (files[i].isDirectory()) {
					System.out.print(" (Ordner)\n");
				}
				else {
					System.out.print(" (Datei)\n");
				}
			}
		}
	}
	
	/**
	 * Prints ALL files and directories in "dir"
	 * @param dir
	 */
	public static void printDirRec(File dir) {
		
		File[] files;
		
		if( dir.isDirectory() )		// directory is file: put file into list.
			files = dir.listFiles();
		else{
			files = new File[1];
			files[0] = dir;
		}
		
		if (files != null) {
			for (int i = 0; i < files.length; i++) {
				System.out.print(files[i].getAbsolutePath());
				if (files[i].isDirectory()) {
					System.out.print(" (Ordner)\n");
					printDirRec(files[i]); // ruft sich selbst mit dem 
						// Unterverzeichnis als Parameter auf
					}
				else {
					System.out.print(" (Datei)\n");
				}
			}
		}
	}
	
	/**
	 * Prints ALL files (also subdirs) in "dir"
	 * @param dir - file or directory
	 */
	public static ArrayList<File> listFilesRec(File dir) {
		
		ArrayList<File> list = new ArrayList<File>();
		
		if(dir.isFile()) {		// directory is file: put file into list. End.
			list.add(dir);
			return list;
		}
		
		File[] files = dir.listFiles();
		if (files != null) {
			for (int i = 0; i < files.length; i++) {
				//System.out.println(files[i].getAbsolutePath());
				//In.readChar();
				if (files[i].isDirectory()) {
					//System.out.print(" (Ordner)\n");
					list.addAll( listFilesRec(files[i]) ); 	// alle Dateien im Unterverzeichnis Hinzufügen
				}
				else {
					//System.out.print(" (Datei)\n");		// Datein hinzufügen
					list.add(files[i]);
				}
			}
		}
		return list;
	}
	
	/**
	 * Prints ALL files (also subdirs) in "dir"
	 * @param dir - file or directory
	 */
	public static ArrayList<File> listFilesRec(File dir, String[] formats) {
		
		ArrayList<File> list = new ArrayList<File>();
		
		if(dir.isFile()) {		// directory is file: put file into list. End.
			list.add(dir);
			return list;
		}
		
		File[] files = dir.listFiles();
		if (files != null) {
			for (int i = 0; i < files.length; i++) {
				//System.out.println(files[i].getAbsolutePath());
				//In.readChar();
				if (files[i].isDirectory()) {
					//System.out.print(" (Ordner)\n");
					list.addAll( listFilesRec(files[i]) ); 	// alle Dateien im Unterverzeichnis Hinzufügen
				}
				else {
					//System.out.print(" (Datei)\n");		// Datein hinzufügen
					File f0 = files[i];
					if( endsWith(f0.getName(), formats) ){
						list.add(f0);	// add picture
					}
					else{
						; // was no picture
					}
				}
			}
		}
		return list;
	}
	
	/**
	 * Prints ALL files (also subdirs) in "dir"
	 * @param dir - file or directory
	 */
	public static ArrayList<File> listPicturesRec(File dir) {
		
		String[] PIC_FORMATS = ImageIO.getReaderFormatNames(); // all formats ;)
		return listFilesRec(dir, PIC_FORMATS);
	}
	
	/**
	 * @param s1
	 * @param ends
	 * @return true if s1 ends with a String of ends. (not case sensitive!)
	 */
	private static boolean endsWith(String s1, String[] ends) {
		
		s1 = s1.toLowerCase();
		
		for(String end : ends){
			if(s1.endsWith(end.toLowerCase()))
				return true;
		}
		return false;
	}
	
	
	/**
	 * Get a unused filename
	 *
	 * @param fileStr		- <path> <\> <name.extension>
	 * @return				- A file string which isn't used at the moment (do not use this for the same file name simultaneously)
	 */
	public static String getNextFile(String fileStr){
		
		int dot = fileStr.lastIndexOf('.');
		String name      = fileStr.substring(0, dot);
		String extension = fileStr.substring(dot);
		
		return getNextFile(name, extension, 1);
	}
	/**
	 * Get a unused filename
	 * 
	 * @param name			- <path> <\> <name without extension>
	 * @param extension		- <.> <anything>
	 * @param nr			- Integer. May start at 1.
	 * @return				- A file string which isn't used at the moment (do not use this for the same file name simultaneously)
	 */
	public static String getNextFile(String name, String extension, int nr){
		
		String fileStr;
		File file;
		
		while(true){
			fileStr = name+nr+extension;
			file = new File(fileStr);
			if(file.exists()){
				// continue;
			}
			else{
				return fileStr;		// new name found
			}
		}

	}
	
	/**
	 * Returns all matching (equalsIngoreCase) Files in this Folder.
	 * 
	 * @param dir
	 * @param find
	 * @return ArrayList<File>
	 */
	public static ArrayList<File> searchFile(File dir, String find) {

		File[] files = dir.listFiles();
		ArrayList<File> matches = new ArrayList<File> ();
		if (files != null) {
			for (int i = 0; i < files.length; i++) {
				
				if (files[i].getName().equalsIgnoreCase(find)) {	// überprüft ob der Dateiname mit dem Suchstring
										 							// übereinstimmt. Groß-/Kleinschreibung wird ignoriert.
					matches.add(files[i]);
				}
			}
		}
		return matches;
	}
	/**
	 * Returns all matching (equalsIngoreCase) Files in this Folder.
	 * 
	 * @param dir
	 * @param find
	 * @return ArrayList<File>
	 */
	public static ArrayList<File> searchFileRec(File dir, String find) {

		File[] files = dir.listFiles();
		ArrayList<File> matches = new ArrayList<File> ();
		if (files != null) {
			for (int i = 0; i < files.length; i++) {
				
				if (files[i].getName().equalsIgnoreCase(find)) { 	// überprüft ob der Dateiname mit dem Suchstring
										 							// übereinstimmt. Groß-/Kleinschreibung wird ignoriert.
					matches.add(files[i]);
				}
				if (files[i].isDirectory()) {
					matches.addAll(searchFile(files[i], find)); 	// fügt der ArrayList die ArrayList mit den
										    						// Treffern aus dem Unterordner hinzu
				}
			}
		}
		return matches;
	}
	
	public static void makeDir(String path){
		File f = new File(path);
		f.mkdirs();
	}
	
	/**
	 * Versucht die Datei, oder einen leeren Ordner, 'fileStr' zu löschen.
	 * Bei Problemen wird eine RuntimeException ('IllegalArgumentException') geworfen.
	 * (Ausführliche Fehlerbeschreibung)
	 * 
	 * @param fileStr  - Pfad + Dateiname
	 * @param fileOnly - true to only delete files, no directories
	 */
	public static void delete(String fileStr, boolean fileOnly) {

	    // A File object to represent the filename
	    File f = new File(fileStr);
	    
	    delete(f, fileOnly);
	}
	
	/**
	 * See full function
	 * @param fileStr - file to delete
	 */
	public static void delete(String fileStr) {
		delete(fileStr, false);
	}
	
	/**
	 * Versucht die Datei, oder einen leeren Ordner, zu löschen.
	 * Bei Problemen wird eine RuntimeException ('IllegalArgumentException') geworfen.
	 * (+ Ausführliche Fehlerbeschreibung)
	 * 
	 * @param f - file to delete
	 */
	public static void delete(File f, boolean fileOnly) {

		//System.out.println("Trying to delete File '"+fileStr+"':");
		
		if(fileOnly && f.isDirectory()){
			 throw new IllegalArgumentException("Delete: File is directory: " + f.getAbsolutePath());
		}
		
	    // Make sure the file or directory exists and isn't write protected
	    if (!f.exists())
	      throw new IllegalArgumentException("Delete: no such file or directory: " + f.getAbsolutePath());

	    if (!f.canWrite())
	      throw new IllegalArgumentException("Delete: write protected: " + f.getAbsolutePath());

	    // If it is a directory, make sure it is empty
	    if (f.isDirectory()) {
	      String[] files = f.list();
	      if (files.length > 0)
	        throw new IllegalArgumentException("Delete: directory not empty: " + f.getAbsolutePath());
	    }

	    // Attempt to delete it
	    boolean success = f.delete();

	    if (!success)
	      throw new IllegalArgumentException("Delete: deletion failed");
	}
	
	/**
	 * See full function
	 * @param f - file to delete
	 */
	public static void delete(File f) {
		delete(f, false);
	}
	
	/**
	 * Deletes a directory or file. (recursive)
	 * @param dir
	 */
	public static void deleteDir(File dir) {

		File[] files = dir.listFiles();
		if (files != null) {
			for (int i = 0; i < files.length; i++) {
				if (files[i].isDirectory()) {
					deleteDir(files[i]); 	// Verzeichnis leeren und anschließend löschen
				}
				else {
					files[i].delete(); 		// Datei löschen
				}
			}
		}
		dir.delete(); // Ordner löschen (oder auch Datei)
	}
	
	/**
	 * Copy directory or file to target directory.
	 * @param source
	 * @param target
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static void copyDir(File source, File target) throws FileNotFoundException, IOException {
		
		if(!target.isDirectory())
			throw new IOException("Target must be a directory!");
		
		File[] files = source.listFiles();
		if(source.isFile()){			// for single file
			files = new File[1];
			files[0] = source;
		}
			
		File newFile = null; // in diesem Objekt wird für jedes File der Zielpfad gespeichert.
				     // 1. Der alte Zielpfad
				     // 2. Das systemspezifische Pfadtrennungszeichen
				     // 3. Der Name des aktuellen Ordners/der aktuellen Datei
		target.mkdirs();	     // erstellt alle benötigten Ordner
		if (files != null) {
			for (int i = 0; i < files.length; i++) {
					newFile = new File(target.getAbsolutePath() + System.getProperty("file.separator") + files[i].getName());
				if (files[i].isDirectory()) {
					copyDir(files[i], newFile);
				}
				else {
					copyFile(files[i], newFile);
				}
			}
		}
	}
	
	/**
	 * Copy a single file to a target location.
	 * @param source
	 * @param target
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static void copyFile(File source, File target) throws FileNotFoundException, IOException {
		
		BufferedInputStream in = new BufferedInputStream(new FileInputStream(source));
		BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(target, true));
		int bytes = 0;
		while ((bytes = in.read()) != -1) { // Datei einlesen
			out.write(bytes); // Datei schreiben
		}
		in.close();
		out.close();
	}
	
	
	/**
	 * TODO: HDD performance?
	 * 
	 * @param file
	 * @return
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static long fileCheckSum(File file) throws FileNotFoundException, IOException {
		
		if(!file.isFile()){			// Error:
			throw new RuntimeException("This is no File!");
		}
		
		// --- Init ---  TODO
		BufferedInputStream in = new BufferedInputStream( new FileInputStream(file) );
		//InputStream in = new FileInputStream(file);
		//InputStream in = new MyTestInputStream(file.length());
		//InputStream in = new MyFileInputStream(file.length());
		
		int  byt=0;
		long checkSum=0; //, single=0;
		
		Timer ti = new Timer();
		ti.start();
		
		while(byt != -1){				// --- Calculate checksum for the whole file: ---
			
//			single=0;
//			for(int i=0; i<8; i++){		// sum up an single long (with bytes and shift):
				byt=in.read();
//				//in.read(new byte[1024*1024]);
//				
//				single =  single << 8;
//				if(byt != -1){
//					single += byt;
//				}
//				//Out.pl( Long.toBinaryString(single) );
//			}
//			
//			checkSum += single;			// add single long to checkSum
		}
		
		ti.stop();
		Out.pl("Runtime = " + ti.getMeasuredTime(0));
		
		in.close();		// close stream
		
		return checkSum;
	}
	
	
	/**
	 * Returns the size of all files directly in THIS directory.
	 * 
	 * @param f - file or directory
	 * @return size of all files directly in THIS directory
	 */
	public static long getSizeDirect(File f) {
		
		long size = 0;
		
		if(f.isFile()){				// -- just one file: --
			return f.length();		// return size
		}
		
		File[] files = f.listFiles();	// get files in directory
		
		if (files == null) {		// -- problem with getting files (not existent?): --
			return -1;				// return zero
		}
			
		for (int i = 0; i < files.length; i++) {	// --- sum up size ---
			if (files[i].isDirectory()) {
				//size += getDirSize(files[i]); 	// Gesamtgröße des Verzeichnisses aufaddieren
			}
			else {
				size += files[i].length(); 			// Größe der Datei aufaddieren
			}
		}
			
		return size;
	}
	
	/**
	 * Returns the size of all files in this directory and all subdirectories.
	 * 
	 * @param f - file or directory
	 * @return
	 */
	public static long getSizeRec(File f) {
		
		if(f.isFile()){					// -- just one file: --
			return f.length();			// return size
		}
		
		long size = 0;
		
		File[] files = f.listFiles();	// get files in directory
		
		if (files == null) {			// -- problem with getting files (not existent?): --
			return 0;					// return zero
		}
			
		for (int i = 0; i < files.length; i++) {	// --- sum up size ---
			if (files[i].isDirectory()) {
				size += getSizeRec(files[i]); 		// Gesamtgröße des Verzeichnisses aufaddieren
			}
			else {
				size += files[i].length(); 			// Größe der Datei aufaddieren
			}
		}
			
		return size;
	}
	
	/**
	 * Check sums for all files in the directory.  TODO
	 * 
	 * @param dir
	 * @return
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static StringBuffer directoryCheckSum(File dir) throws FileNotFoundException, IOException {
		
		if(!dir.exists()){		// Error1:
			throw new RuntimeException("Error1: File or directory '"+dir.getAbsolutePath()+"' does not exist!");
		}
		
		StringBuffer sb = new StringBuffer();	// new Stringbuffer for result
		
		if(dir.isFile()){								// --- just one file: ---
			sb.append(checkSumLine(dir));
		}
		else{										// --- directory: ---
			
			File[] files = dir.listFiles();			// get files from directory
			
			if (files == null) {					// Error2: problem with getting files (not existent?):
				throw new RuntimeException("Error2: Problem with getting files from directory '"+dir.getAbsolutePath()+"'!");
			}
			
			for (int i = 0; i < files.length; i++) {	// --- sum up size ---
				if (files[i].isDirectory()) {
					sb.append(directoryCheckSum(files[i]));
				}
				else {
					sb.append(checkSumLine(files[i]));
				}
			}
		}
		
		return sb;
	}
	private static String checkSumLine(File f) throws FileNotFoundException, IOException{
		String line = "File:     " + f.getName() + "\n" +"Checksum: " + fileCheckSum(f) + " \t " + "Size: " + f.length() + " \t # " + "\n";
		return line;
	}
	
	
	/** Return size in MB **/
	public static double MB(long size){
		return MB(size, 2);
	}
	public static double MB(long size, int komma){
		
		double mb = size / (1024*1024) * komma;
		mb /= komma;
		
		return mb;
	}
	
//	public static List comapareFiles(){
//		Set
//	}
	
	public static boolean compareFiles(File f1, File f2) throws IOException {
		final int buffSize = 128*1024;
		return compareFiles(f1, f2, buffSize);
	}
	public static boolean compareFiles(File f1, File f2, int buffSize) throws IOException {
		
		if(f1.length() != f2.length())				// length must be equal
			return false;							// -
		
		InputStream in1 = new FileInputStream(f1);	// input streams
		InputStream in2 = new FileInputStream(f2);	// -
		
		byte[] buf1 = new byte[buffSize];			// buffer for input stream data
		byte[] buf2 = new byte[buffSize];			// -
		
		int c1=1, c2=1;
		while(c1>0){						// compare:
			c1 = in1.read(buf1);			// read data from both files
			c2 = in2.read(buf2);			// -
			
			if(c1!=c2)						// length of read data must be equal
				return false;				// -
			
			for(int i=0; i<c1; i++){		// compare data
				if(buf1[i] != buf2[i])		// -
					return false;			// -
			}
		}
		
		return true;					// equal. 
	}
	
	/**
	 * Wait for a file which is written externally.
	 * @param maxTries
	 * @param sleepTime
	 * @return true if file exists at the end.
	 */
	public static boolean waitForFile(File file, int maxTries, int sleepTime){
		
//		final int maxTries  = 10;	// tries to find file
//		final int sleepTime = 500;	// ms
		
		if(file.exists())
			return true;
		
		for(int i=0; i<maxTries; i++){

			Tools.sleep(sleepTime);
			
			if(file.exists())
				return true;
		}
		
		return false;
	}
	
	public static boolean waitForFile(File file, long timestamp, int maxTries, int sleepTime){
		
		if(file.exists()){
			if(file.lastModified() >= timestamp){
				return true;
			}
		}
		
		for(int i=0; i<maxTries; i++){
			
			Tools.sleep(sleepTime);
			
//			Out.pl("File timestamp:   "+file.lastModified());
//			Out.pl("System timestamp: "+timestamp);
//			Out.pl("System now:       "+System.currentTimeMillis());
//			Out.pl();
			
			if(file.exists()){
				if(file.lastModified() >= timestamp){
					return true;
				}
			}
		}
		
		return false;
	}
	
	/**
	 * like waitForFile(File ...)
	 * @param fileName
	 * @param maxTries
	 * @param sleepTime
	 * @return true if file exists at the end.
	 */
	public static boolean waitForFile(String fileName, int maxTries, int sleepTime){
		return waitForFile(new File(fileName), maxTries, sleepTime);
	}
	/**
	 * like waitForFile(File, long ...)
	 * @param fileName
	 * @param timestamp
	 * @param maxTries
	 * @param sleepTime
	 * @return
	 */
	public static boolean waitForFile(String fileName, long timestamp, int maxTries, int sleepTime){
		return waitForFile(new File(fileName), timestamp, maxTries, sleepTime);
	}
}
