package net.w_horse.excelpojo.converter;

import java.util.HashMap;

import org.springframework.util.StringUtils;

/**
 * セルとフィールドに対する真偽に関する変換を行うクラス<br>
 * 文字列とboolean, 文字列と文字列の相互変換を行う。<BR>
 * 例えば○×のような真偽を表す文字列とbooleanを相互に変換する。<br>
 * 初期状態では文字列の "true" / "false" と boolean の相互変換を行う。<br>
 * 予め用意しているマッピングは<br>
 * {@link #TYPE_TRUE_FALSE}, {@link #TYPE_YES_NO}, {@link #TYPE_OX},<br>
 * {@link #TYPE_ON_OFF}, {@link #TYPE_OK_CANCEL}, {@link #TYPE_10},<br>
 * {@link #TYPE_10_INTEGER}<br>
 * マッピングの要領は、Map<Object, Object>[]を用意し、<br>
 * 配列の０番目にセル→フィールド、１番目にフィールド→セルのマップを定義する。<br>
 * それぞれのマップには、想定外の値に対する戻り値として、<br>
 * #FALSE}をキーにしたマッピングを定義しておく。<br>
 *
 * @author kawahara
 *
 */
@SuppressWarnings("unchecked")
public class StringBooleanConverter implements CellFieldConverter {
	/**
	 * 文字列とbooleanのマッピング<br>
	 * デフォルトコンストラクタで使用されるタイプ<br>
	 * True/Falseのケース<br>
	 * 文字列の大文字・小文字は無視する<br>
	 * booleanからの変換は小文字の true / false を返す<br>
	 * true：true false:false<br>
	 *
	 */
	public static final HashMap<Object, Object>[] TYPE_TRUE_FALSE;
	/**
	 * 文字列とbooleanのマッピング<br>
	 * Yes/Noのケース<br>
	 * 文字列の大文字・小文字は無視する<br>
	 * Yesの省略としてYをNoの省略としてNを認識する<br>
	 * booleanからの変換は小文字の yes / no を返す<br>
	 * yes：true no:false<br>
	 */
	public static final HashMap<Object, Object>[] TYPE_YES_NO;
	/**
	 * 文字列とbooleanのマッピング<br>
	 * ○/×のケース<br>
	 * ○：true ×:false<br>
	 */
	public static final HashMap<Object, Object>[] TYPE_OX;
	/**
	 * 文字列とbooleanのマッピング<br>
	 * On/Offのケース<br>
	 * 文字列の大文字・小文字は無視する<br>
	 * booleanからの変換は小文字の on / off を返す<br>
	 * on：true off:false<br>
	 */
	public static final HashMap<Object, Object>[] TYPE_ON_OFF;
	/**
	 * 文字列とbooleanのマッピング<br>
	 * 1/0のケース<br>
	 * 1：true 0:false<br>
	 */
	public static final HashMap<Object, Object>[] TYPE_10;
	/**
	 * 数値とbooleanのマッピング<br>
	 * 1/0のケース<br>
	 * 1：true 0:false<br>
	 */
	public static final HashMap<Object, Object>[] TYPE_10_INTEGER;
	/**
	 * 文字列とbooleanのマッピング<br>
	 * OK/CANCELのケース<br>
	 * 文字列の大文字・小文字は無視する<br>
	 * booleanからの変換は小文字の ok / cancel を返す<br>
	 * ok：true cancel:false<br>
	 */
	public static final HashMap<Object, Object>[] TYPE_OK_CANCEL;

	/**
	 * 文字列を返す際のタイプ<br>
	 * NOTHING:何もしない<br>
	 * UPPER_CASE:すべて大文字にする<br>
	 * LOWER_CASE:すべて小文字にする<br>
	 * CAPITALIZE:先頭の文字列を大文字にする<br>
	 * UN_CAPITARIZE:先頭の文字列を小文字にする<br>
	 *
	 * @author kawahara
	 *
	 */
	public enum CapitalizeType {NOTHING, UPPER_CASE, LOWER_CASE, CAPITALIZE, UN_CAPITARIZE};

	private enum FalseType {FALSE}
	public static final FalseType FALSE ;

	static {
		FALSE = FalseType.FALSE;

		TYPE_TRUE_FALSE = new HashMap[2];
		TYPE_TRUE_FALSE[0] = new HashMap<Object, Object>();
		TYPE_TRUE_FALSE[0].put("true", Boolean.TRUE);
		TYPE_TRUE_FALSE[0].put("false", Boolean.FALSE);
		TYPE_TRUE_FALSE[0].put(FALSE, Boolean.FALSE);
		TYPE_TRUE_FALSE[1] = new HashMap<Object, Object>();
		TYPE_TRUE_FALSE[1].put(Boolean.TRUE, "true");
		TYPE_TRUE_FALSE[1].put(Boolean.FALSE, "false");
		TYPE_TRUE_FALSE[1].put(FALSE, "false");

		TYPE_YES_NO = new HashMap[2];
		TYPE_YES_NO[0] = new HashMap<Object, Object>();
		TYPE_YES_NO[0].put("yes", Boolean.TRUE);
		TYPE_YES_NO[0].put("no", Boolean.FALSE);
		TYPE_YES_NO[0].put("y", Boolean.TRUE);
		TYPE_YES_NO[0].put("n", Boolean.FALSE);
		TYPE_YES_NO[0].put(FALSE, Boolean.FALSE);
		TYPE_YES_NO[1] = new HashMap<Object, Object>();
		TYPE_YES_NO[1].put(Boolean.TRUE, "yes");
		TYPE_YES_NO[1].put(Boolean.FALSE, "no");
		TYPE_YES_NO[1].put(FALSE, "no");

		TYPE_OX = new HashMap[2];
		TYPE_OX[0] = new HashMap<Object, Object>();
		TYPE_OX[0].put("○", Boolean.TRUE);
		TYPE_OX[0].put("×", Boolean.FALSE);
		TYPE_OX[0].put(FALSE, Boolean.FALSE);
		TYPE_OX[1] = new HashMap<Object, Object>();
		TYPE_OX[1].put(Boolean.TRUE, "○");
		TYPE_OX[1].put(Boolean.FALSE, "×");
		TYPE_OX[1].put(FALSE, "×");

		TYPE_ON_OFF = new HashMap[2];
		TYPE_ON_OFF[0] = new HashMap<Object, Object>();
		TYPE_ON_OFF[0].put("on", Boolean.TRUE);
		TYPE_ON_OFF[0].put("off", Boolean.FALSE);
		TYPE_ON_OFF[0].put(FALSE, Boolean.FALSE);
		TYPE_ON_OFF[1] = new HashMap<Object, Object>();
		TYPE_ON_OFF[1].put(Boolean.TRUE, "on");
		TYPE_ON_OFF[1].put(Boolean.FALSE, "off");
		TYPE_ON_OFF[1].put(FALSE, "off");

		TYPE_10 = new HashMap[2];
		TYPE_10[0] = new HashMap<Object, Object>();
		TYPE_10[0].put("1", Boolean.TRUE);
		TYPE_10[0].put("0", Boolean.FALSE);
		TYPE_10[0].put(FALSE, Boolean.FALSE);
		TYPE_10[1] = new HashMap<Object, Object>();
		TYPE_10[1].put(Boolean.TRUE, "1");
		TYPE_10[1].put(Boolean.FALSE, "0");
		TYPE_10[1].put(FALSE, "0");

		TYPE_10_INTEGER = new HashMap[2];
		TYPE_10_INTEGER[0] = new HashMap<Object, Object>();
		TYPE_10_INTEGER[0].put(1, Boolean.TRUE);
		TYPE_10_INTEGER[0].put(0, Boolean.FALSE);
		TYPE_10_INTEGER[0].put(FALSE, Boolean.FALSE);
		TYPE_10_INTEGER[1] = new HashMap<Object, Object>();
		TYPE_10_INTEGER[1].put(Boolean.TRUE, 1);
		TYPE_10_INTEGER[1].put(Boolean.FALSE, 0);
		TYPE_10_INTEGER[1].put(FALSE, 0);

		TYPE_OK_CANCEL = new HashMap[2];
		TYPE_OK_CANCEL[0] = new HashMap<Object, Object>();
		TYPE_OK_CANCEL[0].put("ok", Boolean.TRUE);
		TYPE_OK_CANCEL[0].put("cancel", Boolean.FALSE);
		TYPE_OK_CANCEL[0].put(FALSE, Boolean.FALSE);
		TYPE_OK_CANCEL[1] = new HashMap<Object, Object>();
		TYPE_OK_CANCEL[1].put(Boolean.TRUE, "ok");
		TYPE_OK_CANCEL[1].put(Boolean.FALSE, "cancel");
		TYPE_OK_CANCEL[1].put(FALSE, "cancel");
	}


	private HashMap<Object, Object>[] mappings = TYPE_TRUE_FALSE;
	private boolean ignoreCase = true;
	private CapitalizeType capitalizeType = CapitalizeType.NOTHING;

	/**
	 * デフォルトコンストラクタの場合は
	 * マッピングに {@link #TYPE_TRUE_FALSE}を用い、
	 * マッピングの比較に対し、大文字・小文字を無視し、
	 * 文字列を返す際のタイプにCapitalizeType.NOTHINGを用いる
	 */
	public StringBooleanConverter() {
		this.mappings = TYPE_TRUE_FALSE;
		this.ignoreCase = true;
		this.capitalizeType = CapitalizeType.NOTHING;
	}

	@Override
	public Object convertCell2Field(Object value) {
		return getValue(value, this.mappings[0]);
	}

	@Override
	public Object convertField2Cell(Object value) {
		return getValue(value, this.mappings[1]);
	}

	public void setMappings(HashMap<Object, Object>[] mappings) {
		this.mappings = mappings;
	}
	public void setIgnoreCase(boolean ignoreCase) {
		this.ignoreCase = ignoreCase;
	}
	public void setCapitalizeType(CapitalizeType capitalizeType) {
		this.capitalizeType = capitalizeType;
	}

	private Object getValue(Object searchKey, HashMap<Object, Object> mapping) {
		Object retValue = null;
		if (searchKey instanceof String) {
			for (Object key : mapping.keySet()) {
				if (!key.getClass().equals(String.class)) continue;

				if((((String)searchKey).equals((String) key))
					|| (this.ignoreCase && ((String)searchKey).equalsIgnoreCase((String) key))
				) {
					retValue = mapping.get(key);
					break;
				}
			}
		} else {
			retValue = mapping.get(searchKey);
		}
		if (retValue == null) {
			retValue = mapping.get(FALSE);
		}
		return (retValue instanceof String ? capitalize((String) retValue) : retValue);
	}

	private String capitalize(String value) {
		String retValue;
		switch(this.capitalizeType) {
		case UPPER_CASE:
			retValue = value.toUpperCase();
			break;
		case LOWER_CASE:
			retValue = value.toLowerCase();
			break;
		case CAPITALIZE:
			retValue = StringUtils.capitalize(value);
			break;
		case UN_CAPITARIZE:
			retValue = StringUtils.uncapitalize(value);
			break;
		default:
			retValue = value;
		}
		return retValue;
	}

}
