/*
 * Copyright 2009-2020 Tilmann Zaeschke
 *
 * Licensed 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.zoodb.test.jdo;

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.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

import javax.jdo.JDOUserException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.zoodb.jdo.ZooJdoHelper;
import org.zoodb.schema.ZooClass;
import org.zoodb.test.testutil.TestTools;

public class Test_090_IndexManagement {

	@Before
	public void setUp() {
        TestTools.removeDb();
		TestTools.createDb();
		TestTools.defineSchema(TestClass.class);
	}

	@Test
	public void testWithFreshIndex() {
		//fresh DB w/o index
        TestTools.removeDb();
		TestTools.createDb();

		PersistenceManager pm = TestTools.openPM();
		pm.currentTransaction().begin();

		ZooClass s = ZooJdoHelper.schema(pm).addClass(TestClass.class);
		s.createIndex("_long", true);
		assertTrue(s.hasIndex("_long"));

		pm.currentTransaction().rollback();
		pm.currentTransaction().begin();

		s = ZooJdoHelper.schema(pm).addClass(TestClass.class);
		assertFalse(s.hasIndex("_long"));
		s.createIndex("_long", true);

		pm.currentTransaction().commit();
		pm.currentTransaction().begin();

		assertTrue(s.removeIndex("_long"));
		s.createIndex("_long", true);
		assertTrue(s.removeIndex("_long"));
		
		pm.currentTransaction().commit();
		TestTools.closePM(pm);
	}

	
	/**
	 * This should check whether the processing of the schema operation queue works correctly.
	 */
	@Test
	public void testTransient() {
		//fresh DB w/o index
        TestTools.removeDb();
		TestTools.createDb();

		PersistenceManager pm = TestTools.openPM();
		pm.currentTransaction().begin();

		ZooClass s = ZooJdoHelper.schema(pm).addClass(TestClass.class);
		s.createIndex("_long", true);
		assertTrue(s.removeIndex("_long"));
		s.remove();
		//TODO remove class as well in same transaction
		
		pm.currentTransaction().commit();
		TestTools.closePM(pm);
	}

	
	@Test
	public void testNonExistentField() {
		PersistenceManager pm = TestTools.openPM();
		pm.currentTransaction().begin();

		ZooClass s = ZooJdoHelper.schema(pm).getClass(TestClass.class);
		try {
			//non-existent field
			s.removeIndex("xy!!z");
			fail("Should have failed");
		} catch (IllegalArgumentException e) {
			//good
		}
		
		try {
			//non-existent field
			s.hasIndex("xy!!z");
			fail("Should have failed");
		} catch (IllegalArgumentException e) {
			//good
		}
		
		try {
			//non-existent field
			s.isIndexUnique("xy!!z");
			fail("Should have failed");
		} catch (IllegalArgumentException e) {
			//good
		}
		
		try {
			//non-existent field
			s.createIndex("xy!!z", true);
			fail("Should have failed");
		} catch (IllegalArgumentException e) {
			//good
		}
		
		try {
			//re-create existing index
			s.createIndex("xy!!z", false);
			fail("Should have failed");
		} catch (IllegalArgumentException e) {
			//good
		}
		
		try {
			//non-existent field
			s.removeIndex("xy!!z");
			fail("Should have failed");
		} catch (IllegalArgumentException e) {
			//good
		}
		TestTools.closePM(pm);
	}

	
	@Test
	public void testNonExistentIndex() {
		PersistenceManager pm = TestTools.openPM();
		pm.currentTransaction().begin();

		ZooClass s = ZooJdoHelper.schema(pm).getClass(TestClass.class);

		//non-existent index
		assertFalse(s.removeIndex("_long"));
		
		try {
			//non-existent index
			s.isIndexUnique("_long");
			fail("Should have failed");
		} catch (JDOUserException e) {
			//good
		}

		assertFalse(s.hasIndex("_long"));
		
		TestTools.closePM(pm);
	}

	
	@Test
	public void testIndexCreationWithTx() {
		PersistenceManager pm = TestTools.openPM();
		pm.currentTransaction().begin();

		ZooClass s = ZooJdoHelper.schema(pm).getClass(TestClass.class);
		s.createIndex("_long", true);
		assertTrue(s.hasIndex("_long"));
		pm.currentTransaction().rollback();
		pm.currentTransaction().begin();

		assertFalse(s.hasIndex("_long"));
		s.createIndex("_long", true);
		assertTrue(s.hasIndex("_long"));
		
		pm.currentTransaction().commit();
		pm.currentTransaction().begin();

		assertTrue(s.hasIndex("_long"));

		pm.currentTransaction().rollback();
		TestTools.closePM();
		
		pm = TestTools.openPM();
		pm.currentTransaction().begin();
		
		s = ZooJdoHelper.schema(pm).getClass(TestClass.class);
		assertTrue(s.hasIndex("_long"));

		try {
			//re-create existing index
			s.createIndex("_long", true);
			fail("Should have failed");
		} catch (JDOUserException e) {
			//good
		}
		
		pm.currentTransaction().rollback();
		TestTools.closePM();
	}

	@Test
	public void testIndexCreation() {
		PersistenceManager pm = TestTools.openPM();
		pm.currentTransaction().begin();

		ZooClass s = ZooJdoHelper.schema(pm).getClass(TestClass.class);

		s.createIndex("_long", true);
		
		try {
			//re-create existing index
			s.createIndex("_long", true);
			fail("Should have failed");
		} catch (JDOUserException e) {
			//good
		}
		
		try {
			//re-create existing index
			s.createIndex("_long", false);
			fail("Should have failed");
		} catch (JDOUserException e) {
			//good
		}
		
		assertTrue(s.hasIndex("_long"));
		assertTrue(s.isIndexUnique("_long"));
		
		//remove
		assertTrue(s.removeIndex("_long"));
		assertFalse(s.hasIndex("_long"));
		assertFalse(s.removeIndex("_long"));

		//now try non-unique
		s.createIndex("_long", false);
		assertTrue(s.hasIndex("_long"));
		assertFalse(s.isIndexUnique("_long"));

		//remove again
		s.removeIndex("_long");
		assertFalse(s.hasIndex("_long"));
		assertFalse(s.removeIndex("_long"));
		
		TestTools.closePM(pm);
	}

	
	@Test
	public void testIndexCreationWithExitingObjects() {
		createData();
		
		PersistenceManager pm = TestTools.openPM();
		pm.currentTransaction().begin();

		ZooClass s = ZooJdoHelper.schema(pm).getClass(TestClass.class);

		s.createIndex("_long", true);
		
		try {
			//re-create existing index
			s.createIndex("_long", true);
			fail("Should have failed");
		} catch (JDOUserException e) {
			//good
		}
		
		try {
			//re-create existing index
			s.createIndex("_long", false);
			fail("Should have failed");
		} catch (JDOUserException e) {
			//good
		}
		
		assertTrue(s.hasIndex("_long"));
		assertTrue(s.isIndexUnique("_long"));
		
		//remove
		assertTrue(s.removeIndex("_long"));
		assertFalse(s.hasIndex("_long"));
		assertFalse(s.removeIndex("_long"));

		//now try non-unique
		s.createIndex("_long", false);
		assertTrue(s.hasIndex("_long"));
		assertFalse(s.isIndexUnique("_long"));

		//remove again
		assertTrue(s.removeIndex("_long"));
		assertFalse(s.hasIndex("_long"));
		assertFalse(s.removeIndex("_long"));
		
		TestTools.closePM(pm);
	}

	
	private void createData() {
		PersistenceManager pm = TestTools.openPM();
		pm.currentTransaction().begin();

		TestClass tc1 = new TestClass();
		tc1.setData(1, false, 'c', (byte)127, (short)32000, 1234567890L, "xyz", new byte[]{1,2},
				-1.1f, 35);
		pm.makePersistent(tc1);
		tc1 = new TestClass();
		tc1.setData(12, false, 'd', (byte)127, (short)32000, 1234567890L, "xyz", new byte[]{1,2},
				-0.1f, 3);
		pm.makePersistent(tc1);
		tc1 = new TestClass();
		tc1.setData(123, false, 'e', (byte)127, (short)32000, 1234567890L, "xyz", new byte[]{1,2},
				0.f, -3.5);
		pm.makePersistent(tc1);
		tc1 = new TestClass();
		tc1.setData(1234, false, 'f', (byte)127, (short)32000, 1234567890L, "xyz", new byte[]{1,2},
				1.1f, -35);
		pm.makePersistent(tc1);
		tc1 = new TestClass();
		tc1.setData(12345, false, 'g', (byte)127, (short)32000, 1234567890L, "xyz", new byte[]{1,2},
				21.1f, -335);
		pm.makePersistent(tc1);
		
		pm.currentTransaction().commit();
		TestTools.closePM();
	}
	
	@Test
	public void testIndexCreationWithDifferentTypes() {
		createData();
		
		PersistenceManager pm = TestTools.openPM();
		pm.currentTransaction().begin();

		ZooClass s = ZooJdoHelper.schema(pm).getClass(TestClass.class);

		s.createIndex("_int", false);
		s.createIndex("_long", false);
		s.createIndex("_char", false);
		s.createIndex("_byte", false);
		s.createIndex("_short", false);
		s.createIndex("_string", false);
		s.createIndex("_float", false);
		s.createIndex("_double", false);
		s.createIndex("_ref2", false);

		// not indexable
		checkThatDefinitionFails(pm, s, "_bool");
		// array of primitive
		checkThatDefinitionFails(pm, s, "_bArray");
		// object
		checkThatDefinitionFails(pm, s, "_intObj");
		// static primitive
		checkThatDefinitionFails(pm, s, "_staticInt");
		// static string 
		checkThatDefinitionFails(pm, s, "_staticString");
		// transient primitive
		checkThatDefinitionFails(pm, s, "_transientInt");
		// transient string 
		checkThatDefinitionFails(pm, s, "_transientString");
		// object ref
		checkThatDefinitionFails(pm, s, "_object");
		//object/pers ref
		checkThatDefinitionFails(pm, s, "_ref1");
		
		TestTools.closePM(pm);
	}

	
	private void checkThatDefinitionFails(PersistenceManager pm, ZooClass s, String name) {
		try {
			//re-create existing index
			s.createIndex(name, true);
			fail("Should have failed: " + name);
		} catch (IllegalArgumentException e) {
			//good
		}
		pm.currentTransaction().commit();
		pm.currentTransaction().begin();
	}

	@Test
	public void testIndexCreationUniqueOnNonUniqeData() {
		createData();
		
		PersistenceManager pm = TestTools.openPM();
		pm.currentTransaction().begin();

		TestClass tc1 = new TestClass();
		tc1.setInt(200);
		pm.makePersistent(tc1);
		tc1 = new TestClass();
		tc1.setInt(200);
		pm.makePersistent(tc1);

		pm.currentTransaction().commit();
		pm.currentTransaction().begin();
		
		ZooClass s = ZooJdoHelper.schema(pm).getClass(TestClass.class);
		s.createIndex("_int", true);

		try {
			pm.currentTransaction().commit();
			fail();
		} catch (JDOUserException e) {
			//should fail because of non-unique data
		}
		
		TestTools.closePM(pm);
	}

	@Test
	public void testIndexPropagationInClassHierarchy() {
		PersistenceManager pm = TestTools.openPM();
		pm.currentTransaction().begin();
		ZooClass s1 = ZooJdoHelper.schema(pm).addClass(TestClassTiny.class);
		ZooClass s2 = ZooJdoHelper.schema(pm).addClass(TestClassTiny2.class);

		pm.currentTransaction().commit();
		pm.currentTransaction().begin();

		s1 = ZooJdoHelper.schema(pm).getClass(TestClassTiny.class);
		s2 = ZooJdoHelper.schema(pm).getClass(TestClassTiny2.class);
		s1.createIndex("_long", true);
		s2.createIndex("_int", true);

		//rollback and do it again
		pm.currentTransaction().rollback();
		pm.currentTransaction().begin();
		
		s1 = ZooJdoHelper.schema(pm).getClass(TestClassTiny.class);
		s2 = ZooJdoHelper.schema(pm).getClass(TestClassTiny2.class);
		s1.createIndex("_long", true);
		s2.createIndex("_int", true);

		//close and reopen
		pm.currentTransaction().commit();
		TestTools.closePM();
		pm = TestTools.openPM();
		pm.currentTransaction().begin();

		//test modify super-class
		s1 = ZooJdoHelper.schema(pm).getClass(TestClassTiny.class);
		s2 = ZooJdoHelper.schema(pm).getClass(TestClassTiny2.class);
		
		assertTrue(s1.hasIndex("_long"));
		//This is not possible, current policy is that indexing works only through declaring class.
		assertTrue(s2.hasIndex("_long"));
		assertTrue(s2.hasIndex("_int"));
		
		assertEquals(Arrays.toString(s1.getSubClasses().toArray()), 1, s1.getSubClasses().size());
		assertEquals(1, s1.getSubClasses().size());
		assertEquals(0, s2.getSubClasses().size());
		
		try {
			Query q = pm.newQuery("SELECT FROM " + s1.getName() + " WHERE _long1 > 0");
			Collection<?> c = (Collection<?>) q.execute();
			assertEquals(0, c.size());
		} catch (JDOUserException e) {
			//good, class not found, cannot be materialised
		}
		
		Iterator<?> it = s1.getInstanceIterator();
		assertFalse(it.hasNext());
		
		pm.currentTransaction().commit();
		TestTools.closePM();
	}


	
	@After
	public void afterTest() {
		TestTools.closePM();
		TestTools.removeDb();
	}
}
