package org.itscool.commons.util;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;

/**
 * 月別カレンダー情報保持テーブル
 */
public class MonthTable {
    /** DateElementリスト */
    private HashMap days = new HashMap();
    /** 年 */
    private int year;
    /** 月 */
    private int month;
    /** 最大日数 */
    private int maxday;
    
    public Date toMonth(){
        Calendar cal = Calendar.getInstance();
        cal.clear();
        cal.set(Calendar.YEAR, year);
        cal.set(Calendar.MONTH, month-1);
        Date date = cal.getTime();
        return date;
    }
    
    /**
     * 月別カレンダーテーブルを作成します
     */
    public MonthTable(){
        Calendar cal = Calendar.getInstance();
        
        this.year = cal.get(Calendar.YEAR);
        this.month = cal.get(Calendar.MONTH) + 1;
        init(year, month);
    }
    
    public MonthTable(Calendar cal){
        this.year = cal.get(Calendar.YEAR);
        this.month = cal.get(Calendar.MONTH) + 1;
        init(year, month);
    }
    
    public MonthTable(Date date){
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        this.year = cal.get(Calendar.YEAR);
        this.month = cal.get(Calendar.MONTH) + 1;
        init(year, month);
    }
    
    /**
     * 月別カレンダーテーブルを作成します
     * @param year 年
     * @param month 月
     */
    public MonthTable(int year, int month){
        init(year, month);
    }
    
    /**
     * 月別カレンダーテーブル作成のための初期化関数です
     * @param year 年
     * @param month 月
     */
    public void init(int year, int month){
        Calendar cal = Calendar.getInstance();
        if( year > 0 ){
            cal.clear();
            cal.set(Calendar.YEAR, year);
            cal.set(Calendar.MONTH, month-1);
            this.year = year;
            this.month = month;
        }else{
            this.year = cal.get(Calendar.YEAR);
            this.month = cal.get(Calendar.MONTH) + 1;
        }
        
        //当月の日数を取得
        this.maxday = DateFormatUtil.getMaxDay(cal);
        cal.set(Calendar.DAY_OF_MONTH, 1);
        //先月データの作成
        int yobi = cal.get(Calendar.DAY_OF_WEEK);
        if( yobi != Calendar.SUNDAY){
            cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
            for(int i=Calendar.SUNDAY; i<yobi; i++, cal.add(Calendar.DAY_OF_MONTH, 1)){
                addDay(new DateElement(cal, false));
            }
        }
        //当月データの作成
        for(int i=0; i<maxday; i++){
            addDay(new DateElement(cal, false));
            cal.add(Calendar.DAY_OF_MONTH, 1);
        }
        //当月祝日設定
        initHoliday(getYear(), getMonth());
        //来月データの作成
        cal.add(Calendar.DAY_OF_MONTH, -1);
        yobi = cal.get(Calendar.DAY_OF_WEEK);
        if( yobi != Calendar.SATURDAY ){
            for(int i=yobi; i<=(Calendar.SATURDAY); i++, cal.add(Calendar.DAY_OF_MONTH, 1)){
                addDay(new DateElement(cal, false));
            }
        }
        
    }
    
    /**
     * 祝日情報をセットします
     * @param year 年
     * @param month 月
     */
    public void initHoliday(int year, int month){
        switch(month){
            case 1:
                //元日
                addDay(new DateElement(year, 1, 1, true));
                //成人の日（１月第２月曜日
                DateElement holiday2 = getHoliday(year, 1, 2, Calendar.MONDAY);
                addDay(holiday2);
                break;
            case 3:
                //春分の日
                DateElement holiday3 = getHolidayEx(year, 3, 20.69115);
                addDay(holiday3);
                break;
            case 4:
                addDay(new DateElement(year, 4, 29, true));	//みどりの日
                break;
            case 5:
                addDay(new DateElement(year, 5, 3, true));	//憲法記念日
                addDay(new DateElement(year, 5, 4, true));	//国民の休日
                addDay(new DateElement(year, 5, 5, true));	//こどもの日
                break;
            case 7:
                //海の日（７月第３月曜日）
                DateElement holiday7 = getHoliday(year, 7, 3, Calendar.MONDAY);
                addDay(holiday7);
                break;
            case 9:
                //敬老の日（９月第３月曜日）
                DateElement holiday9 = getHoliday(year, 9, 3, Calendar.MONDAY);
                addDay(holiday9);
                //秋分の日
                DateElement holiday9_2 = getHolidayEx(year, 9, 23.09);
                addDay(holiday9_2);
                break;
            case 10:
                //体育の日（１０月第２月曜日）
                DateElement holiday10 = getHoliday(year, 10, 2, Calendar.MONDAY);
                addDay(holiday10);
                break;
            case 11:
                addDay(new DateElement(year, 11, 3, true));	//文化の日
                addDay(new DateElement(year, 11, 23, true));	//勤労感謝の日
                break;
            case 12:
                addDay(new DateElement(year, 12, 23, true));	//天皇の誕生日
                break;
        }
        //振り替え休日の設定
        for(int i=0; i<31; i++){
            DateElement day = getDay(year, month, i+1);
            if( day == null ) break;
            if( !day.isHoliday() )continue;
            if( day.getYobi() != Calendar.SUNDAY ) continue;
            DateElement monday = getDay(year, month, i+2);
            if( monday == null ) break;
            monday.setHoliday(true);
        }
    }
    
    /**
     * 祝日情報を取得します
     * @param year 年
     * @param month 月
     * @param weekOfMonth 当月の何週目かを指定します
     * @param youbi 曜日
     * @return DateElementインスタンスを返します
     */
    public static DateElement getHoliday(int year, int month, int weekOfMonth, int youbi){
        Calendar cal = new GregorianCalendar();
        cal.clear();
        cal.set(Calendar.YEAR, year);
        cal.set(Calendar.MONTH, month-1);
        //cal.set(Calendar.WEEK_OF_MONTH, weekOfMonth);
        cal.set(Calendar.DAY_OF_WEEK, youbi);
        cal.add(Calendar.WEEK_OF_MONTH, weekOfMonth-1);
        int tmpYear = cal.get(Calendar.YEAR);
        int tmpMonth = cal.get(Calendar.MONDAY) + 1;
        int tmpDay = cal.get(Calendar.DAY_OF_MONTH);
        
        return new DateElement(tmpYear, tmpMonth, tmpDay, true);
    }
    
    /**
     * 秋分、春分の日を計算し、指定祝日の日付を取得します<BR>
     * throwDay = 20.69115;	//2000年の太陽の春分点通過日<BR>
     * throwDay = 23.09;	//2000年の太陽の秋分点通過日<BR>
     * @param year 年
     * @param month 月
     * @param throwDay 春分(秋分)点通過日
     * @return DateElementインスタンスを返します
     */
    public static DateElement getHolidayEx(int year, int month, double throwDay){
        //１年ごとの春分点通過日の移動量
        double moveQt = (double)((year-2000) * 0.2421904);
        //閏年によるリセット量
        int resetQt = (int)((year-2000) / 4);
        //求める年の春分日の計算
        int day = (int)(throwDay+moveQt-resetQt);
        
        return new DateElement(year, month, day, true);
    }
    
    /**
     * 日付情報をセットします
     * @param day DateElementインスタンス
     */
    public void addDay(DateElement day){
        StringBuffer key = new StringBuffer();
        key.append(day.getYear());
        key.append(day.getMonth());
        key.append(day.getDay());
        days.put(key.toString(), day);
    }
    
    /**
     * 日付情報を取得します
     * @param year 年
     * @param month 月
     * @param day 日
     * @return DateElementインスタンス
     */
    public DateElement getDay(int year, int month, int day){
        StringBuffer key = new StringBuffer();
        key.append(year);
        key.append(month);
        key.append(day);
        DateElement date = (DateElement)days.get(key.toString());
        return date;
    }
    
    /**
     * 日付を取得します
     * @param cal Calendarインスタンス
     * @return DateElementインスタンス
     */
    public DateElement getDay(Calendar cal){
        int tmpYear = cal.get(Calendar.YEAR);
        int tmpMonth = cal.get(Calendar.MONTH)+1;
        int tmpDay = cal.get(Calendar.DAY_OF_MONTH);
        
        DateElement date = getDay(tmpYear, tmpMonth, tmpDay);
        return date;
    }
    
    /**
     * 日付情報リストを取得します
     * @return DateElementのリストをHashMapで取得します
     */
    public HashMap getDays() {
        return days;
    }
    
    /**
     * 当月の最大日数を取得します
     * @return 最大日数
     */
    public int getMaxday() {
        return maxday;
    }
    
    /**
     * 年を取得します
     * @return 年
     */
    public int getYear() {
        return year;
    }
    
    /**
     * 年をセットします
     * @param year 年
     */
    public void setYear(int year) {
        this.year = year;
    }
    
    /**
     * 月を取得します
     * @return 月
     */
    public int getMonth() {
        return month;
    }
    
    /**
     * 月をセットします
     * @param month 月
     */
    public void setMonth(int month) {
        this.month = month;
    }
}