package project.common;

import java.io.IOException;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;

import core.util.MojiUtil;

/**
 * CSVユーティリティクラス
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class CsvUtil {

	/**
	 * コンストラクタ
	 */
	private CsvUtil() {
		throw new AssertionError();
	}

	/**
	 * CRLF 取得
	 * @param cs Charset
	 * @return CRLFバイト配列
	 */
	public static byte[] crlf(final Charset cs) {
		return MojiUtil.RET_CODE_CRLF.getBytes(cs);
	}

	/**
	 * カンマ取得
	 * @param cs Charset
	 * @return カンマバイト配列
	 */
	public static byte[] comma(final Charset cs) {
		return ",".getBytes(cs);
	}

	/**
	 * タイトルバイト配列化
	 * @param title タイトル
	 * @param cs Charset
	 * @return タイトルバイト配列
	 */
	public static byte[] toByteTitle(final String[] title, final Charset cs) {
		String tmp = toCsv(title) + MojiUtil.RET_CODE_CRLF;
		byte[] ret = MojiUtil.correctGarbled(tmp, cs).getBytes(cs);
		if (0 < ret.length) {
			if (cs.equals(StandardCharsets.UTF_8)) {
				ret = prependBom(ret);
			}
		}
		return ret;
	}

	/**
	 * BOM挿入
	 * @param val 挿入対象
	 * @return 挿入後配列
	 */
	private static byte[] prependBom(final byte[] val) {
		byte[] bom = MojiUtil.utf8Bom();
		byte[] ret = new byte[bom.length + val.length];
		System.arraycopy(val, 0, ret, bom.length, val.length);
		System.arraycopy(bom, 0, ret, 0, bom.length);
		return ret;
	}

	/**
	 * 改行(CR+LF)まで読込
	 * @param reader リーダー
	 * @return 文字列
	 */
	public static String readLine(final Reader reader) {
		try {
			StringBuilder sb = new StringBuilder();
			int ch;
			while (0 <= (ch = reader.read())) {
				if (ch == '\r') {
					int next = reader.read();
					if (next == '\n') {
						return sb.toString();
					}
					sb.append((char) ch);
					if (next < 0) {
						return sb.toString();
					}
					sb.append((char) next);
				} else {
					sb.append((char) ch);
				}
			}
			if (ch < 0 && sb.length() == 0) {
				return null;
			}
			return sb.toString();
		} catch (final IOException ex) {
			throw new UncheckedIOException(ex);
		}
	}

	/**
	 * CSV文字列を配列化
	 * @param val CSV文字列
	 * @return 文字列配列
	 */
	public static String[] toArray(final String val) {
		List<String> l = new ArrayList<>();

		boolean quot = false;
		StringBuilder sb = null;
		int i = 0;
		while (val != null && i < val.length()) {
			if (sb == null) {
				sb = new StringBuilder();
			}

			int cp = val.codePointAt(i);
			if (!quot) {
				if (cp == ',') {
					l.add(sb.toString());
					sb = new StringBuilder();
				} else if (cp == '"' && (i == 0 || val.codePointBefore(i) == ',')) {
					quot = true;
				} else if (cp == '\r') {
					int next = val.offsetByCodePoints(i, 1);
					if (next < val.length() && val.codePointAt(next) == '\n') {
						break;
					}
				} else {
					sb.appendCodePoint(cp);
				}
			} else {
				if (cp == '"') {
					int next = val.offsetByCodePoints(i, 1);
					if (next < val.length()) {
						if (val.codePointAt(next) == '"') {
							sb.appendCodePoint(cp);
							i = next;
						} else {
							quot = false;
						}
					}
				} else {
					sb.appendCodePoint(cp);
				}
			}
			i = val.offsetByCodePoints(i, 1);
		}

		if (sb != null) {
			l.add(sb.toString());
		}
		return l.toArray(new String[l.size()]);
	}

	/**
	 * CSV文字列化
	 * @param vals 文字列配列
	 * @return CSV文字列
	 */
	public static String toCsv(final String... vals) {
		StringJoiner sj = new StringJoiner(",");
		for (int i = 0; vals != null && i < vals.length; i++) {
			sj.add(toCsvValue(vals[i]));
		}
		return sj.toString();
	}

	/**
	 * CSV値化
	 * @param val 文字列
	 * @return CSV値
	 */
	public static String toCsvValue(final String val) {
		return toCsvValue(val, false);
	}

	/**
	 * CSV値化
	 * @param val 文字列
	 * @param quote ダブルクオート付加フラグ
	 * @return CSV値
	 */
	public static String toCsvValue(final String val, final boolean quote) {

		String quot = quote ? "\"" : "";

		StringBuilder sb = new StringBuilder();
		for (int i = 0; val != null && i < val.length(); i = val.offsetByCodePoints(i, 1)) {
			if (val.codePointAt(i) == '"') {
				quot = "\"";
				sb.append("\"");
			} else if (val.codePointAt(i) == ',') {
				quot = "\"";
			} else if (val.codePointAt(i) == '\r') {
				continue;
			} else if (val.codePointAt(i) == '\n') {
				quot = "\"";
			}
			sb.appendCodePoint(val.codePointAt(i));
		}

		return quot + sb.append(quot).toString();
	}
}
