package jp.sourceforge.orangesignal.ta.dataset.loader;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import jp.gr.java_conf.dangan.util.lha.LhaHeader;
import jp.gr.java_conf.dangan.util.lha.LhaInputStream;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;

/**
 * 区切り文字によって区切られたテキストデータの読込みユーティリティークラスを提供します。
 * 
 * @author 杉澤 浩二
 * @since 2.0
 */
class TextDataUtils {

	/**
	 * コンテンツ形式の種類を表す列挙型を提供します。
	 * 
	 * @author <a href="mailto:sugisawa@orangesignal.com">杉澤 浩二</a>
	 */
	private enum ContentType {

		/**
		 * テキスト形式であることを表します。
		 */
		TEXT,

		/**
		 * LHA 圧縮形式であることを表します。
		 */
		LHA,

		/**
		 * ZIP 圧縮形式であることを表します。
		 */
		ZIP;

		/**
		 * 指定された拡張子から列挙型を構築して返します。
		 * 
		 * @param ext 拡張子
		 * @return 拡張子に対応する列挙型
		 */
		public static ContentType valueOfExtension(final String ext) {
			if ("lzh".equalsIgnoreCase(ext))
				return LHA;
			else if ("zip".equalsIgnoreCase(ext))
				return ZIP;
			else
				return TEXT;
		}
	}

	/**
	 * インスタンス化できない事を強制します。
	 */
	private TextDataUtils() {}

	/**
	 * <p>指定されたファイルを読込んでデータのリストを返します。</p>
	 * <p>このメソッドは、指定されたファイルの拡張子によってコンテンツ形式を判断します。</p>
	 * 
	 * @param filename ファイル名
	 * @param encoding エンコーディング
	 * @param separator 区切り文字
	 * @return データのリスト
	 * @throws IOException 入力操作で例外が発生した場合
	 */
	public static List<String[]> read(final String filename, final String encoding, final String separator) throws IOException {
		return read(new File(filename), encoding, separator);
	}

	/**
	 * <p>指定されたファイルを読込んでデータのリストを返します。</p>
	 * <p>このメソッドは、指定されたファイルの拡張子によってコンテンツ形式を判断します。</p>
	 * 
	 * @param file ファイル情報
	 * @param encoding エンコーディング
	 * @param separator 区切り文字
	 * @return データのリスト
	 * @throws IOException 入力操作で例外が発生した場合
	 */
	public static List<String[]> read(final File file, final String encoding, final String separator) throws IOException {
		final byte[] bytes = toByteArray(file);
		switch (ContentType.valueOfExtension(FilenameUtils.getExtension(file.getCanonicalPath()))) {
			case ZIP:
				return loadZip(bytes, encoding, separator);
			case LHA:
				return loadLha(bytes, encoding, separator);
			default:
				return loadRaw(bytes, encoding, separator);
		}
	}

	/**
	 * 指定されたファイルを読込んで内容をバイト配列として返します。
	 * 
	 * @param file ファイル
	 * @return 読込んだファイル内容のバイト配列
	 * @throws IOException ファイルの入出力操作で例外が発生した場合
	 */
	private static byte[] toByteArray(final File file) throws IOException {
		final InputStream in = new FileInputStream(file);
		try {
			return IOUtils.toByteArray(in);
		} finally {
			IOUtils.closeQuietly(in);
		}
	}

	private static List<String[]> loadZip(final byte[] data, final String encoding, final String separator) throws IOException {
		final ZipInputStream zip = new ZipInputStream(new ByteArrayInputStream(data));
		try {
			final List<String[]> results = new ArrayList<String[]>();
			ZipEntry entry = zip.getNextEntry();
			while (entry != null) {
				try {
					results.addAll(loadRaw(IOUtils.toByteArray(zip), encoding, separator));
				} finally {
					zip.closeEntry();
					entry = zip.getNextEntry();
				}
			}

			return results;

		} finally {
			IOUtils.closeQuietly(zip);
		}
	}

	private static List<String[]> loadLha(final byte[] data, final String encoding, final String separator) throws IOException {
		final List<String[]> results = new ArrayList<String[]>();
		final LhaInputStream lha = new LhaInputStream(new ByteArrayInputStream(data));
		try {
			LhaHeader entry = lha.getNextEntry();
			while (entry != null) {
				results.addAll(loadRaw(IOUtils.toByteArray(lha), encoding, separator));
				entry = lha.getNextEntry();
			}
			return results;
		} finally {
			IOUtils.closeQuietly(lha);
		}
	}

	/**
	 * <p>指定された入力データを読込んでデータのリストを返します。</p>
	 * 
	 * @param data データ
	 * @param encoding エンコーディング
	 * @param separator 区切り文字
	 * @return データのリスト
	 * @throws IOException 入力操作で例外が発生した場合
	 */
	private static List<String[]> loadRaw(final byte[] data, final String encoding, final String separator) throws IOException {
		final List<String[]> results = new ArrayList<String[]>();
		final LineIterator iterator = IOUtils.lineIterator(new ByteArrayInputStream(data), encoding);
		try {
			while (iterator.hasNext()) {
				final String line = iterator.nextLine();
				// 空白行や区切り文字のみの行は無視します。
				if (line == null || line.isEmpty() || line.matches("^[" + separator + "]+$"))
					continue;

				results.add(line.split(separator));
			}
		} finally {
			LineIterator.closeQuietly(iterator);
		}
		return results;
	}

}
