package net.oni2.aeinstaller.backend.packages.unpack;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

import org.apache.commons.io.FileUtils;

/**
 * @author Christian Illy
 */
public class Unpacker implements Runnable {
	/**
	 * @author Christian Illy
	 */
	public enum EState {
		/**
		 * Unpacker initialized but not started
		 */
		INIT,
		/**
		 * Unpack running
		 */
		RUNNING,
		/**
		 * Unpack interrupted
		 */
		INTERRUPTED,
		/**
		 * Unpack finished successfully
		 */
		FINISHED,
	};

	private UnpackListener listener;

	private File zip;
	private File target;

	private Thread t = null;

	private EState state = EState.INIT;

	/**
	 * Initialize a new AE package unpacker
	 * 
	 * @param zipFile
	 *            AE zip package
	 * @param targetFolder
	 *            Target folder
	 * @param listener
	 *            Listener for progress updates
	 */
	public Unpacker(File zipFile, File targetFolder, UnpackListener listener) {
		this.listener = listener;
		zip = zipFile;
		target = targetFolder;
	}

	/**
	 * Start the unpack process
	 */
	public synchronized void start() {
		if (t == null) {
			t = new Thread(this);
			t.start();
			state = EState.RUNNING;
			updateStatus();
		}
	}

	/**
	 * Stop (abort) the process
	 */
	public synchronized void stop() {
		if (state != EState.FINISHED) {
			state = EState.INTERRUPTED;
			if (t != null) {
				try {
					t.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				t = null;
			}
			updateStatus();
			if (state != EState.FINISHED) {
				if (target.exists()) {
					try {
						FileUtils.deleteDirectory(target);
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

	private synchronized void updateStatus() {
		listener.statusUpdate(this, state);
	}

	@Override
	public void run() {
		try {
			switch (state) {
				case INTERRUPTED:
					return;
				case RUNNING:
					ZipFile zf = null;
					try {
						int pathStart = 0;
						String pathStartName = "";

						zf = new ZipFile(zip);

						if (target.exists())
							FileUtils.deleteDirectory(target);
						target.mkdirs();

						for (Enumeration<? extends ZipEntry> e = zf.entries(); e
								.hasMoreElements();) {
							ZipEntry ze = e.nextElement();
							if (ze.getName().toLowerCase()
									.endsWith("/mod_info.cfg")
									|| ze.getName().toLowerCase()
											.equals("mod_info.cfg")) {
								pathStart = ze.getName().toLowerCase()
										.indexOf("mod_info.cfg");
								pathStartName = ze.getName().substring(0,
										pathStart);
							}
						}

						for (Enumeration<? extends ZipEntry> e = zf.entries(); e
								.hasMoreElements();) {
							if (state == EState.INTERRUPTED)
								return;
							ZipEntry ze = e.nextElement();
							if (!ze.isDirectory()) {
								if (ze.getName().startsWith(pathStartName)) {
									if (!(ze.getName().endsWith("aei.cfg") || ze
											.getName().endsWith(".DS_Store"))) {
										File targetFile = new File(target, ze
												.getName().substring(pathStart));
										File parent = targetFile
												.getParentFile();
										parent.mkdirs();

										InputStream in = zf.getInputStream(ze);

										int read = 0;
										byte[] data = new byte[1024];
										FileOutputStream fileOut = new FileOutputStream(
												targetFile);
										while ((read = in.read(data, 0, 1024)) != -1) {
											fileOut.write(data, 0, read);
										}
										fileOut.close();
									}
								}
							}
						}
					} catch (ZipException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} finally {
						try {
							if (zf != null)
								zf.close();
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
					break;
				default:
					break;
			}
		} finally {
		}

		state = EState.FINISHED;
		updateStatus();
	}
}
