/*
 * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights
 * Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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 com.amazonaws.util;

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

import java.nio.charset.Charset;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.SimpleTimeZone;

import com.amazonaws.protocol.json.SdkJsonGenerator;
import com.amazonaws.protocol.json.StructuredJsonGenerator;
import com.fasterxml.jackson.core.JsonFactory;

import org.joda.time.DateTime;
import org.joda.time.IllegalFieldValueException;
import org.junit.Test;

public class DateUtilsTest {
    private static final boolean DEBUG = false;
    @Test
    public void tt0031561767() throws ParseException {
        String input = "Fri, 16 May 2014 23:56:46 GMT";
        Date date = DateUtils.parseRFC822Date(input);
        assertEquals(input, DateUtils.formatRFC822Date(date));
    }

    @Test
    public void formatIso8601Date() throws ParseException {
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        sdf.setTimeZone(new SimpleTimeZone(0, "GMT"));
        String expected = sdf.format(date);
        String actual = DateUtils.formatISO8601Date(date);
        assertEquals(expected, actual);

        Date expectedDate = sdf.parse(expected);
        Date actualDate = DateUtils.parseISO8601Date(actual);
        assertEquals(expectedDate, actualDate);
    }

    @Test
    public void formatRfc822Date() throws ParseException {
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
        sdf.setTimeZone(new SimpleTimeZone(0, "GMT"));
        String expected = sdf.format(date);
        String actual = DateUtils.formatRFC822Date(date);
        assertEquals(expected, actual);

        Date expectedDate = sdf.parse(expected);
        Date actualDate = DateUtils.parseRFC822Date(actual);
        assertEquals(expectedDate, actualDate);
    }

    @Test
    public void parseCompressedIso8601Date() throws ParseException {
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
        sdf.setTimeZone(new SimpleTimeZone(0, "GMT"));
        String formatted = sdf.format(date);
        Date expected = sdf.parse(formatted);
        Date actual = DateUtils.parseCompressedISO8601Date(formatted);
        assertEquals(expected, actual);
    }

    @Test
    public void parseRfc822Date() throws ParseException {
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
        sdf.setTimeZone(new SimpleTimeZone(0, "GMT"));
        String formatted = sdf.format(date);
        Date expected = sdf.parse(formatted);
        Date actual = DateUtils.parseRFC822Date(formatted);
        assertEquals(expected, actual);
    }

    @Test
    public void parseIso8601Date() throws ParseException {
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        sdf.setTimeZone(new SimpleTimeZone(0, "GMT"));
        String formatted = sdf.format(date);
        String alternative = DateUtils.iso8601DateFormat.print(date.getTime());
        assertEquals(formatted, alternative);

        Date expectedDate = sdf.parse(formatted);
        Date actualDate = DateUtils.parseISO8601Date(formatted);
        assertEquals(expectedDate, actualDate);
    }

    @Test
    public void parseIso8601Date_usingAlternativeFormat() throws ParseException {
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        sdf.setTimeZone(new SimpleTimeZone(0, "GMT"));
        String formatted = sdf.format(date);
        String alternative = DateUtils.alternateIso8601DateFormat.print(date.getTime());
        assertEquals(formatted, alternative);

        Date expectedDate = sdf.parse(formatted);
        Date actualDate = DateUtils.parseISO8601Date(formatted);
        assertEquals(expectedDate, actualDate);
    }

    @Test
    public void alternateIso8601DateFormat() throws ParseException {
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        sdf.setTimeZone(new SimpleTimeZone(0, "GMT"));
        String expected = sdf.format(date);
        String actual = DateUtils.alternateIso8601DateFormat.print(date.getTime());
        assertEquals(expected, actual);

        Date expectedDate = sdf.parse(expected);
        DateTime actualDateTime = DateUtils.alternateIso8601DateFormat.parseDateTime(actual);
        assertEquals(expectedDate, new Date(actualDateTime.getMillis()));
    }

    @Test
    public void legacyHandlingOfInvalidDate() throws ParseException {
        final String input = "2014-03-06T14:28:58.000Z.000Z";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        sdf.setTimeZone(new SimpleTimeZone(0, "GMT"));
        sdf.parse(input);
    }

    @Test(expected=IllegalArgumentException.class)
    public void invalidDate() throws ParseException {
        final String input = "2014-03-06T14:28:58.000Z.000Z";
        DateUtils.parseISO8601Date(input);
    }
    @Test
    public void test() throws ParseException {
        Date date = new Date();
        System.out.println("         formatISO8601Date: " + DateUtils.formatISO8601Date(date));
        System.out.println("alternateIso8601DateFormat: " + DateUtils.alternateIso8601DateFormat.print(date.getTime()));
    }

    @Test
    public void testIssue233() throws ParseException {
        // https://github.com/aws/aws-sdk-java/issues/233
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        sdf.setTimeZone(new SimpleTimeZone(0, "GMT"));
        final String edgeCase = "292278994-08-17T07:12:55.807Z";
        Date expected = sdf.parse(edgeCase);
        if (DEBUG)
            System.out.println("date: " + expected);
        String formatted = DateUtils.formatISO8601Date(expected);
        if (DEBUG)
            System.out.println("formatted: " + formatted);
        assertEquals(edgeCase, formatted);
        Date parsed = DateUtils.parseISO8601Date(edgeCase);
        if (DEBUG)
            System.out.println("parsed: " + parsed);
        assertEquals(expected, parsed);
        String reformatted = DateUtils.formatISO8601Date(parsed);
        assertEquals(edgeCase, reformatted);
    }

    @Test(expected=IllegalFieldValueException.class)
    public void testIssue233JodaTimeLimit() throws ParseException {
        // https://github.com/aws/aws-sdk-java/issues/233
        String s = DateUtils.iso8601DateFormat.print(Long.MAX_VALUE);
        System.out.println("s: " + s);
        try {
            DateTime dt = DateUtils.iso8601DateFormat.parseDateTime(s);
            fail("Unexpected success: " + dt);
        } catch(IllegalFieldValueException ex) {
            // expected
            throw ex;
        }
    }

    @Test
    public void testIssueDaysDiff() throws ParseException {
        // https://github.com/aws/aws-sdk-java/issues/233
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        sdf.setTimeZone(new SimpleTimeZone(0, "GMT"));
        String edgeCase = "292278994-08-17T07:12:55.807Z";
        String testCase = "292278993-08-17T07:12:55.807Z";
        Date od = sdf.parse(edgeCase);
        Date testd = sdf.parse(testCase);
        long diff = od.getTime() - testd.getTime();
        assertTrue(diff == 365L*24*60*60*1000);
    }

    @Test
    public void testIssue233Overflows() throws ParseException {
        String[] cases = {
                // 1 milli second passed the max time
                "292278994-08-17T07:12:55.808Z",
                // 1 year passed the max year
                "292278995-01-17T07:12:55.807Z", };
        for (String edgeCase : cases) {
            try {
                Date parsed = DateUtils.parseISO8601Date(edgeCase);
                fail("Unexpected success: " + parsed);
            } catch (IllegalArgumentException ex) {
                String msg = ex.getMessage();
                assertTrue(msg
                        .contains("must be in the range [-292275054,292278993]"));
            }
        }
    }

    /**
     * Tests the Date marshalling and unmarshalling. Asserts that the value is
     * same before and after marshalling/unmarshalling
     */
    @Test
    public void testAWSFormatDateUtils() throws Exception {
        testDate(System.currentTimeMillis());
        testDate(1L);
        testDate(0L);
    }

    private void testDate(long dateInMilliSeconds) {
        String serverSpecificDateFormat = DateUtils
                .formatServiceSpecificDate(new Date(dateInMilliSeconds));

        Date parsedDate = DateUtils.parseServiceSpecificDate(String
                .valueOf(serverSpecificDateFormat));

        assertEquals(Long.valueOf(dateInMilliSeconds),
                Long.valueOf(parsedDate.getTime()));

    }

    // See https://forums.aws.amazon.com/thread.jspa?threadID=158756
    @Test
    public void testNumericNoQuote() {
        StructuredJsonGenerator jw = new SdkJsonGenerator(new JsonFactory(), null);
        jw.writeStartObject();
        jw.writeFieldName("foo").writeValue(new Date());
        jw.writeEndObject();
        String s = new String(jw.getBytes(), Charset.forName("UTF-8"));
        // Something like: {"foo":1408378076.135}.
        // Note prior to the changes, it was {"foo":1408414571}
        // (with no decimal point nor places.)
        System.out.println(s);
        final String prefix = "{\"foo\":";
        assertTrue(s, s.startsWith(prefix));
        final int startPos = prefix.length();
        // verify no starting quote for the value
        assertFalse(s, s.startsWith("{\"foo\":\""));
        assertTrue(s, s.endsWith("}"));
        // Not: {"foo":"1408378076.135"}.
        // verify no ending quote for the value
        assertFalse(s, s.endsWith("\"}"));
        final int endPos = s.indexOf("}");
        final int dotPos = s.length()-5;
        assertTrue(s, s.charAt(dotPos) == '.');
        // verify all numeric before '.'
        char[] a = s.toCharArray();
        for (int i=startPos; i < dotPos; i++)
            assertTrue(a[i] <= '9' && a[i] >= '0' );
        int j=0;
        // verify all numeric after '.'
        for (int i=dotPos+1; i < endPos; i++) {
            assertTrue(a[i] <= '9' && a[i] >= '0' );
            j++;
        }
        // verify decimal precision of exactly 3
        assertTrue(j == 3);
    }

    @Test
    public void numberOfDaysSinceEpoch() {
        final long now = System.currentTimeMillis();
        final long days = DateUtils.numberOfDaysSinceEpoch(now);
        final int oneDayMilli = 24*60*60*1000;
        assertTrue(now > days*oneDayMilli);
        assertTrue((now - days*oneDayMilli) <= oneDayMilli);
    }
}