package org.phosphoresce.commons.csv;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.Iterator;

/**
 * CSV関連の管理インタフェースを提供するクラス。<br>
 * 当クラスは指定されたCSV文字列を解析し、CSVRecordListオブジェクトとして提供するインタフェース、
 * 及び、CSVRecordListから指定されたオブジェクトに対してCSVを出力するインタフェースを提供します。<br>
 *
 * @author Kitagawa<br>
 *
 *<!--
 * 更新日		更新者			更新内容
 * 2007/02/16	Kitagawa		新規作成
 *-->
 */
public final class CSVManager implements Serializable {

	/** 解析対象文字列 */
	private String value = null;

	/** クォートトークン解析中フラグ */
	private boolean inQuote = false;

	/**
	 * コンストラクタ<br>
	 * @param value 解析対象文字列
	 */
	private CSVManager(String value) {
		super();
		this.value = value;
	}

	/**
	 * 指定されたCSV形式の文字列をCSVレコードリストオブジェクトとして提供します。<br>
	 * @param value 変換対象CSV形式文字列
	 * @return CSVレコードリストオブジェクト
	 */
	public static CSVRecordList createRecordList(String value) {
		return new CSVManager(value).execute();
	}

	/**
	 * 指定されたFileオブジェクトからCSV形式の文字列を読み込みCSVレコードリストオブジェクトとして提供します。<br>
	 * @param file CSVファイルオブジェクト
	 * @return CSVレコードリストオブジェクト
	 * @throws IOException ファイルオブジェクトからの読み込み中に入出力例外がスローされた場合に発生
	 */
	public static CSVRecordList createRecordList(File file) throws IOException {
		FileInputStream is = new FileInputStream(file);
		CSVRecordList list = createRecordList(is);
		is.close();
		return list;
	}

	/**
	 * 指定されたInputStreamオブジェクトからCSV形式の文字列を読み込みCSVレコードリストオブジェクトとして提供します。<br>
	 * @param is InputStreamオブジェクト
	 * @return CSVレコードリストオブジェクト
	 * @throws IOException InputStreamオブジェクトからの読み込み中に入出力例外がスローされた場合に発生
	 */
	public static CSVRecordList createRecordList(InputStream is) throws IOException {
		StringBuffer buffer = new StringBuffer();
		BufferedReader reader = new BufferedReader(new InputStreamReader(is));
		while (reader.ready()) {
			buffer.append(reader.readLine());
			buffer.append(CSVToken.NEWLINE);
		}
		return createRecordList(buffer.toString());
	}

	/**
	 * 指定されたファイルパスに対してCSVRecordListオブジェクトの内容を出力します。<br>
	 * @param list CSVRecordListオブジェクト
	 * @param path ファイルパス
	 * @throws IOException 指定されたファイルに対しての出力時に入出力例外がスローされた場合に発生
	 */
	public static void save(CSVRecordList list, String path) throws IOException {
		save(list, new File(path));
	}

	/**
	 * 指定されたFileオブジェクトに対してCSVRecordListオブジェクトの内容を出力します。<br>
	 * @param list CSVRecordListオブジェクト
	 * @param file ファイルオブジェクト
	 * @throws IOException 指定されたファイルに対しての出力時に入出力例外がスローされた場合に発生
	 */
	public static void save(CSVRecordList list, File file) throws IOException {
		FileOutputStream os = new FileOutputStream(file);
		save(list, os);
		os.close();
	}

	/**
	 * 指定されたOutputStreamオブジェクトに対してCSVRecordListオブジェクトの内容を出力します。<br>
	 * @param list CSVRecordListオブジェクト
	 * @param os OutputStreamオブジェクト
	 */
	public static void save(CSVRecordList list, OutputStream os) {
		PrintWriter writer = new PrintWriter(new OutputStreamWriter(os));
		for (Iterator iterator = list.iterator(); iterator.hasNext();) {
			CSVRecord tokens = (CSVRecord) iterator.next();
			writer.println(tokens.getRecordValue());
			writer.flush();
		}
		writer.flush();
	}

	/**
	 * クラスが保持しているCSV形式文字列を解析してCSVレコードリストを提供します。<br>
	 * @return CSVレコードリスト
	 */
	private CSVRecordList execute() {

		CSVRecordList recordList = new CSVRecordList();
		CSVRecord tokenList = new CSVRecord();

		StringBuffer token = new StringBuffer();

		for (int i = 0; i <= value.length() - 1; i++) {
			String s1 = String.valueOf(value.charAt(i));
			String s2 = i < value.length() - 1 ? String.valueOf(value.charAt(i + 1)) : "";

			if (!inQuote) {
				/*
				 * クォートトークン解析中ではない場合
				 */
				if (CSVToken.SEPARATOR.equals(s1)) {
					// 解析文字がカンマである場合はトークン解析を終了
					tokenList.add(new CSVToken(CSVToken.decode(token.toString())));
					token = new StringBuffer();
				} else if (CSVToken.NEWLINE.equals(s1)) {
					// 解析文字が改行である場合はレコード解析を終了
					tokenList.add(new CSVToken(CSVToken.decode(token.toString())));
					recordList.add(tokenList);
					token = new StringBuffer();
					tokenList = new CSVRecord();
				} else if (CSVToken.QUOTE.equals(s1)) {
					// 解析文字がクォートである場合はクォート解析フラグを立てる
					token.append(s1);
					inQuote = true;
				} else {
					// 通常文字の場合はバッファに文字を追加
					token.append(s1);
				}
			} else {
				/*
				 * クォートトークン解析中での場合
				 */
				if (CSVToken.SEPARATOR.equals(s1)) {
					// 解析文字がカンマだとしても文字として追加
					token.append(s1);
				} else if (CSVToken.NEWLINE.equals(s1)) {
					// 解析文字が改行だとしても文字として追加
					token.append(s1);
				} else if (CSVToken.QUOTE.equals(s1) && CSVToken.QUOTE.equals(s2)) {
					// 解析文字においてクォートが連続で存在する場合は文字を追加して添字をインクリメント
					token.append(s1);
					token.append(s2);
					i++;
				} else if (CSVToken.QUOTE.equals(s1) && !CSVToken.QUOTE.equals(s2)) {
					// 解析文字においてクォートが単独で存在する場合は文字を追加してクォート解析フラグを解除
					token.append(s1);
					inQuote = false;
				} else {
					// 通常文字の場合はバッファに文字を追加
					token.append(s1);
				}
			}
		}

		return recordList;
	}
}

