/*
 * 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;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.TimeZone;

import jp.sourceforge.glad.calendar.era.JapaneseEra;
import jp.sourceforge.glad.calendar.era.JapaneseEras;
import jp.sourceforge.glad.calendar.text.ISODateFormat;

/**
 * JIS X 0301 に準拠した Calendar のラッパーです。
 * 
 * @author GLAD!!
 */
public class JISCalendar extends ISOCalendar {

    private static final long serialVersionUID = 4111189987351258075L;

    /**
     * 現在日時を使用してカレンダーを構築します。
     */
    public JISCalendar() {
    }

    /**
     * 現在日時を使用してカレンダーを構築します。
     * 
     * @param zone タイムゾーン
     */
    public JISCalendar(TimeZone zone) {
        super(zone);
    }

    /**
     * 現在年と、指定された月日を使用してカレンダーを構築します。
     * 
     * @param month 月
     * @param day   日
     */
    public JISCalendar(int month, int day) {
        super(month, day);
    }

    /**
     * 指定された年月日を使用してカレンダーを構築します。
     * 
     * @param year  年
     * @param month 月
     * @param day   日
     */
    public JISCalendar(int year, int month, int day) {
        super(year, month, day);
    }

    /**
     * 指定された年月日、時分を使用してカレンダーを構築します。
     * 
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   時
     * @param minute 分
     */
    public JISCalendar(
            int year, int month, int day,
            int hour, int minute) {
        super(year, month, day, hour, minute);
    }

    /**
     * 指定された年月日、時分秒を使用してカレンダーを構築します。
     * 
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   時
     * @param minute 分
     * @param second 秒
     */
    public JISCalendar(
            int year, int month, int day,
            int hour, int minute, int second) {
        super(year, month, day, hour, minute, second);
    }

    /**
     * 指定された年月日、時分秒、ミリ秒を使用してカレンダーを構築します。
     * 
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   時
     * @param minute 分
     * @param second 秒
     * @param millis ミリ秒
     */
    public JISCalendar(
            int year, int month, int day,
            int hour, int minute, int second, int millis) {
        super(year, month, day, hour, minute, second, millis);
    }

    /**
     * 基準時点からのミリ秒数を使用してカレンダーを構築します。
     * 
     * @param timeInMillis 基準時点からのミリ秒数
     */
    public JISCalendar(long timeInMillis) {
        super(timeInMillis);
    }

    /**
     * 基準時点からのミリ秒数を使用してカレンダーを構築します。
     * 
     * @param timeInMillis 基準時点からのミリ秒数
     * @param zone タイムゾーン
     */
    public JISCalendar(long timeInMillis, TimeZone zone) {
        super(timeInMillis, zone);
    }

    /**
     * java.util.Calendar を使用してカレンダーを構築します。
     * 
     * @param calendar java.util.Calendar
     */
    public JISCalendar(Calendar calendar) {
        super(calendar);
    }

    /**
     * java.util.Date を使用してカレンダーを構築します。
     * 
     * @param date java.util.Date
     */
    public JISCalendar(java.util.Date date) {
        super(date);
    }

    /**
     * 時点 (Instant) を使用してカレンダーを構築します。
     * 
     * @param instant 時点
     */
    public JISCalendar(Instant instant) {
        super(instant);
    }

    // ---- Japanese Era

    /**
     * 元号を返します。
     * 
     * @return 元号
     */
    @Override
    public int getEra() {
        return JapaneseEras.getInstance().getEraId(calendar);
    }

    /**
     * 元号を設定します。
     * <p>
     * 元の値と異なる場合は、年月日が適用開始日にリセットされます。
     * 
     * @param era 元号
     * @return JISCalendar
     */
    public JISCalendar setEra(int era) {
        if (getEra() != era) {
            JapaneseEra japaneseEra = JapaneseEra.getInstance(era);
            int year  = japaneseEra.getSinceYear();
            int month = japaneseEra.getSinceMonth();
            int day   = japaneseEra.getSinceDay();
            setDate(year, month, day);
        }
        return this;
    }

    /**
     * 明治以前か判定します。
     * 
     * @return 明治以前ならば true
     */
    public boolean isBeforeMeiji() {
        return getEra() == CalendarConsts.BEFORE_MEIJI;
    }

    /**
     * 明治か判定します。
     * 
     * @return 明治ならば true
     */
    public boolean isMeiji() {
        return getEra() == CalendarConsts.MEIJI;
    }

    /**
     * 大正か判定します。
     * 
     * @return 大正ならば true
     */
    public boolean isTaisho() {
        return getEra() == CalendarConsts.TAISHO;
    }

    /**
     * 昭和か判定します。
     * 
     * @return 昭和ならば true
     */
    public boolean isShowa() {
        return getEra() == CalendarConsts.SHOWA;
    }

    /**
     * 平成か判定します。
     * 
     * @return 平成ならば true
     */
    public boolean isHeisei() {
        return getEra() == CalendarConsts.HEISEI;
    }

    // ---- Year of Era

    /**
     * 和暦年を返します。
     * 
     * @return 和暦年
     */
    @Override
    public int getYearOfEra() {
        return JapaneseEras.getInstance().getYearOfEra(calendar);
    }

    /**
     * 和暦年を設定します。
     * 
     * @param yearOfEra 和暦年
     */
    @Override
    public JISCalendar setYearOfEra(int yearOfEra) {
        setYearOfEra(getEra(), yearOfEra);
        return this;
    }

    /**
     * 和暦年を設定します。
     * 
     * @param era 元号
     * @param yearOfEra 和暦年
     */
    @Override
    public JISCalendar setYearOfEra(int era, int yearOfEra) {
        setYear(getGregorianYear(era, yearOfEra));
        return this;
    }

    // ---- Gregorian Year

    /**
     * グレゴリア暦で年を返します。
     * 
     * @return グレゴリア暦の年
     */
    public static int getGregorianYear(int era, int yearOfEra) {
        return JapaneseEras.getInstance().getGregorianYear(era, yearOfEra);
    }

    // ---- Date

    /**
     * 和暦で日付を設定します。
     * 
     * @param era 元号
     * @param yearOfEra 元号の年
     * @param month 月
     * @param day   日
     * @return JISCalendar
     */
    public JISCalendar setDateOfEra(
            int era, int yearOfEra, int month, int day) {
        setDate(getGregorianYear(era, yearOfEra), month, day);
        return this;
    }

    // ---- extends ISOCalendar

    // ---- clear

    /**
     * 値をクリアします。
     * 
     * @return JISCalendar
     */
    @Override
    public JISCalendar clear() {
        super.clear();
        return this;
    }

    // ---- Year

    /**
     * 年を設定します。
     * 
     * @param year 年
     * @return JISCalendar
     */
    @Override
    public JISCalendar setYear(int year) {
        super.setYear(year);
        return this;
    }

    /**
     * 年を設定します。
     * 
     * @param year 年
     * @param correction 日付が存在しない場合の補正方法
     * @return JISCalendar
     */
    @Override
    public JISCalendar setYear(int year, CorrectionType correction) {
        super.setYear(year, correction);
        return this;
    }

    /**
     * 年を加算します。
     * 
     * @param years 年数
     * @return JISCalendar
     */
    @Override
    public JISCalendar addYears(int years) {
        super.addYears(years);
        return this;
    }

    /**
     * 年を加算します。
     * 
     * @param years 年数
     * @param correction 日付が存在しない場合の補正方法
     * @return JISCalendar
     */
    @Override
    public JISCalendar addYears(int years, CorrectionType correction) {
        super.addYears(years, correction);
        return this;
    }

    /**
     * 暦週年を設定します。
     * 
     * @param weekYear
     * @return JISCalendar
     */
    @Override
    public JISCalendar setWeekYear(int weekYear) {
        super.setWeekYear(weekYear);
        return this;
    }

    // ---- Month

    /**
     * 月を設定します。
     * 
     * @param month 月 (1: 1月、2: 2月、...、12: 12月)
     * @return JISCalendar
     */
    @Override
    public JISCalendar setMonth(int month) {
        super.setMonth(month);
        return this;
    }

    /**
     * 月を設定します。
     * 
     * @param month 月 (1: 1月、2: 2月、...、12: 12月)
     * @param correction
     * @return JISCalendar
     */
    @Override
    public JISCalendar setMonth(int month, CorrectionType correction) {
        super.setMonth(month, correction);
        return this;
    }

    /**
     * 月を加算します。
     * 
     * @param months 月数
     * @return JISCalendar
     */
    @Override
    public JISCalendar addMonths(int months) {
        super.addMonths(months);
        return this;
    }

    /**
     * 月を加算します。
     * 
     * @param months 月数
     * @param correction 日付が存在しない場合の補正方法
     * @return JISCalendar
     */
    @Override
    public JISCalendar addMonths(int months, CorrectionType correction) {
        super.addMonths(months, correction);
        return this;
    }

    /**
     * 年の月を設定します。
     * 
     * @param monthOfYear 年の月
     * @return JISCalendar
     */
    @Override
    public JISCalendar setMonthOfYear(int monthOfYear) {
        super.setMonthOfYear(monthOfYear);
        return this;
    }

    // ---- Week

    /**
     * 週を加算します。
     * 
     * @param weeks 週数
     * @return JISCalendar
     */
    @Override
    public JISCalendar addWeeks(int weeks) {
        super.addWeeks(weeks);
        return this;
    }

    /**
     * 年の週を設定します。
     * 
     * @param weekOfYear 年の週
     * @return JISCalendar
     */
    @Override
    public JISCalendar setWeekOfYear(int weekOfYear) {
        super.setWeekOfYear(weekOfYear);
        return this;
    }

    // ---- Day

    /**
     * 日を設定します。
     * 
     * @param day 日
     * @return JISCalendar
     */
    @Override
    public JISCalendar setDay(int day) {
        super.setDay(day);
        return this;
    }

    /**
     * 日を加算します。
     * 
     * @param days 日数
     * @return JISCalendar
     */
    @Override
    public JISCalendar addDays(int days) {
        super.addDays(days);
        return this;
    }

    /**
     * 年間通算日を設定します。
     * 
     * @param dayOfYear 年間通算日
     * @return JISCalendar
     */
    @Override
    public JISCalendar setDayOfYear(int dayOfYear) {
        super.setDayOfYear(dayOfYear);
        return this;
    }

    /**
     * 年の最後の日に設定します。
     * 
     * @return JISCalendar
     */
    @Override
    public JISCalendar setLastDayOfYear() {
        super.setLastDayOfYear();
        return this;
    }

    /**
     * 月の日を設定します。
     * 
     * @param dayOfMonth 月の日
     * @return JISCalendar
     */
    @Override
    public JISCalendar setDayOfMonth(int dayOfMonth) {
        super.setDayOfMonth(dayOfMonth);
        return this;
    }

    /**
     * 月の最後の日に設定します。
     * 
     * @return JISCalendar
     */
    @Override
    public JISCalendar setLastDayOfMonth() {
        super.setLastDayOfMonth();
        return this;
    }

    // ---- Day of Week

    /**
     * 曜日を設定します。
     * 
     * @param dayOfWeek 曜日
     * @return JISCalendar
     */
    @Override
    public JISCalendar setDayOfWeek(int dayOfWeek) {
        super.setDayOfWeek(dayOfWeek);
        return this;
    }

    // ---- Hour

    /**
     * 時を設定します。
     * 
     * @param hour 時
     * @return JISCalendar
     */
    @Override
    public JISCalendar setHour(int hour) {
        super.setHour(hour);
        return this;
    }

    /**
     * 時を加算します。
     * 
     * @param hours 時数
     * @return JISCalendar
     */
    @Override
    public JISCalendar addHours(int hours) {
        super.addHours(hours);
        return this;
    }

    /**
     * 日の時を設定します。
     * 
     * @param hourOfDay 日の時
     * @return JISCalendar
     */
    @Override
    public JISCalendar setHourOfDay(int hourOfDay) {
        super.setHourOfDay(hourOfDay);
        return this;
    }

    // ---- Minute

    /**
     * 分を設定します。
     * 
     * @param minute 分
     * @return JISCalendar
     */
    @Override
    public JISCalendar setMinute(int minute) {
        super.setMinute(minute);
        return this;
    }

    /**
     * 分を加算します。
     * 
     * @param minutes 分数
     * @return JISCalendar
     */
    @Override
    public JISCalendar addMinutes(int minutes) {
        super.addMinutes(minutes);
        return this;
    }

    /**
     * 日の分を設定します。
     * 
     * @param minuteOfDay 日の分
     * @return JISCalendar
     */
    @Override
    public JISCalendar setMinuteOfDay(int minuteOfDay) {
        super.setMinuteOfDay(minuteOfDay);
        return this;
    }

    /**
     * 時の分を設定します。
     * 
     * @param minuteOfHour 日の分
     * @return JISCalendar
     */
    @Override
    public JISCalendar setMinuteOfHour(int minuteOfHour) {
        super.setMinuteOfHour(minuteOfHour);
        return this;
    }

    // ---- Second

    /**
     * 秒を設定します。
     * 
     * @param second 秒
     * @return JISCalendar
     */
    @Override
    public JISCalendar setSecond(int second) {
        super.setSecond(second);
        return this;
    }

    /**
     * 秒を加算します。
     * 
     * @param seconds 秒数
     * @return JISCalendar
     */
    @Override
    public JISCalendar addSeconds(int seconds) {
        super.addSeconds(seconds);
        return this;
    }

    /**
     * 日の秒を設定します。
     * 
     * @param secondOfDay 日の秒
     * @return JISCalendar
     */
    @Override
    public JISCalendar setSecondOfDay(int secondOfDay) {
        super.setSecondOfDay(secondOfDay);
        return this;
    }

    /**
     * 時の秒を設定します。
     * 
     * @param secondOfHour
     * @return JISCalendar
     */
    @Override
    public JISCalendar setSecondOfHour(int secondOfHour) {
        super.setSecondOfHour(secondOfHour);
        return this;
    }

    /**
     * 分の秒を設定します。
     * 
     * @param secondOfMinute 分の秒
     * @return JISCalendar
     */
    @Override
    public JISCalendar setSecondOfMinute(int secondOfMinute) {
        super.setSecondOfMinute(secondOfMinute);
        return this;
    }

    // ---- Millisecond

    /**
     * ミリ秒を設定します。
     * 
     * @param millis ミリ秒
     * @return JISCalendar
     */
    @Override
    public JISCalendar setMillis(int millis) {
        super.setMillis(millis);
        return this;
    }

    /**
     * ミリ秒を加算します。
     * 
     * @param millis ミリ秒
     * @return JISCalendar
     */
    @Override
    public JISCalendar addMillis(int millis) {
        super.addMillis(millis);
        return this;
    }

    /**
     * 日のミリ秒を設定します。
     * 
     * @param millisOfDay 日のミリ秒
     * @return JISCalendar
     */
    @Override
    public JISCalendar setMillisOfDay(int millisOfDay) {
        super.setMillisOfDay(millisOfDay);
        return this;
    }

    /**
     * 時のミリ秒を返します。
     * 
     * @param millisOfHour 時のミリ秒
     * @return JISCalendar
     */
    @Override
    public JISCalendar setMillisOfHour(int millisOfHour) {
        super.setMillisOfHour(millisOfHour);
        return this;
    }

    /**
     * 分のミリ秒を設定します。
     * 
     * @param millisOfMinute 分のミリ秒
     * @return JISCalendar
     */
    @Override
    public JISCalendar setMillisOfMinute(int millisOfMinute) {
        super.setMillisOfMinute(millisOfMinute);
        return this;
    }

    /**
     * 秒のミリ秒を設定します。
     * 
     * @param millisOfSecond 秒のミリ秒
     * @return JISCalendar
     */
    @Override
    public JISCalendar setMillisOfSecond(int millisOfSecond) {
        super.setMillisOfSecond(millisOfSecond);
        return this;
    }

    // ---- Date (YMD)

    /**
     * 日付をまとめて設定します。
     * 
     * @param month 月
     * @param day   日
     * @return JISCalendar
     */
    @Override
    public JISCalendar setDate(int month, int day) {
        super.setDate(month, day);
        return this;
    }

    /**
     * 日付をまとめて設定します。
     * 
     * @param year  年
     * @param month 月
     * @param day   日
     * @return JISCalendar
     */
    @Override
    public JISCalendar setDate(int year, int month, int day) {
        super.setDate(year, month, day);
        return this;
    }

    /**
     * 年間通算日をまとめて設定します。
     * 
     * @param year 年
     * @param dayOfYear 年の日
     * @return JISCalendar
     */
    @Override
    public JISCalendar setOrdinalDate(int year, int dayOfYear) {
        super.setOrdinalDate(year, dayOfYear);
        return this;
    }

    /**
     * 暦週日付をまとめて設定します。
     * 
     * @param weekYear 年
     * @param weekOfYear 年の週
     * @param dayOfWeek  週の日
     * @return JISCalendar
     */
    @Override
    public JISCalendar setWeekDate(
            int weekYear, int weekOfYear, int dayOfWeek) {
        super.setWeekDate(weekYear, weekOfYear, dayOfWeek);
        return this;
    }

    // ---- Time (HMS)

    /**
     * 時刻をまとめて設定します。
     * 
     * @param hour   時
     * @param minute 分
     * @return JISCalendar
     */
    @Override
    public JISCalendar setTime(int hour, int minute) {
        super.setTime(hour, minute);
        return this;
    }

    /**
     * 時刻をまとめて設定します。
     * 
     * @param hour   時
     * @param minute 分
     * @param second 秒
     * @return JISCalendar
     */
    @Override
    public JISCalendar setTime(int hour, int minute, int second) {
        super.setTime(hour, minute, second);
        return this;
    }

    /**
     * 時刻をまとめて設定します。
     * 
     * @param hour   時
     * @param minute 分
     * @param second 秒
     * @param millis ミリ秒
     * @return JISCalendar
     */
    @Override
    public JISCalendar setTime(int hour, int minute, int second, int millis) {
        super.setTime(hour, minute, second, millis);
        return this;
    }

    // ---- DateTime (YMDHMS)

    /**
     * 日時をまとめて設定します。
     * 
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   時
     * @param minute 分
     * @return JISCalendar
     */
    @Override
    public JISCalendar setDateTime(
            int year, int month, int day,
            int hour, int minute) {
        super.setDateTime(year, month, day, hour, minute);
        return this;
    }

    /**
     * 日時をまとめて設定します。
     * 
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   時
     * @param minute 分
     * @param second 秒
     * @return JISCalendar
     */
    @Override
    public JISCalendar setDateTime(
            int year, int month, int day,
            int hour, int minute, int second) {
        super.setDateTime(year, month, day, hour, minute, second);
        return this;
    }

    /**
     * 日時をまとめて設定します。
     * 
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   時
     * @param minute 分
     * @param second 秒
     * @param millis ミリ秒
     * @return JISCalendar
     */
    @Override
    public JISCalendar setDateTime(
            int year, int month, int day,
            int hour, int minute, int second, int millis) {
        super.setDateTime(year, month, day, hour, minute, second, millis);
        return this;
    }

    // ---- TimeInMillis

    /**
     * 基準時点からの通算ミリ秒を設定します。
     * 
     * @param timeInMillis 通算ミリ秒
     * @return JISCalendar
     */
    @Override
    public JISCalendar setTimeInMillis(long timeInMillis) {
        super.setTimeInMillis(timeInMillis);
        return this;
    }

    // ---- TimeZone

    /**
     * タイムゾーンを設定します。
     * 
     * @param zone タイムゾーン
     * @return JISCalendar
     */
    @Override
    public JISCalendar setTimeZone(TimeZone zone) {
        super.setTimeZone(zone);
        return this;
    }

    /**
     * 指定された ID のタイムゾーンを設定します。
     * 
     * @param id タイムゾーンID
     * @return JISCalendar
     */
    @Override
    public JISCalendar setTimeZone(String id) {
        super.setTimeZone(id);
        return this;
    }

    /**
     * オフセットでタイムゾーンを設定します。
     * 
     * @param offset オフセット (ミリ秒)
     * @return JISCalendar
     */
    @Override
    public JISCalendar setTimeZone(int offset) {
        super.setTimeZone(offset);
        return this;
    }

    /**
     * オフセットでタイムゾーンを設定します。
     * 
     * @param signum  符号
     * @param hours   時数
     * @param minutes 分数
     * @return JISCalendar
     */
    @Override
    public JISCalendar setTimeZone(int signum, int hours, int minutes) {
        super.setTimeZone(signum, hours, minutes);
        return this;
    }

    // ---- java.util.Calendar

    /**
     * Calendar を設定します。
     * 
     * @param calendar Calendar
     * @return JISCalendar
     */
    @Override
    public JISCalendar setCalendar(Calendar calendar) {
        super.setCalendar(calendar);
        return this;
    }

    // ---- java.util.Date

    /**
     * java.util.Date で値を設定します。
     * 
     * @param date java.util.Date
     * @return JISCalendar
     */
    @Override
    public JISCalendar setDate(java.util.Date date) {
        super.setDate(date);
        return this;
    }

    // ---- parse

    /**
     * 文字列をカレンダーとして解析します。
     * 
     * @param pattern パターン
     * @param s 文字列
     * @return カレンダー
     */
    public static JISCalendar parse(String pattern, String s) {
        ISODateFormat df = new ISODateFormat(pattern);
        df.setLenient(false);
        try {
            df.parse(s);
            return new JISCalendar(df.getCalendar());
        } catch (ParseException e) {
            throw newIllegalArgumentException(s, e);
        }
    }

    /**
     * 文字列をカレンダーとして解析します。
     * 
     * @param pattern パターン
     * @param s 文字列
     * @param zone タイムゾーン
     * @return カレンダー
     */
    public static JISCalendar parse(String pattern, String s, TimeZone zone) {
        DateFormat df = new ISODateFormat(pattern);
        df.setTimeZone(zone);
        df.setLenient(false);
        try {
            df.parse(s);
            return new JISCalendar(df.getCalendar());
        } catch (ParseException e) {
            throw newIllegalArgumentException(s, e);
        }
    }

    public static JISCalendar parseDate(String s) {
        return parse("yyyy-MM-dd", s);
    }

    public static JISCalendar parseTime(String s) {
        return parse("HH:mm:ss.SSS", s);
    }

    public static JISCalendar parseDateTime(String s) {
        return parse("yyyy-MM-dd'T'HH:mm:ss.SSS", s);
    }

}
