package org.apache.rya.api.resolver.impl;

/*
 * 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.
 */

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.util.Date;
import java.util.GregorianCalendar;

import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.rya.api.domain.RyaType;
import org.apache.rya.api.resolver.RdfToRyaConversions;
import org.apache.rya.api.resolver.RyaTypeResolverException;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.vocabulary.XMLSchema;
import org.junit.Test;

/**
 * Test serializing and deserializing.
 * Notes:
 * The serialization, deserialization fills in some information:
 * If preserving uncertainty, or preserving the source timezone, then don't use XML type tag.
 * 		- uncertainty: missing time hh:mm:ss becomes 00:00:00 
 * 		- uncertainty: missing milliseconds (.123) become .000.
 * 		- uncertainty: missing timezone becomes the system local timezone.
 * 		- timezone: converted to the equivalent Z timezone.  
 * 		- a type XMLSchema.DATE become XMLSchema.DATETIME after deserialized
 * 
 * 		ex: run in timezone eastern time (GMT-5:00): 
 * 			before=       2000-02-02                 type = XMLSchema.DATE
 * 			deserialized= 2000-02-02T05:00:00.000Z   type = XMLSchema.DATETIME
 */
public class DateTimeRyaTypeResolverTest {
    private static final ValueFactory VF = SimpleValueFactory.getInstance();

    @Test
    public void testDateTime() throws Exception {
        long currentTime = 1342182689285l;
        Date date = new Date(currentTime);
        GregorianCalendar gc = new GregorianCalendar();
        gc.setTimeInMillis(date.getTime());
        XMLGregorianCalendar xmlGregorianCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(gc);
        Literal literal = VF.createLiteral(xmlGregorianCalendar);
        byte[] serialize = new DateTimeRyaTypeResolver().serialize(RdfToRyaConversions.convertLiteral(literal));
        RyaType deserialize = new DateTimeRyaTypeResolver().deserialize(serialize);
        assertEquals("2012-07-13T12:31:29.285Z", deserialize.getData());
        assertEquals(XMLSchema.DATETIME, deserialize.getDataType());
    }
	@Test
    public void testFull() throws Exception {
        String currentTime = "2000-01-01T00:00:01.111Z";
		assertSerializeAndDesDateTime("2000-01-01T00:00:01.111Z");
		
    }
	@Test
    public void testNoMilliSeconds() throws Exception {
		assertSerializeAndDesDateTime("2000-01-01T00:00:01Z","2000-01-01T00:00:01.000Z");
		
    }
	@Test
    public void testDateNoTimeNoZone() throws Exception {
        String beforeDate = "2000-02-02";
    	String afterDate="2000-02-0(1|2|3)T\\d\\d:\\d\\d:00\\.000Z";
    	RyaType deserialize = serializeAndDeserialize(beforeDate, XMLSchema.DATE);
	    final String afterActual = deserialize.getData();
		assertTrue("Before='"+beforeDate+"'; Expected should match actual regex after='"+afterDate+"' deserialized:"+afterActual, afterActual.matches(afterDate));
        assertEquals(XMLSchema.DATETIME, deserialize.getDataType());
    }
	@Test
    public void testDateZoneNoTime() throws Exception {
		// if you see this:
		//java.lang.IllegalArgumentException: Invalid format: "2000-02-02Z" is malformed at "Z"
		// use this: "2000-02-02TZ";
        String currentTime = "2000-02-02TZ";
    	RyaType deserialize = serializeAndDeserialize(currentTime, XMLSchema.DATE);
        assertEquals("Before expected should match after actual deserialized:","2000-02-02T00:00:00.000Z", deserialize.getData());
        assertEquals(XMLSchema.DATETIME, deserialize.getDataType());
    }
	@Test
    public void testNoZone() throws Exception {
		String beforeDate = "2000-01-02T00:00:01";
    	String afterDate="2000-01-0(1|2|3)T\\d\\d:\\d\\d:01\\.000Z";
    	RyaType deserialize = serializeAndDeserialize(beforeDate, XMLSchema.DATE);
	    final String afterActual = deserialize.getData();
		assertTrue("Before='"+beforeDate+"'; Expected should match actual regex after='"+afterDate+"' deserialized:"+afterActual, afterActual.matches(afterDate));
        assertEquals(XMLSchema.DATETIME, deserialize.getDataType());
		
    }
    @Test
	public void testMilliSecondsNoZone() throws Exception {
    	String beforeDate="2002-02-02T02:02:02.222";
	String afterDate="2002-02-0(1|2|3)T\\d\\d:\\d\\d:02\\.222.*";
		RyaType deserialize = serializeAndDeserialize(beforeDate, XMLSchema.DATETIME);
	    final String afterActual = deserialize.getData();
		assertTrue("Before='"+beforeDate+"'; Expected should match actual regex after='"+afterDate+"' deserialized:"+afterActual, afterActual.matches(afterDate));
	    assertEquals(XMLSchema.DATETIME, deserialize.getDataType());
		
	}
    @Test
	public void testHistoryAndFuture() throws Exception {
		assertSerializeAndDesDateTime("-2000-01-01T00:00:01Z","-2000-01-01T00:00:01.000Z");
		assertSerializeAndDesDateTime("111-01-01T00:00:01Z","0111-01-01T00:00:01.000Z");
		assertSerializeAndDesDateTime("12345-01-01T00:00:01Z","12345-01-01T00:00:01.000Z");
	}

    @Test
	public void testTimeZone() throws Exception {
		assertSerializeAndDesDateTime(    "2000-01-01T00:00:01+01:00", "1999-12-31T23:00:01.000Z");
		assertSerializeAndDesDateTime(    "2000-01-01T00:00:01+02:30", "1999-12-31T21:30:01.000Z");
		assertSerializeAndDesDateTime("2000-01-01T00:00:01.123-02:00", "2000-01-01T02:00:01.123Z");
		assertSerializeAndDesDateTime(     "111-01-01T00:00:01+14:00", "0110-12-31T10:00:01.000Z" );
		assertSerializeAndDesDateTime(   "12345-01-01T00:00:01-14:00","12345-01-01T14:00:01.000Z");
		assertSerializeAndDesDateTime(       "1-01-01T00:00:01+14:00", "0000-12-31T10:00:01.000Z" ); 
	}

    @Test
    public void testGarbageIn() throws Exception {
        String currentTime = "Blablabla";
		RyaType ryaType = new RyaType(XMLSchema.DATETIME, currentTime );
		Throwable threw=null;
		try {
			new DateTimeRyaTypeResolver().serialize(ryaType);
		} catch (java.lang.IllegalArgumentException exception) {
			threw = exception;
		}
		assertNotNull("Expected to catch bad format message.",threw);
		assertEquals("Caught bad format message.","Invalid format: \"Blablabla\"", threw.getMessage());
    }
	/**
	 * Do the test on the DateTime
	 * @param dateTimeString
	 * @throws RyaTypeResolverException
	 */
	private void assertSerializeAndDesDateTime(String dateTimeString) throws RyaTypeResolverException {
		assertSerializeAndDesDateTime(dateTimeString, dateTimeString); 
	}
	private void assertSerializeAndDesDateTime(String beforeDate, String afterDate ) throws RyaTypeResolverException {
		RyaType deserialize = serializeAndDeserialize(beforeDate, XMLSchema.DATETIME);
	    assertEquals("Before='"+beforeDate+"'; Expected should match actual after deserialized:",afterDate, deserialize.getData());
	    assertEquals(XMLSchema.DATETIME, deserialize.getDataType());
	}
	/**
	 * Serialize a datetime string, then deserialize as a ryaType.
	 * @param dateTimeString
	 * @param type if null , use default: XMLSchema.DATETIME
	 * @return
	 * @throws RyaTypeResolverException
	 */
	private RyaType serializeAndDeserialize(String dateTimeString,  IRI type ) throws RyaTypeResolverException {
		if (type == null) 
			type = XMLSchema.DATETIME;
		RyaType ryaType = new RyaType(type, dateTimeString ); 
	    byte[] serialize = new DateTimeRyaTypeResolver().serialize(ryaType);
	    return new DateTimeRyaTypeResolver().deserialize(serialize);
	}
}
