/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.camel.support;

import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import junit.framework.TestCase;

/**
 * 
 */
public class XMLTokenExpressionIteratorGroupingTest extends TestCase {

    // the input containing multiple Cs
    private static final byte[] TEST_BODY = (
        "<?xml version='1.0' encoding='UTF-8'?>"
        + "<g:A xmlns:g='urn:g'>"
        + "<c:B attr='1' xmlns:c='urn:c'>"
        + "<c:C attr='1'>peach</c:C>"
        + "<c:C attr='2'/>"
        + "<c:C attr='3'>orange</c:C>"
        + "<c:C attr='4'/>"
        + "</c:B>"
        + "<c:B attr='2' xmlns:c='urn:c'>"
        + "<c:C attr='5'>mango</c:C>"
        + "<c:C attr='6'/>"
        + "<c:C attr='7'>pear</c:C>"
        + "<c:C attr='8'/>"
        + "</c:B>"
        + "</g:A>").getBytes();

    // one extracted C in its wrapped context per token
    private static final String[] RESULTS_WRAPPED_SIZE1 = {
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='1' xmlns:c='urn:c'>"
            + "<c:C attr='1'>peach</c:C>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='1' xmlns:c='urn:c'>"
            + "<c:C attr='2'/>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='1' xmlns:c='urn:c'>"
            + "<c:C attr='3'>orange</c:C>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='1' xmlns:c='urn:c'>"
            + "<c:C attr='4'/>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='2' xmlns:c='urn:c'>"
            + "<c:C attr='5'>mango</c:C>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='2' xmlns:c='urn:c'>"
            + "<c:C attr='6'/>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='2' xmlns:c='urn:c'>"
            + "<c:C attr='7'>pear</c:C>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='2' xmlns:c='urn:c'>"
            + "<c:C attr='8'/>"
            + "</c:B>"
            + "</g:A>"};

    // two extracted Cs in their wrapped context per token
    private static final String[] RESULTS_WRAPPED_SIZE2 = {
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='1' xmlns:c='urn:c'>"
            + "<c:C attr='1'>peach</c:C>"
            + "<c:C attr='2'/>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='1' xmlns:c='urn:c'>"
            + "<c:C attr='3'>orange</c:C>"
            + "<c:C attr='4'/>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='2' xmlns:c='urn:c'>"
            + "<c:C attr='5'>mango</c:C>"
            + "<c:C attr='6'/>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='2' xmlns:c='urn:c'>"
            + "<c:C attr='7'>pear</c:C>"
            + "<c:C attr='8'/>"
            + "</c:B>"
            + "</g:A>"};

    // at most three extracted Cs in their common wrapped context per token
    private static final String[] RESULTS_WRAPPED_SIZE3L = {
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='1' xmlns:c='urn:c'>"
            + "<c:C attr='1'>peach</c:C>"
            + "<c:C attr='2'/>"
            + "<c:C attr='3'>orange</c:C>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='1' xmlns:c='urn:c'>"
            + "<c:C attr='4'/>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='2' xmlns:c='urn:c'>"
            + "<c:C attr='5'>mango</c:C>"
            + "<c:C attr='6'/>"
            + "<c:C attr='7'>pear</c:C>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='2' xmlns:c='urn:c'>"
            + "<c:C attr='8'/>"
            + "</c:B>"
            + "</g:A>"};

    // three extracted Cs in their corresponding wrapped contexts per token
    private static final String[] RESULTS_WRAPPED_SIZE3U = {
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='1' xmlns:c='urn:c'>"
            + "<c:C attr='1'>peach</c:C>"
            + "<c:C attr='2'/>"
            + "<c:C attr='3'>orange</c:C>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='1' xmlns:c='urn:c'>"
            + "<c:C attr='4'/>"
            + "</c:B>"
            + "<c:B attr='2' xmlns:c='urn:c'>"
            + "<c:C attr='5'>mango</c:C>"
            + "<c:C attr='6'/>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='2' xmlns:c='urn:c'>"
            + "<c:C attr='7'>pear</c:C>"
            + "<c:C attr='8'/>"
            + "</c:B>"
            + "</g:A>"};

    // four extracted Cs in their wrapped context per token
    private static final String[] RESULTS_WRAPPED_SIZE4 = {
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='1' xmlns:c='urn:c'>"
            + "<c:C attr='1'>peach</c:C>"
            + "<c:C attr='2'/>"
            + "<c:C attr='3'>orange</c:C>"
            + "<c:C attr='4'/>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='2' xmlns:c='urn:c'>"
            + "<c:C attr='5'>mango</c:C>"
            + "<c:C attr='6'/>"
            + "<c:C attr='7'>pear</c:C>"
            + "<c:C attr='8'/>"
            + "</c:B>"
            + "</g:A>"};

    // at most five extracted Cs in their common wrapped context per token
    private static final String[] RESULTS_WRAPPED_SIZE5L = RESULTS_WRAPPED_SIZE4;

    // five extracted Cs in their corresponding wrapped contexts per token
    private static final String[] RESULTS_WRAPPED_SIZE5U = {
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='1' xmlns:c='urn:c'>"
            + "<c:C attr='1'>peach</c:C>"
            + "<c:C attr='2'/>"
            + "<c:C attr='3'>orange</c:C>"
            + "<c:C attr='4'/>"
            + "</c:B>"
            + "<c:B attr='2' xmlns:c='urn:c'>"
            + "<c:C attr='5'>mango</c:C>"
            + "</c:B>"
            + "</g:A>",
        "<?xml version='1.0' encoding='UTF-8'?>"
            + "<g:A xmlns:g='urn:g'>"
            + "<c:B attr='2' xmlns:c='urn:c'>"
            + "<c:C attr='6'/>"
            + "<c:C attr='7'>pear</c:C>"
            + "<c:C attr='8'/>"
            + "</c:B>"
            + "</g:A>"};

    private static final String[] RESULTS_INJECTED_SIZE1 = {
        "<c:C attr='1' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">peach</c:C>",
        "<c:C attr='2' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>",
        "<c:C attr='3' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">orange</c:C>",
        "<c:C attr='4' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>",
        "<c:C attr='5' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">mango</c:C>",
        "<c:C attr='6' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>",
        "<c:C attr='7' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">pear</c:C>",
        "<c:C attr='8' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"};

    private static final String[] RESULTS_INJECTED_SIZE2 = {
        "<group>"
            + "<c:C attr='1' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">peach</c:C>"
            + "<c:C attr='2' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
            + "</group>",
        "<group>"
            + "<c:C attr='3' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">orange</c:C>"
            + "<c:C attr='4' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
            + "</group>",
        "<group>"
            + "<c:C attr='5' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">mango</c:C>"
            + "<c:C attr='6' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
            + "</group>",
        "<group>"
            + "<c:C attr='7' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">pear</c:C>"
            + "<c:C attr='8' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
            + "</group>"};

    private static final String[] RESULTS_INJECTED_SIZE3 = {
        "<group>"
            + "<c:C attr='1' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">peach</c:C>"
            + "<c:C attr='2' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
            + "<c:C attr='3' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">orange</c:C>"
            + "</group>",
        "<group>"
            + "<c:C attr='4' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
            + "<c:C attr='5' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">mango</c:C>"
            + "<c:C attr='6' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
            + "</group>",
        "<group>"
            + "<c:C attr='7' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">pear</c:C>"
            + "<c:C attr='8' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
            + "</group>"};

    private static final String[] RESULTS_INJECTED_SIZE4 = {
        "<group>"
            + "<c:C attr='1' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">peach</c:C>"
            + "<c:C attr='2' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
            + "<c:C attr='3' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">orange</c:C>"
            + "<c:C attr='4' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
            + "</group>",
        "<group>"
            + "<c:C attr='5' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">mango</c:C>"
            + "<c:C attr='6' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
            + "<c:C attr='7' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">pear</c:C>"
            + "<c:C attr='8' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
            + "</group>"};

    private static final String[] RESULTS_INJECTED_SIZE5 = {
        "<group>"
            + "<c:C attr='1' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">peach</c:C>"
            + "<c:C attr='2' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
            + "<c:C attr='3' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">orange</c:C>"
            + "<c:C attr='4' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
            + "<c:C attr='5' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">mango</c:C>"
            + "</group>",
        "<group>"
            + "<c:C attr='6' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
            + "<c:C attr='7' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">pear</c:C>"
            + "<c:C attr='8' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
            + "</group>"};

    private Map<String, String> nsmap;
        
    
    @Override
    public void setUp() {
        nsmap = new HashMap<String, String>();
        nsmap.put("g", "urn:g");
        nsmap.put("c", "urn:c");
    }

    // wrapped mode
    public void testExtractWrappedSize1() throws Exception {
        invokeAndVerify("//c:C", 
            'w', 1, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_WRAPPED_SIZE1);
    }

    public void testExtractWrappedSize2() throws Exception {
        invokeAndVerify("//c:C", 
            'w', 2, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_WRAPPED_SIZE2);
    }

    public void testExtractWrappedSize3L() throws Exception {
        invokeAndVerify("//c:C", 
            'w', 3, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_WRAPPED_SIZE3L);
    }

    // disabled: not working for now as the context extraction across two ancestor paths is not working
    public void disabledtestExtractWrappedSize3U() throws Exception {
        invokeAndVerify("//c:C", 
            'W', 3, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_WRAPPED_SIZE3U);
    }

    public void testExtractWrappedSize4() throws Exception {
        invokeAndVerify("//c:C", 
            'w', 4, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_WRAPPED_SIZE4);
    }

    public void testExtractWrappedSize5L() throws Exception {
        invokeAndVerify("//c:C", 
            'w', 5, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_WRAPPED_SIZE5L);
    }

    // disabled: not working for now as the context extraction across two ancestor paths is not working
    public void disabledtestExtractWrappedSize5U() throws Exception {
        invokeAndVerify("//c:C", 
            'W', 5, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_WRAPPED_SIZE5U);
    }

    // injected mode
    public void testExtractInjectedSize1() throws Exception {
        invokeAndVerify("//c:C", 
            'i', 1, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_INJECTED_SIZE1);
    }

    public void testExtractInjectedSize2() throws Exception {
        invokeAndVerify("//c:C", 
            'i', 2, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_INJECTED_SIZE2);
    }

    public void testExtractInjectedSize3() throws Exception {
        invokeAndVerify("//c:C", 
            'i', 3, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_INJECTED_SIZE3);
    }

    public void testExtractInjectedSize4() throws Exception {
        invokeAndVerify("//c:C", 
            'i', 4, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_INJECTED_SIZE4);
    }

    public void testExtractInjectedSize5() throws Exception {
        invokeAndVerify("//c:C", 
            'i', 5, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_INJECTED_SIZE5);
    }

    public void testExtractWrappedLeftOver() throws Exception {
        final byte[] data = ("<?xml version='1.0' encoding='UTF-8'?><g:A xmlns:g='urn:g'><c:B attr='1' xmlns:c='urn:c'>"
            + "<c:C attr='1'>peach</c:C>"
            + "<c:C attr='2'/>"
            + "<c:C attr='3'>orange</c:C>"
            + "</c:B></g:A>").getBytes();
        final String[] results = {"<?xml version='1.0' encoding='UTF-8'?><g:A xmlns:g='urn:g'><c:B attr='1' xmlns:c='urn:c'>"
                                      + "<c:C attr='1'>peach</c:C><c:C attr='2'/>"
                                      + "</c:B></g:A>",
                                  "<?xml version='1.0' encoding='UTF-8'?><g:A xmlns:g='urn:g'><c:B attr='1' xmlns:c='urn:c'>"
                                      + "<c:C attr='3'>orange</c:C>"
                                      + "</c:B></g:A>"};
        invokeAndVerify("//c:C", 
            'w', 2, new ByteArrayInputStream(data), "utf-8", results);
    }

    private void invokeAndVerify(String path, char mode, int group,
        InputStream in, String charset, String[] expected) 
        throws Exception {
        XMLTokenExpressionIterator xtei = new XMLTokenExpressionIterator(path, mode);
        xtei.setNamespaces(nsmap);
        xtei.setGroup(group);

        Iterator<?> it = xtei.createIterator(in, "utf-8");
        List<String> results = new ArrayList<String>();
        while (it.hasNext()) {
            results.add((String)it.next());
        }
        ((Closeable)it).close();

        assertEquals("token count", expected.length, results.size());
        for (int i = 0; i < expected.length; i++) {
            assertEquals("mismatch [" + i + "]", expected[i], results.get(i));
        }
    }
}
