package com.sysdeo.eclipse.tomcat;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * Zipper
 *
 */
public abstract class Zipper {

	/** BUFFER */
	private static final int BUFFER = 2048;
	/** EMPTY_CRC */
	private static final long EMPTY_CRC = new CRC32().getValue();

	/** outputFile */
	private final File outputFile;
	/** directory */
	private final File directory;
	/** currentDirName */
	private final String currentDirName;

	/**
	 * Constructor
	 * @param outFile File
	 * @param dir File
	 */
	public Zipper(final File outFile, final File dir) {
		this.outputFile = outFile;
		this.directory = dir;
		this.currentDirName = dir.getAbsolutePath();
	}

	/**
	 * zip
	 * @throws IOException IOException
	 */
	public void zip() throws IOException {
		try (FileOutputStream fos = new FileOutputStream(this.outputFile)) {
			try (ZipOutputStream zos = new ZipOutputStream(fos)) {
				zipDir(this.directory, zos);
				zos.flush();
			}
		}
	}

	/**
	 * zipDir
	 * @param dir File
	 * @param zos ZipOutputStream
	 * @throws IOException IOException
	 */
	private void zipDir(final File dir, final ZipOutputStream zos) throws IOException {
		if (!dir.getPath().equals(this.currentDirName)) {
			String entryName = dir.getPath().substring(this.currentDirName.length() + 1);
			entryName = entryName.replace('\\', '/');
			ZipEntry ze = new ZipEntry(entryName + "/");
			if (dir.exists()) {
				ze.setTime(dir.lastModified());
			} else {
				ze.setTime(System.currentTimeMillis());
			}
			ze.setSize(0);
			ze.setMethod(ZipEntry.STORED);
			// This is faintly ridiculous:
			ze.setCrc(EMPTY_CRC);
			zos.putNextEntry(ze);
		}

		if (dir.exists() && dir.isDirectory()) {
			File[] fileList = dir.listFiles();
			for (int i = 0; fileList != null && i < fileList.length; i++) {
				if (fileList[i].isDirectory() && acceptDir(fileList[i])) {
					zipDir(fileList[i], zos);
				}
				if (fileList[i].isFile() && acceptFile(fileList[i])) {
					zipFile(fileList[i], zos);
				}
			}
		}
	}

	/**
	 * zipFile
	 * @param file File
	 * @param zos ZipOutputStream
	 * @throws IOException IOException
	 */
	private void zipFile(final File file, final ZipOutputStream zos) throws IOException {
		if (!file.equals(this.outputFile)) {
			try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file), BUFFER)) {
				String entryName = file.getPath().substring(this.currentDirName.length() + 1);
				entryName = entryName.replace('\\', '/');
				ZipEntry fileEntry = new ZipEntry(entryName);
				zos.putNextEntry(fileEntry);

				byte[] data = new byte[BUFFER];
				int byteCount;
				while ((byteCount = bis.read(data, 0, BUFFER)) != -1) {
					zos.write(data, 0, byteCount);
				}
			}
		}
	}

	/**
	 *
	 * @param dir File
	 * @return boolean
	 */
	protected abstract boolean acceptDir(File dir);

	/**
	 *
	 * @param file File
	 * @return boolean
	 */
	protected abstract boolean acceptFile(File file);
}
