/*
 * 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.qpid.server.jmx.mbeans;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import static org.mockito.Matchers.isNull;
import static org.mockito.Matchers.argThat;

import java.util.Arrays;
import java.util.Collections;

import javax.management.ListenerNotFoundException;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.OperationsException;

import org.apache.qpid.server.jmx.ManagedObjectRegistry;
import org.apache.qpid.server.model.Exchange;
import org.apache.qpid.server.model.LifetimePolicy;
import org.apache.qpid.server.model.Queue;
import org.apache.qpid.server.model.Statistics;
import org.apache.qpid.server.model.VirtualHost;
import org.apache.qpid.server.queue.NotificationCheck;
import org.mockito.ArgumentMatcher;

import junit.framework.TestCase;

public class QueueMBeanTest extends TestCase
{
    private static final String QUEUE_NAME = "QUEUE_NAME";
    private static final String QUEUE_DESCRIPTION = "QUEUE_DESCRIPTION";
    private static final String  QUEUE_TYPE = "QUEUE_TYPE";
    private static final String QUEUE_ALTERNATE_EXCHANGE = "QUEUE_ALTERNATE_EXCHANGE";

    private Queue _mockQueue;
    private Statistics _mockQueueStatistics;
    private VirtualHostMBean _mockVirtualHostMBean;
    private ManagedObjectRegistry _mockManagedObjectRegistry;
    private QueueMBean _queueMBean;

    @Override
    protected void setUp() throws Exception
    {
        _mockQueue = mock(Queue.class);
        _mockQueueStatistics = mock(Statistics.class);
        when(_mockQueue.getName()).thenReturn(QUEUE_NAME);
        when(_mockQueue.getStatistics()).thenReturn(_mockQueueStatistics);
        _mockVirtualHostMBean = mock(VirtualHostMBean.class);

        _mockManagedObjectRegistry = mock(ManagedObjectRegistry.class);
        when(_mockVirtualHostMBean.getRegistry()).thenReturn(_mockManagedObjectRegistry);

        _queueMBean = new QueueMBean(_mockQueue, _mockVirtualHostMBean);
    }

    public void testQueueName()
    {
        assertEquals(QUEUE_NAME, _queueMBean.getName());
    }

    /**********  Statistics **********/

    public void testGetMessageCount() throws Exception
    {
        assertStatistic("messageCount", 1000, Queue.QUEUE_DEPTH_MESSAGES);
    }

    public void testGetReceivedMessageCount() throws Exception
    {
        assertStatistic("receivedMessageCount", 1000l, Queue.TOTAL_ENQUEUED_MESSAGES);
    }

    public void testQueueDepth() throws Exception
    {
        assertStatistic("queueDepth", 4096l, Queue.QUEUE_DEPTH_BYTES);
    }

    public void testActiveConsumerCount() throws Exception
    {
        assertStatistic("activeConsumerCount", 3, Queue.CONSUMER_COUNT_WITH_CREDIT);
    }

    public void testConsumerCount() throws Exception
    {
        assertStatistic("consumerCount", 3, Queue.CONSUMER_COUNT);
    }

    /**********  Simple Attributes **********/

    public void testGetQueueDescription() throws Exception
    {
        assertAttribute("description", QUEUE_DESCRIPTION, Queue.DESCRIPTION);
    }

    public void testSetQueueDescription() throws Exception
    {
        testSetAttribute("description", Queue.DESCRIPTION, "descriptionold", "descriptionnew");
    }

    public void testQueueType() throws Exception
    {
        assertAttribute("queueType", QUEUE_TYPE, Queue.TYPE);
    }

    public void testMaximumDeliveryCount() throws Exception
    {
        assertAttribute("maximumDeliveryCount", 5, Queue.MAXIMUM_DELIVERY_ATTEMPTS);
    }

    public void testOwner() throws Exception
    {
        assertAttribute("owner", "testOwner", Queue.OWNER);
    }

    public void testIsDurable() throws Exception
    {
        when(_mockQueue.isDurable()).thenReturn(true);
        assertTrue(_queueMBean.isDurable());
    }

    public void testIsNotDurable() throws Exception
    {
        when(_mockQueue.isDurable()).thenReturn(false);
        assertFalse(_queueMBean.isDurable());
    }

    public void testIsAutoDelete() throws Exception
    {
        when(_mockQueue.getLifetimePolicy()).thenReturn(LifetimePolicy.AUTO_DELETE);
        assertTrue(_queueMBean.isAutoDelete());
    }

    public void testIsNotAutoDelete() throws Exception
    {
        when(_mockQueue.getLifetimePolicy()).thenReturn(LifetimePolicy.PERMANENT);
        assertFalse(_queueMBean.isAutoDelete());
    }

    public void testGetMaximumMessageAge() throws Exception
    {
        assertAttribute("maximumMessageAge", 10000l, Queue.ALERT_THRESHOLD_MESSAGE_AGE);
    }

    public void testSetMaximumMessageAge() throws Exception
    {
        testSetAttribute("maximumMessageAge", Queue.ALERT_THRESHOLD_MESSAGE_AGE, 1000l, 10000l);
    }

    public void testGetMaximumMessageSize() throws Exception
    {
        assertAttribute("maximumMessageSize", 1024l, Queue.ALERT_THRESHOLD_MESSAGE_SIZE);
    }

    public void testSetMaximumMessageSize() throws Exception
    {
        testSetAttribute("maximumMessageSize", Queue.ALERT_THRESHOLD_MESSAGE_SIZE, 1024l, 2048l);
    }

    public void testGetMaximumMessageCount() throws Exception
    {
        assertAttribute("maximumMessageCount", 5000l, Queue.ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES);
    }

    public void testSetMaximumMessageCount() throws Exception
    {
        testSetAttribute("maximumMessageCount", Queue.ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES, 4000l, 5000l);
    }

    public void testGetMaximumQueueDepth() throws Exception
    {
        assertAttribute("maximumQueueDepth", 1048576l, Queue.ALERT_THRESHOLD_QUEUE_DEPTH_BYTES);
    }

    public void testSetMaximumQueueDepth() throws Exception
    {
        testSetAttribute("maximumQueueDepth", Queue.ALERT_THRESHOLD_QUEUE_DEPTH_BYTES,1048576l , 2097152l);
    }

    public void testGetCapacity() throws Exception
    {
        assertAttribute("capacity", 1048576l, Queue.QUEUE_FLOW_CONTROL_SIZE_BYTES);
    }

    public void testSetCapacity() throws Exception
    {
        testSetAttribute("capacity", Queue.QUEUE_FLOW_CONTROL_SIZE_BYTES,1048576l , 2097152l);
    }

    public void testGetFlowResumeCapacity() throws Exception
    {
        assertAttribute("flowResumeCapacity", 1048576l, Queue.QUEUE_FLOW_RESUME_SIZE_BYTES);
    }

    public void testSetFlowResumeCapacity() throws Exception
    {
        testSetAttribute("flowResumeCapacity", Queue.QUEUE_FLOW_RESUME_SIZE_BYTES,1048576l , 2097152l);
    }

    public void testIsExclusive() throws Exception
    {
        assertAttribute("exclusive", Boolean.TRUE, Queue.EXCLUSIVE);
    }

    public void testIsNotExclusive() throws Exception
    {
        assertAttribute("exclusive", Boolean.FALSE, Queue.EXCLUSIVE);
    }

    public void testSetExclusive() throws Exception
    {
        testSetAttribute("exclusive", Queue.EXCLUSIVE, Boolean.FALSE , Boolean.TRUE);
    }

    /**********  Other attributes **********/

    public void testGetAlternateExchange()
    {
        Exchange mockAlternateExchange = mock(Exchange.class);
        when(mockAlternateExchange.getName()).thenReturn(QUEUE_ALTERNATE_EXCHANGE);

        when(_mockQueue.getAttribute(Queue.ALTERNATE_EXCHANGE)).thenReturn(mockAlternateExchange);

        assertEquals(QUEUE_ALTERNATE_EXCHANGE, _queueMBean.getAlternateExchange());
    }

    public void testGetAlternateExchangeWhenQueueHasNone()
    {
        when(_mockQueue.getAttribute(Queue.ALTERNATE_EXCHANGE)).thenReturn(null);

        assertNull(_queueMBean.getAlternateExchange());
    }

    public void testSetAlternateExchange() throws Exception
    {
        Exchange mockExchange1 = mock(Exchange.class);
        when(mockExchange1.getName()).thenReturn("exchange1");

        Exchange mockExchange2 = mock(Exchange.class);
        when(mockExchange2.getName()).thenReturn("exchange2");

        Exchange mockExchange3 = mock(Exchange.class);
        when(mockExchange3.getName()).thenReturn("exchange3");

        VirtualHost mockVirtualHost = mock(VirtualHost.class);
        when(mockVirtualHost.getExchanges()).thenReturn(Arrays.asList(new Exchange[] {mockExchange1, mockExchange2, mockExchange3}));
        when(_mockQueue.getParent(VirtualHost.class)).thenReturn(mockVirtualHost);

        _queueMBean.setAlternateExchange("exchange2");
        verify(_mockQueue).setAttribute(Queue.ALTERNATE_EXCHANGE, null, mockExchange2);
    }

    public void testSetAlternateExchangeWithUnknownExchangeName() throws Exception
    {
        Exchange mockExchange = mock(Exchange.class);
        when(mockExchange.getName()).thenReturn("exchange1");

        VirtualHost mockVirtualHost = mock(VirtualHost.class);
        when(mockVirtualHost.getExchanges()).thenReturn(Collections.singletonList(mockExchange));
        when(_mockQueue.getParent(VirtualHost.class)).thenReturn(mockVirtualHost);

        try
        {
            _queueMBean.setAlternateExchange("notknown");
            fail("Exception not thrown");
        }
        catch(OperationsException oe)
        {
            // PASS
        }
    }

    public void testRemoveAlternateExchange() throws Exception
    {
        _queueMBean.setAlternateExchange("");
        verify(_mockQueue).setAttribute(Queue.ALTERNATE_EXCHANGE, null, null);
    }

    /**********  Operations **********/

    /**********  Notifications **********/

    public void testNotificationListenerCalled() throws Exception
    {
        NotificationListener listener = mock(NotificationListener.class);
        _queueMBean.addNotificationListener(listener, null, null);

        NotificationCheck notification = mock(NotificationCheck.class);
        String notificationMsg = "Test notification message";

        _queueMBean.notifyClients(notification, _mockQueue, notificationMsg);
        verify(listener).handleNotification(isNotificationWithMessage(notificationMsg),
                                            isNull());
    }

    public void testAddRemoveNotificationListener() throws Exception
    {
        NotificationListener listener1 = mock(NotificationListener.class);
        _queueMBean.addNotificationListener(listener1, null, null);
        _queueMBean.removeNotificationListener(listener1);
    }

    public void testRemoveUnknownNotificationListener() throws Exception
    {
        NotificationListener listener1 = mock(NotificationListener.class);
        try
        {
            _queueMBean.removeNotificationListener(listener1);
            fail("Exception not thrown");
        }
        catch (ListenerNotFoundException e)
        {
            // PASS
        }
    }

    private Notification isNotificationWithMessage(final String expectedMessage)
    {
        return argThat( new ArgumentMatcher<Notification>()
        {
            @Override
            public boolean matches(Object argument)
            {
                Notification actual = (Notification) argument;
                return actual.getMessage().endsWith(expectedMessage);
            }
        });
    }

    private void assertStatistic(String jmxAttributeName, Object expectedValue, String underlyingAttributeName) throws Exception
    {
        when(_mockQueueStatistics.getStatistic(underlyingAttributeName)).thenReturn(expectedValue);
        MBeanTestUtils.assertMBeanAttribute(_queueMBean, jmxAttributeName, expectedValue);
    }

    private void assertAttribute(String jmxAttributeName, Object expectedValue, String underlyingAttributeName) throws Exception
    {
        when(_mockQueue.getAttribute(underlyingAttributeName)).thenReturn(expectedValue);
        MBeanTestUtils.assertMBeanAttribute(_queueMBean, jmxAttributeName, expectedValue);
    }

    private void testSetAttribute(String jmxAttributeName, String underlyingAttributeName, Object originalAttributeValue, Object newAttributeValue) throws Exception
    {
        when(_mockQueue.getAttribute(underlyingAttributeName)).thenReturn(originalAttributeValue);

        MBeanTestUtils.setMBeanAttribute(_queueMBean, jmxAttributeName, newAttributeValue);

        verify(_mockQueue).setAttribute(underlyingAttributeName, originalAttributeValue, newAttributeValue);
    }

}
