/*
 * Copyright (C) 2008-2009 GLAD!! (ITO Yoshiichi)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package jp.sourceforge.glad.calendar.holiday;

import java.io.Serializable;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jp.sourceforge.glad.calendar.ISOCalendar;

/**
 * 祝日。
 * 
 * @author GLAD!!
 */
public class Holiday implements Serializable {

    private static final long serialVersionUID = -5111281083956379738L;

    // ---- constants

    static final String RESOURCE_NAME =
            "jp/sourceforge/glad/calendar/holiday/holiday";

    static final String PREFIX = "holiday.";

    // ---- static fields

    static final ResourceBundle resources =
            ResourceBundle.getBundle(RESOURCE_NAME);

    // ---- fields

    /** 国コード (ISO 3166-1 alpha-2) */
    final String country;

    /** 名前 */
    final String name;

    /** 月 */
    final int month;

    /** 日 (固定日の場合) */
    final int dayOfMonth;

    /** 週 (週と曜日指定の場合) */
    final int weekOfMonth;

    /** 曜日 (週と曜日指定の場合) */
    final int dayOfWeek;

    /** 適用開始年 */
    final Integer startYear;

    /** 適用終了年 */
    final Integer endYear;

    /** 日付の指定 */
    private transient String date = null;

    // ---- constructors

    static final Pattern datePattern = Pattern.compile(
            "(?:([0-1]?[0-9])|[^0-9]+)-" +
            "(?:([0-3]?[0-9])|W([1-5])-([1-7])|[^0-9]+)");

    /**
     * オブジェクトを構築します。
     * 
     * @param country   国コード (ISO 3166-1 alpha-2)
     * @param name      名前
     * @param date      日付の指定
     * @param startYear 適用開始年
     * @param endYear   適用終了年
     */
    Holiday(String country, String name,
            String date, Integer startYear, Integer endYear) {
        checkAvailableYears(startYear, endYear);
        this.country = country;
        this.name = name;
        Matcher matcher = datePattern.matcher(date);
        if (!matcher.matches()) {
            throw new IllegalArgumentException("date=" + date);
        }
        this.month       = parseInt(matcher.group(1));
        this.dayOfMonth  = parseInt(matcher.group(2));
        this.weekOfMonth = parseInt(matcher.group(3));
        this.dayOfWeek   = parseInt(matcher.group(4));
        this.startYear = startYear;
        this.endYear   = endYear;
        this.date = date;
    }

    static void checkAvailableYears(Integer startYear, Integer endYear) {
        if (startYear == null || endYear == null) {
            return;
        }
        // 適用開始年 <= 適用終了年 であること。
        if (startYear.intValue() > endYear.intValue()) {
            throw new IllegalArgumentException(
                    "startYear=" + startYear + ", endYear=" + endYear);
        }
    }

    static int parseInt(String s) {
        if (s == null || s.length() == 0) {
            return 0;
        }
        return Integer.parseInt(s);
    }

    // ---- accessors

    /**
     * 国コードを返します。
     * 
     * @return 国コード (ISO 3166-1 alpha-2)
     */
    public String getCountry() {
        return country;
    }

    /**
     * 定義された生の名前を返します。
     * 
     * @return 名前
     */
    public String getRawName() {
        return name;
    }

    /**
     * 名前を返します。
     * 
     * @return 名前
     */
    public String getName() {
        return getString(PREFIX + country + '.' + name);
    }

    /**
     * 名前を返します。
     * 
     * @param locale ロケール
     * @return 名前
     */
    public String getName(Locale locale) {
        return getString(PREFIX + country + '.' + name, locale);
    }

    /**
     * 月を返します。
     * 
     * @return 月 (固定月でない場合は 0)
     */
    public int getMonth() {
        return month;
    }

    /**
     * 日を返します。
     * 
     * @return 日 (固定日でない場合は 0)
     */
    public int getDayOfMonth() {
        return dayOfMonth;
    }

    /**
     * 週を返します。
     * 
     * @return 週 (固定日の場合は 0)
     */
    public int getWeekOfMonth() {
        return weekOfMonth;
    }

    /**
     * 曜日を返します。
     * 
     * @return 曜日 (固定日の場合は 0)
     */
    public int getDayOfWeek() {
        return dayOfWeek;
    }

    /**
     * 適用開始年を返します。
     * 
     * @return 適用開始年
     */
    public Integer getStartYear() {
        return startYear;
    }

    /**
     * 適用終了年を返します。
     * 
     * @return 適用終了年
     */
    public Integer getEndYear() {
        return endYear;
    }

    // ---- other methods

    /**
     * 指定された年に、この祝日が有効か判定します。
     * 
     * @param year 年
     * @return 有効ならば true
     */
    public boolean isAvailableYear(int year) {
        if (startYear != null && year < startYear) {
            return false;
        }
        if (endYear != null && endYear < year) {
            return false;
        }
        return true;
    }

    /**
     * 日付を指定する文字列を返します。
     */
    String getDate() {
        if (date != null) {
            return date;
        }
        StringBuilder sb = new StringBuilder();
        if (month != 0) {
            sb.append(month / 10);
            sb.append(month % 10);
        } else {
            sb.append("??");
        }
        sb.append('-');
        if (dayOfMonth != 0) {
            sb.append(dayOfMonth / 10);
            sb.append(dayOfMonth % 10);
        } else if (weekOfMonth != 0) {
            sb.append('W');
            sb.append(weekOfMonth);
            sb.append('-');
            sb.append(dayOfWeek);
        } else {
            sb.append("??");
        }
        date = sb.toString();
        return date;
    }

     /**
      * 指定年の祝日の日付を ISOCalendar で返します。
      * 
      * @param year 年
      * @return ISOCalendar
      */
     ISOCalendar getISOCalendar(int year) {
         return getISOCalendar(year, null);
     }

     /**
     * 指定年の祝日の日付を ISOCalendar で返します。
     * 
     * @param year 年
     * @param zone カレンダーのタイムゾーン
     * @return ISOCalendar
     */
    ISOCalendar getISOCalendar(int year, TimeZone zone) {
        if (!isAvailableYear(year)) {
            throw new IllegalArgumentException("year=" + year);
        }
        if (dayOfMonth != 0) {
            // 固定日の場合。
            return new ISOCalendar(year, month, dayOfMonth, zone);
        }
        if (weekOfMonth != 0 && dayOfWeek != 0) {
            // 週と曜日指定の場合。
            ISOCalendar calendar = new ISOCalendar(year, month, 1, zone);
            int days = dayOfWeek - calendar.getDayOfWeek();
            if (days < 0) {
                days += 7;
            }
            days += 7* (weekOfMonth - 1);
            if (days > 0) {
                calendar.addDays(days);
            }
            return calendar;
        }
        throw new IllegalArgumentException("date=" + getDate());
    }

    static String getString(String key) {
        return resources.getString(key);
    }

    static String getString(String key, Locale locale) {
        if (locale == null) {
            return getString(key);
        }
        return ResourceBundle.getBundle(RESOURCE_NAME, locale).getString(key);
    }

    // ---- java.lang.Object

    /**
     * オブジェクトの文字列表現を返します。
     * 
     * @return 文字列表現
     */
    @Override
    public String toString() {
        return name + ": " + getDate();
    }

}
