package batch.base;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.IntStream;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.util.Unbox;

import batch.controller.JobUtil;
import batch.status.JobDetailStatus;
import batch.status.JobState;
import batch.status.JobStatus;
import core.config.Factory;
import core.exception.PhysicalException;
import core.exception.ThrowableUtil;
import core.util.DateUtil;
import core.util.NumberUtil;

/**
 * バッチパラメタ実装
 *
 * @author Tadashi Nakayama
 */
public class BatchParameterImpl implements BatchParameter {

	/** ジョブ処理番号 */
	private volatile long jobSeq = 0;
	/** ジョブ詳細番号 */
	private int jobDtlSeq = 0;
	/** ジョブ名 */
	private String jobName = null;
	/** ユーザID */
	private String uid = null;
	/** プロセスID */
	private long pid = 0;
	/** 起動元IPアドレス */
	private String ip = null;
	/** 処理日時 */
	private Timestamp dateTime = null;
	/** コマンドラインパラメタ */
	private List<String> command = Collections.emptyList();
	/** オンラインパラメタ */
	private List<String> online = Collections.emptyList();

	/**
	 * コンストラクタ
	 */
	public BatchParameterImpl() {
		super();
	}

	/**
	 * コンストラクタ
	 *
	 * @param args コマンドライン引数
	 */
	public BatchParameterImpl(final String... args) {
		setCommandParameter(args);
	}

	/**
	 * オンライン引数設定
	 *
	 * @param name バッチ名
	 * @param datetime 開始日時
	 * @return 設定正常終了の場合 true を返す。
	 */
	@Override
	public boolean setupOnline(final String name, final Timestamp datetime) {
		return this.jobSeq == 0L || setupJobInfo(name, datetime);
	}

	/**
	 * ジョブ情報設定
	 *
	 * @param name バッチ名
	 * @param datetime 開始日時
	 * @return 設定正常終了の場合 true を返す。
	 */
	private boolean setupJobInfo(final String name, final Timestamp datetime) {
		final var js = Factory.create(JobStatus.class);
		final var jds = Factory.create(JobDetailStatus.class);

		try (var conn = JobUtil.getConnection()) {
			// ジョブ管理テーブルのパラメタを取得
			LogManager.getLogger().info("jobseq={}", Unbox.box(this.jobSeq));
			final var job = js.getJobWithLock(conn, this.jobSeq);
			// パラメタ設定
			setJobName(job.getJobName());
			setUid(job.getUid());
			setIp(job.getIp());
			// 引数再作成
			setOnlineParams(job.getExecParam());

			// 中止依頼
			final var ret = JobState.ID_B_EDIT.value() == job.getJobSts();
			if (ret) {
				// ジョブ詳細管理テーブルのステータスを「処理中」でレコード作成
				final var no = jds.insertJobDetail(conn, this.jobSeq, getPid(), name, datetime);
				setJobDtlSeq(no);

				conn.commit();
			}
			return ret;

		} catch (final SQLException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		}
	}

	/**
	 * IPアドレス取得
	 *
	 * @return IPアドレス
	 */
	@Override
	public String getIp() {
		if (this.ip == null) {
			// IPアドレス設定
			try {
				final var ia = InetAddress.getLocalHost();
				setIp(ia.getHostAddress());
			} catch (final UnknownHostException ex) {
				LogManager.getLogger().warn(ex.getMessage(), ex);
				setIp("localhost");
			}
		}
		return this.ip;
	}

	/**
	 * IPアドレス設定
	 *
	 * @param val IPアドレス
	 */
	private void setIp(final String val) {
		this.ip = val;
	}

	/**
	 * ジョブ詳細番号取得
	 *
	 * @return ジョブ詳細番号
	 */
	@Override
	public int getJobDtlSeq() {
		return this.jobDtlSeq;
	}

	/**
	 * ジョブ詳細番号設定
	 *
	 * @param val ジョブ詳細番号
	 */
	private void setJobDtlSeq(final int val) {
		this.jobDtlSeq = val;
	}

	/**
	 * ジョブ番号取得
	 *
	 * @return ジョブ番号
	 */
	@Override
	public long getJobSeq() {
		return this.jobSeq;
	}

	/**
	 * ジョブ番号設定
	 *
	 * @param val ジョブ番号
	 */
	private void setJobSeq(final long val) {
		this.jobSeq = val;
	}

	/**
	 * プロセスID取得
	 *
	 * @return プロセスID
	 */
	@Override
	public long getPid() {
		return this.pid;
	}

	/**
	 * プロセスID設定
	 *
	 * @param val プロセスID
	 */
	private void setPid(final long val) {
		this.pid = val;
	}

	/**
	 * ユーザID取得
	 *
	 * @return ユーザID
	 */
	@Override
	public String getUid() {
		return this.uid;
	}

	/**
	 * ユーザID設定
	 *
	 * @param val ユーザID
	 */
	public void setUid(final String val) {
		this.uid = val;
	}

	/**
	 * ジョブ名取得
	 *
	 * @return ジョブ名
	 */
	@Override
	public String getJobName() {
		return this.jobName;
	}

	/**
	 * ジョブ名設定
	 *
	 * @param val ジョブ名
	 */
	private void setJobName(final String val) {
		this.jobName = val;
	}

	/**
	 * 処理開始日時設定
	 *
	 * @param val 処理開始日時
	 */
	@Override
	public void setDateTime(final Timestamp val) {
		if (val != null && this.dateTime == null) {
			this.dateTime = DateUtil.copyOf(val);
		}
	}

	/**
	 * 処理開始日時取得
	 *
	 * @return 処理開始日時
	 */
	@Override
	public Timestamp getDateTime() {
		return DateUtil.copyOf(this.dateTime);
	}

	/**
	 * コマンドラインパラメタ設定
	 *
	 * @param args 引数
	 */
	@Override
	public void setCommandParameter(final String... args) {
		this.command = new ArrayList<>();
		setCommandParams(args);
	}

	/**
	 * @see batch.base.BatchParameter#getParameter()
	 */
	@Override
	public String[] getParameter() {
		final var ret = new String[this.command.size() + this.online.size()];
		IntStream.range(0, this.command.size()).forEach(i -> ret[i] = this.command.get(i));
		IntStream.range(0, this.online.size()).forEach(
				i -> ret[i + this.command.size()] = this.online.get(i)
		);
		return ret;
	}

	/**
	 * コマンドパラメタ設定
	 *
	 * @param args 引数
	 */
	private void setCommandParams(final String... args) {
		final var currentProcess = ProcessHandle.current();
		setPid(currentProcess.pid());

		// パラメタ読込
		var i = 0;
		while (args != null && i < args.length) {
			if (ID_B_ONLINE_OPTION.equals(args[i])) {
				if (args.length > (i + 1)) {
					setJobSeq(NumberUtil.toLong(args[++i], 0L));
				}
			} else if (ID_B_USER_ID.equals(args[i])) {
				if (args.length > (i + 1)) {
					setUid(args[++i]);
				}
			} else {
				this.command.add(args[i]);
			}
			i++;
		}
	}

	/**
	 * オンライン起動パラメタ設定
	 *
	 * @param args 引数
	 */
	private void setOnlineParams(final String... args) {
		this.online = Optional.ofNullable(args).map(Arrays::asList).orElse(Collections.emptyList());
	}
}
