package junkutil.excel;

import java.util.ArrayList;
import java.util.zip.DataFormatException;

/**
 * CSVを処理する
 * @author Hiroyuki Shiota
 */
public class CSV {
	public static final int MOST_QUOTE = 1;
	public static final int ANY_QUOTE = 2;
	public static final int NO_QUOTE = 3;

	/**
	 * コンストラクタ
	 */
	private CSV() {
	}

	/**
	 * 文字列の配列をMS-CVS形式のレコードを表す文字列に変換する。
	 * このメソッドは戻り値に改行を付加しない。
	 * @param strings 文字列の配列
	 * @param mode クォート・モード
	 * @return MS-CVS形式のレコードを表す文字列
	 */
	public static String stringsToCsv(String [] strings, int mode) {
		return stringsToCsv(strings, ",", mode);
	}

	/**
	 * 文字列の配列をTSV形式のレコードを表す文字列に変換する。
	 * このメソッドは戻り値に改行を付加しない。
	 * @param strings 文字列の配列
	 * @param mode クォート・モード
	 * @return MS-CVS形式のレコードを表す文字列
	 */
	public static String stringsToTsv(String [] strings, int mode) {
		return stringsToCsv(strings, "\t", mode);
	}

	/**
	 * 文字列の配列をTSV形式のレコードを表す文字列に変換する。
	 * このメソッドは戻り値に改行を付加しない。
	 * @param strings 文字列の配列
	 * @param delim デリミタ
	 * @param mode クォート・モード
	 * @return MS-CVS形式のレコードを表す文字列
	 */
	private static String stringsToCsv(String [] strings, String delim, int mode) {
		if (mode < MOST_QUOTE || NO_QUOTE < mode) throw new IllegalArgumentException("mode=" + mode);
		StringBuffer tempStringBuffer = new StringBuffer();
		for (int i = 0; i < strings.length; i++) {
			if (i != 0) {
				tempStringBuffer.append(delim);
			}
			String localCvsSource = strings[i];
			if (localCvsSource == null) {
				throw new NullPointerException("cvsSource[" + i + "] is null.");
			}
			tempStringBuffer.append(toCSV(localCvsSource, mode));
		}
		return new String(tempStringBuffer);
	}

	/**
	 * カラムをCSV形式に変える
	 * @param column
	 * @param mode
	 * @return
	 */
	private static String toCSV(String column, int mode) {
		int idx = column.indexOf('"');
		StringBuffer strBuf = new StringBuffer();
		//指定に応じてクォートで囲む
		if (mode == MOST_QUOTE || (mode == ANY_QUOTE && idx != -1)) strBuf.append("\"");
		for (int i = 0; i < column.length(); i++) {
			char sourceChar = column.charAt(i);
			if (sourceChar == '"') {
				// ダブルクォートがある場合、二つ重ねてエスケープする
				strBuf.append("\"\"");
			} else {
				strBuf.append(sourceChar);
			}
		}
		if (mode == MOST_QUOTE || (mode == ANY_QUOTE && idx != -1)) strBuf.append("\"");
		return new String(strBuf);
	}

	/**
	 * CSV形式の文字列を受け取り、文字列の配列に加工する。
	 * @param csvStr - CSV形式の文字列
	 * @return 文字列の配列
	 */
	public static String [] csvToStrings(String csvStr) throws DataFormatException {
		return csvToStrings(csvStr, ',');
	}
	/**
	 * TSV形式の文字列を受け取り、文字列の配列に加工する。
	 * @param tsvStr - TSV形式の文字列
	 * @return 文字列の配列
	 */
	public static String [] tsvToStrings(String csvStr) throws DataFormatException {
		return csvToStrings(csvStr, '\t');
	}
	/**
	 * CSV形式の文字列を受け取り、文字列の配列に加工する
	 * @param csvStr
	 * @param delim
	 * @return
	 * @throws DataFormatException
	 */
	private static String [] csvToStrings(String csvStr, char delim) throws DataFormatException {
		String [] resultStringArray = null;
		ArrayList columnStringList = new ArrayList();
		int argCSVStringLength = csvStr.length();
		char lastChar = '_'; // dummy
		for (int i = 0; i < argCSVStringLength; i++) {
			// "aaa","bbb""bbb",ccc
			char localChar = csvStr.charAt(i);
			if (localChar == '"') {
				i++;
				if (i > argCSVStringLength) throw new DataFormatException();
				String readedColumnString = readUntilDoubleQuote(csvStr, i); // 次の「"」が来るまで、文字列を読む
				if (lastChar == '"') {
					int lastIndex = columnStringList.size() - 1;
					String lastString = (String)columnStringList.get(lastIndex);
					columnStringList.set(lastIndex, lastString + "\"" + readedColumnString);
				} else {
					columnStringList.add(readedColumnString);
				}
				i += readedColumnString.length();
				lastChar = '"';
			} else if (localChar == delim) {
				if (lastChar == '"') {
					lastChar = delim;
					continue;
				} else {
					columnStringList.add("");
					lastChar = ',';
				}
			} else {
				if (i > argCSVStringLength) throw new DataFormatException();
				String readedColumnString = readUntilColon(csvStr, i); // 次の「,」が来るまで、文字列を読む
				columnStringList.add(readedColumnString);
				i += readedColumnString.length();
				lastChar = '_';
			}
		}
		resultStringArray = new String[columnStringList.size()];
		for (int i = 0; i < columnStringList.size(); i++) {
			String localColumnString = (String)columnStringList.get(i);
			resultStringArray[i] = localColumnString;
		}
		return resultStringArray;
	}
	/**
	 * 「"」である場合、クォート・カラムの開始→次の「"」が来るまで読み取り。
	 * @param argCSVString
	 * @param argStartIndex
	 * @return
	 * @throws DataFormatException
	 */
	private static String readUntilDoubleQuote(String argCSVString, int argStartIndex) throws DataFormatException {
		StringBuffer workStringBuffer = new StringBuffer();
		boolean isFound = false;
		for (int i = argStartIndex; i < argCSVString.length(); i++) {
			char localChar = argCSVString.charAt(i);
			if (localChar != '"') {
				workStringBuffer.append(localChar);
			} else {
				isFound = true;
				break;
			}
		}
		if (!isFound) {
			throw new DataFormatException();
		}
		return new String(workStringBuffer);
	}
	/**
	 * それ以外である場合、アンクォート・カラムの開始→次の「,」がくるまで読み取り。
	 * @param argCSVString
	 * @param argStartIndex
	 * @return
	 * @throws DataFormatException
	 */
	private static String readUntilColon(String argCSVString, int argStartIndex) throws DataFormatException {
		StringBuffer workStringBuffer = new StringBuffer();
		for (int i = argStartIndex; i < argCSVString.length(); i++) {
			char localChar = argCSVString.charAt(i);
			if (localChar != ',') {
				workStringBuffer.append(localChar);
			} else {
				break;
			}
		}
		return new String(workStringBuffer);
	}
}
