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

import org.apache.cassandra.db.marshal.SimpleDateType;
import org.junit.Test;

import java.nio.ByteBuffer;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.time.temporal.ChronoUnit;
import java.util.*;

public class SimpleDateSerializerTest
{
    private static final long millisPerDay = ChronoUnit.DAYS.getDuration().toMillis();

    private String dates[] = new String[]
    {
            "1970-01-01",
            "1970-01-02",
            "1969-12-31",
            "-0001-01-02",
            "-5877521-01-02",
            "2014-01-01",
            "+5881580-01-10", // See java.time.format.SignStyle.EXCEEDS_PAD
            "1920-12-01",
            "1582-10-19"
    };

    private static GregorianCalendar testCalendar = new GregorianCalendar();
    private static SimpleDateFormat dateFormatUTC = new SimpleDateFormat("yyyy-MM-dd");

    {
        testCalendar.setGregorianChange(new Date(Long.MIN_VALUE));
        testCalendar.setTimeZone(TimeZone.getTimeZone("UTC"));
        dateFormatUTC.setCalendar(testCalendar);
        dateFormatUTC.setLenient(false);
    }

    @Test
    public void testDateStringToTimestamp()
    {
        List<String> unparsedDates = new ArrayList<>();
        List<String> badParseResults = new ArrayList<>();
        for (String date : dates)
        {
            try
            {
                Integer days = SimpleDateSerializer.dateStringToDays(date);
                ByteBuffer value = SimpleDateSerializer.instance.serialize(days);
                Integer deserialized = SimpleDateSerializer.instance.deserialize(value);

                String toStringValue = SimpleDateSerializer.instance.toString(deserialized);
                if (!date.equals(toStringValue)) {
                    badParseResults.add(String.format("Failed to parse date correctly.  Expected %s, got %s\n", date, toStringValue));
                }
            }
            catch (MarshalException e)
            {
                System.err.println("Got an exception: " + e);
                unparsedDates.add(date);
            }
        }
        assert unparsedDates.isEmpty() : "Unable to parse: " + unparsedDates;
        assert badParseResults.isEmpty() : "Incorrect parse results: " + badParseResults;
    }

    @Test
    public void testDaysStringToInt()
    {
        Integer value = SimpleDateSerializer.dateStringToDays("12345");
        assert value.compareTo(12345) == 0 : String.format("Failed to parse integer based date.  Expected %s, got %s",
                12345,
                value);
    }

    @Test
    public void testProlepticRange()
    {
        for (int i = 1; i < 31; ++i)
        {
            String date = "1582-10-";
            if (i < 10) date += "0";
            date += i;

            Integer days = SimpleDateSerializer.dateStringToDays(date);

            ByteBuffer value = SimpleDateType.instance.fromString(days.toString());
            Integer deserialized = SimpleDateSerializer.instance.deserialize(value);

            // Serialized values are unsigned int, unwrap bits w/overflow
            deserialized -= Integer.MIN_VALUE;

            Timestamp ts = new Timestamp(deserialized * millisPerDay);
            testCalendar.setTime(ts);

            Date newDate = testCalendar.getTime();
            assert (dateFormatUTC.format(newDate)).equals(date) :
                    String.format("Expected [%s], got [%s]", date, dateFormatUTC.format(newDate).toString());
        }
    }

    @Test (expected=MarshalException.class)
    public void testOutOfBoundsLow()
    {
        Integer days = SimpleDateSerializer.dateStringToDays(new Date(Integer.MIN_VALUE * millisPerDay - millisPerDay).toString());
    }

    @Test (expected=MarshalException.class)
    public void testOutOfBoundsHigh()
    {
        Integer days = SimpleDateSerializer.dateStringToDays(new Date(Integer.MAX_VALUE * millisPerDay + millisPerDay).toString());
    }

    @Test (expected=MarshalException.class)
    public void testBadInput()
    {
        Integer days = SimpleDateSerializer.dateStringToDays("12A-01-01");
    }

    @Test (expected=MarshalException.class)
    public void testBadMonth()
    {
        Integer days = SimpleDateSerializer.dateStringToDays("1000-13-01");
    }

    @Test (expected=MarshalException.class)
    public void testBadDay()
    {
        Integer days = SimpleDateSerializer.dateStringToDays("1000-12-32");
    }

    @Test (expected=MarshalException.class)
    public void testBadDayToMonth()
    {
        Integer days = SimpleDateSerializer.dateStringToDays("1000-09-31");
    }
}
