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

import java.io.ObjectStreamException;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.ResourceBundle;

import jp.sourceforge.glad.calendar.CalendarConsts;

/**
 * 日本の元号。
 * 
 * @author GLAD!!
 */
public class JapaneseEra implements Serializable, Comparable<JapaneseEra> {

    private static final long serialVersionUID = -9089113753946132249L;

    // ---- constants

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

    static final String PREFIX = "japanese-era.";

    // ---- static fields

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

    // ---- fields

    /** ID */
    final int id;

    /** 名前 */
    final String name;

    /** 略称 */
    final String abbr;

    /** 適用開始日 (UTC 基準ミリ秒) */
    final long since;

    /** 適用開始年月日 */
    private transient int[] sinceYMD = null;

    // ---- constructors

    /**
     * オブジェクトを構築します。
     * 
     * @param id    ID
     * @param name  名前
     * @param abbr  略称
     * @param since 適用開始日 (UTC 基準ミリ秒)
     */
    JapaneseEra(int id, String name, String abbr, long since) {
        this.id = id;
        this.name = name;
        this.abbr = abbr;
        this.since = since;
    }

    /**
     * オブジェクトを構築します。
     * 
     * @param id    ID
     * @param name  名前
     * @param abbr  略称
     * @param since 適用開始日 (YYYY-MM-DD の形式)
     */
    JapaneseEra(int id, String name, String abbr, String since) {
        this(id, name, abbr, parseDateInMillisUTC(since));
    }

    /**
     * YYYY-MM-DD 形式の文字列を UTC 基準ミリ秒に変換します。
     * 
     * @param s 文字列
     * @return ミリ秒
     */
    static long parseDateInMillisUTC(String s) {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        df.setTimeZone(CalendarConsts.UTC);
        df.setLenient(false);
        try {
            return df.parse(s).getTime();
        } catch (ParseException e) {
            throw new IllegalArgumentException(s, e);
        }
    }

    // ---- flyweight

    /**
     * 指定された ID のインスタンスを返します。
     * 
     * @param id ID
     * @return インスタンス
     */
    public static JapaneseEra getInstance(int id) {
        return JapaneseEras.getInstance().getJapaneseEra(id);
    }

    // ---- accessors

    /**
     * ID を返します。
     * 
     * @return ID
     */
    public int getId() {
        return id;
    }

    /**
     * 名前を返します。
     * 
     * @return 名前
     */
    public String getName() {
        return name;
    }

    /**
     * 略称を返します。
     * 
     * @return 略称
     */
    public String getAbbr() {
        return abbr;
    }

    /**
     * 適用開始日を返します。
     * 
     * @return 適用開始日 (UTC 基準ミリ秒)
     */
    public long getSince() {
        return since;
    }

    /**
     * 適用開始年を返します。
     * 
     * @return 適用開始年 (西暦)
     */
    public int getSinceYear() {
        return getSinceYMD()[0];
    }

    /**
     * 適用開始月を返します。
     * 
     * @return 適用開始月
     */
    public int getSinceMonth() {
        return getSinceYMD()[1];
    }

    /**
     * 適用開始日を返します。
     * 
     * @return 適用開始日
     */
    public int getSinceDay() {
        return getSinceYMD()[2];
    }

    /**
     * 適用開始年月日を返します。
     * 
     * @return 適用開始年月日
     */
    int[] getSinceYMD() {
        if (sinceYMD == null) {
            Calendar calendar = new GregorianCalendar(CalendarConsts.UTC);
            calendar.setTimeInMillis(since);
            sinceYMD = new int[] {
                calendar.get(Calendar.YEAR),
                calendar.get(Calendar.MONTH) + 1,
                calendar.get(Calendar.DAY_OF_MONTH)
            };
        }
        return sinceYMD;
    }

    // ---- localized names

    /**
     * 短い名前を返します。
     * 
     * @return 短い名前
     */
    public String getShortName() {
        return getString(PREFIX + id + ".short");
    }

    /**
     * 短い名前を返します。
     * 
     * @param locale ロケール
     * @return 短い名前
     */
    public String getShortName(Locale locale) {
        return getString(PREFIX + id + ".short", locale);
    }

    /**
     * 長さが中位の名前を返します。
     * 
     * @return 長さが中位の名前
     */
    public String getMediumName() {
        return getString(PREFIX + id + ".medium");
    }

    /**
     * 長さが中位の名前を返します。
     * 
     * @param locale ロケール
     * @return 長さが中位の名前
     */
    public String getMediumName(Locale locale) {
        return getString(PREFIX + id + ".medium", locale);
    }

    /**
     * 長い名前を返します。
     * 
     * @return 長い名前
     */
    public String getLongName() {
        return getString(PREFIX + id + ".long");
    }

    /**
     * 長い名前を返します。
     * 
     * @param locale ロケール
     * @return 長い名前
     */
    public String getLongName(Locale locale) {
        return getString(PREFIX + id + ".long", locale);
    }

    /**
     * 最初の年の名称を返します。
     * 
     * @return 最初の年の名称
     */
    public static String getFirstYearText() {
        return getString(PREFIX + "first-year");
    }

    /**
     * 最初の年の名称を返します。
     * 
     * @param locale ロケール
     * @return 最初の年の名称
     */
    public static String getFirstYearText(Locale locale) {
        return getString(PREFIX + "first-year", locale);
    }

    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);
    }

    // ---- other methods

    /**
     * 指定和暦年の西暦年を返します。
     * 
     * @param yearOfEra 和暦年
     * @return 西暦年
     */
    public int getGregorianYear(int yearOfEra) {
        if (yearOfEra <= 0) {
            throw new IllegalArgumentException("yearOfEra=" + yearOfEra);
        }
        return getSinceYear() + yearOfEra - 1;
    }

    /**
     * 指定西暦年の和暦年を返します。
     * 
     * @param gYear 西暦年
     * @return 和暦年
     */
    public int getYearOfEra(int gYear) {
        int sinceYear = getSinceYear();
        if (gYear < sinceYear) {
            throw new IllegalArgumentException("gYear=" + gYear);
        }
        return gYear - sinceYear + 1;
    }

    // ---- java.lang.Object

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

    /**
     * オブジェクトのハッシュ値を返します。
     * 
     * @return ハッシュ値
     */
    @Override
    public int hashCode() {
        return id;
    }

    /**
     * 他のオブジェクトと等しいか判定します。
     * 
     * @param other 他のオブジェクト
     * @return 等しければ true
     */
    @Override
    public boolean equals(Object other) {
        if (!(other instanceof JapaneseEra)) {
            return false;
        }
        return equals((JapaneseEra) other);
    }

    /**
     * 他の元号オブジェクトと等しいか判定します。
     * 
     * @param other 他の元号オブジェクト
     * @return 等しければ true
     */
    public boolean equals(JapaneseEra other) {
        if (other == null) {
            return false;
        }
        return (id == other.id && since == other.since);
    }

    // ---- java.lang.Comparable

    /**
     * 他の元号オブジェクトと比較します。
     * 
     * @param other 他の元号オブジェクト
     * @return this < other ならば負の整数、
     *   this = other ならば0、
     *   this > other ならば正の整数
     */
    public int compareTo(JapaneseEra other) {
        long s1 = since;
        long s2 = other.since;
        return (s1 < s2) ? -1 : (s1 == s2) ? 0 : 1;
    }

    // ---- serialization

    /**
     * @serialData JapaneseEras に登録されているオブジェクトを返します。
     */
    private Object readResolve() throws ObjectStreamException {
        try {
            JapaneseEra era = getInstance(id);
            if (era != null) {
                return era;
            }
        } catch (Exception e) {}
        return this;
    }

}
