/**
 * 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.component.rabbitmq.reply;

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

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.AMQP.Queue.DeclareOk;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Envelope;

import org.apache.camel.AsyncCallback;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;

/**
 * A {@link ReplyManager} when using temporary queues.
 *
 * @version 
 */
public class TemporaryQueueReplyManager extends ReplyManagerSupport {

    private RabbitConsumer consumer;

    public TemporaryQueueReplyManager(CamelContext camelContext) {
        super(camelContext);
    }

    protected ReplyHandler createReplyHandler(ReplyManager replyManager, Exchange exchange, AsyncCallback callback,
                                              String originalCorrelationId, String correlationId, long requestTimeout) {
        return new TemporaryQueueReplyHandler(this, exchange, callback, originalCorrelationId, correlationId, requestTimeout);
    }

    public void updateCorrelationId(String correlationId, String newCorrelationId, long requestTimeout) {
        log.trace("Updated provisional correlationId [{}] to expected correlationId [{}]", correlationId, newCorrelationId);

        ReplyHandler handler = correlation.remove(correlationId);
        if (handler != null) {
            correlation.put(newCorrelationId, handler, requestTimeout);
        }
    }

    @Override
    protected void handleReplyMessage(String correlationID, AMQP.BasicProperties properties, byte[] message) {
        ReplyHandler handler = correlation.get(correlationID);
        if (handler == null && endpoint.isUseMessageIDAsCorrelationID()) {
            handler = waitForProvisionCorrelationToBeUpdated(correlationID, message);
        }
        if (handler != null) {
            correlation.remove(correlationID);
            handler.onReply(correlationID, properties, message);
        } else {
            // we could not correlate the received reply message to a matching request and therefore
            // we cannot continue routing the unknown message
            // log a warn and then ignore the message
            log.warn("Reply received for unknown correlationID [{}]. The message will be ignored: {}", correlationID, message);
        }
    }

    @Override
    protected Connection createListenerContainer() throws Exception {

        log.debug("Creating connection");
        Connection conn = endpoint.connect(executorService);

        log.debug("Creating channel");
        Channel channel = conn.createChannel();
        // setup the basicQos
        if (endpoint.isPrefetchEnabled()) {
            channel.basicQos(endpoint.getPrefetchSize(), endpoint.getPrefetchCount(),
                            endpoint.isPrefetchGlobal());
        }

        //Let the server pick a random name for us
        DeclareOk result = channel.queueDeclare();
        log.info("Using temporary queue name: {}", result.getQueue());
        setReplyTo(result.getQueue());

        //TODO check for the RabbitMQConstants.EXCHANGE_NAME header
        channel.queueBind(getReplyTo(), endpoint.getExchangeName(), getReplyTo());

        consumer = new RabbitConsumer(this, channel);
        consumer.start();

        return conn;
    }

    @Override
    protected void doStop() throws Exception {
        super.doStop();
        consumer.stop();
    }

    //TODO combine with class in RabbitMQConsumer
    class RabbitConsumer extends com.rabbitmq.client.DefaultConsumer {

        private final TemporaryQueueReplyManager consumer;
        private final Channel channel;
        private String tag;

        /**
         * Constructs a new instance and records its association to the
         * passed-in channel.
         *
         * @param channel the channel to which this consumer is attached
         */
        public RabbitConsumer(TemporaryQueueReplyManager consumer, Channel channel) {
            super(channel);
            this.consumer = consumer;
            this.channel = channel;
        }

        @Override
        public void handleDelivery(String consumerTag, Envelope envelope,
                                   AMQP.BasicProperties properties, byte[] body) throws IOException {
            consumer.onMessage(properties, body);
        }

        /**
         * Bind consumer to channel
         */
        private void start() throws IOException {
            tag = channel.basicConsume(getReplyTo(), endpoint.isAutoAck(), this);
        }

        /**
         * Unbind consumer from channel
         */
        private void stop() throws IOException, TimeoutException {
            if (channel.isOpen()) {
                if (tag != null) {
                    channel.basicCancel(tag);
                }
                channel.close();
            }
        }
    }

}
