package hiro.yoshioka.extract;

import hiro.yoshioka.util.DirCleaner;
import hiro.yoshioka.util.FileList;
import hiro.yoshioka.util.StringUtil;

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 java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tools.zip.ZipOutputStream;

public class ArchiveManager implements IArchiveManager, Runnable {
	private static Log log = LogFactory.getLog(ArchiveManager.class);

	public static final String ARC_EXTENSION_PATTERN = "(.*)[.](jar|ear|war|zip)";
	private String[] ignore_patterns = { "!hogehogehoge!" };
	private String targetArchiveExtentionPattern;

	File filePath;

	String extractPath = null;

	Stack<String> stackPath = null;

	String folder_name;

	List<String> ignorePatternList = new ArrayList<String>();

	private boolean doMelt = true;

	private ArchiveManagerListener observer;

	Pattern p;

	public static void main(String[] args) {
		try {
			ArchiveManager obj = new ArchiveManager();

			String[] ret = obj.lookup2Strings(new File(
					"/Users/yonsama/tools/jdbc"), ".*[.]class");
			for (String str : ret) {
				System.out.println("str = " + str);
			}

			System.out.println("done...");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public ArchiveManager() {
		this(ARC_EXTENSION_PATTERN);
	}

	public ArchiveManager(String pattern) {
		setArcExtensionPattern(pattern);
		for (int i = 0; i < ignore_patterns.length; i++) {
			ignorePatternList.add(ignore_patterns[i]);
		}
	}

	public void compress(String encoding) throws Exception {
		File top = new File(extractPath);
		File[] files = { new File(extractPath) };
		ZipOutputStream zos = new ZipOutputStream(
				new FileOutputStream(filePath));
		zos.setEncoding(encoding);
		try {
			encode(zos, files, top);
		} finally {
			zos.flush();
			zos.close();
		}

	}

	void encode(ZipOutputStream zos, File[] files, File top) throws Exception {
		byte[] buf = new byte[1024];
		for (File f : files) {
			if (f.isDirectory()) {
				encode(zos, f.listFiles(), top);
			} else {
				String path = f.getPath();
				path = path
						.substring(top.getPath().length() + 1, path.length());
				path = path.replace('\\', '/');
				org.apache.tools.zip.ZipEntry ze = new org.apache.tools.zip.ZipEntry(
						path);
				zos.putNextEntry(ze);
				InputStream is = new BufferedInputStream(new FileInputStream(f));
				while (true) {
					int len = is.read(buf);
					if (len < 0) {
						break;
					}
					zos.write(buf, 0, len);
				}
				is.close();
			}
		}
	}

	public boolean setArcExtensionPattern(String pattern) {
		targetArchiveExtentionPattern = pattern;
		try {
			p = Pattern.compile(targetArchiveExtentionPattern,
					Pattern.CASE_INSENSITIVE);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			p = Pattern
					.compile(ARC_EXTENSION_PATTERN, Pattern.CASE_INSENSITIVE);
		}
		return false;
	}

	public boolean setFile(String filePath) {

		Matcher mat = p.matcher(filePath);
		if (mat.matches()) {
			this.filePath = new File(filePath);
			String name = this.filePath.getName();
			stackPath = new Stack<String>();

			folder_name = name.replace('.', '_');
			stackPath.push(folder_name + File.separator);
			if (log.isDebugEnabled()) {
				log.warn("name=" + name + " foler_name=" + folder_name);
			}
			if (getCreatedDirAtExt().exists()) {
				if (log.isInfoEnabled()) {
					log.info("Delete same Dirs "
							+ new DirCleaner(getCreatedDirAtExt()
									.getAbsolutePath()).deleteAllDir());
				}
			}
			return true;
		} else {
			if (log.isWarnEnabled()) {
				log.warn("This file matched ignore pattern : " + filePath);
			}
			return false;
		}
	}

	public String getExtractPath() {
		return extractPath;
	}

	public void setExtractPath(String path) {
		this.extractPath = path;
	}

	public File getCreatedDirAtExt() {
		File f = new File(extractPath, folder_name);
		if (log.isDebugEnabled()) {
			log.debug("Extract Dir :" + f);
		}
		return f;
	}

	public String addIgnorePattern(String patern) {
		ignorePatternList.add("!" + patern + "!");
		String[] newArrat = new String[ignorePatternList.size()];
		ignore_patterns = (String[]) ignorePatternList.toArray(newArrat);
		return ignorePatternList.toString();
	}

	public boolean extract() {
		ZipFile zf = null;
		try {
			if (filePath == null) {
				log.warn("filePath is null");
				return false;
			}
			File f = new File(extractPath);
			if (!f.exists()) {
				if (!f.mkdirs()) {
					log.warn("mkdir failed");
					return false;
				}
			} else {
				if (log.isInfoEnabled()) {
					log.info("extract dir : " + f);
				}
			}
			zf = new ZipFile(filePath);
			extract(zf);

			return true;
		} catch (IOException ioe) {
			ioe.printStackTrace();
			return false;
		}

	}

	public Map<File, List<ZipEntry>> lookup(File dir, String regex)
			throws FileNotFoundException, IOException {
		File[] fArcs = new FileList(targetArchiveExtentionPattern).getMatches(
				dir, -1);
		if (log.isInfoEnabled()) {
			log.info("target num of archive files:" + fArcs.length);
		}
		Map<File, List<ZipEntry>> fmap = new HashMap<File, List<ZipEntry>>();
		for (int i = 0; i < fArcs.length; i++) {
			ZipFile zf = new ZipFile(fArcs[i]);
			ZipEntry ze;
			Enumeration enums = zf.entries();
			try {
				while (enums.hasMoreElements()) {
					ze = (ZipEntry) enums.nextElement();
					String name = ze.getName();
					if (!ze.isDirectory() && name.matches(regex)) {
						List<ZipEntry> list = null;
						if (!fmap.containsKey(fArcs[i])) {
							list = new ArrayList<ZipEntry>();
							fmap.put(fArcs[i], list);
						} else {
							list = fmap.get(fArcs[i]);
						}
						list.add(ze);
					}
				}
			} finally {
				if (zf != null) {
					zf.close();
				}
			}
		}
		return fmap;
	}

	public String[] lookup2Strings(File dir, String regex)
			throws FileNotFoundException, IOException {
		Map<File, List<ZipEntry>> fmap = lookup(dir, regex);
		Set<String> ret = new LinkedHashSet<String>();
		for (List<ZipEntry> lis : fmap.values()) {
			for (ZipEntry entry : lis) {
				ret.add(entry.getName());
			}
		}

		return ret.toArray(new String[ret.size()]);
	}

	public class EntryInfo {
		ZipFile fZipFile;

		ZipEntry[] fZipEntries;

		private EntryInfo(File f) throws IOException {
			fZipFile = new ZipFile(f);
		}

		public Enumeration getEntries() {
			return fZipFile.entries();
		}

		protected void finalize() throws Throwable {
			dispose();
			super.finalize();
		}

		public InputStream getInputStream(ZipEntry ze) throws ZipException,
				IOException {
			return fZipFile.getInputStream(ze);
		}

		public ZipEntry[] getZipEntries() {
			return fZipEntries;
		}

		public String getWithOutExtentionName(ZipEntry ze) {
			String name = ze.getName();
			Pattern p = Pattern.compile("(.*/)?(.*)([.].*)");
			Matcher m = p.matcher(name);
			m.matches();
			return m.group(2);
		}

		public void dispose() {
			try {
				if (fZipFile != null) {
					fZipFile.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public EntryInfo lookupEntrys(File dir, String regex)
			throws FileNotFoundException, IOException {
		File[] fArcs = new FileList(targetArchiveExtentionPattern).getMatches(
				dir, 1);
		if (log.isInfoEnabled()) {
			log.info("num of archive files:" + fArcs.length);
		}

		if (fArcs.length != 1) {
			return null;
		}
		EntryInfo ei = new EntryInfo(fArcs[0]);

		ZipEntry ze;

		List<ZipEntry> list = new ArrayList<ZipEntry>();

		Enumeration enums = ei.getEntries();

		while (enums.hasMoreElements()) {
			ze = (ZipEntry) enums.nextElement();
			String name = ze.getName();
			if (!ze.isDirectory() && name.matches(regex)) {
				list.add(ze);
			}
		}

		ei.fZipEntries = list.toArray(new ZipEntry[list.size()]);
		return ei;
	}

	private void extract(ZipFile zf) throws FileNotFoundException, IOException {
		ZipEntry ze;
		Enumeration enums = zf.entries();
		if (log.isInfoEnabled()) {
			log.info(zf.toString());
		}
		try {
			while (enums.hasMoreElements()) {
				ze = (ZipEntry) enums.nextElement();
				String name = ze.getName();

				if (observer != null) {
					observer.locationOfExtract(name);
				}
				File f = createFilePath(name);

				if (ze.isDirectory()) {
					processEntryDirectory(createFilePath(name));
					continue;
				}

				if (log.isInfoEnabled()) {
					log.info(f.toString());
				}
				createParentDirectories(f);
				BufferedOutputStream bos = new BufferedOutputStream(
						new FileOutputStream(f));
				int len;
				int bufsize = 1024;

				byte[] buf = new byte[bufsize];
				BufferedInputStream input = new BufferedInputStream(
						zf.getInputStream(ze));
				while ((len = input.read(buf, 0, bufsize)) != -1) {
					bos.write(buf, 0, len);
				}
				bos.close();
				f.setLastModified(ze.getTime());
				Matcher mat = p.matcher(name);
				if (mat.matches()) {
					boolean isExpand = true;
					for (int i = 0; i < ignore_patterns.length; i++) {
						Pattern pi = Pattern.compile(ignore_patterns[i],
								Pattern.CASE_INSENSITIVE);
						Matcher mati = pi.matcher(name);
						if (mati.matches()) {
							isExpand = false;
							break;
						}
					}
					if (isExpand) {
						stackPath.push(name.replace('.', '_') + File.separator);
						processEntryDirectory(
								createFilePath(StringUtil.EMPTY_STRING),
								ze.getTime());
						// processEntryDirectory(createFilePath(""));
						extract(new ZipFile(f));
						if (!doMelt) {
							continue;
						}
						f.delete();
					}
				}
			}
			if (!stackPath.empty()) {
				stackPath.pop();
			}
		} finally {
			if (zf != null) {
				zf.close();
			}
		}
	}

	protected File createFilePath(String entry) throws IOException {
		StringBuilder path = new StringBuilder();
		for (String p : stackPath) {
			path.append(p);
		}
		return new File(new File(extractPath, path.toString()), entry);

	}

	protected void createParentDirectories(File entry) throws IOException {
		if (!entry.getParentFile().exists()) {
			processEntryDirectory(entry.getParentFile());
		}
	}

	protected void processEntryDirectory(File fileDir, long time)
			throws IOException {
		processEntryDirectory(fileDir);
		fileDir.setLastModified(time);
	}

	protected void processEntryDirectory(File fileDir) throws IOException {
		if (fileDir.exists()) {
			if (fileDir.isDirectory() == false) {
				if (log.isWarnEnabled()) {
					log.warn("exists same dir [" + fileDir + "]");
				}
			}
		} else if (fileDir.mkdirs() == false) {
			if (log.isWarnEnabled()) {
				log.warn("fault mkdirs [" + fileDir + "].");
			}
		}
	}

	public void run() {
		boolean ret = extract();
		if (observer != null) {
			observer.doneExtract(ret);
		}
	}

	public void addArchiveManagerListener(ArchiveManagerListener listener) {
		observer = listener;
	}
}
