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

import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

import jp.sourceforge.glad.calendar.ISOCalendar;
import jp.sourceforge.glad.calendar.util.ISOTimeZone;

/**
 * 
 * 
 * @author GLAD!!
 */
public class ISODateFormat extends SimpleDateFormat {

    private static final long serialVersionUID = -8952513135868094122L;

    public static final String BASIC_DATE_FORMAT =
            "yyyyMMdd";
    public static final String BASIC_DATE_SHORT_FORMAT =
            "yyMMdd";
    public static final String BASIC_ORDINAL_DATE_FORMAT =
            "yyyyDDD";
    public static final String BASIC_WEEK_DATE_FORMAT =
            "yyyy'W'wwE";

    public static final String EXTENDED_DATE_FORMAT =
            "yyyy-MM-dd";
    public static final String EXTENDED_DATE_SHORT_FORMAT =
            "yy-MM-dd";
    public static final String EXTENDED_ORDINAL_DATE_FORMAT =
            "yyyy-DDD";
    public static final String EXTENDED_WEEK_DATE_FORMAT =
            "yyyy-'W'ww-E";

    public static final String BASIC_TIME_FORMAT =
            "HHmmss.SSSz";
    public static final String BASIC_TIME_LONG_FORMAT =
            "HHmmssz";
    public static final String BASIC_TIME_MEDIUM_FORMAT =
            "HHmmss";
    public static final String BASIC_TIME_SHORT_FORMAT =
            "HHmm";

    public static final String EXTENDED_TIME_FORMAT =
            "HH:mm:ss.SSSzzzz";
    public static final String EXTENDED_TIME_LONG_FORMAT =
            "HH:mm:sszzzz";
    public static final String EXTENDED_TIME_MEDIUM_FORMAT =
            "HH:mm:ss";
    public static final String EXTENDED_TIME_SHORT_FORMAT =
            "HH:mm";

    public static final String BASIC_DATE_TIME_FORMAT =
            "yyyyMMdd'T'HHmmss.SSSz";
    public static final String BASIC_DATE_TIME_LONG_FORMAT =
            "yyyyMMdd'T'HHmmssz";
    public static final String BASIC_DATE_TIME_MEDIUM_FORMAT =
            "yyyyMMdd'T'HHmmss";
    public static final String BASIC_DATE_TIME_SHORT_FORMAT =
            "yyMMdd'T'HHmm";

    public static final String EXTENDED_DATE_TIME_FORMAT =
            "yyyy-MM-dd'T'HH:mm:ss.SSSzzzz";
    public static final String EXTENDED_DATE_TIME_LONG_FORMAT =
            "yyyy-MM-dd'T'HH:mm:sszzzz";
    public static final String EXTENDED_DATE_TIME_MEDIUM_FORMAT =
            "yyyy-MM-dd'T'HH:mm:ss";
    public static final String EXTENDED_DATE_TIME_SHORT_FORMAT =
            "yy-MM-dd'T'HH:mm";

    public static final int DEFAULT = FULL;

    public static final int ORDINAL_DATE = (SHORT + 1);
    public static final int WEEK_DATE = (SHORT + 2);

    final ISODateFormatSymbols formatSymbols;

    TimeZone timeZone;

    boolean parsing = false;

    public ISODateFormat() {
        this(EXTENDED_DATE_TIME_FORMAT);
    }

    public ISODateFormat(String pattern) {
        this(pattern, new ISODateFormatSymbols());
    }

    ISODateFormat(String pattern, ISODateFormatSymbols formatSymbols) {
        super(pattern, formatSymbols);
        this.formatSymbols = formatSymbols;
        setCalendar(calendar);
    }

    // ----

    public static ISODateFormat date() {
        return new ISODateFormat(EXTENDED_DATE_FORMAT);
    }

    public static ISODateFormat date(int style) {
        return new ISODateFormat(getDatePattern(style));
    }

    public static ISODateFormat time() {
        return new ISODateFormat(EXTENDED_TIME_FORMAT);
    }

    public static ISODateFormat time(int style) {
        return new ISODateFormat(getTimePattern(style));
    }

    public static ISODateFormat timeNoMillis() {
        return new ISODateFormat(EXTENDED_TIME_LONG_FORMAT);
    }

    public static ISODateFormat tTime() {
        return new ISODateFormat("'T'" + EXTENDED_TIME_FORMAT);
    }

    public static ISODateFormat tTimeNoMillis() {
        return new ISODateFormat("'T'" + EXTENDED_TIME_LONG_FORMAT);
    }

    public static ISODateFormat dateTime() {
        return new ISODateFormat(EXTENDED_DATE_TIME_FORMAT);
    }

    public static ISODateFormat dateTime(int style) {
        return new ISODateFormat(getDateTimePattern(style));
    }

    public static ISODateFormat dateTime(int dateStyle, int timeStyle) {
        return new ISODateFormat(getDateTimePattern(dateStyle, timeStyle));
    }

    public static ISODateFormat dateTimeNoMillis() {
        return new ISODateFormat(EXTENDED_DATE_TIME_LONG_FORMAT);
    }

    // ----

    public static ISODateFormat ordinalDate() {
        return new ISODateFormat(EXTENDED_ORDINAL_DATE_FORMAT);
    }

    public static ISODateFormat weekDate() {
        return new ISODateFormat(EXTENDED_WEEK_DATE_FORMAT);
    }

    // ----

    public static ISODateFormat basicDate() {
        return new ISODateFormat(BASIC_DATE_FORMAT);
    }

    public static ISODateFormat basicTime() {
        return new ISODateFormat(BASIC_TIME_FORMAT);
    }

    public static ISODateFormat basicTimeNoMillis() {
        return new ISODateFormat(BASIC_TIME_LONG_FORMAT);
    }

    public static ISODateFormat basicTTime() {
        return new ISODateFormat("'T'" + BASIC_TIME_FORMAT);
    }

    public static ISODateFormat basicTTimeNoMillis() {
        return new ISODateFormat("'T'" + BASIC_TIME_LONG_FORMAT);
    }

    public static ISODateFormat basicDateTime() {
        return new ISODateFormat(BASIC_DATE_TIME_FORMAT);
    }

    public static ISODateFormat basicDateTimeNoMillis() {
        return new ISODateFormat(BASIC_DATE_TIME_LONG_FORMAT);
    }

    // ----

    public static ISODateFormat basicOrdinalDate() {
        return new ISODateFormat(BASIC_ORDINAL_DATE_FORMAT);
    }

    public static ISODateFormat basicWeekDate() {
        return new ISODateFormat(BASIC_WEEK_DATE_FORMAT);
    }

    // ----

    static String getDatePattern(int style) {
        if (FULL <= style && style < SHORT) {
            return EXTENDED_DATE_FORMAT;
        } else if (style == SHORT) {
            return EXTENDED_DATE_SHORT_FORMAT;
        } else if (style == ORDINAL_DATE) {
            return EXTENDED_ORDINAL_DATE_FORMAT;
        } else if (style == WEEK_DATE) {
            return EXTENDED_WEEK_DATE_FORMAT;
        }
        throw new IllegalArgumentException("Illegal style: " + style);
    }

    static String getTimePattern(int style) {
        if (style == FULL) {
            return EXTENDED_TIME_FORMAT;
        } else if (style == LONG) {
            return EXTENDED_TIME_LONG_FORMAT;
        } else if (style == MEDIUM) {
            return EXTENDED_TIME_MEDIUM_FORMAT;
        } else if (style == SHORT) {
            return EXTENDED_TIME_SHORT_FORMAT;
        }
        throw new IllegalArgumentException("Illegal style: " + style);
    }

    static String getDateTimePattern(int style) {
        if (style == FULL) {
            return EXTENDED_DATE_TIME_FORMAT;
        } else if (style == LONG) {
            return EXTENDED_DATE_TIME_LONG_FORMAT;
        } else if (style == MEDIUM) {
            return EXTENDED_DATE_TIME_MEDIUM_FORMAT;
        } else if (style == SHORT) {
            return EXTENDED_DATE_TIME_SHORT_FORMAT;
        }
        throw new IllegalArgumentException("Illegal style: " + style);
    }

    static String getDateTimePattern(int dateStyle, int timeStyle) {
        return getDatePattern(dateStyle) + "'T'" + getTimePattern(timeStyle);
    }

    // ----

    @Override
    public GregorianCalendar getCalendar() {
        return (GregorianCalendar) calendar.clone();
    }

    @Override
    public void setCalendar(Calendar newCalendar) {
        super.setCalendar(new ISOCalendar(newCalendar).toGregorianCalendar());
        timeZone = calendar.getTimeZone();
        addZoneStrings();
    }

    @Override
    public void setTimeZone(TimeZone zone) {
        super.setTimeZone(zone);
        if (!parsing) {
            timeZone = zone;
            addZoneStrings();
        }
    }

    @Override
    public ISODateFormatSymbols getDateFormatSymbols() {
        return (ISODateFormatSymbols) super.getDateFormatSymbols();
    }

    @Override
    public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) {
        throw new UnsupportedOperationException();
    }

    void addZoneStrings() {
        formatSymbols.addZoneStrings(calendar.getTimeZone());
        super.setDateFormatSymbols(formatSymbols);
    }

    public String format(Calendar calendar) {
        setTimeZone(calendar.getTimeZone());
        return format(calendar.getTime());
    }

    public Date parse(String text, ParsePosition pos) {
        parsing = true;
        try {
            calendar.setTimeZone(timeZone);
            Date date = super.parse(text, pos);
            TimeZone zone = calendar.getTimeZone();
            if (zone != timeZone && !(zone instanceof ISOTimeZone)) {
                calendar.setTimeZone(ISOTimeZone.getInstance(calendar));
            }
            return date;
        } finally {
            parsing = false;
        }
    }

    public GregorianCalendar parseCalendar(String source)
            throws ParseException {
        parse(source);
        return getCalendar();
    }

}
