package org.exist.xquery.test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import junit.framework.TestCase;
import junit.textui.TestRunner;

import org.exist.storage.DBBroker;
import org.xmldb.api.DatabaseManager;
import org.xmldb.api.base.Collection;
import org.xmldb.api.base.Database;
import org.xmldb.api.base.ResourceSet;
import org.xmldb.api.base.XMLDBException;
import org.xmldb.api.modules.CollectionManagementService;
import org.xmldb.api.modules.XMLResource;
import org.xmldb.api.modules.XPathQueryService;


public class SeqOpTest extends TestCase {

	private final static String URI = "xmldb:exist://" + DBBroker.ROOT_COLLECTION;
	private final static String DRIVER = "org.exist.xmldb.DatabaseImpl";

	private XPathQueryService query;
	private Collection c;
	
	public static void main(String[] args) {
		TestRunner.run(SeqOpTest.class);
	}
	
	public SeqOpTest(String name) {
		super(name);
	}
	
	public void testReverseEmpty() throws XMLDBException {
		assertSeq(new String[0], "reverse(())");
	}
	
	public void testReverseAtomic1() throws XMLDBException {
		assertSeq(new String[]{"a"}, "reverse(('a'))");
	}
	
	public void testReverseAtomic2() throws XMLDBException {
		assertSeq(new String[]{"b", "a"}, "reverse(('a', 'b'))");
	}
	
	public void testReverseNodes1() throws XMLDBException {
		createDocument("foo", "<top><a/><b/></top>");
		assertSeq(new String[]{"<a/>"}, "reverse(//a)");
	}
	
	public void testReverseNodes2() throws XMLDBException {
		createDocument("foo", "<top><a/><b/></top>");
		assertSeq(new String[]{"<b/>", "<a/>"}, "reverse(/top/*)");
	}
	
	public void testReverseMixed() throws XMLDBException {
		createDocument("foo", "<top><a/><b/></top>");
		assertSeq(new String[]{"c", "<b/>", "<a/>"}, "reverse((/top/*, 'c'))");
	}
	
	public void testRemoveEmpty1() throws XMLDBException {
		assertSeq(new String[0], "remove((), 1)");
	}
	
	public void testRemoveEmpty2() throws XMLDBException {
		assertSeq(new String[0], "remove((), 0)");
	}
	
	public void testRemoveEmpty3() throws XMLDBException {
		assertSeq(new String[0], "remove((), 42)");
	}
	
	public void testRemoveOutOfBounds1() throws XMLDBException {
		assertSeq(new String[]{"a", "b"}, "remove(('a', 'b'), 0)");
	}
	
	public void testRemoveOutOfBounds2() throws XMLDBException {
		assertSeq(new String[]{"a", "b"}, "remove(('a', 'b'), 3)");
	}
	
	public void testRemoveOutOfBounds3() throws XMLDBException {
		assertSeq(new String[]{"a", "b"}, "remove(('a', 'b'), -1)");
	}
	
	public void testRemoveAtomic1() throws XMLDBException {
		assertSeq(new String[]{"b", "c"}, "remove(('a', 'b', 'c'), 1)");
	}
	
	public void testRemoveAtomic2() throws XMLDBException {
		assertSeq(new String[]{"a", "c"}, "remove(('a', 'b', 'c'), 2)");
	}
	
	public void testRemoveAtomic3() throws XMLDBException {
		assertSeq(new String[]{"a", "b"}, "remove(('a', 'b', 'c'), 3)");
	}

	public void testRemoveMixed1() throws XMLDBException {
		createDocument("foo", "<top><a/><b/></top>");
		assertSeq(new String[]{"<b/>", "a", "b", "c"}, "remove((/top/*, 'a', 'b', 'c'), 1)");
	}
	
	public void testRemoveMixed2() throws XMLDBException {
		createDocument("foo", "<top><a/><b/></top>");
		assertSeq(new String[]{"<a/>", "a", "b", "c"}, "remove((/top/*, 'a', 'b', 'c'), 2)");
	}
	
	public void testRemoveMixed3() throws XMLDBException {
		createDocument("foo", "<top><a/><b/></top>");
		assertSeq(new String[]{"<a/>", "<b/>", "b", "c"}, "remove((/top/*, 'a', 'b', 'c'), 3)");
	}
	
	public void testRemoveNodes1() throws XMLDBException {
		createDocument("foo", "<top><a/><b/><c/></top>");
		assertSeq(new String[]{"<b/>", "<c/>"}, "remove(/top/*, 1)");
	}
	
	public void testRemoveNodes2() throws XMLDBException {
		createDocument("foo", "<top><a/><b/><c/></top>");
		assertSeq(new String[]{"<a/>", "<c/>"}, "remove(/top/*, 2)");
	}
	
	public void testRemoveNodes3() throws XMLDBException {
		createDocument("foo", "<top><a/><b/><c/></top>");
		assertSeq(new String[]{"<a/>", "<b/>"}, "remove(/top/*, 3)");
	}
	
	public void testInsertEmpty1() throws XMLDBException {
		assertSeq(new String[0], "insert-before((), 1, ())");
	}
	
	public void testInsertEmpty2() throws XMLDBException {
		assertSeq(new String[]{"a"}, "insert-before((), 1, ('a'))");
	}
	
	public void testInsertEmpty3() throws XMLDBException {
		assertSeq(new String[]{"a"}, "insert-before(('a'), 1, ())");
	}
	
	public void testInsertOutOfBounds1() throws XMLDBException {
		assertSeq(new String[]{"c", "d", "a", "b"}, "insert-before(('a', 'b'), 0, ('c', 'd'))");
	}
	
	public void testInsertOutOfBounds2() throws XMLDBException {
		assertSeq(new String[]{"a", "b", "c", "d"}, "insert-before(('a', 'b'), 3, ('c', 'd'))");
	}
	
	public void testInsertOutOfBounds3() throws XMLDBException {
		assertSeq(new String[]{"a", "b", "c", "d"}, "insert-before(('a', 'b'), 4, ('c', 'd'))");
	}
	
	public void testInsertAtomic1() throws XMLDBException {
		assertSeq(new String[]{"a", "c", "d", "b"}, "insert-before(('a', 'b'), 2, ('c', 'd'))");
	}
	
	public void testInsertAtomic2() throws XMLDBException {
		assertSeq(new String[]{"c", "d", "a", "b"}, "insert-before(('a', 'b'), 1, ('c', 'd'))");
	}
	
	public void testInsertAtomic3() throws XMLDBException {
		assertSeq(new String[]{"a", "a", "b", "b"}, "insert-before(('a', 'b'), 2, ('a', 'b'))");
	}
	
	public void testInsertNodes1() throws XMLDBException {
		createDocument("foo", "<top><x><a/><b/></x><y><c/><d/></y></top>");
		assertSeq(new String[]{"<a/>", "<c/>", "<d/>", "<b/>"}, "insert-before(/top/x/*, 2, /top/y/*)");
	}
	
	public void testInsertNodes2() throws XMLDBException {
		createDocument("foo", "<top><x><a/><b/></x><y><c/><d/></y></top>");
		assertSeq(new String[]{"<c/>", "<d/>", "<a/>", "<b/>"}, "insert-before(/top/x/*, 1, /top/y/*)");
	}

	public void testInsertNodes3() throws XMLDBException {
		createDocument("foo", "<top><x><a/><b/></x><y><c/><d/></y></top>");
		assertSeq(new String[]{"<a/>", "<b/>", "<c/>", "<d/>"}, "insert-before(/top/x/*, 3, /top/y/*)");
	}

	// TODO: currently fails because duplicate nodes are removed
	public void testInsertNodes4() throws XMLDBException {
		createDocument("foo", "<top><x><a/><b/></x><y><c/><d/></y></top>");
		assertSeq(new String[]{"<a/>", "<a/>", "<b/>", "<b/>"}, "insert-before(/top/x/*, 2, /top/x/*)");
	}

	public void testInsertMixed1() throws XMLDBException {
		createDocument("foo", "<top><x><a/><b/></x><y><c/><d/></y></top>");
		assertSeq(new String[]{"<a/>", "c", "<b/>"}, "insert-before(/top/x/*, 2, ('c'))");
	}

	// TODO: currently fails because duplicate nodes are removed
	public void testInsertMixed2() throws XMLDBException {
		createDocument("foo", "<top><x><a/><b/></x><y><c/><d/></y></top>");
		assertSeq(new String[]{"<a/>", "<a/>", "<b/>", "<b/>", "c"}, "insert-before((/top/x/*, 'c'), 2, /top/x/*)");
	}

	private void assertSeq(String[] expected, String q) throws XMLDBException {
		ResourceSet rs = query.query(q);
		assertEquals(expected.length, rs.getSize());
		List a = Arrays.asList(expected);
		List r = new ArrayList((int) rs.getSize());
		for (int i = 0; i < rs.getSize(); i++) r.add(rs.getResource(i).getContent());
		if (!a.equals(r)) fail("expected " + a + ", got " + r);
	}
	
	private XMLResource createDocument(String name, String content) throws XMLDBException {
		XMLResource res = (XMLResource) c.createResource(name, XMLResource.RESOURCE_TYPE);
		res.setContent(content);
		c.storeResource(res);
		return res;
	}

	private Collection setupTestCollection() throws XMLDBException {
		Collection root = DatabaseManager.getCollection(URI, "admin", "");
		CollectionManagementService rootcms = (CollectionManagementService) root.getService("CollectionManagementService", "1.0");
		Collection c = root.getChildCollection("test");
		if(c != null) rootcms.removeCollection("test");
		rootcms.createCollection("test");
		c = DatabaseManager.getCollection(URI+"/test", "admin", "");
		assertNotNull(c);
		return c;
	}

	protected void setUp() {
		try {
			// initialize driver
			Database database = (Database) Class.forName(DRIVER).newInstance();
			database.setProperty("create-database", "true");
			DatabaseManager.registerDatabase(database);
			
			c = setupTestCollection();
			query = (XPathQueryService) c.getService("XPathQueryService", "1.0");

		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException("failed setup", e);
		}
	}
	
	protected void tearDown() {
		try {
			if (c != null) c.close();
            c = null;
            query = null;
		} catch (XMLDBException e) {
			throw new RuntimeException("failed teardown", e);
		}
	}
	
}
