/*
 *******************************************************************************
 * Copyright (C) 2008-2014, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 */
package com.ibm.icu.impl.icuadapter;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;

import com.ibm.icu.impl.Grego;
import com.ibm.icu.impl.jdkadapter.TimeZoneICU;
import com.ibm.icu.util.ULocale;

/**
 * TimeZoneJDK is an adapter class which wraps java.util.TimeZone and
 * implements ICU4J TimeZone APIs.
 */
public class TimeZoneJDK extends com.ibm.icu.util.TimeZone {

    private static final long serialVersionUID = -1137052823551791933L;

    private TimeZone fJdkTz;
    private transient Calendar fJdkCal;
    private static Method mObservesDaylightTime;

    static {
        try {
            mObservesDaylightTime = TimeZone.class.getMethod("observesDaylightTime", (Class[]) null);
        } catch (NoSuchMethodException e) {
            // Java 6 or older
        } catch (SecurityException e) {
            // not visible
        }
    }

    private TimeZoneJDK(TimeZone jdkTz) {
        fJdkTz = (TimeZone)jdkTz.clone();
    }
    
    public static com.ibm.icu.util.TimeZone wrap(TimeZone jdkTz) {
        if (jdkTz instanceof TimeZoneICU) {
            return ((TimeZoneICU)jdkTz).unwrap();
        }
        return new TimeZoneJDK(jdkTz);
    }
    
    public TimeZone unwrap() {
        return (TimeZone)fJdkTz.clone();
    }

    @Override
    public Object clone() {
        if (isFrozen()) {
            return this;
        }
        return cloneAsThawed();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof TimeZoneJDK) {
            return (((TimeZoneJDK)obj).fJdkTz).equals(fJdkTz);
        }
        return false;
    }

    //public String getDisplayName()
    //public String getDisplayName(boolean daylight, int style)
    //public String getDisplayName(Locale locale)
    //public String getDisplayName(ULocale locale)
    @Override
    public String getDisplayName(boolean daylight, int style, Locale locale) {
        return fJdkTz.getDisplayName(daylight, style, locale);
    }
    
    @Override
    public String getDisplayName(boolean daylight, int style, ULocale locale) {
        return fJdkTz.getDisplayName(daylight, style, locale.toLocale());
    }

    @Override
    public int getDSTSavings() {
        return fJdkTz.getDSTSavings();
    }

    @Override
    public String getID() {
        return fJdkTz.getID();
    }

    @Override
    public int getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds) {
        return fJdkTz.getOffset(era, year, month, day, dayOfWeek, milliseconds);
    }

    @Override
    public int getOffset(long date) {
        return fJdkTz.getOffset(date);
    }

    @Override
    public void getOffset(long date, boolean local, int[] offsets) {
        synchronized(this) {
            if (fJdkCal == null) {
                fJdkCal = new GregorianCalendar(fJdkTz);
            }
            if (local) {
                int fields[] = new int[6];
                Grego.timeToFields(date, fields);
                int hour, min, sec, mil;
                int tmp = fields[5];
                mil = tmp % 1000;
                tmp /= 1000;
                sec = tmp % 60;
                tmp /= 60;
                min = tmp % 60;
                hour = tmp / 60;
                fJdkCal.clear();
                fJdkCal.set(fields[0], fields[1], fields[2], hour, min, sec);
                fJdkCal.set(java.util.Calendar.MILLISECOND, mil);

                int doy1, hour1, min1, sec1, mil1;
                doy1 = fJdkCal.get(java.util.Calendar.DAY_OF_YEAR);
                hour1 = fJdkCal.get(java.util.Calendar.HOUR_OF_DAY);
                min1 = fJdkCal.get(java.util.Calendar.MINUTE);
                sec1 = fJdkCal.get(java.util.Calendar.SECOND);
                mil1 = fJdkCal.get(java.util.Calendar.MILLISECOND);

                if (fields[4] != doy1 || hour != hour1 || min != min1 || sec != sec1 || mil != mil1) {
                    // Calendar field(s) were changed due to the adjustment for non-existing time
                    // Note: This code does not support non-existing local time at year boundary properly.
                    // But, it should work fine for real timezones.
                    int dayDelta = Math.abs(doy1 - fields[4]) > 1 ? 1 : doy1 - fields[4];
                    int delta = ((((dayDelta * 24) + hour1 - hour) * 60 + min1 - min) * 60 + sec1 - sec) * 1000 + mil1 - mil;

                    // In this case, we use the offsets before the transition
                    fJdkCal.setTimeInMillis(fJdkCal.getTimeInMillis() - delta - 1);
                }
            } else {
                fJdkCal.setTimeInMillis(date);
            }
            offsets[0] = fJdkCal.get(java.util.Calendar.ZONE_OFFSET);
            offsets[1] = fJdkCal.get(java.util.Calendar.DST_OFFSET);
        }
    }

    @Override
    public int getRawOffset() {
        return fJdkTz.getRawOffset();
    }

    @Override
    public int hashCode() {
        return fJdkTz.hashCode();
    }

    @Override
    public boolean hasSameRules(com.ibm.icu.util.TimeZone other) {
        return other.hasSameRules(TimeZoneJDK.wrap(fJdkTz));
    }

    @Override
    public boolean inDaylightTime(Date date) {
        return fJdkTz.inDaylightTime(date);
    }

    @Override
    public void setID(String ID) {
        if (isFrozen()) {
            throw new UnsupportedOperationException("Attempt to modify a frozen TimeZoneJDK instance.");
        }
        fJdkTz.setID(ID);
    }

    @Override
    public void setRawOffset(int offsetMillis) {
        if (isFrozen()) {
            throw new UnsupportedOperationException("Attempt to modify a frozen TimeZoneJDK instance.");
        }
        fJdkTz.setRawOffset(offsetMillis);
    }

    @Override
    public boolean useDaylightTime() {
        return fJdkTz.useDaylightTime();
    }

    @Override
    public boolean observesDaylightTime() {
        if (mObservesDaylightTime != null) {
            // Java 7+
            try {
                return (Boolean)mObservesDaylightTime.invoke(fJdkTz, (Object[]) null);
            } catch (IllegalAccessException e) {
            } catch (IllegalArgumentException e) {
            } catch (InvocationTargetException e) {
            }
        }
        return super.observesDaylightTime();
    }

    // Freezable stuffs
    private volatile transient boolean fIsFrozen = false;

    @Override
    public boolean isFrozen() {
        return fIsFrozen;
    }

    @Override
    public com.ibm.icu.util.TimeZone freeze() {
        fIsFrozen = true;
        return this;
    }

    @Override
    public com.ibm.icu.util.TimeZone cloneAsThawed() {
        TimeZoneJDK tz = (TimeZoneJDK)super.cloneAsThawed();
        tz.fJdkTz = (TimeZone)fJdkTz.clone();
        tz.fJdkCal = null;  // To be instantiated when necessary
        tz.fIsFrozen = false;
        return tz;
    }

}
