// Copyright (c) 2007-Present Pivotal Software, Inc.  All rights reserved.
//
// This software, the RabbitMQ Java client library, is triple-licensed under the
// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2
// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see
// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2.  For the ASL,
// please see LICENSE-APACHE2.
//
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
// either express or implied. See the LICENSE file for specific language governing
// rights and limitations of this software.
//
// If you have any questions regarding licensing, please contact us at
// info@rabbitmq.com.


package com.rabbitmq.client.test.functional;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import org.junit.Test;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.GetResponse;

/**
 * This tests whether bindings are created and nuked properly.
 *
 * The tests attempt to declare durable queues on a secondary node, if
 * present, and that node is restarted as part of the tests while the
 * primary node is still running. That way we exercise any node-down
 * handler code in the server.
 *
 */
public class BindingLifecycle extends BindingLifecycleBase {

    /**
     * This tests that when you purge a queue, all of its messages go.
     */
    @Test public void queuePurge() throws IOException {

        Binding binding = setupExchangeBindings(false);
        channel.basicPublish(binding.x, binding.k, null, payload);

        // Purge the queue, and test that we don't recieve a message
        channel.queuePurge(binding.q);

        GetResponse response = channel.basicGet(binding.q, true);
        assertNull("The response SHOULD BE null", response);

        deleteExchangeAndQueue(binding);
    }

    /**
     * See bug 21854:
     * "When Queue.Purge is called, sent-but-unacknowledged messages are no
     * longer purged, even if the channel they were sent down is not
     * (Tx-)transacted."
     */
    @SuppressWarnings("deprecation")
    @Test public void unackedPurge() throws IOException {
        Binding binding = setupExchangeBindings(false);
        channel.basicPublish(binding.x, binding.k, null, payload);

        GetResponse response = channel.basicGet(binding.q, false);
        assertFalse(response.getEnvelope().isRedeliver());
        assertNotNull("The response SHOULD NOT BE null", response);

        // If we purge the queue the unacked message should still be there on
        // recover.
        channel.queuePurge(binding.q);
        response = channel.basicGet(binding.q, true);
        assertNull("The response SHOULD BE null", response);

        channel.basicRecover();
        response = channel.basicGet(binding.q, false);
        channel.basicRecover();
        assertTrue(response.getEnvelope().isRedeliver());
        assertNotNull("The response SHOULD NOT BE null", response);

        // If we recover then purge the message should go away
        channel.queuePurge(binding.q);
        response = channel.basicGet(binding.q, true);
        assertNull("The response SHOULD BE null", response);

        deleteExchangeAndQueue(binding);
    }

    /**
     * This tests whether when you delete an exchange, that any
     * bindings attached to it are deleted as well.
     */
    @Test public void exchangeDelete() throws IOException {

        boolean durable = true;
        Binding binding = setupExchangeAndRouteMessage(true);

        // Nuke the exchange and repeat this test, this time you
        // expect nothing to get routed

        channel.exchangeDelete(binding.x);
        channel.exchangeDeclare(binding.x, "direct");

        sendUnroutable(binding);

        channel.queueDelete(binding.q);
    }

    /**
     * This tests whether the server checks that an exchange is
     * actually being used when you try to delete it with the ifunused
     * flag.
     *
     * To test this, you try to delete an exchange with a queue still
     * bound to it and expect the delete operation to fail.
     */
    @Test public void exchangeIfUnused() throws IOException {

        boolean durable = true;
        Binding binding = setupExchangeBindings(true);

        try {
            channel.exchangeDelete(binding.x, true);
        }
        catch (IOException e) {
            checkShutdownSignal(AMQP.PRECONDITION_FAILED, e);
            openChannel();
            deleteExchangeAndQueue(binding);
            return;
        }

        fail("Exchange delete should have failed");
    }

    /**
     * This tests whether the server checks that an auto_delete
     * exchange actually deletes the bindings attached to it when it
     * is deleted.
     *
     * To test this, you declare and auto_delete exchange and bind an
     * auto_delete queue to it.
     *
     * Start a consumer on this queue, send a message, let it get
     * consumed and then cancel the consumer
     *
     * The unsubscribe should cause the queue to auto_delete, which in
     * turn should cause the exchange to auto_delete.
     *
     * Then re-declare the queue again and try to rebind it to the same exhange.
     *
     * Because the exchange has been auto-deleted, the bind operation
     * should fail.
     */
    @Test public void exchangeAutoDelete() throws IOException, TimeoutException {
        doAutoDelete(false, 1);
    }

    /**
     * Runs something similar to testExchangeAutoDelete, but adds
     * different queues with the same binding to the same exchange.
     *
     * The difference should be that the original exchange should not
     * get auto-deleted
     */
    @Test public void exchangeAutoDeleteManyBindings() throws IOException, TimeoutException {
        doAutoDelete(false, 10);
    }

    /**
     *
     */
    @Test public void exchangePassiveDeclare() throws IOException {
        channel.exchangeDeclare("testPassive", "direct");
        channel.exchangeDeclarePassive("testPassive");

        try {
            channel.exchangeDeclarePassive("unknown_exchange");
            fail("Passive declare of an unknown exchange should fail");
        }
        catch (IOException ioe) {
            checkShutdownSignal(AMQP.NOT_FOUND, ioe);
        }
    }

    /**
     * Test the behaviour of queue.unbind
     */
    @Test public void unbind() throws Exception {
        for (String exchange: new String[]{"amq.fanout", "amq.direct", "amq.topic", "amq.headers"}) {
            testUnbind(exchange);
        }
    }

    public void testUnbind(String exchange) throws Exception {
        Binding b = new Binding(channel.queueDeclare().getQueue(),
                                exchange,
                                "quay");

        // failure cases

        Binding[] tests = new Binding[] {
            new Binding("unknown_queue", b.x, b.k),
            new Binding(b.q, "unknown_exchange", b.k),
            new Binding("unknown_unknown", "exchange_queue", b.k),
            new Binding(b.q, b.x, "unknown_rk"),
            new Binding("unknown_queue", "unknown_exchange", "unknown_rk")
        };

        for (int i = 0; i < tests.length; i++) {
            Binding test = tests[i];
            // check we can unbind all sorts of things that don't exist
            channel.queueUnbind(test.q, test.x, test.k);
        }

        // success case

        channel.queueBind(b.q, b.x, b.k);
        sendRoutable(b);
        channel.queueUnbind(b.q, b.x, b.k);
        sendUnroutable(b);
    }

}
