/*
 * Copyright (C) 2020 favdb
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package storybook.tools.zip;

import i18n.I18N;
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.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import storybook.tools.LOG;

/**
 * inspired by
 * http://www.avajava.com/tutorials/lessons/how-do-i-zip-a-directory-and-all-its-contents.html
 *
 * @author favdb
 */
public class ZipUtil {

	private static final String TT = "ZipUtil";

	public static boolean copyTo(InputStream is, String out) {
		try {
			FileOutputStream os = new FileOutputStream(new File(out));
			byte[] buffer = new byte[1024];
			int length;
			while ((length = is.read(buffer)) > 0) {
				os.write(buffer, 0, length);
			}
			is.close();
			os.close();
		} catch (IOException ex) {
			LOG.err("copy zipped file to \"" + out + "\" error", ex);
			return false;
		}
		return true;
	}

	private ZipUtil() {
		// empty
	}

	private static final String EPUB_EXT = ".epub";

	public static void unzip(File zipFilePath, Path destDir) throws FileNotFoundException, IOException {
		FileInputStream fis;
		//buffer for read and write data to file
		byte[] buffer = new byte[1024];
		fis = new FileInputStream(zipFilePath);
		ZipInputStream zis = new ZipInputStream(fis);
		ZipEntry ze = zis.getNextEntry();
		while (ze != null) {
			String fileName = ze.getName();
			File newFile = new File(destDir + File.separator + fileName);
			if (ze.isDirectory()) {
				new File(newFile.getParent()).mkdirs();
			} else {
				//create directories for sub directories in zip
				new File(newFile.getParent()).mkdirs();
				FileOutputStream fos = new FileOutputStream(newFile);
				int len;
				while ((len = zis.read(buffer)) > 0) {
					fos.write(buffer, 0, len);
				}
				fos.close();
				//close this ZipEntry
				zis.closeEntry();
			}
			ze = zis.getNextEntry();
		}
		//close last ZipEntry
		zis.closeEntry();
		zis.close();
		fis.close();
	}

	public static void unZip(String source, String outDir) throws IOException {
		File folder = new File(outDir);
		new File(folder.getParent()).mkdirs();
		ZipInputStream zis;
		if (source.startsWith("http://") || source.startsWith("https://")) {
			URL url = new URL(source);
			zis = new ZipInputStream(url.openStream());
		} else {
			zis = new ZipInputStream(new FileInputStream(source));
		}
		ZipEntry entry = zis.getNextEntry();
		while (entry != null) {
			String fname = entry.getName();
			File newFile = new File(outDir + File.separator + fname);
			newFile.getParentFile().mkdirs();
			if (entry.isDirectory()) {
				newFile.mkdirs();
			} else {
				writeFile(zis, newFile);
			}
			entry = zis.getNextEntry();
		}
		zis.closeEntry();
	}

	private static void writeFile(ZipInputStream inputStream, File file) throws IOException {
		byte[] buffer = new byte[1024];
		file.createNewFile();
		try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
			int length;
			while ((length = inputStream.read(buffer)) > 0) {
				fileOutputStream.write(buffer, 0, length);
			}
		}
	}

	public static void zipFromDir(String src, File dest, String... mime) {
		//App.trace("ZipUtil.zipFromDir(" + src + ", to=" + dest + ")");
		File directoryToZip = new File(src);
		List<File> fileList = getAllFiles(directoryToZip);
		writeZipFile(dest, fileList, src, mime);
	}

	public static void zipFromDir(String src, ZipOutputStream dest) {
		File directoryToZip = new File(src);
		List<File> fileList = new ArrayList<>();
		getAllFiles(directoryToZip, fileList);
		for (File file : fileList) {
			if (!file.isDirectory() && !addToZip(file, dest)) {
				break;
			}
		}
	}

	public static void zipFromDirPath(String src, ZipOutputStream dest, String path) {
		File directoryToZip = new File(src);
		List<File> fileList = new ArrayList<>();
		getAllFiles(directoryToZip, fileList);
		for (File file : fileList) {
			if (!file.isDirectory() && !addToZipPath(file, dest, path)) {
				break;
			}
		}
	}

	public static List<File> getAllFiles(File src) {
		List<File> list = new ArrayList<>();
		for (File file : src.listFiles()) {
			list.add(file);
			if (file.isDirectory()) {
				getAllFiles(file, list);
			}
		}
		list.sort(Comparator.reverseOrder());
		return list;
	}

	public static void getAllFiles(File src, List<File> fileList) {
		for (File file : src.listFiles()) {
			fileList.add(file);
			if (file.isDirectory()) {
				getAllFiles(file, fileList);
			}
		}
		fileList.sort(Comparator.reverseOrder());
	}

	private static final String ZIP_ERR = "epub.zip.error";

	public static void writeZipFile(File dest, List<File> fileList, String srcDir, String... mime) {
		try {
			try (FileOutputStream fos = new FileOutputStream(dest.getAbsolutePath()); ZipOutputStream zos = new ZipOutputStream(fos)) {
				ZipEntry entry = new ZipEntry("mimetype");
				entry.setMethod(ZipEntry.STORED);
				if (mime != null && mime.length > 0) {
					byte[] bytes = mime[0].getBytes();
					entry.setSize(bytes.length);
					CRC32 crc = new CRC32();
					crc.update(bytes);
					entry.setCrc(crc.getValue());
					zos.putNextEntry(entry);
					zos.write(bytes, 0, bytes.length);
					zos.flush();
					zos.closeEntry();
				}
				for (File file : fileList) {
					if (!file.isDirectory() && !addToZip(file, zos, srcDir)) {
						break;
					}
				}
				zos.finish();
			}
		} catch (FileNotFoundException ex) {
			LOG.err(I18N.getMsg(ZIP_ERR), ex);
		} catch (IOException ex) {
			LOG.err(I18N.getMsg(ZIP_ERR), ex);
		}
	}

	public static void writeZipFile(File dest, List<File> fileList) {
		try {
			try (FileOutputStream fos = new FileOutputStream(dest.getAbsolutePath()); ZipOutputStream zos = new ZipOutputStream(fos)) {
				for (File file : fileList) {
					if (!file.isDirectory() && !addToZip(file, zos)) {
						break;
					}
				}
				zos.finish();
			}
		} catch (FileNotFoundException ex) {
			LOG.err(I18N.getMsg(ZIP_ERR), ex);
		} catch (IOException ex) {
			LOG.err(I18N.getMsg(ZIP_ERR), ex);
		}
	}

	public static boolean addToZip(File file, ZipOutputStream zos) {
		//App.trace(TT + ".addToZip(file=" + file.getAbsolutePath() + ", zos)");
		try (FileInputStream fis = new FileInputStream(file)) {
			String n = file.getAbsolutePath();
			String zipFilePath = file.getAbsolutePath().substring(n.lastIndexOf("epub") + 5);
			ZipEntry zipEntry = new ZipEntry(zipFilePath);
			zos.putNextEntry(zipEntry);
			byte[] bytes = new byte[1024];
			int length;
			while ((length = fis.read(bytes)) >= 0) {
				zos.write(bytes, 0, length);
			}
			zos.flush();
			zos.closeEntry();
		} catch (FileNotFoundException ex) {
			LOG.err(I18N.getMsg(ZIP_ERR), ex);
			return (false);
		} catch (IOException ex) {
			LOG.err(I18N.getMsg(ZIP_ERR), ex);
			return (false);
		}
		return (true);
	}

	public static boolean addToZip(File file, ZipOutputStream zos, String src) {
		//App.trace(TT + ".addToZip(file=" + file.getAbsolutePath() + ", zos, src=" + src + ")");
		try (FileInputStream fis = new FileInputStream(file)) {
			String n = file.getAbsolutePath();
			String zipFilePath = file.getAbsolutePath().substring(n.lastIndexOf(src) + src.length() + 1);
			ZipEntry zipEntry = new ZipEntry(zipFilePath);
			if (zipEntry.getName().equals("mimetype")) {
				return true;
			}
			zos.putNextEntry(zipEntry);
			byte[] bytes = new byte[1024];
			int length;
			while ((length = fis.read(bytes)) >= 0) {
				zos.write(bytes, 0, length);
			}
			zos.flush();
			zos.closeEntry();
		} catch (FileNotFoundException ex) {
			LOG.err(I18N.getMsg(ZIP_ERR), ex);
			return (false);
		} catch (IOException ex) {
			LOG.err(I18N.getMsg(ZIP_ERR), ex);
			return (false);
		}
		return (true);
	}

	public static boolean addToZipPath(File file, ZipOutputStream zos, String path) {
		//App.trace("ZipUtil.addToZip(src="+src+", file="+file.getAbsolutePath()+", zos");
		try (FileInputStream fis = new FileInputStream(file)) {
			String n = file.getAbsolutePath();
			String zipFilePath = path + file.getAbsolutePath().substring(n.lastIndexOf("epub") + 5);
			ZipEntry zipEntry = new ZipEntry(zipFilePath);
			zos.putNextEntry(zipEntry);
			byte[] bytes = new byte[1024];
			int length;
			while ((length = fis.read(bytes)) >= 0) {
				zos.write(bytes, 0, length);
			}
			zos.flush();
			zos.closeEntry();
		} catch (FileNotFoundException ex) {
			LOG.err(I18N.getMsg(ZIP_ERR), ex);
			return (false);
		} catch (IOException ex) {
			LOG.err(I18N.getMsg(ZIP_ERR), ex);
			return (false);
		}
		return (true);
	}

	public static String getContent(InputStream zis) {
		try {
			StringBuilder sb = new StringBuilder();
			byte[] buffer = new byte[1024];
			int len;
			while ((len = zis.read(buffer)) > 0) {
				sb.append(buffer);
			}
			return sb.toString();
		} catch (IOException ex) {
			LOG.err("error getting zipped content", ex);
		}
		return "";
	}

}
