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

import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

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

import org.joda.time.Chronology;
import org.joda.time.DateTimeZone;
import org.joda.time.chrono.AssembledChronology;
import org.joda.time.chrono.GJChronology;

/**
 * 
 * 
 * @author GLAD!!
 */
public class JapaneseChronology extends AssembledChronology {

    /** Serialization lock */
    private static final long serialVersionUID = -1077096017589406711L;

    static final DateTimeZone JST = DateTimeZone.forID("Asia/Tokyo");

    static final JapaneseEras cEras = JapaneseEras.getInstance();

    /** Cache of zone to chronology */
    static final Map<DateTimeZone, JapaneseChronology> cCache =
            new WeakHashMap<DateTimeZone, JapaneseChronology>();

    /** UTC instance of the chronology */
    static final JapaneseChronology INSTANCE_UTC = getInstance(DateTimeZone.UTC);

    /** JST instance of the chronology */
    static final JapaneseChronology INSTANCE_JST = getInstance(JST);

    /**
     * Standard instance of a Japanese Chronology,
     * that matches Sun's JapaneseImperialCalendar class.
     * <p>
     * The time zone of the returned instance is UTC.
     */
    public static JapaneseChronology getInstanceUTC() {
        return INSTANCE_UTC;
    }

    /**
     * Standard instance of a Japanese Chronology,
     * that matches Sun's JapaneseImperialCalendar class.
     * <p>
     * The time zone of the returned instance is JST.
     */
    public static JapaneseChronology getInstanceJST() {
        return INSTANCE_JST;
    }

    /**
     * Standard instance of a Japanese Chronology,
     * that matches Sun's JapaneseImperialCalendar class.
     */
    public static JapaneseChronology getInstance() {
        return getInstance(DateTimeZone.getDefault());
    }

    /**
     * Standard instance of a Japanese Chronology,
     * that matches Sun's JapaneseImperialCalendar class.
     * <p>
     * @param zone  the time zone to use, null is default
     */
    public static JapaneseChronology getInstance(DateTimeZone zone) {
        if (zone == null) {
            zone = DateTimeZone.getDefault();
        }
        synchronized (cCache) {
            JapaneseChronology chrono = cCache.get(zone);
            if (chrono == null) {
                chrono = new JapaneseChronology(GJChronology.getInstance(zone));
                cCache.put(zone, chrono);
            }
            return chrono;
        }
    }

    // Constructors and instance variables
    //-----------------------------------------------------------------------

    /**
     * Restricted constructor.
     */
    JapaneseChronology(Chronology base) {
        super(base, null);
    }

    /**
     * Serialization singleton
     */
    private Object readResolve() {
        Chronology base = getBase();
        return base == null ? getInstanceUTC() : getInstance(base.getZone());
    }

    // Conversion
    //-----------------------------------------------------------------------

    /**
     * Gets the Chronology in the UTC time zone.
     * 
     * @return the chronology in UTC
     */
    public Chronology withUTC() {
        return INSTANCE_UTC;
    }

    /**
     * Gets the Chronology in the JST time zone.
     * 
     * @return the chronology in JST
     */
    public Chronology withJST() {
        return INSTANCE_JST;
    }

    /**
     * Gets the Chronology in a specific time zone.
     * 
     * @param zone  the zone to get the chronology in, null is default
     * @return the chronology
     */
    public Chronology withZone(DateTimeZone zone) {
        if (zone == null) {
            zone = DateTimeZone.getDefault();
        }
        if (zone == getZone()) {
            return this;
        }
        return getInstance(zone);
    }

    // Output
    //-----------------------------------------------------------------------

    /**
     * Gets a debugging toString.
     * 
     * @return a debugging string
     */
    public String toString() {
        String str = "JapaneseChronology";
        DateTimeZone zone = getZone();
        if (zone != null) {
            str += '[' + zone.getID() + ']';
        }
        return str;
    }

    protected void assemble(Fields fields) {
        fields.era = new JapaneseEraDateTimeField(this);
        fields.eras = fields.era.getDurationField();
        fields.yearOfEra = new JapaneseYearDateTimeField(this);
    }

    List<JapaneseEra> getJapaneseEras() {
        return cEras.getJapaneseEras();
    }

    JapaneseEra getJapaneseEra(long instant) {
        int year  = year().get(instant);
        int month = monthOfYear().get(instant);
        int day   = dayOfMonth().get(instant);
        return cEras.getJapaneseEra(year, month, day);
    }

    int getEra(long instant) {
        return getJapaneseEra(instant).getId();
    }

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

    int getMinEra() {
        return cEras.getMinimumEraId();
    }

    int getMaxEra() {
        return cEras.getMaximumEraId();
    }

    int getYearOfEra(long instant) {
        int year  = year().get(instant);
        int month = monthOfYear().get(instant);
        int day   = dayOfMonth().get(instant);
        return cEras.getYearOfEra(year, month, day);
    }

    /**
     * 和暦で年を設定します。
     * 
     * @param instant
     * @param yearOfEra 年 (和暦)
     * @return
     */
    long setYearOfEra(long instant, int yearOfEra) {
        JapaneseEra era = getJapaneseEra(instant);
        int gYear = era.getGregorianYear(yearOfEra);
        return year().set(instant, gYear);
    }

    /**
     * 和暦で年を設定します。
     * 
     * @param instant
     * @param era 元号
     * @param yearOfEra 年 (和暦)
     * @return
     */
    long setYearOfEra(long instant, int era, int yearOfEra) {
        int gYear = cEras.getGregorianYear(era, yearOfEra);
        return year().set(instant, gYear);
    }

    /**
     * 日付を設定します。
     * <p>
     * 時刻部分は変更されません。
     * 
     * @param instant
     * @param year  年 (西暦)
     * @param month 月
     * @param day   日
     * @return
     */
    long setDate(long instant, int year, int month, int day) {
        int millisOfDay = millisOfDay().get(instant);
        return getDateTimeMillis(year, month, day, millisOfDay);
    }

    /**
     * 和暦で日付を設定します。
     * <p>
     * 時刻部分は変更されません。
     * 
     * @param instant
     * @param era 元号
     * @param yearOfEra 年 (和暦)
     * @param month 月
     * @param day   日
     * @return
     */
    long setDateOfEra(long instant,
            int era, int yearOfEra, int month, int day) {
        int gYear = cEras.getGregorianYear(era, yearOfEra);
        int millisOfDay = millisOfDay().get(instant);
        return getDateTimeMillis(gYear, month, day, millisOfDay);
    }

}
