/*
 * 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 Affero General Public License
 * as published by the Free Software Foundation, either version 3
 * 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 Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package jp.mosp.time.utils;

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

import jp.mosp.framework.base.MospException;
import jp.mosp.framework.base.MospParams;
import jp.mosp.framework.utils.DateUtility;
import jp.mosp.platform.utils.PlatformUtility;
import jp.mosp.time.constant.TimeConst;

/**
 * 勤怠管理における有用なメソッドを提供する。<br><br>
 */
public class TimeUtility {
	
	/**
	 * 日付休暇設定における取得期間(無期限)の年。<br>
	 * Date型の最大年。<br>
	 */
	public static final int		DATE_UNLIMITED_YEAR				= 5874897;
	
	/**
	 * 日付休暇設定における取得期間(無期限)の日。<br>
	 */
	public static final int		DATE_YEAR_LAST_MONTH			= 31;
	
	/**
	 * MosPアプリケーション設定キー(年月指定時の基準日)。<br>
	 */
	public static final String	APP_YEAR_MONTH_TARGET_DATE		= "YearMonthTargetDate";
	
	/**
	 * MosPアプリケーション設定キー(年度の開始月)。<br>
	 */
	public static final String	APP_FISCAL_START_MONTH			= "FiscalStartMonth";
	
	/**
	 * 翌月の基準日判断係数。<br>
	 */
	public static final int		TARGET_DATE_NEXT_MONTH			= 100;
	
	/**
	 * メインメニューキー(勤怠報告)。<br>
	 */
	public static final String	MAIN_MENU_TIME_INMPUT			= "menuTimeInput";
	
	/**
	 * メニューキー(残業申請)。<br>
	 */
	public static final String	MENU_OVERTIME_REQUEST			= "OvertimeRequest";
	
	/**
	 * メニューキー(休暇申請)。<br>
	 */
	public static final String	MENU_HOLIDAY_REQUEST			= "HolidayRequest";
	
	/**
	 * メニューキー(振出・休出申請)。<br>
	 */
	public static final String	MENU_WORK_ON_HOLIDAY_REQUEST	= "WorkOnHolidayRequest";
	
	/**
	 * メニューキー(代休申請)。<br>
	 */
	public static final String	MENU_SUB_HOLIDAY_REQUEST		= "SubHolidayRequest";
	
	/**
	 * メニューキー(勤務形態変更申請)。<br>
	 */
	public static final String	MENU_WORK_TYPE_CHANGE_REQUEST	= "WorkTypeChangeRequest";
	
	/**
	 * メニューキー(承認解除申請)。<br>
	 */
	public static final String	MENU_CANCELLATION_REQUEST		= "CancellationRequest";
	
	
	/**
	 * 他クラスからのインスタンス化を防止する。<br>
	 */
	private TimeUtility() {
		// 処理無し
	}
	
	/**
	 * 対象年月及び締日から締期間初日を取得する。<br>
	 * @param cutoffDate  締日
	 * @param targetYear  対象年
	 * @param targetMonth 対象月
	 * @return 締期間初日
	 * @throws MospException 日付操作に失敗した場合
	 */
	public static Date getCutoffFirstDate(int cutoffDate, int targetYear, int targetMonth) throws MospException {
		// 月末締の場合
		if (cutoffDate == TimeConst.CUTOFF_DATE_LAST_DAY) {
			// 締期間初日設定(対象年月の初日)
			return DateUtility.getFirstDateOfMonth(targetYear, targetMonth);
		}
		// 当月締日判断
		if (cutoffDate > TimeConst.CUTOFF_DATE_THIS_MONTH_MAX) {
			// 当月締の場合
			// 対象年月、締日で日付を取得(締期間初日作成準備)
			Date date = DateUtility.getDate(targetYear, targetMonth, cutoffDate);
			// 月を戻す
			date = DateUtility.addMonth(date, -1);
			// 日を加算(締日の翌日が初日)
			return DateUtility.addDay(date, 1);
		} else {
			// 翌月締の場合
			// 対象年月、締日で日付を取得(締期間初日作成準備)
			Date date = DateUtility.getDate(targetYear, targetMonth, cutoffDate);
			// 締期間初日設定(前月の締日+1日)(翌月締の場合)
			return DateUtility.addDay(date, 1);
		}
	}
	
	/**
	 * 対象年月及び締日から締期間最終日を取得する。<br>
	 * @param cutoffDate  締日
	 * @param targetYear  対象年
	 * @param targetMonth 対象月
	 * @return 締期間最終日
	 * @throws MospException 日付操作に失敗した場合
	 */
	public static Date getCutoffLastDate(int cutoffDate, int targetYear, int targetMonth) throws MospException {
		// 月末締の場合
		if (cutoffDate == TimeConst.CUTOFF_DATE_LAST_DAY) {
			// 締期間最終日取得(対象年月の最終日)
			return DateUtility.getLastDateOfMonth(targetYear, targetMonth);
		}
		// 当月締日判断
		if (cutoffDate > TimeConst.CUTOFF_DATE_THIS_MONTH_MAX) {
			// 当月締の場合
			// 対象年月、締日で日付を取得(締期間初日作成準備)
			Date date = DateUtility.getDate(targetYear, targetMonth, cutoffDate);
			// 月を戻す
			date = DateUtility.addMonth(date, -1);
			// 日を加算(締日の翌日が初日)
			date = DateUtility.addDay(date, 1);
			// 締期間最終日取得(年月の締日)(当月締の場合)
			return DateUtility.getDate(targetYear, targetMonth, cutoffDate);
		} else {
			// 翌月締の場合
			// 年、月、締日で日付を取得(締期間最終日作成準備)
			Date date = DateUtility.getDate(targetYear, targetMonth, cutoffDate);
			// 締期間最終日設定(翌月の締日)(翌月締の場合)
			return DateUtility.addMonth(date, 1);
		}
	}
	
	/**
	 * 対象年月における締期間の基準日を取得する。<br>
	 * @param cutoffDate 締日
	 * @param targetYear 対象年
	 * @param targetMonth 対象月
	 * @return 締期間基準日
	 * @throws MospException 日付操作に失敗した場合
	 */
	public static Date getCutoffTermTargetDate(int cutoffDate, int targetYear, int targetMonth) throws MospException {
		return getCutoffLastDate(cutoffDate, targetYear, targetMonth);
	}
	
	/**
	 * 対象年月における締期間の集計日を取得する。<br>
	 * @param cutoffDate 締日
	 * @param targetYear 対象年
	 * @param targetMonth 対象月
	 * @return 締期間集計日
	 * @throws MospException 日付操作に失敗した場合
	 */
	public static Date getCutoffCalculationDate(int cutoffDate, int targetYear, int targetMonth) throws MospException {
		return getCutoffLastDate(cutoffDate, targetYear, targetMonth);
	}
	
	/**
	 * 対象日付及び締日から対象年月日が含まれる締月を取得する。<br>
	 * @param cutoffDate 締日
	 * @param targetDate 対象日付
	 * @return 締月(締月初日)
	 * @throws MospException 日付操作に失敗した場合
	 */
	public static Date getCutoffMonth(int cutoffDate, Date targetDate) throws MospException {
		// 月末締の場合
		if (cutoffDate == TimeConst.CUTOFF_DATE_LAST_DAY) {
			// 対象年月は対象日年月
			return DateUtility.getFirstDateOfMonth(targetDate);
		}
		// 対象日の日を取得
		int day = DateUtility.getDay(targetDate);
		// 当月締日判断
		if (cutoffDate > TimeConst.CUTOFF_DATE_THIS_MONTH_MAX) {
			// 当月締の場合
			// 日と締日を比較
			if (cutoffDate < day) {
				// 対象年月は対象日年月の翌月(当月締で日が締日より先の場合)
				Date yearMonth = DateUtility.addMonth(targetDate, 1);
				return DateUtility.getFirstDateOfMonth(yearMonth);
			} else {
				// 対象年月は対象日年月(当月締で日が締日以前の場合)
				return DateUtility.getFirstDateOfMonth(targetDate);
			}
		} else {
			// 翌月締の場合
			// 日と締日を比較
			if (cutoffDate < day) {
				// 対象年月は対象日年月(翌月締で日が締日より先の場合)
				return DateUtility.getFirstDateOfMonth(targetDate);
			} else {
				// 対象年月は対象日年月の前月(翌月締で日が締日以前の場合)
				Date yearMonth = DateUtility.addMonth(targetDate, -1);
				return DateUtility.getFirstDateOfMonth(yearMonth);
			}
		}
	}
	
	/**
	 * 年月指定時の基準日を取得する。<br>
	 * XMLファイルから基準日を取得する。<br>
	 * @param targetYear  指定年
	 * @param targetMonth 指定月
	 * @param mospParams  MosP処理情報
	 * @return 年月指定日の基準日
	 * @throws MospException 日付操作に失敗した場合
	 */
	public static Date getYearMonthTargetDate(int targetYear, int targetMonth, MospParams mospParams)
			throws MospException {
		// MosP処理情報から基準日を取得
		int yearMonthTargetDate = mospParams.getApplicationProperty(APP_YEAR_MONTH_TARGET_DATE,
				TimeConst.CUTOFF_DATE_LAST_DAY);
		// 基準日が末日か判断
		if (yearMonthTargetDate == TimeConst.CUTOFF_DATE_LAST_DAY) {
			return DateUtility.getLastDateOfMonth(targetYear, targetMonth);
		}
		// 基準月用日付準備(基準年月初日)
		Date date = DateUtility.getFirstDateOfMonth(targetYear, targetMonth);
		// 翌月判断
		if (yearMonthTargetDate > TARGET_DATE_NEXT_MONTH) {
			// 翌月にする
			date = DateUtility.addMonth(date, 1);
			// 基準日再取得
			yearMonthTargetDate = yearMonthTargetDate % TARGET_DATE_NEXT_MONTH;
		}
		// 年月基準日を取得する
		return DateUtility.getDate(DateUtility.getYear(date), DateUtility.getMonth(date), yearMonthTargetDate);
	}
	
	/**
	 * 日付指定時の基準年月を取得する。<br>
	 * <br>
	 * XMLファイルから基準日を取得し、指定日付が含まれる年月(基準年月)を算出する。<br>
	 * 検索プルダウンを設定する際など、基準日と指定日時により基準年月が変化する。<br>
	 * <br>
	 * @param targetDate 指定日付
	 * @param mospParams MosP処理情報
	 * @return 日付指定日の基準年月(基準年月の初日)
	 */
	public static Date getTargetYearMonth(Date targetDate, MospParams mospParams) {
		// MosP処理情報から基準日を取得
		int yearMonthTargetDate = mospParams.getApplicationProperty(APP_YEAR_MONTH_TARGET_DATE,
				TimeConst.CUTOFF_DATE_LAST_DAY);
		// 基準日が末日か判断
		if (yearMonthTargetDate == TimeConst.CUTOFF_DATE_LAST_DAY) {
			// 指定日時の月の初日を取得
			return DateUtility.getFirstDateOfMonth(targetDate);
		}
		// 指定日時の日を取得
		int targetDay = DateUtility.getDay(targetDate);
		// 基準月用日付準備(指定日付の年月の初日)
		Date date = DateUtility.getFirstDateOfMonth(targetDate);
		// 翌月(翌1日～15日)判断
		if (yearMonthTargetDate > TARGET_DATE_NEXT_MONTH) {
			// 基準日再取得
			yearMonthTargetDate = yearMonthTargetDate % TARGET_DATE_NEXT_MONTH;
			// 指定日付の日が基準日以下の場合
			if (yearMonthTargetDate >= targetDay) {
				// 指定日付の年月の前月を取得
				date = DateUtility.addMonth(date, -1);
			}
			return date;
		}
		// 指定日付の日が基準日より大きい場合(翌1日～15日でなく)
		if (targetDay > yearMonthTargetDate) {
			// 指定日付の年月の翌月を取得
			date = DateUtility.addMonth(date, 1);
		}
		return date;
	}
	
	/**
	 * 年度と月を指定して実際の年月を取得する。<br>
	 * 例)対象年度：2011、対象月：3とした場合、2012年3月1日になる。<br>
	 * @param fiscalYear 対象年度
	 * @param month 対象月
	 * @param mospParams MosP処理情報
	 * @return 実際の年月(年月の初日)
	 * @throws MospException 日付操作に失敗した場合
	 */
	public static Date getFiscalYearMonth(int fiscalYear, int month, MospParams mospParams) throws MospException {
		// MosP処理情報から年度の開始月を取得
		int fiscalStartMonth = mospParams.getApplicationProperty(APP_FISCAL_START_MONTH, TimeConst.CODE_DEFAULT_MONTH);
		// 実際の年を準備
		int year = fiscalYear;
		// 年度の開始月確認
		if (month < fiscalStartMonth) {
			year++;
		}
		// 年月の初日を取得
		return DateUtility.getFirstDateOfMonth(year, month);
	}
	
	/**
	 * 年度を指定して4月の初日を取得する。<br>
	 * 例)対象年度：2011とした場合、2011年4月1日になる。<br>
	 * @param fiscalYear 対象年度
	 * @param mospParams MosP処理情報
	 * @return 実際の年月(年月の初日)
	 * @throws MospException 日付操作に失敗した場合
	 */
	public static Date getFiscalYearFirstDate(int fiscalYear, MospParams mospParams) throws MospException {
		// MosP処理情報から年度の開始月を取得
		int fiscalStartMonth = mospParams.getApplicationProperty(APP_FISCAL_START_MONTH, TimeConst.CODE_DEFAULT_MONTH);
		// 実際の年を準備
		int year = fiscalYear;
		// 年度の4月1初日を取得
		return DateUtility.getFirstDateOfMonth(year, fiscalStartMonth);
	}
	
	/**
	 * 年度を指定して対象年度の最終日を取得する。<br>
	 * 例)対象年度：2011とした場合、2012年3月31日になる。<br>
	 * @param fiscalYear 対象年度
	 * @param mospParams MosP処理情報
	 * @return 対象年度の最終日
	 * @throws MospException 日付操作に失敗した場合
	 */
	public static Date getFiscalYearLastDate(int fiscalYear, MospParams mospParams) throws MospException {
		// 対象年に+1をした来年度の初日(4月1)を取得
		Date fiscalYearFirstDate = getFiscalYearFirstDate(fiscalYear + 1, mospParams);
		// 来年度の初日の前日
		return DateUtility.addDay(fiscalYearFirstDate, -1);
	}
	
	/**
	 * 対象日付が含まれる年度を取得する。
	 * @param date 対象日付	
	 * @param mospParams MosP処理情報
	 * @return 対象日付が含まれる年度
	 */
	public static int getFiscalYear(Date date, MospParams mospParams) {
		// MosP処理情報から年度の開始月を取得
		int fiscalStartMonth = mospParams.getApplicationProperty(APP_FISCAL_START_MONTH, TimeConst.CODE_DEFAULT_MONTH);
		int year = DateUtility.getYear(date);
		int month = DateUtility.getMonth(date);
		if (month < fiscalStartMonth) {
			year--;
		}
		return year;
	}
	
	/**
	 * 勤怠管理用機能コードセットを取得する。<br>
	 * @return 勤怠管理用機能コードセット
	 */
	public static Set<String> getTimeFunctionSet() {
		// 勤怠管理用機能コードセット準備
		Set<String> set = new HashSet<String>();
		set.add(TimeConst.CODE_FUNCTION_WORK_MANGE);
		set.add(TimeConst.CODE_FUNCTION_OVER_WORK);
		set.add(TimeConst.CODE_FUNCTION_VACATION);
		set.add(TimeConst.CODE_FUNCTION_WORK_HOLIDAY);
		set.add(TimeConst.CODE_FUNCTION_COMPENSATORY_HOLIDAY);
		set.add(TimeConst.CODE_FUNCTION_DIFFERENCE);
		set.add(TimeConst.CODE_FUNCTION_WORK_TYPE_CHANGE);
		return set;
	}
	
	/**
	 * 休暇設定における取得期間(無期限)を取得する。
	 * @return 無期限
	 * @throws MospException 日付操作に失敗した場合
	 */
	public static Date getUnlimitedDate() throws MospException {
		return DateUtility.getDate(DATE_UNLIMITED_YEAR, TimeConst.CODE_DEFINITION_YEAR, DATE_YEAR_LAST_MONTH);
	}
	
	/**
	 * 初日から最終日の日リストを取得する。<br>
	 * @param firstDate 初日
	 * @param lastDate  最終日
	 * @return 日リスト
	 */
	public static List<Date> getDateList(Date firstDate, Date lastDate) {
		// 日リスト準備
		List<Date> list = new ArrayList<Date>();
		// 日の確認
		if (firstDate == null || lastDate == null) {
			// 空リスト
			return list;
		}
		// 初日を基に対象日を準備
		Date date = (Date)firstDate.clone();
		// 対象日毎に処理
		while (!date.after(lastDate)) {
			list.add(date);
			date = DateUtility.addDay(date, 1);
		}
		return list;
	}
	
	/**
	 * 残業申請が有効であるかを確認する。<br>
	 * MosP処理情報中のメインメニュー設定情報群にあるメニュー設定で、判断する。<br>
	 * @param mospParams MosP処理情報
	 * @return 確認結果(true：残業申請が有効、false：無効)
	 */
	public static boolean isOvertimeRequestValid(MospParams mospParams) {
		return PlatformUtility.isTheMenuValid(mospParams, MAIN_MENU_TIME_INMPUT, MENU_OVERTIME_REQUEST);
	}
	
	/**
	 * 残業申請が利用できるかを確認する。<br>
	 * 対象メニューが有効であり、
	 * MosP処理情報に設定されたユーザが対象メニューを利用できるかで、判断する。<br>
	 * @param mospParams MosP処理情報
	 * @return 確認結果(true：残業申請利用可、false：不可)
	 */
	public static boolean isOvertimeRequestAvailable(MospParams mospParams) {
		return PlatformUtility.isTheMenuAvailable(mospParams, MAIN_MENU_TIME_INMPUT, MENU_OVERTIME_REQUEST);
	}
	
	/**
	 * 休暇申請が有効であるかを確認する。<br>
	 * MosP処理情報中のメインメニュー設定情報群にあるメニュー設定で、判断する。<br>
	 * @param mospParams MosP処理情報
	 * @return 確認結果(true：休暇申請が有効、false：無効)
	 */
	public static boolean isHolidayRequestValid(MospParams mospParams) {
		return PlatformUtility.isTheMenuValid(mospParams, MAIN_MENU_TIME_INMPUT, MENU_HOLIDAY_REQUEST);
	}
	
	/**
	 * 休暇申請が利用できるかを確認する。<br>
	 * 対象メニューが有効であり、
	 * MosP処理情報に設定されたユーザが対象メニューを利用できるかで、判断する。<br>
	 * @param mospParams MosP処理情報
	 * @return 確認結果(true：休暇申請利用可、false：不可)
	 */
	public static boolean isHolidayRequestAvailable(MospParams mospParams) {
		return PlatformUtility.isTheMenuAvailable(mospParams, MAIN_MENU_TIME_INMPUT, MENU_HOLIDAY_REQUEST);
	}
	
	/**
	 * 振出・休出申請が有効であるかを確認する。<br>
	 * MosP処理情報中のメインメニュー設定情報群にあるメニュー設定で、判断する。<br>
	 * @param mospParams MosP処理情報
	 * @return 確認結果(true：振出・休出申請が有効、false：無効)
	 */
	public static boolean isWorkOnHolidayRequestValid(MospParams mospParams) {
		return PlatformUtility.isTheMenuValid(mospParams, MAIN_MENU_TIME_INMPUT, MENU_WORK_ON_HOLIDAY_REQUEST);
	}
	
	/**
	 * 振出・休出申請が利用できるかを確認する。<br>
	 * 対象メニューが有効であり、
	 * MosP処理情報に設定されたユーザが対象メニューを利用できるかで、判断する。<br>
	 * @param mospParams MosP処理情報
	 * @return 確認結果(true：振出・休出申請利用可、false：不可)
	 */
	public static boolean isWorkOnHolidayRequestAvailable(MospParams mospParams) {
		return PlatformUtility.isTheMenuAvailable(mospParams, MAIN_MENU_TIME_INMPUT, MENU_WORK_ON_HOLIDAY_REQUEST);
	}
	
	/**
	 * 代休申請が有効であるかを確認する。<br>
	 * MosP処理情報中のメインメニュー設定情報群にあるメニュー設定で、判断する。<br>
	 * @param mospParams MosP処理情報
	 * @return 確認結果(true：代休申請が有効、false：無効)
	 */
	public static boolean isSubHolidayRequestValid(MospParams mospParams) {
		return PlatformUtility.isTheMenuValid(mospParams, MAIN_MENU_TIME_INMPUT, MENU_SUB_HOLIDAY_REQUEST);
	}
	
	/**
	 * 代休申請が利用できるかを確認する。<br>
	 * 対象メニューが有効であり、
	 * MosP処理情報に設定されたユーザが対象メニューを利用できるかで、判断する。<br>
	 * @param mospParams MosP処理情報
	 * @return 確認結果(true：代休申請利用可、false：不可)
	 */
	public static boolean isSubHolidayRequestAvailable(MospParams mospParams) {
		return PlatformUtility.isTheMenuAvailable(mospParams, MAIN_MENU_TIME_INMPUT, MENU_SUB_HOLIDAY_REQUEST);
	}
	
	/**
	 * 勤務形態変更申請が有効であるかを確認する。<br>
	 * MosP処理情報中のメインメニュー設定情報群にあるメニュー設定で、判断する。<br>
	 * @param mospParams MosP処理情報
	 * @return 確認結果(true：勤務形態変更申請が有効、false：無効)
	 */
	public static boolean isWorkTypeChangeRequestValid(MospParams mospParams) {
		return PlatformUtility.isTheMenuValid(mospParams, MAIN_MENU_TIME_INMPUT, MENU_WORK_TYPE_CHANGE_REQUEST);
	}
	
	/**
	 * 勤務形態変更申請が利用できるかを確認する。<br>
	 * 対象メニューが有効であり、
	 * MosP処理情報に設定されたユーザが対象メニューを利用できるかで、判断する。<br>
	 * @param mospParams MosP処理情報
	 * @return 確認結果(true：勤務形態変更申請利用可、false：不可)
	 */
	public static boolean isWorkTypeChangeRequestAvailable(MospParams mospParams) {
		return PlatformUtility.isTheMenuAvailable(mospParams, MAIN_MENU_TIME_INMPUT, MENU_WORK_TYPE_CHANGE_REQUEST);
	}
	
	/**
	 * 承認解除申請が有効であるかを確認する。<br>
	 * MosP処理情報中のメインメニュー設定情報群にあるメニュー設定で、判断する。<br>
	 * @param mospParams MosP処理情報
	 * @return 確認結果(true：承認解除申請が有効、false：無効)
	 */
	public static boolean isCancellationRequestValid(MospParams mospParams) {
		return PlatformUtility.isTheMenuValid(mospParams, MAIN_MENU_TIME_INMPUT, MENU_CANCELLATION_REQUEST);
	}
	
}
