//
// https://www.ramecera.it/myscrewdriver.html
// e-mail: screwdriver@ramecera.it
//
/* This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License v1.0 which accompanies this
 * distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html */

package it.ramecera.salad.file;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Calendar;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.regex.PatternSyntaxException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import it.ramecera.salad.everyday.ExactException;
import it.ramecera.salad.everyday.Mix;

/**
 * <p>
 * Class for various operations on files.
 * </p>
 * <p>
 * Company: Ramecera™<br>
 * Creation date: 2020-04-09<br>
 * Last updated: 2020-09-15<br>
 * </p>
 * <p>
 * Changes:<br>
 * 2020-04-09: inserted method: public static void <b>estraiFiltroZip
 * </b> (final String fileZipOut, File destDir, final String filter)
 * <br>
 * </p>
 * 
 * @author M. Maglioni * */
public class Abacus {

	private static final String sep = System.getProperty(
			"file.separator");

	/**
	 * <p>
	 * Deletion of files from a directory and all its subdirectories, at
	 * all depths. Eventually the path remains but is empty.
	 * </p>
	 * <p>
	 * Creation date: 2020-04-09<br>
	 * Last updated: 2020-06-03
	 * </p>
	 * <p>
	 * Changes:<br>
	 * 
	 * @param y the path to be demolished
	 * @throws ExactException error in the parameters provided */
	public static void destroyDirectory(final String y)

			throws ExactException {

		if (y == null) {
			throw new ExactException(
					"The directory name for method \"eraseFiles\" is null.",
					101000, "Abacus", "");
		}
		Folder folder = new Folder();
		File[] elencoE = folder.fileObjects(".+", y);
		if (elencoE == null) {
			throw new ExactException("The directory " + Mix.dress_A(y)
					+ " does not exist.", 101004, "Abacus", "");
		}
		for (File x : elencoE) {
			try {
				if (x.isFile()) {
					x.delete();
				} else if (x.isDirectory()) {
					destroyDirectory(x.getCanonicalPath());
					x.delete();
				}
			} catch (IOException e) {
				throw new ExactException("Errore di I/O", 101001,
						"Abacus", "");
			}
		}
	}

	/**
	 * <p>
	 * Deleting the files of a directory, filtered according to a
	 * pattern.
	 * </p>
	 * <p>
	 * Creation date: 2020-04-09<br>
	 * Last updated: 2020-04-09
	 * </p>
	 * <p>
	 * Changes:<br>
	 * 
	 * @return quantity of deleted files
	 * @param pattern the pattern to filter the files, according to the
	 *        codes of the class java.util.regex.Pattern.
	 * @param y the path where to look for the files
	 * @throws ExactException error code in the provided parameters */
	public static int eraseFiles(final String pattern, final String y)

			throws ExactException {

		if (pattern == null) {
			throw new ExactException(
					"The pattern for method \"eraseFiles\" is null.",
					101002, "Abacus", "");
		}
		if (y == null) {
			throw new ExactException(
					"The directory name for method \"eraseFiles\" is null.",
					101003, "Abacus", "");
		}
		// if necessary, I put a slash in the queue
		String y1 = Mix.dress_D(y);
		File f = new File(y1);
		int i = 0;
		try {
			String[] g = f.list(Mix.fileFilter(pattern));
			for (i = 0; i < g.length; i++) {
				File h = new File(y1 + g[i]);
				h.delete();
			}
		} catch (PatternSyntaxException pse) {
			return 0;
		}
		return i;
	}

	/**
	 * <p>
	 * Find the most recent file among those present in a directory,
	 * filtered according to a pattern.
	 * </p>
	 * <p>
	 * Creation date: 2020-04-09<br>
	 * Last updated: 2020-04-09
	 * </p>
	 * <p>
	 * Changes:<br>
	 * 21/01 / 09: eliminated the milliseconds from the final result.
	 * <br>
	 * </p>
	 * 
	 * @return the most recent modification date, expressed as long
	 *         number
	 * @param x the pattern to filter the files, according to the codes
	 *        of the java.util class. regex.Pattern.
	 * @param y the directory to search for files
	 * @since SVutensiliJ4_1.2.2, class 1.1.0 */
	public static long ilPiuGiovaneFile_A(final String x,
			final String y) {

		// if necessary, I put a slash in the queue
		String y1 = Mix.dress_CU(y);
		File f = new File(y1);
		String[] g = f.list(Mix.fileFilter(x));
		int i;
		long dateW = 0;
		String nomeFileW = null;
		for (i = 0; i < g.length; i++) {
			File h = new File(y1 + g[i]);
			if (h.lastModified() > dateW) {
				dateW = h.lastModified();
				nomeFileW = h.getName();
			}
		}
		nomeFileW = nomeFileW + ""; // just to avoid the hassle of the
								    // warning
		return dateW / 1000 * 1000;
	}

	/**
	 * <p>
	 * Find the most recent file among those present in a directory,
	 * filtered according to a pattern. It is returned not the name of
	 * the file, but its modification date, expressed as a Calendar
	 * object.
	 * </p>
	 * <p>
	 * Creation date: 2020-04-09<br>
	 * Last updated: 2020-04-09
	 * </p>
	 * <p>
	 * Changes:<br>
	 * 21/01 / 09: milliseconds removed from the final result.<br>
	 * </p>
	 * 
	 * @return the most recent modification date, expressed as a
	 *         Calendar object; returns the date 2020-04-09 if the
	 *         directory is empty
	 * @param x the pattern to filter the files, according to the codes
	 *        of the class java.util.regex.Pattern.
	 * @param y the directory to search for files
	 * @since SVutensiliJ4_1.2.2, class 1.1.0 */
	public static Calendar ilPiuGiovaneFile_B(final String x,
			final String y) {

		// if necessary, I put a slash in the queue
		TimeZone tz = TimeZone.getTimeZone("Europe/Paris");
		Calendar calendarW = Calendar.getInstance(tz);
		String y1 = Mix.dress_CU(y);
		File f = new File(y1);
		String[] g = f.list(Mix.fileFilter(x));
		if (g.length == 0) {
			calendarW.set(Calendar.YEAR, 1900);
			calendarW.set(Calendar.MONTH, 0);
			calendarW.set(Calendar.DAY_OF_MONTH, 1);
			return calendarW;
		}
		int i;
		Calendar lastModified = Calendar.getInstance(tz);
		calendarW.setTimeInMillis(0);
		String nomeFileW = null;
		for (i = 0; i < g.length; i++) {
			File h = new File(y1 + g[i]);
			lastModified.setTimeInMillis(h.lastModified());
			if (lastModified.after(calendarW)) {
				calendarW = (Calendar) lastModified.clone();
				nomeFileW = h.getName();
			}
		}
		nomeFileW = nomeFileW + ""; // just to avoid the hassle of the
								    // warning
		calendarW.set(Calendar.MILLISECOND, 0);
		return calendarW;
	}

	/**
	 * <p>
	 * Count of files in a directory, filtered according to a pattern.
	 * </p>
	 * <p>
	 * Creation date: 2020-04-09<br>
	 * Last updated: 2020-04-09
	 * </p>
	 * <p>
	 * Changes:<br>
	 * 
	 * @return quantity of files found
	 * @param x the pattern to filter the files, according to the codes
	 *        of the class java.util.regex.Pattern.
	 * @param y path where to search for files
	 * @see "prog. bigExport_2.0.0, class ExportSchema_1" */
	public static int contaFile(final String x, final String y) {

		// if necessary, I put a slash in the queue
		File f = new File(y);
		if (!f.exists() || !f.isDirectory())
			return -1;
		String[] g = f.list(Mix.fileFilter(x));
		return g.length;
	}

	/**
	 * <p>
	 * Physical copy of one file to another, declaring a maximum length.
	 * </p>
	 * <p>
	 * Creation date: 2020-04-09<br>
	 * Last updated: 2020-04-09
	 * </p>
	 * <p>
	 * Changes:<br>
	 * 20/05 08: sets the condition that the file does not already exist
	 * identically<br>
	 * 2020-04-09: elimination of the preventive cancellation because it
	 * is useless<br>
	 * 2020-04-09: reintroduced the delete estimate<br>
	 * </p>
	 * 
	 * @param in file in unput
	 * @param out file in output
	 * @param max maximum file length
	 * @throws Exception ordinary exception */
	public static void copyFile(File in, File out, final int max)

			throws Exception {

		/* this is an economy: do nothing if the output already exists
		 * and is identical */
		if (out.exists() && !out.isDirectory() && in
				.lastModified() == out.lastModified() && in
						.length() == out.length())
			return;

		if (out.exists() && !out.isDirectory())
			out.delete();
		FileInputStream fis = new FileInputStream(in);
		FileOutputStream fos = new FileOutputStream(out);
		byte[] buf = new byte[max];
		int i = 0;
		while ((i = fis.read(buf)) != -1) {
			fos.write(buf, 0, i);
		}
		fis.close();
		fos.close();
	}

	/**
	 * <p>
	 * Physical copy of one file on another; overload of the previous
	 * one. It differs from the previous one because there is no need to
	 * declare the maximum length.
	 * </p>
	 * <p>
	 * Creation date: 2020-04-09<br>
	 * Last updated: 2020-04-09
	 * </p>
	 * <p>
	 * Changes:<br>
	 * 2013-08- 07: added the instruction setting the last modification
	 * date of the output file
	 * </p>
	 * 
	 * @param in file in unput
	 * @param out file in output
	 * @throws IOException standard exception */
	public static void copyFile(File in, File out)

			throws IOException {

		/* this is an economy: do nothing if the output already exists
		 * and is identical */
		if (out.exists() && !out.isDirectory() && in
				.lastModified() == out.lastModified() && in
						.length() == out.length())
			return;

		if (out.exists() && !out.isDirectory())
			out.delete();
		FileInputStream fis = new FileInputStream(in);
		FileOutputStream fos = new FileOutputStream(out);
		byte[] buf = new byte[(int) in.length()];
		int i = 0;
		while ((i = fis.read(buf)) != -1) {
			fos.write(buf, 0, i);
		}
		fis.close();
		fos.close();
		out.setLastModified(in.lastModified());
	}

	/**
	 * <p>
	 * Physical copy of two files, queued, on another; an LF is placed
	 * between the two.
	 * </p>
	 * <p>
	 * Creation date: 2020-04-09<br>
	 * Last updated: 2020-04-09
	 * </p>
	 * <p>
	 * Changes:<br>
	 * </p>
	 * 
	 * @param i0 file in unput
	 * @param i1 file in unput
	 * @param o file in output
	 * @throws Exception ordinary exception */
	public static void mergeFile(String i0, String i1, String o)

			throws Exception {

		File in0 = new File(i0);
		File in1 = new File(i1);
		File out = new File(o);
		if (out.exists() && !out.isDirectory())
			out.delete();
		FileInputStream fis0 = new FileInputStream(in0);
		FileInputStream fis1 = new FileInputStream(in1);
		FileOutputStream fos = new FileOutputStream(out);
		byte[] buf = new byte[(int) in0.length()];
		int i = 0;
		while ((i = fis0.read(buf)) != -1) {
			fos.write(buf, 0, i);
		}
		fis0.close();
		fos.write(10); // line feed character
		buf = new byte[(int) in1.length()];
		i = 0;
		while ((i = fis1.read(buf)) != -1) {
			fos.write(buf, 0, i);
		}
		fis1.close();
		fos.close();
	}

	/**
	 * <p>
	 * Create a directory, if it doesn't already exist, after making
	 * sure it isn't a file.<br>
	 * It can only create one directory at a time.<br>
	 * Example: I have to create directory b in path C:/a;<br>
	 * I must put the string: "C:/a/b" as the argument of the method,
	 * and I must first verify that C:/a exists.<br>
	 * </p>
	 * <p>
	 * Creation date: 2020-09-03<br>
	 * Last updated: 2020-09-03
	 * </p>
	 * <p>
	 * Modifications:<br>
	 * </p>
	 * 
	 * @param path to the directory to be built, complete with path */
	public static void buildDirectory(final String path) {

		File cradle = new File(path);
		if (cradle.isFile())
			cradle.delete();
		if (!cradle.exists())
			cradle.mkdir();
	}

	/**
	 * <p>
	 * Overload of the preceding method.
	 * </p>
	 * <p>
	 * Creation date: 2020-09-03<br>
	 * Last updated: 2020-09-03
	 * </p>
	 * <p>
	 * Modifications:<br>
	 * </p>
	 * 
	 * @param cradle directory to be created */
	public static void buildDirectory(File cradle) {

		if (cradle.isFile())
			cradle.delete();
		if (!cradle.exists())
			cradle.mkdir();
	}

	/**
	 * <p>
	 * Create a complete path, that is, make a succession of directories
	 * creations.
	 * </p>
	 * <p>
	 * Creation date: 2020-04-09<br>
	 * Last updated: 2020-04-09<br>
	 * Author: M. Maglioni
	 * </p>
	 * <p>
	 * Changes :<br>
	 * </p>
	 * 
	 * @param path the directory to be built, complete with path */
	public static void costruisciDirectoryCompleta(final String path) {
		StringTokenizer tokens = new StringTokenizer(path, System
				.getProperty("file.separator"));
		String[] segmenti = new String[tokens.countTokens()];
		int segmentiQ = 0;
		while (tokens.hasMoreTokens()) {
			segmenti[segmentiQ] = tokens.nextToken();
			segmentiQ++;
		}
		String percorso = segmenti[0];
		int i = 1;
		while (i < segmenti.length) {
			percorso += System.getProperty("file.separator")
					+ segmenti[i];
			buildDirectory(percorso);
			i++;
		}
	}

	/**
	 * <p>
	 * Create a complete path, that is, make a succession of directories
	 * creations.
	 * </p>
	 * <p>
	 * Creation date: 2020-04-09<br>
	 * Last updated: 2020-04-09<br>
	 * Author: M. Maglioni
	 * </p>
	 * <p>
	 * Changes :<br>
	 * </p>
	 * 
	 * @param path the directory to be built, complete with path
	 * @param separator the separator (slash or backslash) inside the
	 *        name of a path */
	public static void buildDirectoryCompletely(final String path,
			final String separator) {
		if (path.length() == 0) {
			return;
		}
		StringTokenizer tokens = new StringTokenizer(path, separator);
		String[] segmentE = new String[tokens.countTokens()];
		int segmentsQ = 0;
		while (tokens.hasMoreTokens()) {
			segmentE[segmentsQ] = tokens.nextToken();
			segmentsQ++;
		}
		String percorso = segmentE[0];
		int i = 1;
		while (i < segmentE.length) {
			percorso += System.getProperty("file.separator")
					+ segmentE[i];
			buildDirectory(percorso);
			i++;
		}
	}

	/**
	 * <p>
	 * Removing the extension from a file name. Deprecated on 2020-04-09
	 * because it is not exact: if the file has no extension, and is
	 * preceded by a directory name containing a dot, it gives an
	 * incorrect result. The correct method to use is
	 * Mix.estraiExtension.
	 * </p>
	 * <p>
	 * Creation date: 2020-04-09<br>
	 * Last updated: 2020-04-09
	 * </p>
	 * <p>
	 * Changes:<br>
	 * </p>
	 * 
	 * @return the name of the file without extension (nor dot, of
	 *         course)
	 * @param s the name of the complete file */
	@Deprecated
	public static String senzaExtension(final String s) {
		int i = s.lastIndexOf(".");
		if (i == -1)
			return s;
		else
			return s.substring(0, i);
	}

	/**
	 * <p>
	 * Creation date: 2020-05-19<br>
	 * Last updated: 2020-05-19<br>
	 * Class: Abacus
	 * 
	 * @author M. Maglioni
	 *         <p>
	 *         Description:
	 *         </p>
	 * @param fileZipInput name of the input file
	 * @param destDir output directory
	 * @throws FileNotFoundException obvious
	 * @throws IOException obvious */
	public static void extractAllFromZip(final String fileZipInput,
			File destDir)

			throws FileNotFoundException, IOException {

		byte[] buffer = new byte[1024];
		ZipInputStream zis = new ZipInputStream(new FileInputStream(
				fileZipInput));
		ZipEntry zipEntry = zis.getNextEntry();
		while (zipEntry != null) {
			File newFile = newFile(destDir, zipEntry);
			String s = destDir.getAbsolutePath().replaceAll("\\x5c",
					"/") + "/" + zipEntry.getName();
			if (zipEntry.getName().endsWith("/")) {
				Abacus.buildDirectoryCompletely(s, "/");
			} else {
				String t[] = Mix.analizzaPath(s);
				Abacus.buildDirectoryCompletely(t[0], "/");
				FileOutputStream fos = new FileOutputStream(newFile);
				int len;
				while ((len = zis.read(buffer)) > 0) {
					fos.write(buffer, 0, len);
				}
				fos.close();
			}
			zipEntry = zis.getNextEntry();
		}
		zis.closeEntry();
		zis.close();
	}

	/**
	 * <p>
	 * Creation date: 2020-05-19<br>
	 * Last updated: 2020-09-14<br>
	 * Class: Abacus
	 * 
	 * @author M. Maglioni
	 *         <p>
	 *         Description: extraction of files from a zip archive,
	 *         filtered by a "startWith" and by an "except"
	 *         </p>
	 *         <p>
	 *         History:<br>
	 *         The description of N# is in project plan report.<br>
	 *         2020-09-14 implementation of N16<br>
	 *         </p>
	 * @param fileZipInput name of the input file
	 * @param destDir output directory
	 * @param filter obvious
	 * @param except I don't have to copy the "dummy.txt" files, because
	 *        they were added to the jar only to allow the creation of
	 *        some empty directories.
	 * @throws FileNotFoundException obvious
	 * @throws IOException obvious */
	public static void extractFromZipFilter(final String fileZipInput,
			File destDir, final String filter, final String except)

			throws FileNotFoundException, IOException {

		byte[] buffer = new byte[1024];
		ZipInputStream zis = new ZipInputStream(new FileInputStream(
				fileZipInput));
		ZipEntry zipEntry = zis.getNextEntry();
		while (zipEntry != null) {
			if (zipEntry.toString().startsWith(filter)) {
				File newFile = newFile(destDir, zipEntry);
				String s = destDir.getAbsolutePath().replaceAll("\\x5c",
						"/") + "/" + zipEntry.getName();
				if (zipEntry.getName().endsWith("/")) {
					Abacus.buildDirectoryCompletely(s, "/");
				} else {
					String t[] = Mix.analizzaPath(s);
					Abacus.buildDirectoryCompletely(t[0], "/");
					if (!newFile.getName().equals(except) | except
							.equals("")) {
						FileOutputStream fos = new FileOutputStream(
								newFile);
						int len;
						while ((len = zis.read(buffer)) > 0) {
							fos.write(buffer, 0, len);
						}
						fos.close();
					}
				}
			}
			zipEntry = zis.getNextEntry();
		}
		zis.closeEntry();
		zis.close();
	}

	private static File newFile(File destinationDir, ZipEntry zipEntry)

			throws IOException {

		File destFile = new File(destinationDir, zipEntry.getName());

		String destDirPath = destinationDir.getCanonicalPath();
		String destFilePath = destFile.getCanonicalPath();
		if (!destFilePath.startsWith(destDirPath + File.separator)) {
			throw new IOException("Entry is outside of the target dir: "
					+ zipEntry.getName());
		}
		return destFile;
	}

	/**
	 * <p>
	 * Creation date: 2020-09-15<br>
	 * Class: Abacus
	 * </p>
	 * <p>
	 * Description: delete a directory, with all its content.
	 * </p>
	 * 
	 * @param path object to delete
	 * @throws IOException ordinary exception */
	final public static void deleteDirectoryRecursion(Path path)

			throws IOException {

		LinkOption[] nfl = new LinkOption[] {
				LinkOption.NOFOLLOW_LINKS };
		deleteDirectoryRecursion(path, nfl);
	}

	/**
	 * <p>
	 * Creation date: 2020-09-15<br>
	 * Class: Abacus
	 * </p>
	 * <p>
	 * Description: delete a directory, with all its content.
	 * </p>
	 * 
	 * @param path object to delete
	 * @param nfl link options
	 * @throws IOException ordinary exception */
	final public static void deleteDirectoryRecursion(Path path,
			LinkOption[] nfl)

			throws IOException {

		if (!Files.exists(path, nfl)) {
			return;
		}
		if (Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS)) {
			try (DirectoryStream<Path> entries = Files
					.newDirectoryStream(path)) {
				for (Path entry : entries) {
					deleteDirectoryRecursion(entry, nfl);
				}
			}
		}
		Files.delete(path);
	}

	/**
	 * <p>
	 * Creation date: 2020-09-08<br>
	 * Last updated: 2020-09-15<br>
	 * Class: Abacus
	 * </p>
	 * <p>
	 * Description: copy all content of a directory.
	 * </p>
	 * 
	 * @param inPath input object
	 * @param outPath output object
	 * @param replace replace-existing optio
	 * @throws IOException ordinary exception */
	final public static void copyDirectoryRecursion(Path inPath,
			Path outPath, final boolean replace)

			throws IOException {

		LinkOption[] nfl = new LinkOption[] {
				LinkOption.NOFOLLOW_LINKS };
		copyDirectoryRecursion(inPath, outPath, replace, nfl);
	}

	/**
	 * <p>
	 * Creation date: 2020-09-08<br>
	 * Last updated: 2020-09-15<br>
	 * Class: Abacus
	 * </p>
	 * <p>
	 * Description: copy all content of a directory.
	 * </p>
	 * 
	 * @param inPath input object
	 * @param outPath output object
	 * @param replace replace-existing option
	 * @param nfl link options
	 * @throws IOException ordinary exception */
	final public static void copyDirectoryRecursion(Path inPath,
			Path outPath, final boolean replace, LinkOption[] nfl)

			throws IOException {

		if (Files.exists(outPath, nfl) && !Files.isDirectory(outPath,
				nfl)) {
			throw new IOException("The output object " + Mix.dress_A(
					outPath.toString()) + " is not a directory");
		} else if (!Files.exists(outPath, nfl)) {
			Files.createDirectory(outPath);
		}
		try (DirectoryStream<Path> entries = Files.newDirectoryStream(
				inPath)) {
			for (Path entry : entries) {
				Path n = new File(outPath.toFile().getAbsolutePath()
						+ sep + entry.getFileName()).toPath();
				if (Files.isDirectory(entry,
						LinkOption.NOFOLLOW_LINKS)) {
					if (!Files.exists(n)) {
						Files.createDirectory(n);
					}
					copyDirectoryRecursion(entry, n, replace, nfl);
				} else if (replace) {
					Files.copy(entry, n,
							StandardCopyOption.COPY_ATTRIBUTES,
							StandardCopyOption.REPLACE_EXISTING);
				} else {
					Files.copy(entry, n,
							StandardCopyOption.COPY_ATTRIBUTES);
				}
			}
		}
	}

	/**
	 * <p>
	 * Creation date: 2020-09-08<br>
	 * Last updated: 2020-09-15<br>
	 * Class: Abacus
	 * </p>
	 * <p>
	 * Description: delete a directory, with all its content;
	 * overloading of the preceding one.
	 * </p>
	 * 
	 * @param file object to delete
	 * @throws IOException ordinary exception */
	final public static void deleteDirectoryRecursion(File file)

			throws IOException {

		deleteDirectoryRecursion(file.toPath());
	}

	/**
	 * <p>
	 * Creation date: 2020-09-08<br>
	 * Last updated: 2020-09-15<br>
	 * Class: Abacus
	 * </p>
	 * <p>
	 * Description: copy a directory, with all its content; overloading
	 * of the preceding one.
	 * </p>
	 * 
	 * @param inputFile input object
	 * @param outputFile output object
	 * @param replace replace-existing option
	 * @throws IOException standard exception */
	final public static void copyDirectoryRecursion(File inputFile,
			File outputFile, final boolean replace)

			throws IOException {

		copyDirectoryRecursion(inputFile.toPath(), outputFile.toPath(),
				replace);
	}
}