/**
 * 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.activemq.filter;

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

import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import junit.framework.TestCase;

public class DestinationMapTest extends TestCase {
    protected DestinationMap map = new DestinationMap();

    protected ActiveMQDestination d1 = createDestination("TEST.D1");
    protected ActiveMQDestination d2 = createDestination("TEST.BAR.D2");
    protected ActiveMQDestination d3 = createDestination("TEST.BAR.D3");
    protected ActiveMQDestination compositeDestination1 = createDestination("TEST.D1,TEST.BAR.D2");
    protected ActiveMQDestination compositeDestination2 = createDestination("TEST.D1,TEST.BAR.D3");

    protected Object v1 = "value1";
    protected Object v2 = "value2";
    protected Object v3 = "value3";
    protected Object v4 = "value4";
    protected Object v5 = "value5";
    protected Object v6 = "value6";

    public void testCompositeDestinations() throws Exception {
        ActiveMQDestination d1 = createDestination("TEST.BAR.D2");
        ActiveMQDestination d2 = createDestination("TEST.BAR.D3");
        map.put(d1, d1);
        map.put(d2, d2);
        map.get(createDestination("TEST.BAR.D2,TEST.BAR.D3"));
    }

    public void testSimpleDestinations() throws Exception {
        map.put(d1, v1);
        map.put(d2, v2);
        map.put(d3, v3);

        assertMapValue(d1, v1);
        assertMapValue(d2, v2);
        assertMapValue(d3, v3);
    }

    public void testQueueAndTopicWithSameName() throws Exception {
        ActiveMQQueue q1 = new ActiveMQQueue("foo");
        ActiveMQTopic t1 = new ActiveMQTopic("foo");

        map.put(q1, v1);
        map.put(t1, v2);

        assertMapValue(q1, v1);
        assertMapValue(t1, v2);
    }

    public void testSimpleDestinationsWithMultipleValues() throws Exception {
        map.put(d1, v1);
        map.put(d2, v2);
        map.put(d2, v3);

        assertMapValue(d1, v1);
        assertMapValue("TEST.BAR.D2", v2, v3);
        assertMapValue(d3, null);
    }

    public void testSimpleAndCompositeDestinations() throws Exception {
        map.put(d1, v1);
        map.put(compositeDestination1, v2);
        map.put(compositeDestination2, v3);

        assertMapValue("TEST.D1", v1, v2, v3);
        assertMapValue(d2, v2);
        assertMapValue(d3, v3);
        assertMapValue(compositeDestination1.toString(), v1, v2, v3);
        assertMapValue(compositeDestination2.toString(), v1, v2, v3);

        map.remove(compositeDestination1, v2);
        map.remove(compositeDestination2, v3);

        assertMapValue("TEST.D1", v1);
    }

    public void testLookupOneStepWildcardDestinations() throws Exception {
        map.put(d1, v1);
        map.put(d2, v2);
        map.put(d3, v3);

        assertMapValue("TEST.D1", v1);
        assertMapValue("TEST.*", v1);
        assertMapValue("*.D1", v1);
        assertMapValue("*.*", v1);

        assertMapValue("TEST.BAR.D2", v2);
        assertMapValue("TEST.*.D2", v2);
        assertMapValue("*.BAR.D2", v2);
        assertMapValue("*.*.D2", v2);

        assertMapValue("TEST.BAR.D3", v3);
        assertMapValue("TEST.*.D3", v3);
        assertMapValue("*.BAR.D3", v3);
        assertMapValue("*.*.D3", v3);

        assertMapValue("TEST.BAR.D4", null);

        assertMapValue("TEST.BAR.*", v2, v3);
    }

    public void testLookupMultiStepWildcardDestinations() throws Exception {
        map.put(d1, v1);
        map.put(d2, v2);
        map.put(d3, v3);

        List<Object> allValues = Arrays.asList(new Object[] {v1, v2, v3});

        assertMapValue(">", allValues);
        assertMapValue("TEST.>", allValues);
        assertMapValue("*.>", allValues);
        assertMapValue("TEST.*.>", allValues);
        assertMapValue("TEST.*.*.>", v2,v3);

        assertMapValue("FOO.>", null);
    }

    public void testStoreWildcardWithOneStepPath() throws Exception {
        put("TEST.*", v1);
        put("TEST.D1", v2);
        put("TEST.BAR.*", v2);
        put("TEST.BAR.D3", v3);

        assertMapValue("FOO", null);
        assertMapValue("TEST.FOO", v1);
        assertMapValue("TEST.D1", v1, v2);

        assertMapValue("TEST.FOO.FOO", null);
        assertMapValue("TEST.BAR.FOO", v2);
        assertMapValue("TEST.BAR.D3", v2, v3);

        assertMapValue("TEST.*", v1, v2);
        assertMapValue("*.D1", v1, v2);
        assertMapValue("*.*", v1, v2);
        assertMapValue("TEST.*.*", v2, v3);
        assertMapValue("TEST.BAR.*", v2, v3);
        assertMapValue("*.*.*", v2, v3);
        assertMapValue("*.BAR.*", v2, v3);
        assertMapValue("*.BAR.D3", v2, v3);
        assertMapValue("*.*.D3", v2, v3);
    }

    public void testStoreWildcardInMiddleOfPath() throws Exception {
        put("TEST.*", v1);
        put("TEST.D1", v2);
        put("TEST.BAR.*", v2);
        put("TEST.XYZ.D3", v3);
        put("TEST.XYZ.D4", v4);
        put("TEST.BAR.D3", v5);
        put("TEST.*.D2", v6);

        assertMapValue("TEST.*.D3", v2, v3, v5);
        assertMapValue("TEST.*.D4", v2, v4);

        assertMapValue("TEST.*", v1, v2);
        assertMapValue("TEST.*.*", v2, v3, v4, v5, v6);
        assertMapValue("TEST.*.>", v1, v2, v3, v4, v5, v6);
        assertMapValue("TEST.>", v1, v2, v3, v4, v5, v6);
        assertMapValue("TEST.>.>", v1, v2, v3, v4, v5, v6);
        assertMapValue("*.*.D3", v2, v3, v5);
        assertMapValue("TEST.BAR.*", v2, v5, v6);

        assertMapValue("TEST.BAR.D2", v2, v6);
        assertMapValue("TEST.*.D2", v2, v6);
        assertMapValue("TEST.BAR.*", v2, v5, v6);
    }

    public void testDoubleWildcardDoesNotMatchLongerPattern() throws Exception {
        put("TEST.*", v1);
        put("TEST.BAR.D3", v2);

        assertMapValue("*.*.D3", v2);
    }

    public void testWildcardAtEndOfPathAndAtBeginningOfSearch() throws Exception {
        put("TEST.*", v1);

        assertMapValue("*.D1", v1);
    }

    public void testAnyPathWildcardInMap() throws Exception {
        put("TEST.FOO.>", v1);

        assertMapValue("TEST.FOO.BAR.WHANOT.A.B.C", v1);
        assertMapValue("TEST.FOO.BAR.WHANOT", v1);
        assertMapValue("TEST.FOO.BAR", v1);

        assertMapValue("TEST.*.*", v1);
        assertMapValue("TEST.BAR", null);

        assertMapValue("TEST.FOO", v1);
    }

    public void testSimpleAddRemove() throws Exception {
        put("TEST.D1", v2);

        assertEquals("Root child count", 1, map.getTopicRootChildCount());

        assertMapValue("TEST.D1", v2);

        remove("TEST.D1", v2);

        assertEquals("Root child count", 0, map.getTopicRootChildCount());
        assertMapValue("TEST.D1", null);
    }

    public void testMQTTMappedWildcards() throws Exception {
        put("TopicA", v1);
        put(".TopicA", v2);
        put("TopicA.", v3);
        put(".", v4);
        put("..TopicA", v5);
        put("..", v6);

        // test wildcard patterns "#", "+", "+/#", "/+", "+/", "+/+", "+/+/", "+/+/+"
        assertMapValue(">", v1, v2, v3, v4, v5, v6);
        assertMapValue("*", v1);
        assertMapValue("*.>", v1, v2, v3, v4, v5, v6);
        assertMapValue(".*", v2, v4);
        assertMapValue("*.", v3, v4);
        assertMapValue("*.*", v2, v3, v4);
        assertMapValue("*.*.", v6);
        assertMapValue("*.*.*", v5, v6);
    }

    public void testStoreAndLookupAllWildcards() throws Exception {
        loadSample2();

        assertSample2();

        // lets remove everything and add it back
        remove("TEST.FOO", v1);

        assertMapValue("TEST.FOO", v2, v3, v4);
        assertMapValue("TEST.*", v2, v3, v4, v6);
        assertMapValue("*.*", v2, v3, v4, v6);

        remove("TEST.XYZ", v6);

        assertMapValue("TEST.*", v2, v3, v4);
        assertMapValue("*.*", v2, v3, v4);

        remove("TEST.*", v2);

        assertMapValue("TEST.*", v3, v4);
        assertMapValue("*.*", v3, v4);

        remove(">", v4);

        assertMapValue("TEST.*", v3);
        assertMapValue("*.*", v3);

        remove("TEST.>", v3);
        remove("TEST.FOO.BAR", v5);

        assertMapValue("FOO", null);
        assertMapValue("TEST.FOO", null);
        assertMapValue("TEST.D1", null);

        assertMapValue("TEST.FOO.FOO", null);
        assertMapValue("TEST.BAR.FOO", null);
        assertMapValue("TEST.FOO.BAR", null);
        assertMapValue("TEST.BAR.D3", null);

        assertMapValue("TEST.*", null);
        assertMapValue("*.*", null);
        assertMapValue("*.D1", null);
        assertMapValue("TEST.*.*", null);
        assertMapValue("TEST.BAR.*", null);

        loadSample2();

        assertSample2();

        remove(">", v4);
        remove("TEST.*", v2);

        assertMapValue("FOO", null);
        assertMapValue("TEST.FOO", v1, v3);
        assertMapValue("TEST.D1", v3);

        assertMapValue("TEST.FOO.FOO", v3);
        assertMapValue("TEST.BAR.FOO", v3);
        assertMapValue("TEST.FOO.BAR", v3, v5);
        assertMapValue("TEST.BAR.D3", v3);

        assertMapValue("TEST.*", v1, v3, v6);
        assertMapValue("*.*", v1, v3, v6);
        assertMapValue("*.D1", v3);
        assertMapValue("TEST.*.*", v3, v5);
        assertMapValue("TEST.BAR.*", v3);
    }

    public void testAddAndRemove() throws Exception {

        put("FOO.A", v1);
        assertMapValue("FOO.>", v1);

        put("FOO.B", v2);
        assertMapValue("FOO.>", v1, v2);

        map.removeAll(createDestination("FOO.A"));

        assertMapValue("FOO.>", v2);
    }

    public void testRemoveWildcard() throws Exception {
        put("FOO.A", v1);
        put("FOO.>", v2);

        map.removeAll(createDestination("FOO.>"));

        assertMapValue("FOO.A", null);

        put("FOO.A", v1);
        put("FOO.>", v2);

        map.remove(createDestination("FOO.>"), v2);

        assertMapValue("FOO.A", v1);
    }

    protected void loadSample2() {
        put("TEST.FOO", v1);
        put("TEST.*", v2);
        put("TEST.>", v3);
        put(">", v4);
        put("TEST.FOO.BAR", v5);
        put("TEST.XYZ", v6);
    }

    protected void assertSample2() {
        assertMapValue("FOO", v4);
        assertMapValue("TEST.FOO", v1, v2, v3, v4);
        assertMapValue("TEST.D1", v2, v3, v4);

        assertMapValue("TEST.FOO.FOO", v3, v4);
        assertMapValue("TEST.BAR.FOO", v3, v4);
        assertMapValue("TEST.FOO.BAR", v3, v4, v5);
        assertMapValue("TEST.BAR.D3", v3, v4);

        assertMapValue("TEST.*", v1, v2, v3, v4, v6);
        assertMapValue("*.*", v1, v2, v3, v4, v6);
        assertMapValue("*.D1", v2, v3, v4);
        assertMapValue("TEST.*.*", v3, v4, v5);
        assertMapValue("TEST.BAR.*", v3, v4);
    }

    protected void put(String name, Object value) {
        map.put(createDestination(name), value);
    }

    protected void remove(String name, Object value) {
        ActiveMQDestination destination = createDestination(name);
        map.remove(destination, value);
    }

    protected void assertMapValue(String destinationName, Object expected) {
        ActiveMQDestination destination = createDestination(destinationName);
        assertMapValue(destination, expected);
    }

    protected void assertMapValue(String destinationName, Object expected1, Object expected2) {
        assertMapValue(destinationName, Arrays.asList(new Object[] {expected1, expected2}));
    }

    protected void assertMapValue(String destinationName, Object expected1, Object expected2, Object expected3) {
        assertMapValue(destinationName, Arrays.asList(new Object[] {expected1, expected2, expected3}));
    }

    protected void assertMapValue(String destinationName, Object expected1, Object expected2, Object expected3, Object expected4) {
        assertMapValue(destinationName, Arrays.asList(new Object[] {expected1, expected2, expected3, expected4}));
    }

    protected void assertMapValue(String destinationName, Object expected1, Object expected2, Object expected3, Object expected4, Object expected5) {
        assertMapValue(destinationName, Arrays.asList(new Object[] {expected1, expected2, expected3, expected4, expected5}));
    }

    protected void assertMapValue(String destinationName, Object expected1, Object expected2, Object expected3, Object expected4, Object expected5, Object expected6) {
        assertMapValue(destinationName, Arrays.asList(new Object[] {expected1, expected2, expected3, expected4, expected5, expected6}));
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    protected void assertMapValue(ActiveMQDestination destination, Object expected) {
        List expectedList = null;
        if (expected == null) {
            expectedList = Collections.EMPTY_LIST;
        } else if (expected instanceof List) {
            expectedList = (List)expected;
        } else {
            expectedList = new ArrayList();
            expectedList.add(expected);
        }
        Collections.sort(expectedList);
        Set actualSet = map.get(destination);
        List actual = new ArrayList(actualSet);
        Collections.sort(actual);
        assertEquals("map value for destinationName:  " + destination, expectedList, actual);
    }

    protected ActiveMQDestination createDestination(String name) {
        return new ActiveMQTopic(name);
    }
}
