/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.log4j.pattern;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import org.apache.log4j.pattern.CachedDateFormat;

import java.text.DateFormat;
import java.util.TimeZone;
import java.util.Date;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.Calendar;

/**
 * Unit test {@link AbsoluteTimeDateFormat}.
 *
 * @author Curt Arnold
 */
public final class CachedDateFormatTest extends TestCase {

    /**
     * Test constructor
     *
     * @param name String test name
     */
    public CachedDateFormatTest(String name) {
        super(name);
    }

    private static DateFormat createAbsoluteTimeDateFormat(TimeZone timeZone) {
        DateFormat df = new SimpleDateFormat("HH:mm:ss,SSS");
        df.setTimeZone(timeZone);
        return df;
    }

    /**
     * Timezone representing GMT.
     */
    private static final TimeZone GMT = TimeZone.getTimeZone("GMT");

    /**
     * Timezone for Chicago, Ill. USA.
     */
    private static final TimeZone CHICAGO = TimeZone.getTimeZone("America/Chicago");

    /**
     * Test multiple calls in close intervals.
     */
    public void test1() {
        // subsequent calls within one minute
        // are optimized to reuse previous formatted value
        // make a couple of nearly spaced calls
        DateFormat gmtFormat = new CachedDateFormat(createAbsoluteTimeDateFormat(GMT), 1000);
        long ticks = 12601L * 86400000L;
        Date jul1 = new Date(ticks);
        assertEquals("00:00:00,000", gmtFormat.format(jul1));
        Date plus8ms = new Date(ticks + 8);
        assertEquals("00:00:00,008", gmtFormat.format(plus8ms));
        Date plus17ms = new Date(ticks + 17);
        assertEquals("00:00:00,017", gmtFormat.format(plus17ms));
        Date plus237ms = new Date(ticks + 237);
        assertEquals("00:00:00,237", gmtFormat.format(plus237ms));
        Date plus1415ms = new Date(ticks + 1415);
        assertEquals("00:00:01,415", gmtFormat.format(plus1415ms));
    }

    /**
     * Check for interaction between caches.
     */
    public void test2() {
        Date jul2 = new Date(12602L * 86400000L);
        DateFormat gmtFormat = new CachedDateFormat(createAbsoluteTimeDateFormat(GMT), 1000);
        DateFormat chicagoFormat = new CachedDateFormat(createAbsoluteTimeDateFormat(CHICAGO), 1000);
        assertEquals("00:00:00,000", gmtFormat.format(jul2));
        assertEquals("19:00:00,000", chicagoFormat.format(jul2));
        assertEquals("00:00:00,000", gmtFormat.format(jul2));
    }

    /**
     * Test multiple calls in close intervals prior to 1 Jan 1970.
     */
    public void test3() {
        // subsequent calls within one minute
        // are optimized to reuse previous formatted value
        // make a couple of nearly spaced calls
        DateFormat gmtFormat = new CachedDateFormat(createAbsoluteTimeDateFormat(GMT), 1000);
        //
        // if the first call was exactly on an integral
        // second, it would not test the round toward zero compensation
        long ticks = -7L * 86400000L;
        Date jul1 = new Date(ticks + 8);
        assertEquals("00:00:00,008", gmtFormat.format(jul1));
        Date plus8ms = new Date(ticks + 16);
        assertEquals("00:00:00,016", gmtFormat.format(plus8ms));
        Date plus17ms = new Date(ticks + 23);
        assertEquals("00:00:00,023", gmtFormat.format(plus17ms));
        Date plus237ms = new Date(ticks + 245);
        assertEquals("00:00:00,245", gmtFormat.format(plus237ms));
        Date plus1415ms = new Date(ticks + 1423);
        assertEquals("00:00:01,423", gmtFormat.format(plus1415ms));
    }

    public void test4() {
        // subsequent calls within one minute are optimized to reuse previous
        // formatted value. make a couple of nearly spaced calls
        // (Note: 'Z' is JDK 1.4, using 'z' instead.)
        SimpleDateFormat baseFormat = new SimpleDateFormat("EEE, MMM dd, HH:mm:ss.SSS z", Locale.ENGLISH);
        DateFormat cachedFormat = new CachedDateFormat(baseFormat, 1000);
        //
        // use a date in 2000 to attempt to confuse the millisecond locator
        long ticks = 11141L * 86400000L;
        Date jul1 = new Date(ticks);
        assertEquals(baseFormat.format(jul1), cachedFormat.format(jul1));
        Date plus8ms = new Date(ticks + 8);
        baseFormat.format(plus8ms);
        cachedFormat.format(plus8ms);
        assertEquals(baseFormat.format(plus8ms), cachedFormat.format(plus8ms));
        Date plus17ms = new Date(ticks + 17);
        assertEquals(baseFormat.format(plus17ms), cachedFormat.format(plus17ms));
        Date plus237ms = new Date(ticks + 237);
        assertEquals(baseFormat.format(plus237ms), cachedFormat.format(plus237ms));
        Date plus1415ms = new Date(ticks + 1415);
        assertEquals(baseFormat.format(plus1415ms), cachedFormat.format(plus1415ms));
    }

    public void test5() {
        // subsequent calls within one minute
        // are optimized to reuse previous formatted value
        // make a couple of nearly spaced calls
        // (Note: 'Z' is JDK 1.4, using 'z' instead.)
        Locale thai = new Locale("th", "TH");
        SimpleDateFormat baseFormat = new SimpleDateFormat("EEE, MMM dd, HH:mm:ss.SSS z", thai);
        DateFormat cachedFormat = new CachedDateFormat(baseFormat, 1000);
        //
        // use a date in the year 2000 CE to attempt to confuse the millisecond locator
        long ticks = 11141L * 86400000L;

        String sx;
        Date jul1 = new Date(ticks);
        sx = cachedFormat.format(jul1);
        System.out.println(baseFormat.format(jul1));
        System.out.println(sx);
        assertEquals(baseFormat.format(jul1), sx);

        sx = cachedFormat.format(jul1);
        System.out.println(baseFormat.format(jul1));
        System.out.println(sx);
        assertEquals(baseFormat.format(jul1), sx);

        Date plus8ms = new Date(ticks + 8);
        sx = cachedFormat.format(plus8ms);
        System.out.println(baseFormat.format(plus8ms));
        System.out.println(sx);

        assertEquals(baseFormat.format(plus8ms), sx);

        Date plus17ms = new Date(ticks + 17);
        assertEquals(baseFormat.format(plus17ms), cachedFormat.format(plus17ms));

        Date plus237ms = new Date(ticks + 237);
        assertEquals(baseFormat.format(plus237ms), cachedFormat.format(plus237ms));

        Date plus1415ms = new Date(ticks + 1415);
        assertEquals(baseFormat.format(plus1415ms), cachedFormat.format(plus1415ms));
    }

    /**
     * Checks that getNumberFormat does not return null.
     */
    public void test6() {
        assertNotNull(new CachedDateFormat(new SimpleDateFormat(), 1000).getNumberFormat());
    }

    /**
     * Set time zone on cached and check that it is effective.
     */
    public void test8() {
        DateFormat baseFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");
        baseFormat.setTimeZone(GMT);
        DateFormat cachedFormat = new CachedDateFormat(baseFormat, 1000);
        Date jul4 = new Date(12603L * 86400000L);
        assertEquals("2004-07-04 00:00:00,000", cachedFormat.format(jul4));
        cachedFormat.setTimeZone(TimeZone.getTimeZone("GMT-6"));
        assertEquals("2004-07-03 18:00:00,000", cachedFormat.format(jul4));
    }

    /**
     * Test of caching when less than three millisecond digits are specified.
     */
    public void test9() {
        // (Note: 'Z' is JDK 1.4, using 'z' instead.)
        DateFormat baseFormat = new SimpleDateFormat("yyyy-MMMM-dd HH:mm:ss,SS z", Locale.US);
        DateFormat cachedFormat = new CachedDateFormat(baseFormat, 1000);
        TimeZone cet = TimeZone.getTimeZone("GMT+1");
        cachedFormat.setTimeZone(cet);

        Calendar c = Calendar.getInstance();
        c.set(2004, Calendar.DECEMBER, 12, 20, 0);
        c.set(Calendar.SECOND, 37);
        c.set(Calendar.MILLISECOND, 23);
        c.setTimeZone(cet);

        String expected = baseFormat.format(c.getTime());
        String s = cachedFormat.format(c.getTime());
        assertEquals(expected, s);

        c.set(2005, Calendar.JANUARY, 1, 0, 0);
        c.set(Calendar.SECOND, 13);
        c.set(Calendar.MILLISECOND, 905);

        expected = baseFormat.format(c.getTime());
        s = cachedFormat.format(c.getTime());
        assertEquals(expected, s);
    }

    /**
     * Test when millisecond position moves but length remains constant.
     */
    public void test10() {
        DateFormat baseFormat = new SimpleDateFormat("MMMM SSS EEEEEE", Locale.US);
        DateFormat cachedFormat = new CachedDateFormat(baseFormat, 1000);
        TimeZone cet = TimeZone.getTimeZone("GMT+1");
        cachedFormat.setTimeZone(cet);

        Calendar c = Calendar.getInstance();
        c.set(2004, Calendar.OCTOBER, 5, 20, 0);
        c.set(Calendar.SECOND, 37);
        c.set(Calendar.MILLISECOND, 23);
        c.setTimeZone(cet);

        String expected = baseFormat.format(c.getTime());
        String s = cachedFormat.format(c.getTime());
        assertEquals(expected, s);

        c.set(2004, Calendar.NOVEMBER, 1, 0, 0);
        c.set(Calendar.MILLISECOND, 23);
        expected = baseFormat.format(c.getTime());
        s = cachedFormat.format(c.getTime());
        assertEquals(expected, s);

        c.set(Calendar.MILLISECOND, 984);
        expected = baseFormat.format(c.getTime());
        s = cachedFormat.format(c.getTime());
        assertEquals(expected, s);
    }

    /**
     * Test that tests if caching is skipped if only "SS" is specified.
     */
    public void test11() {
        //
        // Earlier versions could be tricked by "SS0" patterns.
        //
        String badPattern = "ss,SS0";
        SimpleDateFormat simpleFormat = new SimpleDateFormat(badPattern);
        SimpleDateFormat baseFormat = new SimpleDateFormat(badPattern);
        DateFormat gmtFormat = new CachedDateFormat(simpleFormat, 1000);
        gmtFormat.setTimeZone(GMT);
        baseFormat.setTimeZone(GMT);

        //
        // The first request has to 100 ms after an ordinal second
        // to push the literal zero out of the pattern check
        long ticks = 11142L * 86400000L;
        Date jul2 = new Date(ticks + 120);
        String expected = baseFormat.format(jul2);
        assertEquals(expected, gmtFormat.format(jul2));
        jul2.setTime(ticks + 87);

        //
        // Cache gives 00,087
        expected = baseFormat.format(jul2);
        assertEquals(expected, gmtFormat.format(jul2));

    }

    /**
     * Check pattern location for ISO8601
     */
    public void test12() {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");
        long ticks = 11142L * 86400000L;
        String formatted = df.format(new Date(ticks));
        int millisecondStart = CachedDateFormat.findMillisecondStart(ticks, formatted, df);
        assertEquals(20, millisecondStart);
    }

    /**
     * Check pattern location for DATE
     */
    public void test13() {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        long ticks = 11142L * 86400000L;
        String formatted = df.format(new Date(ticks));
        int millisecondStart = CachedDateFormat.findMillisecondStart(ticks, formatted, df);
        assertEquals(CachedDateFormat.NO_MILLISECONDS, millisecondStart);
    }

    /**
     * Check pattern location for ABSOLUTE
     */
    public void test14() {
        SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss,SSS");
        long ticks = 11142L * 86400000L;
        String formatted = df.format(new Date(ticks));
        int millisecondStart = CachedDateFormat.findMillisecondStart(ticks, formatted, df);
        assertEquals(9, millisecondStart);
    }

    /**
     * Check pattern location for single S
     */
    public void test15() {
        SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss,S");
        long ticks = 11142L * 86400000L;
        String formatted = df.format(new Date(ticks));
        int millisecondStart = CachedDateFormat.findMillisecondStart(ticks, formatted, df);
        assertEquals(CachedDateFormat.UNRECOGNIZED_MILLISECONDS, millisecondStart);
    }

    /**
     * Check pattern location for single SS
     */
    public void test16() {
        SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss,SS");
        long ticks = 11142L * 86400000L;
        String formatted = df.format(new Date(ticks));
        int millisecondStart = CachedDateFormat.findMillisecondStart(ticks, formatted, df);
        assertEquals(CachedDateFormat.UNRECOGNIZED_MILLISECONDS, millisecondStart);
    }

    /**
     * Check caching when multiple SSS appear in pattern
     */
    public void test17() {
        Date jul2 = new Date(12602L * 86400000L);
        String badPattern = "HH:mm:ss,SSS HH:mm:ss,SSS";
        SimpleDateFormat simpleFormat = new SimpleDateFormat(badPattern);
        simpleFormat.setTimeZone(GMT);
        DateFormat cachedFormat = new CachedDateFormat(simpleFormat, 1000);
        String s = cachedFormat.format(jul2);
        assertEquals("00:00:00,000 00:00:00,000", s);
        jul2.setTime(jul2.getTime() + 120);
        assertEquals("00:00:00,120 00:00:00,120", simpleFormat.format(jul2));
        s = cachedFormat.format(jul2);
        //
        // TODO: why is this returning ,120 ... , 120
        //
        // assertEquals("00:00:00,120 00:00:00,000", s) ;

        int maxValid = CachedDateFormat.getMaximumCacheValidity(badPattern);
        assertEquals(1, maxValid);
    }

    public static Test xsuite() {
        TestSuite suite = new TestSuite();
        suite.addTest(new CachedDateFormatTest("test5"));
        // suite.addTest(new CachedDateFormatTest("testS2"));
        return suite;
    }

}
