/*
 * 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.test.client;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

import javax.jms.Connection;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.Message;

import org.apache.log4j.Logger;
import org.apache.qpid.AMQException;
import org.apache.qpid.client.AMQNoConsumersException;
import org.apache.qpid.client.AMQNoRouteException;
import org.apache.qpid.client.AMQQueue;
import org.apache.qpid.framing.AMQShortString;
import org.apache.qpid.protocol.AMQConstant;

/**
 * Provides utility methods for checking exceptions that are thrown on the client side when a message is
 * not routable.
 *
 * Exception objects are passed either explicitly as method parameters or implicitly
 * by previously doing {@link Connection#setExceptionListener(ExceptionListener)}.
 */
public class UnroutableMessageTestExceptionListener implements ExceptionListener
{
    private static final Logger _logger = Logger.getLogger(UnroutableMessageTestExceptionListener.class);

    /**
     * Number of seconds to check for an event that should should NOT happen
     */
    private static final int NEGATIVE_TIMEOUT = 2;

    /**
     * Number of seconds to keep checking for an event that should should happen
     */
    private static final int POSITIVE_TIMEOUT = 30;

    private BlockingQueue<JMSException> _exceptions = new ArrayBlockingQueue<JMSException>(1);

    @Override
    public void onException(JMSException e)
    {
        _logger.info("Received exception " + e);
        _exceptions.add(e);
    }

    public void assertReceivedNoRouteWithReturnedMessage(Message message, String intendedQueueName)
    {
        JMSException exception = getReceivedException();
        assertNoRouteExceptionWithReturnedMessage(exception, message, intendedQueueName);
    }

    public void assertReceivedNoRoute(String intendedQueueName)
    {
        JMSException exception = getReceivedException();
        assertNoRoute(exception, intendedQueueName);
    }

    public void assertReceivedNoConsumersWithReturnedMessage(Message message)
    {
        JMSException exception = getReceivedException();
        AMQNoConsumersException noConsumersException = (AMQNoConsumersException) exception.getLinkedException();
        assertNotNull("AMQNoConsumersException should be linked to JMSException", noConsumersException);
        Message bounceMessage = (Message) noConsumersException.getUndeliveredMessage();
        assertNotNull("Bounced Message is expected", bounceMessage);

        try
        {
            assertEquals("Unexpected message is bounced", message.getJMSMessageID(), bounceMessage.getJMSMessageID());
        }
        catch (JMSException e)
        {
            throw new RuntimeException("Couldn't check exception", e);
        }
    }

    public void assertReceivedReturnedMessageWithLongExceptionMessage(Message message, AMQQueue queue)
    {
        JMSException exception = getReceivedException();
        assertNoRouteException(exception, message);
        AMQShortString exchangeName = queue.getExchangeName();
        String expectedMessage = "Error: No Route for message [Exchange: " + exchangeName.asString().substring(0, 220) + "...";
        assertTrue("Unexpected exception message: " + exception.getMessage(), exception.getMessage().contains(expectedMessage));
    }

    public void assertNoRouteExceptionWithReturnedMessage(
            JMSException exception, Message message, String intendedQueueName)
    {
        assertNoRoute(exception, intendedQueueName);

        assertNoRouteException(exception, message);
    }

    private void assertNoRouteException(JMSException exception, Message message)
    {
        AMQNoRouteException noRouteException = (AMQNoRouteException) exception.getLinkedException();
        assertNotNull("AMQNoRouteException should be linked to JMSException", noRouteException);
        Message bounceMessage = (Message) noRouteException.getUndeliveredMessage();
        assertNotNull("Bounced Message is expected", bounceMessage);

        try
        {
            assertEquals("Unexpected message is bounced", message.getJMSMessageID(), bounceMessage.getJMSMessageID());
        }
        catch (JMSException e)
        {
            throw new RuntimeException("Couldn't check exception", e);
        }
    }

    public void assertNoRoute(JMSException exception, String intendedQueueName)
    {
        assertTrue(
                exception + " message should contain intended queue name",
                exception.getMessage().contains(intendedQueueName));

        AMQException noRouteException = (AMQException) exception.getLinkedException();
        assertNotNull("AMQException should be linked to JMSException", noRouteException);

        assertEquals(AMQConstant.NO_ROUTE, noRouteException.getErrorCode());
        assertTrue(
                "Linked exception " + noRouteException + " message should contain intended queue name",
                noRouteException.getMessage().contains(intendedQueueName));
    }


    public void assertNoException()
    {
        try
        {
            assertNull("Unexpected JMSException", _exceptions.poll(NEGATIVE_TIMEOUT, TimeUnit.SECONDS));
        }
        catch (InterruptedException e)
        {
            throw new RuntimeException("Couldn't check exception", e);
        }
    }

    private JMSException getReceivedException()
    {
        try
        {
            JMSException exception = _exceptions.poll(POSITIVE_TIMEOUT, TimeUnit.SECONDS);
            assertNotNull("JMSException is expected", exception);
            return exception;
        }
        catch(InterruptedException e)
        {
            throw new RuntimeException("Couldn't check exception", e);
        }
    }
}
