package jp.mosp.time.bean.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import jp.mosp.framework.base.MospException;
import jp.mosp.framework.utils.DateUtility;
import jp.mosp.time.base.TimeBean;
import jp.mosp.time.bean.AttendanceCalcBeanInterface;
import jp.mosp.time.bean.ScheduleUtilBeanInterface;
import jp.mosp.time.bean.TimeRecordBeanInterface;
import jp.mosp.time.bean.TimeRecordReferenceBeanInterface;
import jp.mosp.time.bean.TimeRecordRegistBeanInterface;
import jp.mosp.time.bean.WorkTypeItemReferenceBeanInterface;
import jp.mosp.time.constant.TimeConst;
import jp.mosp.time.constant.TimeMessageConst;
import jp.mosp.time.dto.settings.AttendanceDtoInterface;
import jp.mosp.time.dto.settings.GoOutDtoInterface;
import jp.mosp.time.dto.settings.RestDtoInterface;
import jp.mosp.time.dto.settings.TimeRecordDtoInterface;
import jp.mosp.time.dto.settings.WorkTypeItemDtoInterface;
import jp.mosp.time.portal.bean.impl.PortalTimeCardBean;
import jp.mosp.time.utils.TimeRecordUtility;

/**
 * 打刻クラス。
 */
public class TimeRecordBean extends AttendanceListRegistBean implements TimeRecordBeanInterface {
	
	/**
	 * 最大休憩回数。<br>
	 */
	protected static final int						MAX_REST_TIMES	= 6;
	
	/**
	 * 打刻データ参照クラス。
	 */
	protected TimeRecordReferenceBeanInterface		timeRecordReference;
	
	/**
	 * 打刻データ登録クラス。
	 */
	protected TimeRecordRegistBeanInterface			timeRecordRegist;
	
	/**
	 * 勤務形態参照クラス
	 */
	protected WorkTypeItemReferenceBeanInterface	workTypeItem;
	
	/**
	 * スケジュールユーティリティクラス。
	 */
	protected ScheduleUtilBeanInterface				scheduleUtil;
	
	
	/**
	 * {@link TimeBean#TimeBean()}を実行する。<br>
	 */
	public TimeRecordBean() {
		super();
	}
	
	@Override
	public void initBean() throws MospException {
		// 継承元クラスのメソッドを実行
		super.initBean();
		timeRecordReference = (TimeRecordReferenceBeanInterface)createBean(TimeRecordReferenceBeanInterface.class);
		timeRecordRegist = (TimeRecordRegistBeanInterface)createBean(TimeRecordRegistBeanInterface.class);
		workTypeItem = (WorkTypeItemReferenceBeanInterface)createBean(WorkTypeItemReferenceBeanInterface.class);
		scheduleUtil = (ScheduleUtilBeanInterface)createBean(ScheduleUtilBeanInterface.class);
	}
	
	@Override
	public void recordStartWork(String personalId, Date recordTime) throws MospException {
		// ポータル時刻を打刻
		recordPortalTime(personalId, recordTime, recordTime, PortalTimeCardBean.RECODE_START_WORK);
		// 対象個人ID設定
		this.personalId = personalId;
		// 対象日設定
		targetDate = DateUtility.getDate(recordTime);
		// 勤怠データ取得
		AttendanceDtoInterface dto = attendanceReference.findForKey(this.personalId, targetDate, TIMES_WORK_DEFAULT);
		// 勤怠情報確認
		if (dto != null) {
			// エラーメッセージ設定
			TimeRecordUtility.addAlreadyRecordedErrorMessage(TimeRecordUtility.getNameStartWork(mospParams),
					targetDate, mospParams);
			return;
		}
		// 勤怠データ作成
		dto = getAttendanceDto(recordTime, recordTime, null, false, false, false, false, false);
		// エラーメッセージ確認
		if (mospParams.hasErrorMessage()) {
			return;
		}
		// 始業・終業必須チェック
		attendanceRegist.checkTimeExist(dto);
		// 妥当性チェック
		attendanceRegist.checkValidate(dto);
		// 申請の相関チェック
		attendanceRegist.checkDraft(dto);
		// エラー確認
		if (mospParams.hasErrorMessage()) {
			return;
		}
		// ワークフロー番号設定
		draft(dto);
		// 処理結果確認
		if (mospParams.hasErrorMessage()) {
			return;
		}
		// 勤務形態に登録されている休憩情報のリストを取得
		List<RestDtoInterface> restList = getRestList(recordTime);
		// 休憩情報毎に処理
		for (RestDtoInterface restDto : restList) {
			//	休憩時間登録
			restRegist.regist(restDto);
		}
		// 勤怠データ登録
		attendanceRegist.regist(dto);
	}
	
	@Override
	public void recordEndWork(String personalId, Date recordTime) throws MospException {
		// 対象個人ID設定
		this.personalId = personalId;
		// 対象日設定及び対象日の勤怠情報取得
		AttendanceDtoInterface dto = setTargetDate(TimeRecordUtility.getNameEndWork(mospParams));
		// 処理結果確認
		if (mospParams.hasErrorMessage()) {
			return;
		}
		// ポータル時刻を打刻
		recordPortalTime(personalId, dto.getWorkDate(), recordTime, PortalTimeCardBean.RECODE_END_WORK);
		// 休憩デフォルト日時取得
		Date restDefaultTime = getRestDefaultTime();
		// 休憩情報取得(0も登録されている)
		List<RestDtoInterface> list = restDao.findForList(this.personalId, targetDate, TIMES_WORK_DEFAULT);
		// 休憩情報毎に処理
		for (RestDtoInterface restDto : list) {
			// 休憩戻り確認
			if (restDefaultTime.equals(restDto.getRestStart()) == false && restDefaultTime.equals(restDto.getRestEnd())) {
				// エラーメッセージ設定
				TimeRecordUtility.addStartNotRecordedErrorMessage(TimeRecordUtility.getNameEndWork(mospParams),
						targetDate, TimeRecordUtility.getNameEndRest(mospParams), mospParams);
				return;
			}
		}
		// ワークフロー番号設定
		apply(dto);
		// エラー確認
		if (mospParams.hasErrorMessage()) {
			return;
		}
		// 登録用勤怠データを取得(各種自動計算実施)
		dto = getAttendanceDto(dto.getStartTime(), dto.getActualStartTime(), recordTime, false, true, false, false,
				false);
		// 処理結果確認
		if (mospParams.hasErrorMessage()) {
			return;
		}
		// 始業・終業必須チェック
		attendanceRegist.checkTimeExist(dto);
		// 妥当性チェック
		attendanceRegist.checkValidate(dto);
		// 申請の相関チェック
		attendanceRegist.checkAppli(dto);
		// 処理結果確認
		if (mospParams.hasErrorMessage()) {
			return;
		}
		// 勤怠データ登録
		attendanceRegist.regist(dto);
		// 代休登録
		registSubHoliday(dto);
	}
	
	@Override
	public void recordStartRest(String personalId, Date recordTime) throws MospException {
		// 対象個人ID設定
		this.personalId = personalId;
		// 対象日設定及び対象日の勤怠情報取得
		setTargetDate(TimeRecordUtility.getNameStartRest(mospParams));
		// 処理結果確認
		if (mospParams.hasErrorMessage()) {
			return;
		}
		// 休憩情報取得
		List<RestDtoInterface> list = restDao.findForList(this.personalId, targetDate, TIMES_WORK_DEFAULT);
		// 公用外出取得
		List<GoOutDtoInterface> publicList = goOutReference.getPublicGoOutList(personalId, targetDate);
		// 私用外出取得
		List<GoOutDtoInterface> privateList = goOutReference.getPrivateGoOutList(personalId, targetDate);
		for (GoOutDtoInterface goOutDto : publicList) {
			if (checkRestDuplication(recordTime, goOutDto.getGoOutStart(), goOutDto.getGoOutEnd())) {
				// 公用外出と重複している場合
				StringBuffer sb = new StringBuffer();
				sb.append(mospParams.getName("Official"));
				sb.append(mospParams.getName("GoingOut"));
				sb.append(goOutDto.getTimesGoOut());
				addStartRestDuplicationErrorMessage(DateUtility.getStringDateAndDay(recordTime), sb.toString());
				return;
			}
		}
		for (GoOutDtoInterface goOutDto : privateList) {
			if (checkRestDuplication(recordTime, goOutDto.getGoOutStart(), goOutDto.getGoOutEnd())) {
				// 私用外出と重複している場合
				addStartRestDuplicationErrorMessage(DateUtility.getStringDateAndDay(recordTime),
						mospParams.getName("PrivateGoingOut" + goOutDto.getTimesGoOut()));
				return;
			}
		}
		// 休憩デフォルト日時取得
		Date restDefaultTime = getRestDefaultTime();
		// 処理対象情報準備
		RestDtoInterface dto = null;
		// 休憩情報毎に処理
		for (RestDtoInterface restDto : list) {
			if (checkRestDuplication(recordTime, restDto.getRestStart(), restDto.getRestEnd())) {
				// 休憩と重複している場合
				addStartRestDuplicationErrorMessage(DateUtility.getStringDateAndDay(recordTime),
						mospParams.getName("Rest" + restDto.getRest()));
				return;
			}
			// 休憩入日時確認
			if (restDefaultTime.equals(restDto.getRestStart())) {
				// 処理対象情報設定
				dto = restDto;
				dto.setRestStart(recordTime);
				break;
			}
			// 休憩戻り確認
			if (restDefaultTime.equals(restDto.getRestEnd())) {
				// エラーメッセージ追加
				TimeRecordUtility.addAlreadyRecordedErrorMessage(TimeRecordUtility.getNameStartRest(mospParams),
						targetDate, mospParams);
				return;
			}
		}
		// 処理対象情報確認
		if (dto == null && list.size() < MAX_REST_TIMES) {
			// 処理対象情報作成
			dto = restRegist.getInitDto();
			dto.setPersonalId(this.personalId);
			dto.setWorkDate(targetDate);
			dto.setTimesWork(TIMES_WORK_DEFAULT);
			dto.setRest(list.size() + 1);
			dto.setRestStart(recordTime);
			dto.setRestEnd(restDefaultTime);
			dto.setRestTime(0);
		}
		// 処理対象情報確認
		if (dto == null) {
			// エラーメッセージ設定
			TimeRecordUtility.addOverLimitErrorMessage(targetDate, mospParams);
			return;
		}
		// 休憩情報登録
		restRegist.regist(dto);
	}
	
	@Override
	public void recordEndRest(String personalId, Date recordTime) throws MospException {
		// 対象個人ID設定
		this.personalId = personalId;
		// 対象日設定及び対象日の勤怠情報取得
		setTargetDate(TimeRecordUtility.getNameEndRest(mospParams));
		// 処理結果確認
		if (mospParams.hasErrorMessage()) {
			return;
		}
		// 勤怠関連各種情報取得
		setTimeDtos(false, false);
		// エラーメッセージ確認
		if (mospParams.hasErrorMessage()) {
			return;
		}
		// 休憩情報取得
		List<RestDtoInterface> list = restDao.findForList(this.personalId, targetDate, TIMES_WORK_DEFAULT);
		// 公用外出取得
		List<GoOutDtoInterface> publicList = goOutReference.getPublicGoOutList(personalId, targetDate);
		// 私用外出取得
		List<GoOutDtoInterface> privateList = goOutReference.getPrivateGoOutList(personalId, targetDate);
		for (GoOutDtoInterface goOutDto : publicList) {
			if (checkRestDuplication(recordTime, goOutDto.getGoOutStart(), goOutDto.getGoOutEnd())) {
				// 公用外出と重複している場合
				StringBuffer sb = new StringBuffer();
				sb.append(mospParams.getName("Official"));
				sb.append(mospParams.getName("GoingOut"));
				sb.append(goOutDto.getTimesGoOut());
				addEndRestDuplicationErrorMessage(DateUtility.getStringDateAndDay(recordTime), sb.toString());
				return;
			}
		}
		for (GoOutDtoInterface goOutDto : privateList) {
			if (checkRestDuplication(recordTime, goOutDto.getGoOutStart(), goOutDto.getGoOutEnd())) {
				// 私用外出と重複している場合
				addEndRestDuplicationErrorMessage(DateUtility.getStringDateAndDay(recordTime),
						mospParams.getName("PrivateGoingOut" + goOutDto.getTimesGoOut()));
				return;
			}
		}
		// 休憩デフォルト日時取得
		Date restDefaultTime = getRestDefaultTime();
		// 処理対象情報準備
		RestDtoInterface dto = null;
		// 休憩情報毎に処理
		for (RestDtoInterface restDto : list) {
			if (checkRestDuplication(recordTime, restDto.getRestStart(), restDto.getRestEnd())) {
				// 休憩と重複している場合
				addEndRestDuplicationErrorMessage(DateUtility.getStringDateAndDay(recordTime),
						mospParams.getName("Rest" + restDto.getRest()));
				return;
			}
			// 休憩入日時確認
			if (restDefaultTime.equals(restDto.getRestStart()) == false && restDefaultTime.equals(restDto.getRestEnd())) {
				// 処理対象情報設定
				dto = restDto;
				dto.setRestEnd(recordTime);
				// 休憩時間計算
				dto.setRestTime(restRegist.getCalcRestTime(dto.getRestStart(), dto.getRestEnd(), timeSettingDto));
				break;
			}
		}
		// 処理対象情報確認
		if (dto == null) {
			// エラーメッセージ設定
			TimeRecordUtility.addStartNotRecordedErrorMessage(TimeRecordUtility.getNameEndRest(mospParams), targetDate,
					TimeRecordUtility.getNameStartRest(mospParams), mospParams);
			return;
		}
		// 休憩情報登録
		restRegist.regist(dto);
	}
	
	@Override
	public void recordStartWork() throws MospException {
		Date systemTime = getSystemTime();
		// 対象個人ID(ログインユーザの個人ID)及び打刻日時(システム日時)を取得し打刻
		recordStartWork(mospParams.getUser().getPersonalId(), systemTime);
	}
	
	@Override
	public void recordEndWork() throws MospException {
		Date systemTime = getSystemTime();
		// 対象個人ID(ログインユーザの個人ID)及び打刻日時(システム日時)を取得し打刻
		recordEndWork(mospParams.getUser().getPersonalId(), systemTime);
	}
	
	@Override
	public void recordStartRest() throws MospException {
		// 対象個人ID(ログインユーザの個人ID)及び打刻日時(システム日時)を取得し打刻
		recordStartRest(mospParams.getUser().getPersonalId(), getSystemTime());
	}
	
	@Override
	public void recordEndRest() throws MospException {
		// 対象個人ID(ログインユーザの個人ID)及び打刻日時(システム日時)を取得し打刻
		recordEndRest(mospParams.getUser().getPersonalId(), getSystemTime());
	}
	
	/**
	 * 休憩のデフォルト日時を取得する。<br>
	 * @return 休憩のデフォルト日時
	 * @throws MospException インスタンスの取得及びSQL実行に失敗した場合
	 */
	protected Date getRestDefaultTime() throws MospException {
		AttendanceCalcBeanInterface attendanceCalc = (AttendanceCalcBeanInterface)createBean(
				AttendanceCalcBeanInterface.class, targetDate);
		return attendanceCalc.getAttendanceTime(targetDate, "0", "0");
	}
	
	/**
	 * 打刻対象日を設定する。<br>
	 * <br>
	 * 打刻した日の勤怠情報が存在し終業時間が登録されていない場合は、
	 * 打刻した日が打刻対象日となる。<br>
	 * <br>
	 * 打刻した日の勤怠情報が存在しない場合、前日の勤怠情報を確認する。<br>
	 * 前日の勤怠情報が存在し終業時間が登録されていない場合は、
	 * 前日が打刻対象日となる。<br>
	 * <br>
	 * 上記の場合以外は、打刻対象日を設定しない。<br>
	 * <br>
	 * @param process 打刻対象処理
	 * @return 打刻対象日の勤怠情報
	 * @throws MospException 勤怠情報の取得に失敗した場合
	 */
	protected AttendanceDtoInterface setTargetDate(String process) throws MospException {
		// 対象日設定(システム日付)
		Date recordDate = getSystemDate();
		// 勤怠データ取得
		AttendanceDtoInterface dto = attendanceReference.findForKey(personalId, recordDate, TIMES_WORK_DEFAULT);
		// 打刻した日の勤怠情報が存在しない場合
		if (dto == null) {
			// 前日の勤怠を取得
			Date beforeDate = addDay(recordDate, -1);
			dto = attendanceReference.findForKey(personalId, beforeDate, TIMES_WORK_DEFAULT);
			// 前日の勤怠を確認
			if (dto != null && dto.getEndTime() == null) {
				// 対象日として前日を設定(深夜に日付を超えて打刻した場合等)
				targetDate = beforeDate;
			} else {
				// エラーメッセージ設定
				TimeRecordUtility.addStartNotRecordedErrorMessage(process, recordDate,
						TimeRecordUtility.getNameStartWork(mospParams), mospParams);
			}
		} else if (dto.getEndTime() != null) {
			// エラーメッセージ設定
			TimeRecordUtility.addAlreadyRecordedErrorMessage(TimeRecordUtility.getNameEndWork(mospParams), recordDate,
					mospParams);
		} else {
			targetDate = recordDate;
		}
		return dto;
	}
	
	/**
	 * 休憩時刻重複チェック。<br>
	 * @param recordTime 打刻時刻
	 * @param startTime 開始時刻
	 * @param endTime 終了時刻
	 * @return 重複している場合true、そうでない場合false
	 * @throws MospException インスタンスの取得及びSQL実行に失敗した場合
	 */
	protected boolean checkRestDuplication(Date recordTime, Date startTime, Date endTime) throws MospException {
		Date defaultTime = getRestDefaultTime();
		// 打刻時刻が休憩開始時刻以降且つ休憩終了時刻より前の場合
		return !defaultTime.equals(startTime) && !defaultTime.equals(endTime) && !recordTime.before(startTime)
				&& recordTime.before(endTime);
	}
	
	/**
	 * 休憩開始時刻重複エラーメッセージを追加する。<br>
	 * @param stringTagetDate 表示用対象日付
	 * @param name 置換文字列
	 */
	protected void addStartRestDuplicationErrorMessage(String stringTagetDate, String name) {
		StringBuffer sb = new StringBuffer();
		sb.append(mospParams.getName("RestTime"));
		sb.append(mospParams.getName("Into"));
		String[] rep = { stringTagetDate, sb.toString(), name };
		mospParams.addErrorMessage(TimeMessageConst.MSG_TIME_DUPLICATION_CHECK, rep);
	}
	
	/**
	 * 休憩終了時刻重複エラーメッセージを追加する。<br>
	 * @param stringTagetDate 表示用対象日付
	 * @param name 置換文字列
	 */
	protected void addEndRestDuplicationErrorMessage(String stringTagetDate, String name) {
		StringBuffer sb = new StringBuffer();
		sb.append(mospParams.getName("RestTime"));
		sb.append(mospParams.getName("Return"));
		String[] rep = { stringTagetDate, sb.toString(), name };
		mospParams.addErrorMessage(TimeMessageConst.MSG_TIME_DUPLICATION_CHECK, rep);
	}
	
	/**
	 * ポータル時刻を打刻する。<br>
	 * @param personalId 個人ID
	 * @param workDate 勤務日
	 * @param recordType 打刻区分
	 * @param recordTime 打刻時刻
	 * @throws MospException インスタンスの取得及びSQL実行に失敗した場合
	 */
	protected void recordPortalTime(String personalId, Date workDate, Date recordTime, String recordType)
			throws MospException {
		if (timeRecordReference.findForKey(personalId, workDate, TIMES_WORK_DEFAULT, recordType) != null) {
			return;
		}
		TimeRecordDtoInterface dto = timeRecordRegist.getInitDto();
		setDtoFields(dto);
		dto.setPersonalId(personalId);
		dto.setWorkDate(workDate);
		dto.setRecordType(recordType);
		dto.setRecordTime(recordTime);
		// 新規登録
		timeRecordRegist.insert(dto);
		if (mospParams.hasErrorMessage()) {
			return;
		}
		// コミット
		timeRecordRegist.commit();
	}
	
	/**
	 * 勤務形態に登録されている休憩情報のリストを取得する。
	 * 始業時間以前の休憩情報は含めない。
	 * @param recordTime 始業時間
	 * @return 勤務形態に登録されている休憩情報のリスト
	 * @throws MospException インスタンスの取得及びSQL実行に失敗した場合
	 */
	protected List<RestDtoInterface> getRestList(Date recordTime) throws MospException {
		// カレンダに登録されている勤務形態コードを取得
		String workTypeCode = scheduleUtil.getScheduledWorkTypeCode(personalId, recordTime);
		// 勤務形態項目取得
		WorkTypeItemDtoInterface workTypeItemDto = workTypeItem.getWorkTypeItemInfo(workTypeCode, targetDate,
				TimeConst.CODE_WORKEND);
		// 勤務形態項目がある場合
		if (workTypeItemDto == null) {
			return new ArrayList<RestDtoInterface>();
		}
		// 勤務形態の終業時刻を取得
		int hour = DateUtility.getHour(workTypeItemDto.getWorkTypeItemValue());
		int minute = DateUtility.getMinute(workTypeItemDto.getWorkTypeItemValue());
		int year = DateUtility.getYear(targetDate);
		int month = DateUtility.getMonth(targetDate);
		int day = DateUtility.getDay(targetDate);
		// 休憩情報登録
		return registRest(recordTime, DateUtility.getDateTime(year, month, day, hour, minute));
	}
	
	/**
	 * DTOに値を設定する。<br>
	 * @param dto 対象DTO
	 */
	protected void setDtoFields(TimeRecordDtoInterface dto) {
		dto.setPersonalId(mospParams.getUser().getPersonalId());
		dto.setWorkDate(getSystemDate());
		dto.setTimesWork(TIMES_WORK_DEFAULT);
		dto.setRecordType("");
		dto.setRecordTime(getSystemTime());
	}
}
