// Copyright 2008-2014 severally by the contributors
//
// 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 net.sf.practicalxml.xpath;

import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

import javax.xml.namespace.QName;
import javax.xml.xpath.XPathFunction;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import net.sf.practicalxml.AbstractTestCase;
import net.sf.practicalxml.DomUtil;
import net.sf.practicalxml.xpath.XPathWrapperFactory.CacheType;


public class TestXPathWrapperFactory
extends AbstractTestCase
{
    public TestXPathWrapperFactory(String name)
    {
        super(name);
    }


//----------------------------------------------------------------------------
//  Test Data
//----------------------------------------------------------------------------

    public final static String  NS1 = "ns1";
    public final static String  NS2 = "ns2";

    Document _dom;
    Element _root;
    Element _child1;
    Element _child2;
    Element _child3;

    @Override
    protected void setUp()
    {
        _root = DomUtil.newDocument("root");
        _child1 = DomUtil.appendChild(_root, "child");
        _child2 = DomUtil.appendChild(_root, NS1, "child");
        _child2.setAttribute("attr", "123");
        _child3 = DomUtil.appendChild(_root, NS2, "child");
        _child3.setAttribute("attr", "456");
        _dom = _root.getOwnerDocument();
    }


//----------------------------------------------------------------------------
//  Testcases
//----------------------------------------------------------------------------

    public void testBasicOperation() throws Exception
    {
        XPathWrapperFactory xpf = new XPathWrapperFactory()
                                  .bindNamespace("x", NS1)
                                  .bindNamespace("y", NS2)
                                  .bindVariable("v1", "123")
                                  .bindVariable(new QName(NS1,"v2"), "456");

        XPathWrapper xp1 = xpf.newXPath("//child");
        List<Node> eval1 = xp1.evaluate(_dom);
        assertSame("no namespace", 1,       eval1.size());
        assertSame("no namespace", _child1, eval1.get(0));

        XPathWrapper xp2 = xpf.newXPath("//x:child");
        List<Node> eval2 = xp2.evaluate(_dom);
        assertSame("retrieve by NS1", 1,       eval2.size());
        assertSame("retrieve by NS1", _child2, eval2.get(0));

        XPathWrapper xp3 = xpf.newXPath("//y:child");
        List<Node> eval3 = xp3.evaluate(_dom);
        assertSame("retrieve by NS2", 1,       eval3.size());
        assertSame("retrieve by NS2", _child3, eval3.get(0));

        XPathWrapper xp4 = xpf.newXPath("//*[@attr=$v1]");
        List<Node> eval4 = xp4.evaluate(_dom);
        assertSame("retrieve by variable", 1,       eval4.size());
        assertSame("retrieve by variable", _child2, eval4.get(0));

        XPathWrapper xp5 = xpf.newXPath("//*[@attr=$x:v2]");
        List<Node> eval5 = xp5.evaluate(_dom);
        assertSame("retrieve by namespaced variable", 1,       eval5.size());
        assertSame("retrieve by namespaced variable", _child3, eval5.get(0));
    }


    public void testRebindingVariableReplacesPreviousValue() throws Exception
    {
        // this test exists primarily as a counter-example to the "cache not
        // affected by config changes" test below

        XPathWrapperFactory xpf = new XPathWrapperFactory()
                                  .bindVariable("v1", "123");

        XPathWrapper xp1 = xpf.newXPath("//*[@attr=$v1]");
        List<Node> eval1 = xp1.evaluate(_dom);
        assertSame("retrieve by variable", 1,       eval1.size());
        assertSame("retrieve by variable", _child2, eval1.get(0));

        xpf.bindVariable("v1", "456");

        XPathWrapper xp2 = xpf.newXPath("//*[@attr=$v1]");
        List<Node> eval2 = xp2.evaluate(_dom);
        assertSame("retrieve by variable", 1,       eval2.size());
        assertSame("retrieve by variable", _child3, eval2.get(0));
    }


    public void testBindFunction() throws Exception
    {
        AbstractFunction<?> fn1 = new XPathTestHelpers.GetNamespaceAF(NS1, "myfunc");
        XPathFunction fn2 = new XPathTestHelpers.GetNamespaceSF();

        XPathWrapperFactory xpf = new XPathWrapperFactory()
                                  .bindNamespace("ns", NS1)
                                  .bindFunction(fn1)
                                  .bindFunction(fn1, "fn")
                                  .bindFunction(new QName(NS1, "myfunc2"), fn2)
                                  .bindFunction(new QName(NS1, "myfunc3"), fn2, 2)
                                  .bindFunction(new QName(NS1, "myfunc3"), fn2, 4, 5);

        XPathWrapper xp1 = xpf.newXPath("ns:myfunc(.)");
        assertEquals("", xp1.evaluateAsString(_child1));
        assertEquals(NS1, xp1.evaluateAsString(_child2));

        XPathWrapper xp2 = xpf.newXPath("fn:myfunc(.)");
        assertEquals("", xp2.evaluateAsString(_child1));
        assertEquals(NS1, xp2.evaluateAsString(_child2));

        XPathWrapper xp3 = xpf.newXPath("ns:myfunc2(.)");
        assertEquals("", xp3.evaluateAsString(_child1));
        assertEquals(NS1, xp3.evaluateAsString(_child2));

        XPathWrapper xp4 = xpf.newXPath("ns:myfunc3(.,.)");
        assertEquals("", xp4.evaluateAsString(_child1));
        assertEquals(NS1, xp4.evaluateAsString(_child2));

        XPathWrapper xp5 = xpf.newXPath("ns:myfunc3(.,.,.,.)");
        assertEquals("", xp5.evaluateAsString(_child1));
        assertEquals(NS1, xp5.evaluateAsString(_child2));
    }


    public void testSingleSharedFunctionInstance() throws Exception
    {
        XPathTestHelpers.CountingAF fn1 = new XPathTestHelpers.CountingAF(NS1, "myfunc");

        XPathWrapperFactory xpf = new XPathWrapperFactory()
                                  .bindNamespace("ns", NS1)
                                  .bindFunction(fn1);

        // create one path that uses the function
        XPathWrapper xp1 = xpf.newXPath("ns:myfunc(.)");
        assertEquals("0", xp1.evaluateAsString(_child1));
        assertEquals(1, fn1.count);

        // then another, where count shouldn't reset to 0
        XPathWrapper xp2 = xpf.newXPath("ns:myfunc(.)");
        assertEquals("1", xp2.evaluateAsString(_child1));
        assertEquals(2, fn1.count);

        // and count should keep increasing when we return to first
        assertEquals("2", xp1.evaluateAsString(_child1));
        assertEquals(3, fn1.count);
    }


    public void testNoCache() throws Exception
    {
        XPathWrapperFactory xpf = new XPathWrapperFactory(CacheType.NONE);

        XPathWrapper xp1 = xpf.newXPath("//foo");
        XPathWrapper xp2 = xpf.newXPath("//foo");
        assertNotSame(xp1, xp2);
    }


    public void testSimpleCache() throws Exception
    {
        XPathWrapperFactory xpf = new XPathWrapperFactory(CacheType.SIMPLE);

        XPathWrapper xp1 = xpf.newXPath("//foo");
        XPathWrapper xp2 = xpf.newXPath("//foo");
        assertSame(xp1, xp2);

        // note: no multi-thread is-same test, because we explicitly disallow
        // use of simple change in multi-threaded environment
    }


    public void testThreadsafeCache() throws Exception
    {
        final XPathWrapperFactory xpf = new XPathWrapperFactory(CacheType.THREADSAFE);

        final AtomicReference<XPathWrapper> xp1 = new AtomicReference<XPathWrapper>();
        final AtomicReference<XPathWrapper> xp2 = new AtomicReference<XPathWrapper>();
        final AtomicReference<XPathWrapper> xp3 = new AtomicReference<XPathWrapper>();

        Thread t1 = new Thread(new Runnable()
        {
            public void run()
            {
                xp1.set(xpf.newXPath("//foo"));
                xp2.set(xpf.newXPath("//foo"));
            }
        });

        Thread t2 = new Thread(new Runnable()
        {
            public void run()
            {
                xp3.set(xpf.newXPath("//foo"));
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        assertSame("wrappers created on same thread",         xp1.get(), xp2.get());
        assertNotSame("wrappers created on different thread", xp1.get(), xp3.get());
    }


    public void testCachedInstanceNotUpdatedWhenFactoryConfigurationChanges() throws Exception
    {

        XPathWrapperFactory xpf = new XPathWrapperFactory(CacheType.SIMPLE)
                                  .bindVariable("v1", "123");

        XPathWrapper xp1 = xpf.newXPath("//*[@attr=$v1]");
        List<Node> eval1 = xp1.evaluate(_dom);
        assertSame("retrieve by variable", 1,       eval1.size());
        assertSame("retrieve by variable", _child2, eval1.get(0));

        // this would affect new (non-cached) expressions, but not the one we already have
        xpf.bindVariable("v1", "456");

        XPathWrapper xp2 = xpf.newXPath("//*[@attr=$v1]");
        List<Node> eval2 = xp2.evaluate(_dom);
        assertSame("retrieve by variable", 1,       eval2.size());
        assertSame("retrieve by variable", _child2, eval2.get(0));
    }
}
