// Copyright 2008-2012 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.util;

import java.util.Iterator;
import java.util.NoSuchElementException;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

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

import junit.framework.TestCase;


public class TestNodeListIterator
extends TestCase
{
    public final static String  EL_ROOT   = "root";
    public final static String  EL_CHILD1 = "child1";
    public final static String  EL_CHILD3 = "child2";
    public final static String  TX_CHILD2 = "some text";

    private Document _doc;
    private Element _root;
    private Element _child1;
    private Text _child2;
    private Element _child3;


    @Override
    protected void setUp() throws Exception
    {
        _doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        _root = _doc.createElement(EL_ROOT);
        _child1 = _doc.createElement(EL_CHILD1);
        _child2 = _doc.createTextNode(TX_CHILD2);
        _child3 = _doc.createElement(EL_CHILD3);

        _doc.appendChild(_root);
        _root.appendChild(_child1);
        _root.appendChild(_child2);
        _root.appendChild(_child3);
    }


//----------------------------------------------------------------------------
//  Test Cases
//----------------------------------------------------------------------------

    public void testBasicIteration() throws Exception
    {
        Iterator<Node> itx = new NodeListIterator(_root.getChildNodes());

        assertTrue(itx.hasNext());
        assertSame(_child1, itx.next());

        assertTrue(itx.hasNext());
        assertSame(_child2, itx.next());

        assertTrue(itx.hasNext());
        assertSame(_child3, itx.next());

        assertFalse(itx.hasNext());
    }


    public void testFilterByClass() throws Exception
    {
        NodeList list = _root.getChildNodes();

        Iterator<Node> itx1 = new NodeListIterator(list, Element.class);
        assertTrue(itx1.hasNext());
        assertSame(_child1, itx1.next());
        assertTrue(itx1.hasNext());
        assertSame(_child3, itx1.next());
        assertFalse(itx1.hasNext());

        Iterator<Node> itx2 = new NodeListIterator(list, Text.class);
        assertTrue(itx2.hasNext());
        assertSame(_child2, itx2.next());
        assertFalse(itx2.hasNext());
    }


    public void testIterationOffTheEnd() throws Exception
    {
        Iterator<Node> itx = new NodeListIterator(_root.getChildNodes());
        while (itx.hasNext())
            itx.next();

        try
        {
            itx.next();
            fail("able to iterate off end of list");
        }
        catch (NoSuchElementException ee)
        {
            // success
        }
    }


    public void testRemove() throws Exception
    {
        Iterator<Node> itx = new NodeListIterator(_root.getChildNodes());

        itx.next();
        itx.next();
        itx.remove();

        assertTrue(itx.hasNext());
        assertSame(_child3, itx.next());

        assertFalse(itx.hasNext());

        // verify that DOM was changed

        NodeList list = _root.getChildNodes();
        assertEquals(2, list.getLength());
        assertSame(_child1, list.item(0));
        assertSame(_child3, list.item(1));
    }


    public void testRemoveAtEndOfIteration() throws Exception
    {
        Iterator<Node> itx = new NodeListIterator(_root.getChildNodes());

        itx.next();
        itx.next();
        itx.next();
        assertFalse(itx.hasNext());

        itx.remove();

        // verify that DOM was changed

        NodeList list = _root.getChildNodes();
        assertEquals(2, list.getLength());
        assertSame(_child1, list.item(0));
        assertSame(_child2, list.item(1));
    }


    public void testRemoveFailsIfNextNotCalled() throws Exception
    {
        Iterator<Node> itx = new NodeListIterator(_root.getChildNodes());

        try
        {
            itx.remove();
            fail("remove() succeeded without initial next()");
        }
        catch (IllegalStateException ee)
        {
            // success
        }
    }


    public void testRemoveFailsIfCalledTwice() throws Exception
    {
        Iterator<Node> itx = new NodeListIterator(_root.getChildNodes());

        itx.next();
        itx.next();
        itx.remove();

        try
        {
            itx.remove();
            fail("remove() succeeded without intervening next()");
        }
        catch (IllegalStateException ee)
        {
            // success
        }
    }


    // this is a negative test for the implementation hack where we iterate
    // sibling links -- Element.getElementsByTagName() doesn't use this
    public void testIterationOfTagnameExtract() throws Exception
    {
        NodeList nodes = _root.getElementsByTagName(EL_CHILD1);
        assertEquals(1, nodes.getLength());

        NodeListIterator itx = new NodeListIterator(nodes);
        assertTrue(itx.hasNext());
        assertSame(_child1, itx.next());
        assertFalse(itx.hasNext());
    }


    // this is a negative test for the implementation hack where we iterate
    // sibling links -- XPath results don't use this
    public void testIterationOfXPathExtract() throws Exception
    {
        XPath xpath = XPathFactory.newInstance().newXPath();
        NodeList nodes = (NodeList)xpath.evaluate(
                                "/" + EL_ROOT + "/" + EL_CHILD1,
                                _root,
                                XPathConstants.NODESET);
        assertEquals(1, nodes.getLength());

        NodeListIterator itx = new NodeListIterator(nodes);
        assertTrue(itx.hasNext());
        assertSame(_child1, itx.next());
        assertFalse(itx.hasNext());
    }


    // this is a "white box" test to verify that we don't have any unexpected
    // side-effects from hasNext()
    public void testHasNextWhitebox() throws Exception
    {
        Iterator<Node> itx = new NodeListIterator(_root.getChildNodes());

        // more asserts than there are nodes
        assertTrue(itx.hasNext());
        assertTrue(itx.hasNext());
        assertTrue(itx.hasNext());
        assertTrue(itx.hasNext());
        assertTrue(itx.hasNext());

        // and we should still get the first node on next
        assertSame(_child1, itx.next());

        // and the correct node without calling hasNext()
        assertSame(_child2, itx.next());
    }

}
