/*
 * MosP - Mind Open Source Project    http://www.mosp.jp/
 * Copyright (C) MIND Co., Ltd.       http://www.e-mind.co.jp/
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package jp.mosp.kintai.payroll.action;

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

import jp.mosp.common.CommonConst;
import jp.mosp.common.common.BaseVo;
import jp.mosp.common.common.MospConst;
import jp.mosp.common.common.MospException;
import jp.mosp.common.common.MospUtility;
import jp.mosp.common.part.MosPCodePart;
import jp.mosp.common.utils.DateUtil;
import jp.mosp.common.utils.StringUtil;
import jp.mosp.kintai.common.action.AttendanceTotalAction;
import jp.mosp.kintai.common.part.CalculatePart;
import jp.mosp.kintai.payroll.dto.KdWorkDto;
import jp.mosp.kintai.payroll.dto.MKintaiDto;
import jp.mosp.kintai.payroll.dto.MKintaiKeisanDto;
import jp.mosp.kintai.payroll.dto.MKintaiKinmuDto;
import jp.mosp.kintai.payroll.vo.AttendanceCompileCardVo;

/**
 * @author yoshida
 *
 */
public class AttendanceCompileCardAction extends AttendanceTotalAction {
	
	// コマンド
	private static final String			CMD_COMPILE_CARD_SHOW	= "P1030";
	private static final String			CMD_COMPILE_CARD_REGIST	= "P1031";
	
	/**
	 * 集計終了日
	 */
	private Date						periodEnd;
	/**
	 * 対象年月
	 */
	private Date						targetMonth;
	
	/**
	 * 勤怠集計用リスト作成
	 */
	private final List<MKintaiDto>		listMKintai				= new ArrayList<MKintaiDto>();
	
	/**
	 *  旧勤務形態別集計用リスト作成
	 */
	private final List<MKintaiKinmuDto>	listMKinmu				= new ArrayList<MKintaiKinmuDto>();
	
	/**
	 *  新勤務形態別集計用リスト作成
	 */
	private final List<KdWorkDto>		listKdWork				= new ArrayList<KdWorkDto>();
	

	/**
	 * コンストラクタ
	 */
	public AttendanceCompileCardAction() {
		super();
	}
	
	/**
	 * アクション
	 */
	public void action() throws Exception {
		// VOの取得
		prepareVo(false);
		// 初期処理
		init();
		// コマンド毎の処理
		if (cmd.equals(CMD_COMPILE_CARD_SHOW)) {
			// 表示
			show();
		} else if (cmd.equals(CMD_COMPILE_CARD_REGIST)) {
			// 計算
			runAttendanceCompile();
		} else {
			throw new MospException(MospConst.EX_CMD_INVALID);
		}
	}
	
	@Override
	protected BaseVo getSpecificVo() {
		return new AttendanceCompileCardVo();
	}
	
	@Override
	protected void init() throws Exception {
		// DBコネクション取得
		getConnection();
		// PAYROLL権限確認
		confirmPayrollAuth();
		AttendanceCompileCardVo vo = (AttendanceCompileCardVo)getVo();
		// ユーザー情報取得
		getInfo(user.getUserId());
		// プルダウンのセット
		vo.aryPltSection = getSectionScopeArrayForPayroll();
	}
	
	@Override
	protected void show() throws Exception {
		AttendanceCompileCardVo vo = (AttendanceCompileCardVo)getVo();
		// 勤怠計算対象年月取得
		Date calcMonth = getCalcMonth();
		// 勤怠計算対象年月セット
		vo.setHidCompileYear(String.valueOf(MospUtility.getYear(calcMonth)));
		vo.setHidCompileMonth(String.valueOf(MospUtility.getMonth(calcMonth)));
		// 選択所属コードセット
		vo.setPltSection(userBasis.getSCode());
		// 選択期間のセット
		vo.setLblStartCompileDate(DateUtil.convDateFormatted(getStartDate(calcMonth)));
		vo.setLblEndCompileDate(DateUtil.convDateFormatted(getEndDate(calcMonth)));
		// モード設定
		setMode();
	}
	
	/**
	 * 勤怠計算処理
	 * @throws Exception 	例外処理が発生した場合
	 */
	private void runAttendanceCompile() throws Exception {
		AttendanceCompileCardVo vo = (AttendanceCompileCardVo)getVo();
		// 入力項目取得
		vo.setParams(request);
		// 集計基本設定
		targetMonth = getCalcMonth();
		periodEnd = getEndDate(getCalcMonth());
		// 勤怠情報設定
		setCalcParam();
		if (StringUtil.isNull(vo.getPltSection())) {
			// 全社選択時
			compileAll();
		} else if (StringUtil.isNotNull(vo.getTxtKCode())) {
			// 社員コード入力時
			compileKCode(vo.getTxtKCode());
		} else {
			// 社員コード未入力
			// 選択所属のみ計算
			compileSection(vo.getPltSection());
		}
		// コミット
		commit();
		// 一覧表示へ
		setNextCmd(CompileAmendmentListAction.CMD_AMENDMENT_LIST_RE_SEARCH);
		// 一覧への情報をセットする
		setNextCmdInfo();
	}
	
	/**
	 * 全社員
	 * @throws Exception 	例外処理が発生した場合
	 */
	private void compileAll() throws Exception {
		// 全所属一覧取得
		String[] arySection = part().section().getSectionCodeArrayAll();
		for (String section : arySection) {
			// 所属在籍社員一覧
			String[] arySectionUsers = part().human().getBasisArray(section, targetMonth);
			for (String kCode : arySectionUsers) {
				// 勤怠データ取得
				getAttendanceList(kCode, targetMonth);
				// 勤怠集計用
				listMKinmu.addAll(getWorkTypeList(kCode, targetMonth, attendanceList));
				// 改正労働基準法施行日判断
				if (MosPCodePart.afterLegalReformDate(periodEnd)) {
					// 新勤怠集計に設定
					listKdWork.add(compile().getNewCompiledInfo(kCode, targetMonth, attendanceList));
				} else {
					// 旧勤怠集計に設定
					listMKintai.add(compile().getCompiledInfo(kCode, targetMonth, attendanceList));
				}
			}
		}
		// 勤怠集計削除
		String yearMonth = DateUtil.convDateToStringYearMonthShort(targetMonth);
		// 改正労働基準法施行日判断
		if (MosPCodePart.afterLegalReformDate(periodEnd)) {
			// テーブルロックの準備
			prepareTableLock();
			// 対象テーブルの追加
			addTargetTable(compile().getKdWorkDao(), true);
			// テーブルロック
			lockTables();
			// 削除
			compile().getKdWorkDao().delete(yearMonth);
			// 登録処理
			registKdWork(listKdWork);
			// テーブルロック解除
			unLockTables();
		} else {
			// テーブルロック
			compile().getMKintaiDao().tableLock(yearMonth);
			// データの削除
			compile().getMKintaiDao().delete(yearMonth);
			// 勤怠集計登録
			registMKintai(listMKintai);
		}
		// 勤務形態別集計削除
		// テーブルロック
		compile().getMKintaiKinmuDao().tableLock(yearMonth);
		// 削除
		compile().getMKintaiKinmuDao().delete(yearMonth);
		// 勤務形態別集計登録
		registMKinmu(listMKinmu);
	}
	
	/**
	 * 選択所属社員
	 * @param pltSection 対象所属コード
	 * @throws Exception 	例外処理が発生した場合
	 */
	private void compileSection(String pltSection) throws Exception {
		// 選択所属社員コード一覧の取得
		String[] aryUsers = part().human().getBasisArray(pltSection, targetMonth);
		int listSize = 0;
		for (String kCode : aryUsers) {
			// 勤怠データ取得
			getAttendanceList(kCode, targetMonth);
			// 新勤怠集計に設定
			listMKinmu.addAll(getWorkTypeList(kCode, targetMonth, attendanceList));
			// 改正労働基準法施行日判断
			if (MosPCodePart.afterLegalReformDate(periodEnd)) {
				// 新勤怠集計に設定
				listKdWork.add(compile().getNewCompiledInfo(kCode, targetMonth, attendanceList));
			} else {
				// 旧勤怠集計に設定
				listMKintai.add(compile().getCompiledInfo(kCode, targetMonth, attendanceList));
			}
		}
		// 勤怠集計削除・登録
		String yearMonth = DateUtil.convDateToStringYearMonthShort(targetMonth);
		// 改正労働基準法施行日判断
		if (MosPCodePart.afterLegalReformDate(periodEnd)) {
			// テーブルロックの準備
			prepareTableLock();
			// 対象テーブルの追加
			addTargetTable(compile().getKdWorkDao(), true);
			// テーブルロック（ロック開始）
			lockTables();
			// 新勤怠集計のサイズを設定
			listSize = listKdWork.size();
		} else {
			// テーブルロック
			compile().getMKintaiDao().tableLock(listMKintai, yearMonth);
			// 旧勤怠集計のサイズを設定
			listSize = listMKintai.size();
		}
		// 登録件数
		int insertCount = 0;
		// 回す回数
		int rev = listSize / CommonConst.NUM_DIVIDE_NUMBER + 1;
		if (listSize % CommonConst.NUM_DIVIDE_NUMBER == 0) {
			rev--;
		}
		for (int i = 0; i < rev; i++) {
			List<MKintaiDto> kintaiList = new ArrayList<MKintaiDto>();
			List<KdWorkDto> kdWorkList = new ArrayList<KdWorkDto>();
			// リストをdiv件ごと分割する。
			for (int k = 0; k < CommonConst.NUM_DIVIDE_NUMBER; k++) {
				int count = k + i * CommonConst.NUM_DIVIDE_NUMBER;
				if (count >= listSize) {
					break;
				}
				// 改正労働基準法施行日判断
				if (MosPCodePart.afterLegalReformDate(periodEnd)) {
					KdWorkDto kdWorkDto = listKdWork.get(count);
					if (chkExistDto(kdWorkDto)) {
						kdWorkList.add(kdWorkDto);
					} else {
						break;
					}
				} else {
					MKintaiDto mKintaiDto = listMKintai.get(count);
					if (chkExistDto(mKintaiDto)) {
						kintaiList.add(mKintaiDto);
					} else {
						break;
					}
				}
			}
			// 改正労働基準法施行日判断
			if (MosPCodePart.afterLegalReformDate(periodEnd)) {
				// リストを配列にセットする。
				KdWorkDto[] aryKdWork = kdWorkList.toArray(new KdWorkDto[kdWorkList.size()]);
				// 削除処理
				compile().getKdWorkDao().delete(aryKdWork, yearMonth);
				// 登録処理
				insertCount += compile().getKdWorkDao().insert(aryKdWork);
				// テーブルロック解除
				unLockTables();
			} else {
				// リストを配列にセットする。
				MKintaiDto[] aryKintai = kintaiList.toArray(new MKintaiDto[kintaiList.size()]);
				// 削除処理
				compile().getMKintaiDao().delete(aryKintai, yearMonth);
				// 登録処理
				insertCount += compile().getMKintaiDao().insert(aryKintai);
			}
		}
		if (insertCount != listSize) {
			throw new MospException(MospConst.EX_INSERT_FAILED);
		}
		// 勤務形態別集計削除
		// テーブルロック
		compile().getMKintaiKinmuDao().tableLock(aryUsers, yearMonth);
		// 配列をdiv件ごと分割する。
		int i = 0;
		while ((aryUsers.length - (i * CommonConst.NUM_DIVIDE_NUMBER)) >= CommonConst.NUM_DIVIDE_NUMBER) {
			String[] aryKcode = new String[CommonConst.NUM_DIVIDE_NUMBER];
			System.arraycopy(aryUsers, i * CommonConst.NUM_DIVIDE_NUMBER, aryKcode, 0, CommonConst.NUM_DIVIDE_NUMBER);
			// データの削除
			compile().getMKintaiKinmuDao().delete(aryKcode, yearMonth);
			i++;
		}
		if (aryUsers.length % CommonConst.NUM_DIVIDE_NUMBER != 0) {
			String[] aryKcode = new String[aryUsers.length % CommonConst.NUM_DIVIDE_NUMBER];
			System.arraycopy(aryUsers, i * CommonConst.NUM_DIVIDE_NUMBER, aryKcode, 0, aryKcode.length);
			// データの削除
			compile().getMKintaiKinmuDao().delete(aryKcode, yearMonth);
		}
		// 勤務形態別集計登録
		registMKinmu(listMKinmu);
	}
	
	/**
	 * 社員個人
	 * @param kCode 対象社員コード
	 * @throws Exception 	例外処理が発生した場合
	 */
	private void compileKCode(String kCode) throws Exception {
		// 個人のみ計算
		if (part().human().isExist(kCode)) {
			// 勤怠データ取得
			getAttendanceList(kCode, targetMonth);
			// 対象年月
			String yearMonth = DateUtil.convDateToStringYearMonthShort(targetMonth);
			// 改正労働基準法施行日判断
			if (MosPCodePart.afterLegalReformDate(periodEnd)) {
				listKdWork.add(compile().getNewCompiledInfo(kCode, targetMonth, attendanceList));
				// テーブルロック
				prepareTableLock();
				// KdWorkをWRITE
				addTargetTable(compile().getKdWorkDao(), true);
				// テーブルロック（ロック開始）
				lockTables();
				// データ取得
				KdWorkDto currentDto = compile().getKdWorkDao().findForUpdate(kCode, yearMonth);
				// 削除
				if (chkExistDto(currentDto)) {
					// 削除
					compile().getKdWorkDao().delete(currentDto);
				}
				// 登録処理
				registKdWork(listKdWork);
				// テーブルロック解除
				unLockTables();
			} else {
				listMKintai.add(compile().getCompiledInfo(kCode, targetMonth, attendanceList));
				// テーブルロック
				compile().getMKintaiDao().tableLock(kCode, yearMonth);
				// データ取得
				MKintaiDto currentDto = compile().getMKintaiDao().findForUpdate(kCode, yearMonth);
				if (chkExistDto(currentDto)) {
					// 削除
					compile().getMKintaiDao().delete(currentDto);
				}
				// 勤怠集計登録
				registMKintai(listMKintai);
			}
			listMKinmu.addAll(getWorkTypeList(kCode, targetMonth, attendanceList));
			// 勤務形態別集計削除
			compile().getMKintaiKinmuDao().tableLock(kCode, DateUtil.convDateToStringYearMonthShort(targetMonth));
			compile().getMKintaiKinmuDao().delete(kCode, DateUtil.convDateToStringYearMonthShort(targetMonth));
			// 勤務形態別集計登録
			registMKinmu(listMKinmu);
		}
	}
	
	/**
	 * 勤務形態別登録準備
	 * @param kCode			対象社員コード
	 * @param targetMonth	対象年月
	 * @param attendanceList	対象出勤簿リスト
	 * @return	勤務形態別勤怠集計リスト
	 * @throws Exception	例外処理が発生した場合
	 */
	private List<MKintaiKinmuDto> getWorkTypeList(String kCode, Date targetMonth, List<?> attendanceList)
			throws Exception {
		List<MKintaiKinmuDto> kinmuList = new ArrayList<MKintaiKinmuDto>();
		// 勤務形態別集計登録
		String[] aryWorkType = part().worktype().getWorkTypeCodeArrayAll();
		for (String workTypeCode : aryWorkType) {
			// 登録データのセット
			kinmuList.add(part().compile().getCompileForWorkType(kCode, targetMonth, workTypeCode, attendanceList,
					inputUnit));
		}
		return kinmuList;
	}
	
	/**
	 * 登録処理（勤怠集計）
	 * @param listMKintai	勤怠集計DTOリスト
	 * @throws Exception 	例外処理が発生した場合
	 */
	private void registMKintai(List<MKintaiDto> listMKintai) throws Exception {
		// 登録件数
		int insertCount = 0;
		int listSize = listMKintai.size();
		// 回す回数
		int rev = listSize / CommonConst.NUM_DIVIDE_NUMBER + 1;
		if (listSize % CommonConst.NUM_DIVIDE_NUMBER == 0) {
			rev--;
		}
		for (int i = 0; i < rev; i++) {
			List<MKintaiDto> currentList = new ArrayList<MKintaiDto>();
			// リストをdiv件ごと分割する。
			for (int k = 0; k < CommonConst.NUM_DIVIDE_NUMBER; k++) {
				int count = k + i * CommonConst.NUM_DIVIDE_NUMBER;
				if (count >= listSize) {
					break;
				}
				MKintaiDto mKintaiDto = listMKintai.get(count);
				if (chkExistDto(mKintaiDto)) {
					currentList.add(mKintaiDto);
				} else {
					break;
				}
			}
			// 登録処理			
			insertCount += compile().getMKintaiDao().insert(currentList.toArray(new MKintaiDto[currentList.size()]));
		}
		if (insertCount != listSize) {
			throw new MospException(MospConst.EX_INSERT_FAILED);
		}
	}
	
	/**
	 * 登録処理（勤怠集計）
	 * @param listKdWork	勤怠集計DTOリスト
	 * @throws Exception 	例外処理が発生した場合
	 */
	private void registKdWork(List<KdWorkDto> listKdWork) throws Exception {
		// 登録件数
		int insertCount = 0;
		int listSize = listKdWork.size();
		// 回す回数
		int rev = listSize / CommonConst.NUM_DIVIDE_NUMBER + 1;
		if (listSize % CommonConst.NUM_DIVIDE_NUMBER == 0) {
			rev--;
		}
		for (int i = 0; i < rev; i++) {
			List<KdWorkDto> currentList = new ArrayList<KdWorkDto>();
			// リストをdiv件ごと分割する。
			for (int k = 0; k < CommonConst.NUM_DIVIDE_NUMBER; k++) {
				int count = k + i * CommonConst.NUM_DIVIDE_NUMBER;
				if (count >= listSize) {
					break;
				}
				KdWorkDto mKintaiDto = listKdWork.get(count);
				if (chkExistDto(mKintaiDto)) {
					currentList.add(mKintaiDto);
				} else {
					break;
				}
			}
			// 登録処理			
			insertCount += compile().getKdWorkDao().insert(currentList.toArray(new KdWorkDto[currentList.size()]));
		}
		if (insertCount != listSize) {
			throw new MospException(MospConst.EX_INSERT_FAILED);
		}
	}
	
	/**
	 * 登録処理（勤務形態別）
	 * @param listMKinmu	勤務形態別集計DTOリスト
	 * @throws Exception	例外処理が発生した場合
	 */
	private void registMKinmu(List<MKintaiKinmuDto> listMKinmu) throws Exception {
		// 登録件数
		int insertCount = 0;
		int listSize = listMKinmu.size();
		// 回す回数
		int rev = listSize / CommonConst.NUM_DIVIDE_NUMBER + 1;
		if (listSize % CommonConst.NUM_DIVIDE_NUMBER == 0) {
			rev--;
		}
		for (int i = 0; i < rev; i++) {
			List<MKintaiKinmuDto> currentList = new ArrayList<MKintaiKinmuDto>();
			// リストをdiv件ごと分割する。
			for (int k = 0; k < CommonConst.NUM_DIVIDE_NUMBER; k++) {
				int count = k + i * CommonConst.NUM_DIVIDE_NUMBER;
				if (count >= listSize) {
					break;
				}
				MKintaiKinmuDto mKinmuDto = listMKinmu.get(count);
				if (chkExistDto(mKinmuDto)) {
					currentList.add(mKinmuDto);
				} else {
					break;
				}
			}
			// 配列をセットする。			
			insertCount += compile().getMKintaiKinmuDao().insert(
					currentList.toArray(new MKintaiKinmuDto[currentList.size()]));
		}
		if (insertCount != listSize) {
			throw new MospException(MospConst.EX_INSERT_FAILED);
		}
	}
	
	/**
	 * モード設定（計算情報）
	 * @throws Exception	例外処理が発生した場合
	 */
	private void setMode() throws Exception {
		AttendanceCompileCardVo vo = (AttendanceCompileCardVo)getVo();
		MKintaiKeisanDto dto = part().calculate().findForCalc();
		if (chkExistDto(dto)) {
			if (CalculatePart.isCalculateFinish(dto)) {
				// モード設定
				vo.setMode(AttendanceCompileCardVo.MODE_IMPOSSIBLE);
				// メッセージ用計算年月
				// 選択年月計算完了
				vo.addErrMessage(getMessage(CommonConst.MSG_ALREADY_CALCULATE,
				// メッセージ用計算年月
						dto.getKTNen() + CommonConst.STR_UNDER_SEPARATOR + dto.getKTTuki()));
			} else {
				vo.setMode(AttendanceCompileCardVo.MODE_UPDATE);
			}
		} else {
			// モード設定
			vo.setMode(AttendanceCompileCardVo.MODE_IMPOSSIBLE);
			// 計算情報無し
			vo.addErrMessage(getMessage(MospConst.MSG_NO_DATA, ""));
		}
	}
	
	/**
	 * 一覧への情報セット
	 * @throws Exception	例外処理が発生した場合
	 */
	private void setNextCmdInfo() throws Exception {
		AttendanceCompileCardVo vo = (AttendanceCompileCardVo)getVo();
		// 選択所属コードのセット
		request.setAttribute(COMPILE_SECTION, vo.getPltSection());
		// 選択社員コードのセット
		request.setAttribute(COMPILE_K_CODE, vo.getTxtKCode());
		// メッセージ設定
		if (StringUtil.isNotNull(vo.getTxtKCode()) && part().human().isNotUseKCode(vo.getTxtKCode())) {
			setMessage(getMessage(MospConst.MSG_NO_CODE, ""));
		} else {
			setMessage(getMessage(MospConst.MSG_UPDATE, ""));
		}
	}
	
}
