/*
 * 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.common.utils;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * <p>祝祭日取得クラス</p>
 * 祝日法による国民の祝日の取得<br>
 * <br>
 * 0.0.2（追加）<br>
 * ハッピーマンデー<br>
 * 春分の日、秋分の日の取得<br>
 * <br>
 * 0.0.3（追加）<br>
 * 振替休日、国民の祝日<br>
 * 個別祝日取得<br>
 * Mapオブジェクト取得<br>
 * <br>
 * @author yoshida
 *	@version		0.0.3
 */
public class JapanHolidayUtil {
	
	/**
	 * 成人の日
	 */
	public static final String	SEIJIN_NAME					= "成人の日";
	/**
	 * 海の日
	 */
	public static final String	UMI_NAME					= "海の日";
	/**
	 * 敬老の日
	 */
	public static final String	KEIROU_NAME					= "敬老の日";
	/**
	 * 体育の日
	 */
	public static final String	TAIIKU_NAME					= "体育の日";
	/**
	 * 春分の日
	 */
	public static final String	SYUNBUN_NAME				= "春分の日";
	/**
	 * 秋分の日
	 */
	public static final String	SYUUBUN_NAME				= "秋分の日";
	/**
	 * 元日
	 */
	public static final String	GANJITSU_NAME				= "元日";
	/**
	 * 建国記念の日
	 */
	private static final String	KENKOKU_KINEN_NAME			= "建国記念の日";
	/**
	 * 昭和の日
	 */
	private static final String	SYOUWA_NAME					= "昭和の日";
	/**
	 * 憲法記念日
	 */
	private static final String	KENPOU_KINEN_NAME			= "憲法記念日";
	/**
	 * みどりの日
	 */
	private static final String	MIDORI_NAME					= "みどりの日";
	/**
	 * こどもの日
	 */
	private static final String	KODOMO_NAME					= "こどもの日";
	/**
	 * 文化の日
	 */
	private static final String	BUNKA_NAME					= "文化の日";
	/**
	 * 勤労感謝の日
	 */
	private static final String	KINROU_KANSYA_NAME			= "勤労感謝の日";
	/**
	 * 天皇誕生日
	 */
	private static final String	TENNOU_TANJOU_NAME			= "天皇誕生日";
	/**
	 * 国民の休日
	 */
	private static final String	KOKUMIN_NO_SYUKUJITU_NAME	= "国民の祝日";
	/**
	 * 振替休日
	 */
	private static final String	FURIKAE_NAME				= "振替休日";
	
	private static final int	YEAR_1900					= 1900;
	private static final int	YEAR_2000					= 2000;
	private static final int	YEAR_2003					= 2003;
	private static final int	YEAR_2006					= 2006;
	private static final int	YEAR_2007					= 2007;
	
	/**
	 * 祝日配列
	 */
	private String[][]			arySyuku					= new String[15][2];
	
	/**
	 * 祝日マップ
	 */
	private Map<Date, String>	holidayMap;
	
	/**
	 * 対象年
	 */
	private int					year;
	

	/**
	 * コンストラクタ
	 */
	public JapanHolidayUtil() {
		year = 0;
	}
	
	/**
	 * コンストラクタ
	 * @param year 対象年度
	 * <p>祝祭日配列作成</p>
	 */
	public JapanHolidayUtil(int year) {
		this.year = year;
		createHolidayMap();
	}
	
	/**
	 * Calendarインスタンス取得
	 * @return	Calendarインスタンス
	 */
	private static Calendar getCalendar() {
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.HOUR_OF_DAY, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		return cal;
	}
	
	/**
	 * 指定曜日可否メソッド
	 * @param date		対象年月日
	 * @param dayOfWeek	対象曜日
	 * @return
	 * <p>
	 * 対象の曜日だった場合true、そうでない場合false
	 * </p>
	 */
	public boolean isDayOfWeek(Date date, int dayOfWeek) {
		Calendar cal = getCalendar();
		cal.setTime(date);
		if (cal.get(Calendar.DAY_OF_WEEK) == dayOfWeek) {
			return true;
		}
		return false;
	}
	
	/**
	 * 祝日配列取得
	 * @return 祝日配列
	 */
	public String[][] getArySyuku() {
		return arySyuku.clone();
	}
	
	/**
	 * 対象年設定
	 * @param year	対象年
	 */
	public void setYear(int year) {
		this.year = year;
	}
	
	/**
	 * 祝日マップ取得
	 * @return	祝日マップ
	 */
	public Map<Date, String> getHolidayMap() {
		return holidayMap;
	}
	
	/**
	 * 祝祭日Map生成
	 */
	public void createHolidayMap() {
		// 成人の日
		addHolidayMap(getSeijinDate(year), SEIJIN_NAME);
		// 海の日
		addHolidayMap(getUmiDate(year), UMI_NAME);
		// 敬老の日
		addHolidayMap(getKeirouDate(year), KEIROU_NAME);
		// 体育の日
		addHolidayMap(getTaiikuDate(year), TAIIKU_NAME);
		// 春分の日
		addHolidayMap(getSyunbunDate(year), SYUNBUN_NAME);
		// 秋分の日
		addHolidayMap(getSyuubunDate(year), SYUUBUN_NAME);
		// 元日
		addHolidayMap(getGanjitsuDate(year), GANJITSU_NAME);
		// 建国記念の日
		addHolidayMap(getKenkokuKinenDate(year), KENKOKU_KINEN_NAME);
		// 憲法記念日
		addHolidayMap(getKenpouKinenDate(year), KENPOU_KINEN_NAME);
		// こどもの日
		addHolidayMap(getKodomoDate(year), KODOMO_NAME);
		// 文化の日
		addHolidayMap(getBunkaDate(year), BUNKA_NAME);
		// 勤労感謝の日
		addHolidayMap(getKinrouKansyaDate(year), KINROU_KANSYA_NAME);
		// 天皇誕生日
		addHolidayMap(getTennouTanjouDate(year), TENNOU_TANJOU_NAME);
		// みどりの日は2006年まで4月29日、昭和の日は2007年から導入
		// 国民の休日は2006年まで5月4日、みどりの日は2007年から導入
		String midoriName = MIDORI_NAME;
		String syouwaName = SYOUWA_NAME;
		// みどりの日
		// 国民の休日
		if (year < YEAR_2007) {
			midoriName = KOKUMIN_NO_SYUKUJITU_NAME;
			syouwaName = MIDORI_NAME;
		}
		addHolidayMap(getSyouwaDate(year), syouwaName);
		addHolidayMap(getMidoriDate(year), midoriName);
		// 振替休日を追加
		addSubstituteDate();
		// 配列に変換
		convertMapToArray();
	}
	
	/**
	 * Map追加
	 * @param date		対象年月日
	 * @param name		対象祝祭日名
	 */
	private void addHolidayMap(Date date, String name) {
		if (holidayMap == null) {
			holidayMap = new TreeMap<Date, String>();
		}
		holidayMap.put(date, name);
	}
	
	/**
	 * 振替休日追加
	 */
	private void addSubstituteDate() {
		Calendar cal = getCalendar();
		Set<Date> keySet = holidayMap.keySet();
		TreeMap<Date, String> addMap = new TreeMap<Date, String>();
		Date formerHoliday = null;
		for (Date date : keySet) {
			// 第三条第二項
			// 「国民の祝日」が日曜日に当たるときは、その日後においてその日に最も近い「国民の祝日」でない日を休日とする。 
			// 日曜だった場合、最も近い「国民の祝日」でない日に「振替休日」を追加する。
			if (isDayOfWeek(date, Calendar.SUNDAY)) {
				cal.setTime(date);
				cal.add(Calendar.DAY_OF_MONTH, 1);
				if (year > YEAR_2006) {
					// 既に登録されている休日と重複した場合、一日ずらす。
					while (holidayMap.containsKey(cal.getTime())) {
						cal.add(Calendar.DAY_OF_MONTH, 1);
					}
				}
				if (!holidayMap.containsKey(cal.getTime())) {
					addMap.put(cal.getTime(), FURIKAE_NAME);
				}
			}
			// 第三条第三項
			// その前日及び翌日が「国民の祝日」である日（「国民の祝日」でない日に限る。）は、休日とする。 
			if (formerHoliday != null) {
				cal.setTime(formerHoliday);
				cal.add(Calendar.DAY_OF_MONTH, 2);
				if (cal.getTime().compareTo(date) == 0) {
					cal.add(Calendar.DAY_OF_MONTH, -1);
					addMap.put(cal.getTime(), KOKUMIN_NO_SYUKUJITU_NAME);
				}
			}
			formerHoliday = date;
		}
		holidayMap.putAll(addMap);
	}
	
	/**
	 * 配列変換
	 */
	private void convertMapToArray() {
		if (holidayMap != null) {
			Set<Date> dateSet = holidayMap.keySet();
			arySyuku = new String[dateSet.size()][2];
			int i = 0;
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
			for (Date date : dateSet) {
				arySyuku[i][0] = sdf.format(date);
				arySyuku[i][1] = holidayMap.get(date);
				i++;
			}
		}
	}
	
	/**
	 * 成人の日
	 * @param year 対象年
	 * @return	成人の日(1月第2月曜日)
	 * <p>
	 * 1999年以前は(1月15日)
	 * </p>
	 */
	public static Date getSeijinDate(int year) {
		Calendar cal = getCalendar();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 0);
		if (year < YEAR_2000) {
			cal.set(Calendar.DAY_OF_MONTH, 15);
		} else {
			cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
			cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, 2);
		}
		return cal.getTime();
	}
	
	/**
	 * 海の日
	 * @param year  対象年
	 * @return	海の日(7月第3月曜日)
	 * <p>
	 * 2002年以前は(7月20日)
	 * </p>
	 */
	public static Date getUmiDate(int year) {
		Calendar cal = getCalendar();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 7 - 1);
		if (year < YEAR_2003) {
			cal.set(Calendar.DAY_OF_MONTH, 20);
		} else {
			cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
			cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, 3);
		}
		return cal.getTime();
	}
	
	/**
	 * 敬老の日
	 * @param year 対象年
	 * @return	敬老の日(9月第3月曜日)
	 * <p>
	 * 2002年以前は(9月15日)
	 * </p>
	 */
	public static Date getKeirouDate(int year) {
		Calendar cal = getCalendar();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 9 - 1);
		if (year < YEAR_2003) {
			cal.set(Calendar.DAY_OF_MONTH, 15);
		} else {
			cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
			cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, 3);
		}
		return cal.getTime();
	}
	
	/**
	 * 体育の日
	 * @param year  対象年
	 * @return	体育の日(10月第2月曜日)
	 * <p>
	 * 1999年以前は(10月10日)
	 * </p>
	 */
	public static Date getTaiikuDate(int year) {
		Calendar cal = getCalendar();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 10 - 1);
		if (year < YEAR_2000) {
			cal.set(Calendar.DAY_OF_MONTH, 10);
		} else {
			cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
			cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, 2);
		}
		return cal.getTime();
	}
	
	/**
	 * 春分の日
	 * @param year  対象年
	 * @return	春分の日(3月xx日)
	 */
	public static Date getSyunbunDate(int year) {
		Calendar cal = getCalendar();
		final double param1 = 21.4471d;
		final double param2 = 0.242377d;
		final double param3 = 4.0d;
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 3 - 1);
		cal.set(Calendar.DAY_OF_MONTH, Double.valueOf(
				param1 + (param2 * (year - YEAR_1900)) - Math.floor((year - YEAR_1900) / param3)).intValue());
		return cal.getTime();
	}
	
	/**
	 *  秋分の日
	 * @param year  対象年
	 *  @return 秋分の日(9月xx日)
	 */
	public static Date getSyuubunDate(int year) {
		Calendar cal = getCalendar();
		final double param1 = 23.8896d;
		final double param2 = 0.242032d;
		final double param3 = 4.0d;
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 9 - 1);
		cal.set(Calendar.DAY_OF_MONTH, Double.valueOf(
				param1 + (param2 * (year - YEAR_1900)) - Math.floor((year - YEAR_1900) / param3)).intValue());
		return cal.getTime();
	}
	
	/**
	 * 元日
	 * @param year  対象年
	 * @return	元日(1月1日)
	 */
	public static Date getGanjitsuDate(int year) {
		Calendar cal = getCalendar();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 1 - 1);
		cal.set(Calendar.DAY_OF_MONTH, 1);
		return cal.getTime();
	}
	
	/**
	 * 建国記念の日
	 * @param year  対象年
	 * @return	建国記念の日(2月11日)
	 */
	public static Date getKenkokuKinenDate(int year) {
		Calendar cal = getCalendar();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 2 - 1);
		cal.set(Calendar.DAY_OF_MONTH, 11);
		return cal.getTime();
	}
	
	/**
	 * 憲法記念日
	 * @param year  対象年
	 * @return	憲法記念日(5月3日)
	 */
	public static Date getKenpouKinenDate(int year) {
		Calendar cal = getCalendar();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 5 - 1);
		cal.set(Calendar.DAY_OF_MONTH, 3);
		return cal.getTime();
	}
	
	/**
	 * こどもの日
	 * @param year  対象年
	 * @return	こどもの日(5月5日)
	 */
	public static Date getKodomoDate(int year) {
		Calendar cal = getCalendar();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 5 - 1);
		cal.set(Calendar.DAY_OF_MONTH, 5);
		return cal.getTime();
	}
	
	/**
	 * 文化の日
	 * @param year  対象年
	 * @return	文化の日(11月3日)
	 */
	public static Date getBunkaDate(int year) {
		Calendar cal = getCalendar();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 11 - 1);
		cal.set(Calendar.DAY_OF_MONTH, 3);
		return cal.getTime();
	}
	
	/**
	 * 勤労感謝の日
	 * @param year  対象年
	 * @return	勤労感謝の日(11月23日)
	 */
	public static Date getKinrouKansyaDate(int year) {
		Calendar cal = getCalendar();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 11 - 1);
		cal.set(Calendar.DAY_OF_MONTH, 23);
		return cal.getTime();
	}
	
	/**
	 * 天皇誕生日
	 * @param year  対象年
	 * @return	天皇誕生日(12月23日)
	 */
	public static Date getTennouTanjouDate(int year) {
		Calendar cal = getCalendar();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 12 - 1);
		cal.set(Calendar.DAY_OF_MONTH, 23);
		return cal.getTime();
	}
	
	/**
	 * 昭和の日
	 * @param year  対象年
	 * @return	昭和の日(4月29日)
	 */
	public static Date getSyouwaDate(int year) {
		final int day = 29;
		Calendar cal = getCalendar();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 4 - 1);
		cal.set(Calendar.DAY_OF_MONTH, day);
		return cal.getTime();
	}
	
	/**
	 * みどりの日
	 * @param year  対象年
	 * @return	みどりの日(5月4日)
	 */
	public static Date getMidoriDate(int year) {
		Calendar cal = getCalendar();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 5 - 1);
		cal.set(Calendar.DAY_OF_MONTH, 4);
		return cal.getTime();
	}
	
}
