/*
 * 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.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>
	 */
	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>
	 * 例)対象年度：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 {
		// TODO
		// MosP処理情報から年度の開始月を取得
		int fiscalStartMonth = mospParams.getApplicationProperty(APP_FISCAL_START_MONTH, TimeConst.CODE_DEFAULT_MONTH);
		// 実際の年を準備
		int year = fiscalYear;
		// 年度の4月1初日を取得
		return DateUtility.getFirstDateOfMonth(year, fiscalStartMonth);
	}
	
	/**
	 * 対象日付が含まれる年度を取得する。
	 * @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);
		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;
	}
	
}
