/*
 * 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.logging.log4j.core.util.datetime;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

/**
 * Compare FastDateParser with SimpleDateFormat
 *
 * Copied from Apache Commons Lang 3 on 2016-11-16.
 */
@RunWith(Parameterized.class)
public class FastDateParserSDFTest {

    @Parameters(name= "{index}: {0} {1} {2}")
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object [][]{
                // General Time zone tests
                {"z yyyy", "GMT 2010",       Locale.UK, true}, // no offset specified, but this is allowed as a TimeZone name
                {"z yyyy", "GMT-123 2010",   Locale.UK, false},
                {"z yyyy", "GMT-1234 2010",  Locale.UK, false},
                {"z yyyy", "GMT-12:34 2010", Locale.UK, true},
                {"z yyyy", "GMT-1:23 2010",  Locale.UK, true},
                // RFC 822 tests
                {"z yyyy", "-1234 2010",     Locale.UK, true},
                {"z yyyy", "-12:34 2010",    Locale.UK, false},
                {"z yyyy", "-123 2010",      Locale.UK, false},
                // year tests
                { "MM/dd/yyyy", "01/11/12",  Locale.UK, true},
                { "MM/dd/yy", "01/11/12",    Locale.UK, true},

                // LANG-1089
                { "HH", "00",    Locale.UK, true}, // Hour in day (0-23)
                { "KK", "00",    Locale.UK, true}, // Hour in am/pm (0-11)
                { "hh", "00",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
                { "kk", "00",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0

                { "HH", "01",    Locale.UK, true}, // Hour in day (0-23)
                { "KK", "01",    Locale.UK, true}, // Hour in am/pm (0-11)
                { "hh", "01",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
                { "kk", "01",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0

                { "HH", "11",    Locale.UK, true}, // Hour in day (0-23)
                { "KK", "11",    Locale.UK, true}, // Hour in am/pm (0-11)
                { "hh", "11",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
                { "kk", "11",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0

                { "HH", "12",    Locale.UK, true}, // Hour in day (0-23)
                { "KK", "12",    Locale.UK, true}, // Hour in am/pm (0-11)
                { "hh", "12",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
                { "kk", "12",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0

                { "HH", "13",    Locale.UK, true}, // Hour in day (0-23)
                { "KK", "13",    Locale.UK, true}, // Hour in am/pm (0-11)
                { "hh", "13",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
                { "kk", "13",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0

                { "HH", "23",    Locale.UK, true}, // Hour in day (0-23)
                { "KK", "23",    Locale.UK, true}, // Hour in am/pm (0-11)
                { "hh", "23",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
                { "kk", "23",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0

                { "HH", "24",    Locale.UK, true}, // Hour in day (0-23)
                { "KK", "24",    Locale.UK, true}, // Hour in am/pm (0-11)
                { "hh", "24",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
                { "kk", "24",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0

                { "HH", "25",    Locale.UK, true}, // Hour in day (0-23)
                { "KK", "25",    Locale.UK, true}, // Hour in am/pm (0-11)
                { "hh", "25",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
                { "kk", "25",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0

                { "HH", "48",    Locale.UK, true}, // Hour in day (0-23)
                { "KK", "48",    Locale.UK, true}, // Hour in am/pm (0-11)
                { "hh", "48",    Locale.UK, true}, // Hour in am/pm (1-12), i.e. midday/midnight is 12, not 0
                { "kk", "48",    Locale.UK, true}, // Hour in day (1-24), i.e. midnight is 24, not 0
                });
    }

    private final String format;
    private final String input;
    private final Locale locale;
    private final boolean valid;
    private final TimeZone timeZone = TimeZone.getDefault();

    public FastDateParserSDFTest(final String format, final String input, final Locale locale, final boolean valid) {
        this.format = format;
        this.input = input;
        this.locale = locale;
        this.valid = valid;
    }

    private void checkParse(final String formattedDate) {
        final SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
        sdf.setTimeZone(timeZone);
        final DateParser fdf = new FastDateParser(format, timeZone, locale);
        Date expectedTime=null;
        Class<?> sdfE = null;
        try {
            expectedTime = sdf.parse(formattedDate);
            if (!valid) {
                // Error in test data
                throw new RuntimeException("Test data error: expected SDF parse to fail, but got " + expectedTime);
            }
        } catch (final ParseException e) {
            if (valid) {
                // Error in test data
                throw new RuntimeException("Test data error: expected SDF parse to succeed, but got " + e);
            }
            sdfE = e.getClass();
        }
        Date actualTime = null;
        Class<?> fdfE = null;
        try {
            actualTime = fdf.parse(formattedDate);
            if (!valid) {
                // failure in test
                fail("Expected FDP parse to fail, but got " + actualTime);
            }
        } catch (final ParseException e) {
            if (valid) {
                // failure in test
                fail("Expected FDP parse to succeed, but got " + e);
            }
            fdfE = e.getClass();
        }
        if (valid) {
            assertEquals(locale.toString()+" "+formattedDate +"\n",expectedTime, actualTime);
        } else {
            assertEquals(locale.toString()+" "+formattedDate + " expected same Exception ", sdfE, fdfE);
        }
    }

    private void checkParsePosition(final String formattedDate) {
        final SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
        sdf.setTimeZone(timeZone);
        final DateParser fdf = new FastDateParser(format, timeZone, locale);

        final ParsePosition sdfP = new ParsePosition(0);
        final Date expectedTime = sdf.parse(formattedDate, sdfP);
        final int sdferrorIndex = sdfP.getErrorIndex();
        if (valid) {
            assertEquals("Expected SDF error index -1 ", -1, sdferrorIndex);
            final int endIndex = sdfP.getIndex();
            final int length = formattedDate.length();
            if (endIndex != length) {
                // Error in test data
                throw new RuntimeException("Test data error: expected SDF parse to consume entire string; endindex " + endIndex + " != " + length);
            }
        } else {
            final int errorIndex = sdfP.getErrorIndex();
            if (errorIndex == -1) {
                throw new RuntimeException("Test data error: expected SDF parse to fail, but got " + expectedTime);
            }
        }

        final ParsePosition fdfP = new ParsePosition(0);
        final Date actualTime = fdf.parse(formattedDate, fdfP);
        final int fdferrorIndex = fdfP.getErrorIndex();
        if (valid) {
            assertEquals("Expected FDF error index -1 ", -1, fdferrorIndex);
            final int endIndex = fdfP.getIndex();
            final int length = formattedDate.length();
            assertEquals("Expected FDF to parse full string " + fdfP, length, endIndex);
            assertEquals(locale.toString()+" "+formattedDate +"\n", expectedTime, actualTime);
        } else {
            assertNotEquals("Test data error: expected FDF parse to fail, but got " + actualTime, -1, fdferrorIndex);
            assertTrue("FDF error index ("+ fdferrorIndex + ") should approxiamate SDF index (" + sdferrorIndex + ")",
                    sdferrorIndex - fdferrorIndex <= 4);
        }
    }

    @Test
    public void testLowerCase() throws Exception {
        checkParse(input.toLowerCase(locale));
    }

    @Test
    public void testLowerCasePP() throws Exception {
        checkParsePosition(input.toLowerCase(locale));
    }

    @Test
    public void testOriginal() throws Exception {
        checkParse(input);
    }

    @Test
    public void testOriginalPP() throws Exception {
        checkParsePosition(input);
    }

    @Test
    public void testUpperCase() throws Exception {
        checkParse(input.toUpperCase(locale));
    }
    @Test
    public void testUpperCasePP() throws Exception {
        checkParsePosition(input.toUpperCase(locale));
    }
}
