/*
 *
 * Copyright (c) 2006 The Apache Software Foundation
 *
 * Licensed 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.queue;

import org.apache.log4j.Logger;
import org.apache.mina.common.ByteBuffer;
import org.apache.qpid.framing.AMQDataBlock;
import org.apache.qpid.framing.AMQFrame;
import org.apache.qpid.framing.BasicDeliverBody;
import org.apache.qpid.server.AMQChannel;
import org.apache.qpid.server.protocol.AMQProtocolSession;
import org.apache.qpid.AMQException;

/**
 * Encapsulation of a supscription to a queue.
 * <p/>
 * Ties together the protocol session of a subscriber, the consumer tag that
 * was given out by the broker and the channel id.
 * <p/>
 */
public class SubscriptionImpl implements Subscription
{
    private static final Logger _logger = Logger.getLogger(SubscriptionImpl.class);

    public final AMQChannel channel;

    public final AMQProtocolSession protocolSession;

    public final String consumerTag;

    private final Object sessionKey;

    /**
     * True if messages need to be acknowledged
     */
    private final boolean _acks;

    public static class Factory implements SubscriptionFactory
    {
        public SubscriptionImpl createSubscription(int channel, AMQProtocolSession protocolSession, String consumerTag, boolean acks)
            throws AMQException
        {
            return new SubscriptionImpl(channel, protocolSession, consumerTag, acks);
        }

        public SubscriptionImpl createSubscription(int channel, AMQProtocolSession protocolSession, String consumerTag)
            throws AMQException
        {
            return new SubscriptionImpl(channel, protocolSession, consumerTag);
        }
    }

    public SubscriptionImpl(int channelId, AMQProtocolSession protocolSession,
                            String consumerTag, boolean acks)
        throws AMQException
    {
        AMQChannel channel = protocolSession.getChannel(channelId);
        if (channel == null) {
            throw new NullPointerException("channel not found in protocol session");
        }

        this.channel = channel;
        this.protocolSession = protocolSession;
        this.consumerTag = consumerTag;
        sessionKey = protocolSession.getKey();
        _acks = acks;
    }

    public SubscriptionImpl(int channel, AMQProtocolSession protocolSession,
                            String consumerTag)
        throws AMQException
    {
        this(channel, protocolSession, consumerTag, false);
    }

    public boolean equals(Object o)
    {
        return (o instanceof SubscriptionImpl) && equals((SubscriptionImpl) o);
    }

    /**
     * Equality holds if the session matches and the channel and consumer tag are the same.
     */
    private boolean equals(SubscriptionImpl psc)
    {
        return sessionKey.equals(psc.sessionKey)
                && psc.channel == channel
                && psc.consumerTag.equals(consumerTag);
    }

    public int hashCode()
    {
        return sessionKey.hashCode();
    }

    public String toString()
    {
        return "[channel=" + channel + ", consumerTag=" + consumerTag + ", session=" + protocolSession.getKey() + "]";
    }

    public void send(AMQMessage msg, AMQQueue queue) throws AMQException
    {
        if (msg != null)
        {
            final long deliveryTag = channel.getNextDeliveryTag();
            ByteBuffer deliver = createEncodedDeliverFrame(deliveryTag, msg.getRoutingKey(), msg.getExchangeName());
            AMQDataBlock frame = msg.getDataBlock(deliver, channel.getChannelId());
            if (_acks)
            {
                channel.addUnacknowledgedMessage(msg, deliveryTag, consumerTag, queue);
            }
            protocolSession.writeFrame(frame);
            // if we do not need to wait for client acknowledgements we can decrement
            // the reference count immediately
            if (!_acks)
            {
                msg.decrementReference();
                msg.dequeue(queue);
            }
            else
            {
                //move the msg to the back of the persistently recorded queue while
                //witing for ack
                msg.requeue(queue);
            }
        }
        else
        {
            _logger.error("Attempt to send Null message", new NullPointerException());
        }
    }

    public boolean isSuspended()
    {
        return channel.isSuspended();
    }

    /**
     * Callback indicating that a queue has been deleted.
     * @param queue
     */
    public void queueDeleted(AMQQueue queue)
    {
        channel.queueDeleted(queue);
    }

    private ByteBuffer createEncodedDeliverFrame(long deliveryTag, String routingKey, String exchange)
    {
        AMQFrame deliverFrame = BasicDeliverBody.createAMQFrame(channel.getChannelId(), consumerTag,
                                                                deliveryTag, false, exchange,
                                                                routingKey);
        ByteBuffer buf = ByteBuffer.allocate((int) deliverFrame.getSize()); // XXX: Could cast be a problem?
        deliverFrame.writePayload(buf);
        buf.flip();
        return buf;
    }
}
