package project.svc.generic.csv.extract;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import core.util.BooleanUtil;
import core.util.NumberUtil;

/**
 * クエリ構築
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class QueryBuilder {

	/**
	 * 演算子
	 * @author Tadashi Nakayama
	 */
	private enum Operator {
		/** 等価 */
		EQ("="),
		/** 不等 */
		NE("!="),
		/** 小なり */
		LT("<"),
		/** 大なり */
		GT(">"),
		/** 小なり等価 */
		LE("<="),
		/** 大なり等価 */
		GE(">="),
		/** 内 */
		IN("IN"),
		/** あいまい */
		LK("LIKE");

		/** 値 */
		private final String value;

		/**
		 * コンストラクタ
		 * @param val SQL文字列
		 */
		Operator(final String val) {
			this.value = val;
		}

		/**
		 * 値取得
		 * @return 値
		 */
		public String value() {
			return this.value;
		}
	}

	/** エリアス */
	private static final String PREFIX = "D.";

	/** 演算子集合 */
	private static final EnumSet<Operator> OPERATOR_SET = EnumSet.allOf(Operator.class);
	/** 論理演算子表 */
	private static final Map<String, String> LOGIC;
	/** 集計関数表 */
	private static final Map<String, String> FUNCTION;

	/** クエリ */
	private final String query;
	/** クエリ条件 */
	private final String where;
	/** 項目 */
	private final String[] items;
	/** ラベル */
	private final String[] labels;
	/** 条件リスト */
	private final List<String> condition = new ArrayList<>();

	/** グループ */
	private String[] group;
	/** 集約 */
	private String[] aggregation = null;
	/** 行順序 */
	private Number[] order;
	/** 順序方向 */
	private String[] desc = null;
	/** 出力順 */
	private String[] column;

	static {
		LOGIC = Map.of(
			"01", "AND",
			"02", "OR",
			"03", "(",
			"04", ")",
			"05", "((",
			"06", "))"
		);

		FUNCTION = Map.of(
			"1", "AVG",
			"2", "COUNT",
			"3", "MAX",
			"4", "MIN",
			"5", "SUM"
		);
	}

	/**
	 * コンストラクタ
	 * @param base 基本クエリ
	 * @param cond 基本条件
	 * @param item 出力項目
	 * @param label 出力名
	 */
	public QueryBuilder(final String base, final String cond,
			final String[] item, final String[] label) {
		this.query = base;
		this.where = cond;
		this.items = (item != null) ? item.clone() : null;
		this.labels = (label != null) ? label.clone() : null;
	}

	/**
	 * グループ設定
	 * @param nums グループ配列
	 */
	public void setGroup(final String[] nums) {
		this.group = (nums != null) ? nums.clone() : null;
		for (var i = 0; this.group != null && i < this.group.length; i++) {
			if (isGroup(i)) {
				return;
			}
		}
		this.group = null;
	}

	/**
	 * 集約設定
	 * @param nums 集約配列
	 */
	public void setAggregation(final String[] nums) {
		this.aggregation = (nums != null) ? nums.clone() : null;
		for (var i = 0; this.aggregation != null && i < this.aggregation.length; i++) {
			if (isAggregation(i)) {
				return;
			}
		}
		this.aggregation = null;
	}

	/**
	 * 順序設定
	 * @param nums 順序配列
	 */
	public void setOrderSort(final Number[] nums) {
		this.order = (nums != null) ? nums.clone() : null;
	}

	/**
	 * 順序方向設定
	 * @param nums 順序方向配列
	 */
	public void setOrderKbn(final String[] nums) {
		this.desc = (nums != null) ? nums.clone() : null;
	}

	/**
	 * 出力設定
	 * @param nums 出力配列
	 */
	public void setColumnOutput(final String[] nums) {
		this.column = (nums != null) ? nums.clone() : null;
	}

	/**
	 * 条件設定
	 * @param size 条件数
	 * @param item 項目
	 * @param ope 演算子
	 * @param cond 条件
	 */
	public void setCondition(final int size, final String[] ope,
			final String[] cond, final String[] item) {
		if (0 < ope.length) {
			var preop = "";
			addCondition("05", "", "", "", "");
			for (var i = 0; i < ope.length; i++) {
				if (0 < i && i % size == 0) {
					addCondition("04", "", "", "", "");
					addCondition("02", "", "", "", "03");
					preop = "";
				}

				if (!Objects.toString(ope[i], "").isEmpty()
						&& !Objects.toString(cond[i], "").isEmpty()) {
					addCondition(preop, item[i % size], ope[i], cond[i], "");
					preop = "01";
				}
			}
			addCondition("06", "", "", "", "");
		}
	}

	/**
	 * 条件追加
	 * @param pre 前論理演算子
	 * @param item 項目
	 * @param ope 演算子
	 * @param cond 条件
	 * @param post 後論理演算子
	 */
	public void addCondition(final String pre, final String item,
			final String ope, final String cond, final String post) {
		this.condition.add(toLogic(pre, item, ope, cond, post));
	}

	/**
	 * クエリ構築
	 * @return クエリ
	 */
	public String build() {
		return (getSelect()
				+ "FROM (\n"
				+ getFrom()
				+ ") D\n"
				+ getWhere()
				+ getGroupBy()
				+ getOrderBy()).replace(";", "");
	}

	/**
	 * タイトル文字列取得
	 * @param order 順序配列
	 * @param title タイトル配列
	 * @return タイトル文字列
	 */
	public static String[] getTitle(final String[] order, final String[] title) {
		final var list = new ArrayList<String>();
		for (var i = 0; order != null && i < order.length; i++) {
			if (BooleanUtil.toBool(order[i]) && i < title.length) {
				list.add(title[i]);
			}
		}
		return list.toArray(new String[list.size()]);
	}

	/**
	 * SELECT句取得
	 * @return SELECT句
	 */
	private String getSelect() {
		final var sb = new StringBuilder();
		for (var i = 0; this.column != null && i < this.column .length; i++) {
			if (isOutput(i) && i < this.items.length) {
				if (0 < sb.length()) {
					sb.append(",\n");
				}
				if (isAggregation(i)) {
					sb.append(getFunction(this.aggregation[i])).append("(");
					sb.append("  ").append(PREFIX).append(this.items[i]).append(")");
				} else if (isGroup(i)) {
					sb.append("  ").append(PREFIX).append(this.items[i]);
				} else if (this.aggregation != null) {
					sb.append("  ''");
				} else {
					sb.append("  ").append(PREFIX).append(this.items[i]);
				}
				sb.append(getItemAlias(i));
			}
		}
		return "SELECT\n" + sb.append("\n").toString();
	}

	/**
	 * 項目別名作成
	 * @param num 項目位置
	 * @return 別名
	 */
	private String getItemAlias(final int num) {
		if (0 <= num && num < this.labels.length) {
			if (!Objects.toString(this.labels[num], "").isEmpty()) {
				return " AS " + this.labels[num];
			}
		}
		return "";
	}

	/**
	 * 出力対象判断
	 * @param loc 位置
	 * @return 出力対象の場合 true を返す。
	 */
	private boolean isOutput(final int loc) {
		return this.column != null && 0 <= loc && loc < this.column.length
				&& BooleanUtil.toBool(this.column[loc]);
	}

	/**
	 * グループ対象判断
	 * @param loc 位置
	 * @return グループ対象の場合 true を返す。
	 */
	private boolean isGroup(final int loc) {
		return this.group != null && 0 <= loc && loc < this.group.length
				&& BooleanUtil.toBool(this.group[loc]);
	}

	/**
	 * 集約対象判断
	 * @param loc 位置
	 * @return 集約対象の場合 true を返す。
	 */
	private boolean isAggregation(final int loc) {
		return this.aggregation != null && 0 <= loc && loc < this.aggregation.length
				&& !Objects.toString(this.aggregation[loc], "").isEmpty()
				&& NumberUtil.toInt(this.aggregation[loc], 0) != 0;
	}

	/**
	 * 降順判断
	 * @param loc 位置
	 * @return 降順の場合 true を返す。
	 */
	private boolean isDesc(final int loc) {
		return this.desc != null && 0 <= loc && loc < this.desc.length
				&& BooleanUtil.toBool(this.desc[loc]);
	}

	/**
	 * FROM句取得
	 * @return FROM句
	 */
	private String getFrom() {
		return this.query + "\n" + Objects.toString(this.where, "");
	}

	/**
	 * WHERE句取得
	 * @return WEHRE句
	 */
	private String getWhere() {
		final var ret = new StringBuilder();

		if (!this.condition.isEmpty()) {
			ret.append("WHERE \n");
			for (final var line : this.condition) {
				ret.append(line).append("\n");
			}
		}

		return ret.toString();
	}

	/**
	 * GROUPBY句取得
	 * @return GROUPBY句
	 */
	private String getGroupBy() {
		final var sb = new StringBuilder();
		for (var i = 0; this.column != null && i < this.column.length; i++) {
			if (isOutput(i) && isGroup(i) && !isAggregation(i) && i < this.items.length) {
				if (0 < sb.length()) {
					sb.append(",");
				}
				sb.append(PREFIX).append(this.items[i]);
			}
		}
		if (0 < sb.length()) {
			return "GROUP BY \n" + sb.toString() + "\n";
		}
		return sb.toString();
	}

	/**
	 * ORDERBY句取得
	 * @return ORDERBY句
	 */
	private String getOrderBy() {
		final var sb = new StringBuilder();
		for (var i = 0; this.order != null && i < this.order.length; i++) {
			final var num = getIndex(this.order, i + 1);
			if (0 <= num && !Objects.toString(this.desc[num], "").isEmpty()) {
				if (0 < sb.length()) {
					sb.append(", ");
				}
				sb.append(PREFIX).append(this.items[num]);
				if (isDesc(num)) {
					sb.append(" DESC");
				}
			}
		}
		if (0 < sb.length()) {
			return "ORDER BY \n  " + sb.toString() + "\n";
		}
		return sb.toString();
	}

	/**
	 * 指定値を含む位置を取得
	 * @param array 指定配列
	 * @param val 指定文字列
	 * @return 存在位置
	 */
	private static int getIndex(final Number[] array, final int val) {
		for (var i = 0; i < array.length; i++) {
			if (array[i].intValue() == val) {
				return i;
			}
		}
		return -1;
	}

	/**
	 * 集計関数取得
	 * @param val 実現値
	 * @return 集計関数
	 */
	private String getFunction(final String val) {
		return Objects.toString(FUNCTION.get(val), "");
	}

	/**
	 * 条件作成
	 * @param pre 前論理演算子
	 * @param item 項目
	 * @param ope 演算子
	 * @param cond 条件
	 * @param post 後論理演算子
	 * @return 条件文字列
	 */
	private String toLogic(final String pre, final String item,
			final String ope, final String cond, final String post) {
		final var sb = new StringBuilder();
		if (!pre.isEmpty()) {
			sb.append(" ");
			sb.append(getLogic(pre));
		}
		if (!item.isEmpty()) {
			sb.append(" ").append(PREFIX);
			sb.append(item);
		}
		if (!ope.isEmpty()) {
			sb.append(" ");
			sb.append(getOperator(ope));
		}
		if (!cond.isEmpty()) {
			sb.append(" ");
			sb.append(cond);
		}
		if (!post.isEmpty()) {
			sb.append(" ");
			sb.append(getLogic(post));
		}
		return sb.toString();
	}

	/**
	 * 論理演算子取得
	 * @param lgc 論理演算子実現値
	 * @return 論理演算子
	 */
	private String getLogic(final String lgc) {
		return Objects.toString(LOGIC.get(lgc), "");
	}

	/**
	 * 演算子取得
	 * @param ope 演算子実現値
	 * @return 演算子
	 */
	private String getOperator(final String ope) {
		for (final var op : OPERATOR_SET) {
			if (op.name().equals(ope)) {
				return op.value();
			}
		}
		return "";
	}
}
