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

import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.WeakHashMap;

/**
 * 
 * @author GLAD!!
 */
public class ISOTimeZone extends TimeZone {

    private static final long serialVersionUID = -5111359454747884189L;

    static final int MILLIS_PER_MINUTE = 60 * 1000;

    static final Map<Integer, ISOTimeZone> timeZoneMap =
            new WeakHashMap<Integer, ISOTimeZone>();

    final int offset;

    transient String shortName = null;

    ISOTimeZone(int offset) {
        if (offset % MILLIS_PER_MINUTE != 0) {
            throw new IllegalArgumentException("Illegal offset: " + offset);
        }
        this.offset = offset;
        super.setID(format(offset, LONG));
    }

    public static ISOTimeZone getInstance() {
        return getInstance(TimeZone.getDefault());
    }

    public static ISOTimeZone getInstance(int offset) {
        synchronized (timeZoneMap) {
            // 弱参照にするためオブジェクトを新たに生成する。
            Integer key = new Integer(offset);
            ISOTimeZone instance = timeZoneMap.get(key);
            if (instance == null) {
                instance = new ISOTimeZone(offset);
                timeZoneMap.put(key, instance);
            }
            return instance;
        }
    }

    public static ISOTimeZone getInstance(int signum, int hours, int minutes) {
        int offset = (hours * 60 + minutes) * MILLIS_PER_MINUTE;
        return getInstance(signum < 0 ? -offset : offset);
    }

    public static ISOTimeZone getInstance(String id) {
        return getInstance(TimeZone.getTimeZone(id));
    }

    public static ISOTimeZone getInstance(String id, long timeInMillis) {
        return getInstance(TimeZone.getTimeZone(id), timeInMillis);
    }

    public static ISOTimeZone getInstance(TimeZone zone) {
        return getInstance(zone, System.currentTimeMillis());
    }

    public static ISOTimeZone getInstance(TimeZone zone, long timeInMillis) {
        if (zone instanceof ISOTimeZone) {
            return (ISOTimeZone) zone;
        } else {
            return getInstance(zone.getOffset(timeInMillis));
        }
    }

    public static ISOTimeZone getInstance(Calendar calendar) {
        return getInstance(calendar.getTimeZone(), calendar.getTimeInMillis());
    }

    @Override
    public void setID(String id) {
        throw new UnsupportedOperationException();
    }

    public int getOffset(
            int era, int year, int month, int day,
            int dayOfWeek, int millisOfDay) {
        return offset;
    }

    @Override
    public int getOffset(long timeInMillis) {
        return offset;
    }

    public int getRawOffset() {
        return offset;
    }

    public void setRawOffset(int offsetMillis) {
        throw new UnsupportedOperationException();
    }

    public String getDisplayName(int style) {
        if (style == LONG) {
            return getID();
        }
        if (style == SHORT) {
            if (shortName == null) {
                shortName = format(offset, SHORT);
            }
            return shortName;
        }
        throw new IllegalArgumentException("Illegal style: " + style);
    }

    @Override
    public String getDisplayName(boolean daylight, int style, Locale locale) {
        return getDisplayName(style);
    }

    static final char[] DECIMALS = {
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
    };

    static String format(int offset, int style) {
        if (offset == 0) {
            return "Z";
        }
        int minutesOfDay = Math.abs(offset) / MILLIS_PER_MINUTE;
        int hours   = minutesOfDay / 60;
        int minutes = minutesOfDay % 60;
        StringBuilder sb = new StringBuilder(6);
        sb.append(offset < 0 ? '-' : '+');
        sb.append(DECIMALS[hours / 10]);
        sb.append(DECIMALS[hours % 10]);
        if (style == LONG) {
            sb.append(':');
        }
        sb.append(DECIMALS[minutes / 10]);
        sb.append(DECIMALS[minutes % 10]);
        return sb.toString().intern();
    }

    public boolean useDaylightTime() {
        return false;
    }

    public boolean inDaylightTime(Date date) {
        return false;
    }

    @Override
    public int hashCode() {
        return offset;
    }

    @Override
    public boolean equals(Object other) {
        if (!(other instanceof ISOTimeZone)) {
            return false;
        }
        return equals((ISOTimeZone) other);
    }

    public boolean equals(ISOTimeZone other) {
        if (other == null) {
            return false;
        }
        return offset == other.offset;
    }

    @Override
    public String toString() {
        return getID();
    }

    @Override
    public ISOTimeZone clone() {
        return (ISOTimeZone) super.clone();
    }

    public static final ISOTimeZone UTC = getInstance(0);
    public static final ISOTimeZone JST = getInstance("Asia/Tokyo");

}
