package org.exist.xquery.test;

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

import org.exist.storage.DBBroker;
import org.xmldb.api.DatabaseManager;
import org.xmldb.api.base.*;
import org.xmldb.api.modules.*;


public class DeepEqualTest 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(DeepEqualTest.class);
	}
	
	public DeepEqualTest(String name) {
		super(name);
	}
	
	public void testAtomic1() {
		assertQuery(true, "deep-equal('hello', 'hello')");
	}
	
	public void testAtomic2() {
		assertQuery(false, "deep-equal('hello', 'goodbye')");
	}
	
	public void testAtomic3() {
		assertQuery(true, "deep-equal(42, 42)");
	}
	
	public void testAtomic4() {
		assertQuery(false, "deep-equal(42, 17)");
	}
	
	public void testAtomic5() {
		assertQuery(false, "deep-equal(42, 'hello')");
	}
	
	public void testAtomic6() {
		assertQuery(true, "deep-equal( 1. , xs:integer(1) )" );
		assertQuery(true, "deep-equal( xs:double(1) , xs:integer(1) )" );
	}
	
	public void testEmptySeq() {
		assertQuery(true, "deep-equal((), ())");
	}
	
	public void testDiffLengthSeq1() {
		assertQuery(false, "deep-equal((), 42)");
	}
	
	public void testDiffLengthSeq2() {
		assertQuery(false, "deep-equal((), (42, 'hello'))");
	}
	
	public void testDiffKindNodes1() {
		createDocument("test", "<test key='value'>hello</test>");
		assertQuery(false, "deep-equal(/test, /test/@key)");
	}
	
	public void testDiffKindNodes2() {
		createDocument("test", "<test key='value'>hello</test>");
		assertQuery(false, "deep-equal(/test, /test/text())");
	}
	
	public void testDiffKindNodes3() {
		createDocument("test", "<test key='value'>hello</test>");
		assertQuery(false, "deep-equal(/test/@key, /test/text())");
	}
	
	public void testSameNode1() {
		createDocument("test", "<test key='value'>hello</test>");
		assertQuery(true, "deep-equal(/test, /test)");
	}
		
	public void testSameNode2() {
		createDocument("test", "<test key='value'>hello</test>");
		assertQuery(true, "deep-equal(/test/@key, /test/@key)");
	}
		
	public void testSameNode3() {
		createDocument("test", "<test key='value'>hello</test>");
		assertQuery(true, "deep-equal(/test/text(), /test/text())");
	}
	
	public void testDocuments1() {
		createDocument("test1", "<test key='value'>hello</test>");
		createDocument("test2", "<test key='value'>hello</test>");
		assertQuery(true, "deep-equal(document('test1'), document('test2'))");
	}
	
	public void testDocuments2() {
		createDocument("test1", "<test key='value'>hello</test>");
		createDocument("test2", "<notatest/>");
		assertQuery(false, "deep-equal(document('test1'), document('test2'))");
	}
	
	public void testText1() {
		createDocument("test", "<test><g1><a>1</a><b>2</b></g1><g2><c>1</c><d>2</d></g2></test>");
		assertQuery(true, "deep-equal(//a/text(), //c/text())");
	}
	
	public void testText2() {
		createDocument("test", "<test><g1><a>1</a><b>2</b></g1><g2><c>1</c><d>2</d></g2></test>");
		assertQuery(false, "deep-equal(//a/text(), //b/text())");
	}
	
	public void testText3() {
		createDocument("test", "<test><g1><a>1</a><b>2</b></g1><g2><c>1</c><d>2</d></g2></test>");
		assertQuery(true, "deep-equal(//g1/text(), //g2/text())");
	}
	
	public void testText4() {
		createDocument("test", "<test><a>12</a><b>1<!--blah-->2</b></test>");
		assertQuery(false, "deep-equal(//a/text(), //b/text())");
	}
	
	public void testAttributes1() {
		createDocument("test", "<test><e1 a='1'/><e2 a='1' b='2' c='1'/><e3 a='2'/></test>");
		assertQuery(true, "deep-equal(//e1/@a, //e2/@a)");
	}
	
	public void testAttributes2() {
		createDocument("test", "<test><e1 a='1'/><e2 a='1' b='2' c='1'/><e3 a='2'/></test>");
		assertQuery(false, "deep-equal(//e1/@a, //e2/@b)");
	}
	
	public void testAttributes3() {
		createDocument("test", "<test><e1 a='1'/><e2 a='1' b='2' c='1'/><e3 a='2'/></test>");
		assertQuery(false, "deep-equal(//e1/@a, //e2/@c)");
	}
	
	public void testAttributes4() {
		createDocument("test", "<test><e1 a='1'/><e2 a='1' b='2' c='1'/><e3 a='2'/></test>");
		assertQuery(false, "deep-equal(//e1/@a, //e3/@a)");
	}
	
	public void testNSAttributes1() {
		createDocument("test", "<test xmlns:n='urn:blah' xmlns:p='urn:foo' xmlns:q='urn:blah'><e1 n:a='1'/><e2 n:a='1' p:a='1' p:b='1'/><e3 n:a='2'/><e4 q:a='1'/></test>");
		assertQuery(true, "declare namespace n = 'urn:blah'; declare namespace p = 'urn:foo'; declare namespace q = 'urn:blah'; deep-equal(//e1/@n:a, //e2/@n:a)");
	}
	
	public void testNSAttributes2() {
		createDocument("test", "<test xmlns:n='urn:blah' xmlns:p='urn:foo' xmlns:q='urn:blah'><e1 n:a='1'/><e2 n:a='1' p:a='1' p:b='1'/><e3 n:a='2'/><e4 q:a='1'/></test>");
		assertQuery(true, "declare namespace n = 'urn:blah'; declare namespace p = 'urn:foo'; declare namespace q = 'urn:blah'; deep-equal(//e1/@q:a, //e4/@n:a)");
	}
	
	public void testNSAttributes3() {
		createDocument("test", "<test xmlns:n='urn:blah' xmlns:p='urn:foo' xmlns:q='urn:blah'><e1 n:a='1'/><e2 n:a='1' p:a='1' p:b='1'/><e3 n:a='2'/><e4 q:a='1'/></test>");
		assertQuery(false, "declare namespace n = 'urn:blah'; declare namespace p = 'urn:foo'; declare namespace q = 'urn:blah'; deep-equal(//e1/@n:a, //e2/@p:a)");
	}
	
	public void testNSAttributes4() {
		createDocument("test", "<test xmlns:n='urn:blah' xmlns:p='urn:foo' xmlns:q='urn:blah'><e1 n:a='1'/><e2 n:a='1' p:a='1' p:b='1'/><e3 n:a='2'/><e4 q:a='1'/></test>");
		assertQuery(false, "declare namespace n = 'urn:blah'; declare namespace p = 'urn:foo'; declare namespace q = 'urn:blah'; deep-equal(//e1/@n:a, //e2/@p:b)");
	}
	
	public void testNSAttributes5() {
		createDocument("test", "<test xmlns:n='urn:blah' xmlns:p='urn:foo' xmlns:q='urn:blah'><e1 n:a='1'/><e2 n:a='1' p:a='1' p:b='1'/><e3 n:a='2'/><e4 q:a='1'/></test>");
		assertQuery(false, "declare namespace n = 'urn:blah'; declare namespace p = 'urn:foo'; declare namespace q = 'urn:blah'; deep-equal(//e1/@n:a, //e3/@n:a)");
	}
	
	public void testElements1() {
		createDocument("test", "<test><a/><a/></test>");
		assertQuery(true, "deep-equal(/test/*[1], /test/*[2])");
	}
	
	public void testElements2() {
		createDocument("test", "<test><a/><b/></test>");
		assertQuery(false, "deep-equal(/test/*[1], /test/*[2])");
	}
	
	public void testElements3() {
		createDocument("test", "<test><a a='1' b='2'/><a b='2' a='1'/></test>");
		assertQuery(true, "deep-equal(/test/*[1], /test/*[2])");
	}
	
	public void testElements4() {
		createDocument("test", "<test><a a='1'/><a b='2' a='1'/></test>");
		assertQuery(false, "deep-equal(/test/*[1], /test/*[2])");
	}
	
	public void testElements5() {
		createDocument("test", "<test><a a='1' c='2'/><a b='2' a='1'/></test>");
		assertQuery(false, "deep-equal(/test/*[1], /test/*[2])");
	}
	
	public void testElements6() {
		createDocument("test", "<test><a a='1' b='2'/><a a='2' b='2'/></test>");
		assertQuery(false, "deep-equal(/test/*[1], /test/*[2])");
	}
	
	public void testElements7() {
		createDocument("test", "<test><a>hello</a><a>hello</a></test>");
		assertQuery(true, "deep-equal(/test/*[1], /test/*[2])");
	}
	
	public void testElements8() {
		createDocument("test", "<test><a>hello</a><a>bye</a></test>");
		assertQuery(false, "deep-equal(/test/*[1], /test/*[2])");
	}
	
	public void testElements9() {
		createDocument("test", "<test><a><!--blah--></a><a/></test>");
		assertQuery(true, "deep-equal(/test/*[1], /test/*[2])");
	}
	
	public void testElements10() {
		createDocument("test", "<test><a><b/><!--blah-->hello</a><a><b/>hello</a></test>");
		assertQuery(true, "deep-equal(/test/*[1], /test/*[2])");
	}
	
	public void testElements11() {
		createDocument("test", "<test><a><b/>hello</a><a>hello</a></test>");
		assertQuery(false, "deep-equal(/test/*[1], /test/*[2])");
	}
	
	public void testElements12() {
		createDocument("test", "<test><a><b/></a><a>hello</a></test>");
		assertQuery(false, "deep-equal(/test/*[1], /test/*[2])");
	}
	
	public void testElements13() {
		createDocument("test", "<test><a><b/></a><a><b/>hello</a></test>");
		assertQuery(false, "deep-equal(/test/*[1], /test/*[2])");
	}

	//Courtesy : Dizzz
	public void testElements14() {
		//Includes a reference node
		String query =
			"let $parSpecs1 := <ParameterSpecifications/> " +
			"let $funSpecs2 := " +
			" <FunctionSpecifications>" +
			"  <FunctionName>Func2</FunctionName>" +
			"  { $parSpecs1 }" +
			" </FunctionSpecifications>" +
			"return " +
			" deep-equal("+
			"  <FunctionVerifications>" +
			"   <FunctionName>Func2</FunctionName>" +
			"  </FunctionVerifications>" +
			"," +
			"  <FunctionVerifications>" +
			"   { $funSpecs2/FunctionName }" +
			"  </FunctionVerifications>"+
			" )";			
		assertQuery(true, query);
	}
        
        public void testElements15() {
        
            String query ="let $funSpecs :="+
                    "<FunctionSpecifications>"+
                    "<FunctionName>Func2</FunctionName>"+
                    "</FunctionSpecifications>"+
                    "let $funVers1 :="+
                    "<FunctionVerifications>"+
                    "<FunctionName>Func2</FunctionName>"+
                    "</FunctionVerifications>"+
                    "let $funVers2 :="+
                    "<FunctionVerifications>"+
                    "{$funSpecs/FunctionName}"+
                    "</FunctionVerifications>"+
                    "return "+
                    "deep-equal($funVers1, $funVers2)";
            assertQuery(true, query);
        }

        public void testElements16() {
            // [ 1462061 ] Issue with deep-equal() "DeepestEqualBug"
            String query =
                    "declare namespace ve = \"ournamespace\";"+
                    "declare function ve:functionVerifications($pars as element()*) as element() {"+
                    "<FunctionVerifications>"+
                    "<ParameterVerifications>{$pars[Name eq \"Par1\"]}</ParameterVerifications>"+
                    "</FunctionVerifications>"+
                    "};"+
                    "let $par1 := <Parameter><Name>Par1</Name></Parameter>"+
                    "let $funVers2 := "+
                    "<FunctionVerifications><ParameterVerifications> {$par1}"+
                    "</ParameterVerifications></FunctionVerifications> "+
                    "return "+
                    "deep-equal($funVers2, ve:functionVerifications($par1))";
            assertQuery(true,query);
        }
        
        public void testElements17() {
            // Test deep-equal is used with in-memory nodes 
            String query =
                    "let $one := <foo/> "+
                    "let $two := <bar/> "+
                    "return "+
                    "deep-equal($one, $two)";
            assertQuery(false,query);
        }

        public void testReferenceNode() {
                    String query =
                    "let $expr1 := <Value>Hello</Value> "+
                    "return "+
                    "deep-equal( <Result><Value>Hello</Value></Result>,"+
                    "<Result><Value>{$expr1/node()}</Value></Result> )";
            assertQuery(true,query);    
	}
        
        public void testReferenceNode2() {
        	String query = "declare namespace dst = \"http://www.test.com/DeeperEqualTest\"; "
                            + "declare function dst:value($value as element(Value), "
                            + "$result as element(Result)) as element(Result) { "
                            + "<Result><Value>{($result/Value/node(), $value/node())}</Value> </Result>}; "
                            + "let $value1 := <Value>hello</Value> "
                            + "let $result0 := <Result><Value/></Result> "
                            + "let $result1 := dst:value($value1, $result0) "
                            + "let $value2 := <Value/> "
                            + "let $result2 := dst:value($value2, $result1) "
                            + "return deep-equal($result1, $result2)";
        	assertQuery(true,query);    
	}

    public void testReferenceNode3() {
        createDocument("test", "<root><value>A</value><value>B</value></root>");
        // two adjacent reference text nodes from another document should be merged into one
        assertQuery(true,
                "let $a := <v>{/root/value[1]/node(), /root/value[2]/node()}</v>" +
                "let $b := <v>AB</v>" +
                "return deep-equal($a, $b)");
        // one reference node after a text node
        assertQuery(true,
                "let $a := <v>{/root/value[1]/node(), /root/value[2]/node()}</v>" +
                "let $b := <v>A{/root/value[2]/node()}</v>" +
                "return deep-equal($a, $b)");
        // reference node before a text node
        assertQuery(true,
                "let $a := <v>{/root/value[1]/node(), /root/value[2]/node()}</v>" +
                "let $b := <v>{/root/value[1]/node()}B</v>" +
                "return deep-equal($a, $b)");
        // reference node before an atomic value
        assertQuery(true,
                "let $a := <v>{/root/value[1]/node(), 'B'}</v>" +
                "let $b := <v>AB</v>" +
                "return deep-equal($a, $b)");
        // reference node after an atomic value
        assertQuery(true,
                "let $a := <v>{'A', /root/value[2]/node()}</v>" +
                "let $b := <v>AB</v>" +
                "return deep-equal($a, $b)");
    }

        public void testsiblingCornerCase() {
        	String query = "declare  namespace ve = 'http://www.test.com/deepestEqualError'; " +
            "declare function ve:functionVerifications() as element(FunctionVerifications) { " +
            "let $parVers := " + 
            "<ParameterVerifications> " +
            "  <Parameter/> " +
            "  <PassedLevel>ATP</PassedLevel> " +
            "  <PassedLevel>PE</PassedLevel> " +
            "  <PassedLevel>SPC</PassedLevel> " +
            "  <Specification>ATP</Specification> " +
            "  <Specification>PE</Specification> " +
            "  <Specification>SPC</Specification> " +
            "</ParameterVerifications> " +
            "let $dummy := $parVers/PassedLevel  (: cause deep-equal bug!!! :) " +
            "return " +
            "<FunctionVerifications> " +
            "  <PassedLevel>ATP</PassedLevel> " +
            "  <PassedLevel>PE</PassedLevel> " +
            "  <PassedLevel>SPC</PassedLevel> " +
            "  {$parVers} " +
            "</FunctionVerifications> " +
          "}; " +
          "let $expected := " +
          "  <FunctionVerifications> " +
          "    <PassedLevel>ATP</PassedLevel> " +
          "    <PassedLevel>PE</PassedLevel> " +
          "    <PassedLevel>SPC</PassedLevel> " +
          "    <ParameterVerifications> " +
          "      <Parameter/> " +
          "      <PassedLevel>ATP</PassedLevel> " +
          "      <PassedLevel>PE</PassedLevel> " +
          "      <PassedLevel>SPC</PassedLevel> " +
          "      <Specification>ATP</Specification> " +
          "      <Specification>PE</Specification> " +
          "      <Specification>SPC</Specification> " +
          "    </ParameterVerifications> " +
          "  </FunctionVerifications> " +
          "let $got := ve:functionVerifications() " +
          "return deep-equal($expected, $got)";
        	assertQuery(true,query);
        }
        
         public void testSequenceError1() {
        	String query = "declare namespace ds = \"http://www.test.com/SequenceError\"; "
                        +"declare function ds:result(  $current as element(Result)?, "
                        +"$value  as element(Value)?) as element(Result) {"
                        +"<Result> <Value>{ ($current/Value/node(), $value/node()) }</Value> </Result> };"
                        +"let $v1 := <Value>1234</Value> " 
                        +"let $result1 := ds:result((), $v1) "
                        +"let $v2 := <Value>abcd</Value> "
                        +"let $expected := <Value>{($v1, $v2)/node()}</Value> "
                        +"let $result2 := ds:result($result1, $v2) "
                        +"return deep-equal($expected, $result2/Value)";
//                for(int i=1; i<20;i++){
//                    System.out.println("nr="+i);
                    assertQuery(true,query);
//               }

        };


	
	public void testNSElements1() {
		createDocument("test", "<test xmlns:p='urn:foo' xmlns:q='urn:foo'><p:a/><q:a/></test>");
		assertQuery(true, "deep-equal(/test/*[1], /test/*[2])");
	}
	
	public void testNSElements2() {
		createDocument("test", "<test xmlns:p='urn:foo' xmlns:q='urn:bar'><p:a/><q:a/></test>");
		assertQuery(false, "deep-equal(/test/*[1], /test/*[2])");
	}
        

	
	public void testForLoop() {
		try {
			ResourceSet rs = query.query("let $set := <root><b>test</b><c><a>test</a></c><d><a>test</a></d></root>, $test := <c><a>test</a></c> for $node in $set/* return deep-equal($node, $test)");
			assertEquals(3, rs.getSize());
			assertEquals("false", rs.getResource(0).getContent());
			assertEquals("true", rs.getResource(1).getContent());
			assertEquals("false", rs.getResource(2).getContent());
	    } catch (Exception e) {            
	        fail(e.getMessage());  
	    }			
	}
	
	private void assertQuery(boolean expected, String q) {
		try {
			ResourceSet rs = query.query(q);
			assertEquals(1, rs.getSize());
			assertEquals(Boolean.toString(expected), rs.getResource(0).getContent());
	    } catch (Exception e) {            
	        fail(e.getMessage());  
	    }			
	}
	
	private XMLResource createDocument(String name, String content) {
		try {
			XMLResource res = (XMLResource) c.createResource(name, XMLResource.RESOURCE_TYPE);
			res.setContent(content);
			c.storeResource(res);
			return res;
	    } catch (Exception e) {            
	        fail(e.getMessage());  
	    }
	    return null;
	}

	private Collection setupTestCollection() {
		try {
			Collection root = DatabaseManager.getCollection(URI, "admin", "");
			CollectionManagementService rootcms = (CollectionManagementService) root.getService("CollectionManagementService", "1.0");
			Collection cc = root.getChildCollection("test");
			if(cc != null) rootcms.removeCollection("test");
			rootcms.createCollection("test");
			cc = DatabaseManager.getCollection(URI+"/test", "admin", "");
			assertNotNull(cc);
			return cc;
	    } catch (Exception e) {            
	        fail(e.getMessage());  
	    }	
	    return null;
	}

	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);
		}
	}
	
}
