/*
 * NativeCapable is Dynamic Install and Loading Framework for Java Application and Applet.
 * Copyright (C) 2008  Shinobu Izumi
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package jp.ac.kyutech.ai.ylab.shiva.utils.nativecapable;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;

/**
 * 様々な便利機能を集めたユーティリティクラスです。
 * 
 * @author shinobu
 * 
 */
public class Misc {
	/**
	 * Enlarge array size
	 * 
	 * @param ba
	 * @param size
	 * @return
	 */
	public static byte[] fillup(byte[] ba, int size) {
		byte[] res = new byte[ba.length + size];

		System.arraycopy(ba, 0, res, 0, ba.length);

		return res;
	}

	/**
	 * Read all from input stream.
	 * 
	 * @param is
	 * @return
	 */
	public static byte[] readAll(InputStream is) {
		byte[] tmp = new byte[(int) Math.pow(2, 24)];
		int n = -1;
		byte[] result = new byte[0];
		int idx = 0;

		BufferedInputStream bis = new BufferedInputStream(is);

		try {
			while ((n = bis.read(tmp)) != -1) {
				result = fillup(result, n);
				System.arraycopy(tmp, 0, result, idx, n);
				idx += n;
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		return result;
	}

	/**
	 * 指定されたクラスからルートディレクトリを取得します。<br>
	 * Jar化されている場合はinstallDir引数で指定されたディレクトリに解凍後、指定ディレクトリをルートとして返します。
	 * 
	 * @param targetClass
	 *            探索するクラス
	 * @param installDir
	 *            Jarファイルを解凍するディレクトリ(存在しない場合は作成します)
	 * @return targetClassにより指定されたクラスのルート
	 * @throws IOException
	 */
	public static File getRootDirAccess(Class<?> targetClass, File installDir)
			throws IOException {
		// Find target class
		URL url = targetClass.getResource(targetClass.getSimpleName()
				+ ".class");
		System.out.println("finding root dir for : "
				+ targetClass.getSimpleName() + ".class");
		System.out.println(url);

		File dir = null;

		String protocol = url.getProtocol();

		// Jar化されている場合は、
		if (protocol.equals("jar")) {

			installDir.mkdirs();

			File jarFile = null;
			// URLの要らない部分を取って、
			Pattern p = Pattern.compile("(http:/.*)!/");
			Matcher m = p.matcher(url.toString());
			if (m.find()) {
				URL u = new URL(m.group(1));
				byte[] ba = Misc
						.readAll(new BufferedInputStream(u.openStream()));
				File f = new File(installDir, UUID.randomUUID() + ".jar");
				BufferedOutputStream bos = new BufferedOutputStream(
						new FileOutputStream(f));
				bos.write(ba);
				jarFile = f;
			} else {
				// ローカルの場合、
				p = Pattern.compile("(file:/.*)!/");
				m = p.matcher(url.toString());
				if (m.find()) {
					try {
						jarFile = new File(new URI(m.group(1)));
					} catch (URISyntaxException ex) {
						ex.printStackTrace();
					}
				}
			}
			if (jarFile == null) {
				System.out.println("Couldn't find : " + url);
				return null;
			}
			// その親ディレクトリ。
			Misc.tryUnZip(jarFile, installDir);
			return installDir;
		}

		// Jar化されていない場合は、
		else if (protocol.equals("file")) {

			// クラスファイルの場所を特定し、
			File classFile = null;
			try {
				classFile = new File(new URI(url.toString()));
			} catch (URISyntaxException ex) {
				ex.printStackTrace();
			}

			Package pack = targetClass.getPackage();

			// 無名パッケージの場合は、
			if (pack == null) {

				// その親ディレクトリ。
				dir = classFile.getParentFile();
			}

			// パッケージ名がある場合は、
			else {
				// パッケージの階層の分だけ上に上がる。
				String packName = pack.getName();
				String[] words = packName.split("\\.");
				dir = classFile.getParentFile();
				for (int i = 0; i < words.length; i++) {
					dir = dir.getParentFile();
				}
			}
		} else {
			System.out.println("Fileオブジェクトにできません");
			return null;
		}
		return dir;
	}

	/**
	 * ZipInputStreamを使ってZip解凍する。
	 * 
	 * @param filename
	 *            ZIPファイルストリーム
	 * @throws IOException
	 *             入出力エラー
	 */
	public static void tryUnZip(InputStream is, File dir) throws IOException {

		File f = File.createTempFile("tmp_", "");
		FileOutputStream out = new FileOutputStream(f);
		out.write(readAll(is));
		out.close();

		tryUnZip(f, dir);
	}

	/**
	 * 複数のエンコーディングでZipの解凍を試します。
	 * 
	 * @param filename
	 *            ZIPファイルストリーム
	 * @throws IOException
	 *             入出力エラー
	 */
	public static void tryUnZip(File f, File dir) throws IOException {
		try {
			unZip(f, dir, "UTF-8");
			return;
		} catch (IOException e) {
		}
		try {
			unZip(f, dir, "MS932");
			return;
		} catch (IOException e) {
		}
		try {
			unZip(f, dir, "euc-jp");
			return;
		} catch (IOException e) {
		}

		throw new IOException("Could not unzip file : " + f);
	}

	/**
	 * ZipInputStreamを使ってZip解凍する。
	 * 
	 * @param filename
	 *            ZIPファイルストリーム
	 * @throws IOException
	 *             入出力エラー
	 */
	public static void unZip(InputStream is, File dir, String encoding)
			throws IOException {

		File f = File.createTempFile("tmp_", "");
		FileOutputStream out = new FileOutputStream(f);
		out.write(readAll(is));
		out.close();

		unZip(f, dir, encoding);
	}

	/**
	 * エンコードを指定してZip解凍する。
	 * 
	 * @param filename
	 *            ZIPファイル名
	 * @throws IOException
	 *             入出力エラー
	 */
	public static void unZip(File f, File outDir, String encoding)
			throws IOException {
		ZipFile zipFile = new ZipFile(f, encoding);
		Enumeration<?> e = zipFile.getEntries();
		while (e.hasMoreElements()) {
			ZipEntry entry = (ZipEntry) e.nextElement();
			if (entry.isDirectory()) {
				new File(entry.getName()).mkdirs();
			} else {
				File parent = new File(outDir, entry.getName()).getParentFile();
				parent.mkdirs();
				FileOutputStream out = new FileOutputStream(new File(outDir,
						entry.getName()));
				InputStream is = zipFile.getInputStream(entry);
				out.write(readAll(is));
				out.close();
				is.close();
			}
		}
		zipFile.close();
	}

}
