package project.web.generic.csv;

import java.util.Arrays;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Stream;

import core.exception.LogicalException;
import core.util.BooleanUtil;
import online.context.check.InputCheck;
import online.model.Band;
import online.model.ModelUtil;
import online.model.UniModel;
import online.struts.action.BrowseAction;
import online.struts.action.UniForm;
import project.check.TopMessage;
import project.check.attribute.HanEisuCheck;
import project.check.attribute.HankakuCheck;
import project.check.attribute.LongNumeralCheck;
import project.check.attribute.NumberCheck;
import project.check.attribute.NumeralCheck;
import project.check.existence.MustCheck;
import project.check.master.MasterCheck;
import project.check.master.NoMasterCheck;
import project.check.range.LengthRangeCheck;
import project.common.StringUtil;
import project.master.MsgUtil;
import project.svc.generic.QueryService;
import project.svc.generic.csv.define.DefineTestQuery;
import project.web.InstanceFactory;

/**
 * 汎用CSV展開アクション
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class ExtractAction implements BrowseAction, CsvAction {

	/** キー項目 */
	private static final String ID = "Id";
	/** バージョン項目 */
	private static final String VERSION = "Version";
	/** 定義名 */
	private static final String DEFINE_NAME = "DefineName";
	/** ユーザパターン名 */
	private static final String USER_PATTERN_NAME = "UserPatternName";
	/** ヘッダー区分 */
	private static final String HEADER = "Header";
	/** 項目 */
	private static final String ITEM_VALUE = "ItemValue";
	/** 項目ラベル */
	private static final String ITEM_LABEL = "ItemLabel";
	/** カラム出力 */
	private static final String OUTPUT = "Output";
	/** 集計 */
	private static final String AGGREGATION_KBN = "AggregationKbn";
	/** グループ */
	private static final String GROUPING = "Grouping";
	/** 改頁 */
	private static final String PAGE_BREAK = "PageBreak";
	/** 行順項目 */
	private static final String ORDER_ITEM = "OrderItem";
	/** 行順方向 */
	private static final String ORDER_FLG = "OrderFlg";
	/** 条件項目名 */
	private static final String ITEM_NAME = "ItemName";
	/** 条件演算子 */
	private static final String OPERATOR = "Operator";
	/** 条件 */
	private static final String CONDITION = "Condition";

	/**
	 * 初期表示
	 * @param model 汎用モデル
	 * @return 処理結果
	 */
	public String get(final UniForm model) {
		if (!model.hasValue(ID)) {
			final var ic = InstanceFactory.create(InputCheck.class, model);
			ic.add(ID, new LongNumeralCheck());
			ic.populate();
		}

		if (select("project.svc.generic.csv.extract.SelectHeader", model)) {
			select("project.svc.generic.csv.extract.SelectItem", model);
			select("project.svc.generic.csv.extract.SelectCondition", model);
		} else {
			if (!MsgUtil.hasTopMessage(model)) {
				if (model.hasQueryString()) {
					return ID_NG_REDIRECT;
				}
				MsgUtil.putTopMessage(model, "ZZ000000014");
			}
		}

		return ID_VIEW;
	}

	/**
	 * 検索
	 * @param model 汎用モデル
	 * @return 処理結果
	 */
	public String search(final UniForm model) {

		final var ic = InstanceFactory.create(InputCheck.class, model);
		ic.onError(new TopMessage("ZZ000000005"));
		ic.add(DEFINE_NAME, new MustCheck());
		ic.populate();

		select("project.svc.generic.csv.extract.SelectDetail", model);

		model.setValue(HEADER, BooleanUtil.toFlag(true));
		model.setValue(USER_PATTERN_NAME, model.getString(DEFINE_NAME) + "（未登録）");
		model.noValue(OUTPUT, AGGREGATION_KBN, GROUPING, PAGE_BREAK, ORDER_FLG);

		return ID_VIEW;
	}

	/**
	 * 登録処理
	 * @param model 汎用モデル
	 * @return 処理結果
	 */
	public String register(final UniForm model) {
		checkInput(model, true);

		setValues(model);

		if (!model.hasValue(ID)) {
			// 作成
			select("project.svc.generic.csv.define.SelectId", model);
			model.aliasKey(ID, "UserPatternNo");
			update("project.svc.generic.csv.extract.InsertHeader", model);

			updateDetail(model);
			MsgUtil.putTopMessage(model, "ZZ000000020");
			return ID_OK;
		}

		// 更新
		if (1 != update("project.svc.generic.csv.extract.UpdateHeader", model)) {
			MsgUtil.putTopMessage(model, "ZZ000000019");
		} else {
			updateDetail(model);
			MsgUtil.putTopMessage(model, "ZZ000000020");
			return ID_OK;
		}

		return ID_VIEW;
	}

	/**
	 * 削除処理
	 * @param model 汎用モデル
	 * @return 処理結果
	 */
	public String delete(final UniForm model) {
		final var ic = InstanceFactory.create(InputCheck.class, model);
		ic.onError(new TopMessage("ZZ000000005"));
		ic.add(ID, new MustCheck());
		ic.add(ID, new LongNumeralCheck());
		ic.add(VERSION, new MustCheck());
		ic.add(VERSION, new NumeralCheck());
		ic.populate();

		select("project.svc.generic.csv.extract.SelectHeader", model);
		if (1 != update("project.svc.generic.csv.extract.DeleteHeader", model)) {
			MsgUtil.putTopMessage(model, "ZZ000000019");
			return ID_VIEW;
		}

		update("project.svc.generic.csv.extract.DeleteItem", model);
		update("project.svc.generic.csv.extract.DeleteCondition", model);

		model.noValue(ID, VERSION);

		MsgUtil.putTopMessage(model, "ZZ000000020");
		return "DELETED";
	}

	/**
	 * クエリテスト
	 * @param model 汎用モデル
	 * @return 処理結果
	 */
	public String test(final UniForm model) {
		checkInput(model, false);

		final var schema = getSchema(model);
		if (schema == null) {
			MsgUtil.putTopMessage(model, "ZZ000000005");
			return ID_NG;
		}

		setValues(model);

		final var tq = InstanceFactory.create(DefineTestQuery.class, model);
		if (tq.tryQuery(schema, getQueryBuilder(model).build())) {
			MsgUtil.putTopMessage(model, "ZZ000000020");
		} else {
			MsgUtil.putTopMessage(model, "ZZ000000005");
			model.addValue(ModelUtil.TAG_MESSAGE, tq.getMessage());
		}

		return ID_NG;
	}

	/**
	 * クエリダウンロード
	 * @param model 汎用モデル
	 * @return 処理結果
	 */
	public String download(final UniForm model) {
		checkInput(model, false);

		setValues(model);
		model.aliasKey(DEFINE_NAME, USER_PATTERN_NAME);

		if (!model.hasValue(ID)) {
			model.setValue(ID, Long.valueOf(0));
		}

		return "DOWNLOAD";
	}

	/**
	 * View用処理
	 * @param model 汎用モデル
	 */
	public void view(final UniForm model) {
		select("project.svc.generic.csv.extract.SelectPattern", model);
	}

	/**
	 * DB項目設定
	 * @param model 汎用モデル
	 */
	private void setValues(final UniForm model) {
		model.setValue(CONDITION_COLUMN, model.getArraySize(ITEM_NAME));
		model.setValue(OUTPUT_FLG, toFlgArray(model, OUTPUT));
		model.setValue(GROUP_FLG, toFlgArray(model, GROUPING));
		model.setValue(ORDER_SORT, getRowOrderArray(model));
		model.setValue(ORDER_KBN, getOrderKbnArray(model));
		model.setValue("BreakFlg", toFlgArray(model, PAGE_BREAK));
	}

	/**
	 * チェックボックス配列から変換
	 * @param model 汎用モデル
	 * @param item 項目名
	 * @return カラム順配列
	 */
	private String[] toFlgArray(final UniForm model, final String item) {
		final var ret = new String[model.getArraySize(ITEM_VALUE)];
		Arrays.fill(ret, BooleanUtil.toFlag(false));
		for (final var band : Band.iterable(model, ITEM_VALUE)) {
			if (Stream.of(model.getStringArray(item)).anyMatch(
					Predicate.isEqual(band.string(ITEM_VALUE)))) {
				ret[band.index()] = BooleanUtil.toFlag(true);
			}
		}
		return ret;
	}

	/**
	 * 行順配列
	 * @param model 汎用モデル
	 * @return 行順配列
	 */
	private Integer[] getRowOrderArray(final UniForm model) {
		final var ret = new Integer[model.getArraySize(ITEM_VALUE)];
		for (final var band : Band.iterable(model, ITEM_VALUE)) {
			ret[band.index()] = find(band.string(ITEM_VALUE), model.getStringArray(ORDER_ITEM)) + 1;
		}
		return ret;
	}

	/**
	 * 行順フラグ配列
	 * @param model 汎用モデル
	 * @return 行順フラグ配列
	 */
	private String[] getOrderKbnArray(final UniForm model) {
		final var ret = new String[model.getArraySize(ITEM_VALUE)];
		for (final var band : Band.iterable(model, ITEM_VALUE)) {
			final var loc = find(band.string(ITEM_VALUE), model.getStringArray(ORDER_ITEM));
			if (0 <= loc && loc < model.getArraySize(ORDER_FLG)) {
				final var val = model.getStringArray(ORDER_FLG)[loc];
				if (!Objects.toString(val, "").isEmpty()) {
					ret[band.index()] = val;
				}
			}
		}
		return ret;
	}

	/**
	 * 検索
	 * @param val 検索対象
	 * @param array 配列
	 * @return 存在位置 存在しない場合、または、valがnullの場合 -1 を返す。
	 */
	private int find(final String val, final String[] array) {
		for (var i = 0; i < array.length; i++) {
			if (Objects.equals(val, array[i])) {
				return i;
			}
		}
		return -1;
	}

	/**
	 * 入力チェック
	 * @param model 汎用モデル
	 * @param name ユーザパタン名チェックフラグ
	 */
	private void checkInput(final UniForm model, final boolean name) {
		final var ic = InstanceFactory.create(InputCheck.class, model);
		ic.onError(new TopMessage("ZZ000000005"));
		ic.add(ID, new LongNumeralCheck());
		ic.add(VERSION, new MustCheck());
		ic.add(VERSION, new NumeralCheck());
		ic.add(ITEM_VALUE, new HankakuCheck());
		ic.add(ITEM_LABEL, new LengthRangeCheck(0, 256));
		ic.add(DEFINE_NAME, new MustCheck());
		if (name) {
			ic.add(USER_PATTERN_NAME, new MustCheck());
		}
		ic.add(HEADER, new HanEisuCheck());
		ic.add(OUTPUT, new HankakuCheck());
		ic.add(AGGREGATION_KBN, new NumberCheck());
		ic.add(GROUPING, new HankakuCheck());
		ic.add(PAGE_BREAK, new HankakuCheck());
		ic.add(ORDER_ITEM, new HankakuCheck());
		ic.add(ORDER_FLG, new NumberCheck());
		ic.add(ITEM_NAME, new HankakuCheck());
		ic.add(OPERATOR, new HankakuCheck());
		ic.add(CONDITION, new LengthRangeCheck(0, 256));
		ic.populate();

		if (model.hasValue(ID)) {
			ic.add(ID, new MasterCheck("ZZ000000018",
					"project.svc.generic.csv.extract.SelectHeader"));
		} else if (name) {
			ic.add(USER_PATTERN_NAME, new NoMasterCheck("ZZ000000021",
					"project.svc.generic.csv.extract.SelectUserPattern"));
		}
		ic.add(DEFINE_NAME, new MasterCheck("ZZ000000013",
				"project.svc.generic.csv.extract.SelectQuery"));
		ic.add(ITEM_NAME, new MasterCheck("ZZ000000013",
				"project.svc.generic.csv.extract.SelectDetailId"));
		ic.check();

		checkItems(model);
	}

	/**
	 * 項目存在チェック
	 * @param model 汎用モデル
	 */
	private void checkItems(final UniForm model) {
		final var ql = InstanceFactory.create(QueryService.class, model);
		ql.setQueryFile("project.svc.generic.csv.extract.SelectDetail");
		ql.search();

		model.setValue(ITEM_VALUE, StringUtil.trim(model.getStringArray(ITEM_VALUE)));
		hasItem(ITEM_VALUE, model, ql.getResultModel());
		hasItem(ITEM_LABEL, model, ql.getResultModel());
	}

	/**
	 * 項目存在チェック
	 * @param item 項目名
	 * @param model 汎用モデル
	 * @param um 検索結果
	 */
	private void hasItem(final String item, final UniForm model, final UniModel um) {
		if (!Arrays.asList(um.getStringArray(item)).containsAll(
				Arrays.asList(model.getStringArray(item)))) {
			throw LogicalException.create(ID_SYS_ERROR);
		}
	}

	/**
	 * 詳細更新
	 * @param model 汎用モデル
	 */
	private void updateDetail(final UniForm model) {
		select("project.svc.generic.csv.extract.SelectHeader", model);
		update("project.svc.generic.csv.extract.DeleteItem", model);
		update("project.svc.generic.csv.extract.DeleteCondition", model);
		updateArray("project.svc.generic.csv.extract.InsertItem", model, ITEM_VALUE);
		updateArray("project.svc.generic.csv.extract.InsertCondition", model, CONDITION);
	}
}
