/*
 *
 * 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.protocol.v1_0;

import java.io.EOFException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.qpid.AMQException;
import org.apache.qpid.amqp_1_0.codec.ValueHandler;
import org.apache.qpid.amqp_1_0.messaging.SectionEncoder;
import org.apache.qpid.amqp_1_0.messaging.SectionEncoderImpl;
import org.apache.qpid.amqp_1_0.transport.SendingLinkEndpoint;
import org.apache.qpid.amqp_1_0.type.AmqpErrorException;
import org.apache.qpid.amqp_1_0.type.Binary;
import org.apache.qpid.amqp_1_0.type.DeliveryState;
import org.apache.qpid.amqp_1_0.type.Outcome;
import org.apache.qpid.amqp_1_0.type.Section;
import org.apache.qpid.amqp_1_0.type.Symbol;
import org.apache.qpid.amqp_1_0.type.UnsignedByte;
import org.apache.qpid.amqp_1_0.type.UnsignedInteger;
import org.apache.qpid.amqp_1_0.type.codec.AMQPDescribedTypeRegistry;
import org.apache.qpid.amqp_1_0.type.messaging.Accepted;
import org.apache.qpid.amqp_1_0.type.messaging.AmqpValue;
import org.apache.qpid.amqp_1_0.type.messaging.ApplicationProperties;
import org.apache.qpid.amqp_1_0.type.messaging.Data;
import org.apache.qpid.amqp_1_0.type.messaging.Header;
import org.apache.qpid.amqp_1_0.type.messaging.Modified;
import org.apache.qpid.amqp_1_0.type.messaging.Properties;
import org.apache.qpid.amqp_1_0.type.messaging.Released;
import org.apache.qpid.amqp_1_0.type.messaging.Source;
import org.apache.qpid.amqp_1_0.type.messaging.StdDistMode;
import org.apache.qpid.amqp_1_0.type.transaction.TransactionalState;
import org.apache.qpid.amqp_1_0.type.transport.SenderSettleMode;
import org.apache.qpid.amqp_1_0.type.transport.Transfer;
import org.apache.qpid.framing.AMQShortString;
import org.apache.qpid.framing.BasicContentHeaderProperties;
import org.apache.qpid.framing.FieldTable;
import org.apache.qpid.typedmessage.TypedBytesContentReader;
import org.apache.qpid.typedmessage.TypedBytesFormatException;
import org.apache.qpid.server.filter.FilterManager;
import org.apache.qpid.server.logging.LogActor;
import org.apache.qpid.server.message.AMQMessage;
import org.apache.qpid.server.message.MessageMetaData_1_0;
import org.apache.qpid.server.message.MessageTransferMessage;
import org.apache.qpid.server.message.ServerMessage;
import org.apache.qpid.server.protocol.AMQSessionModel;
import org.apache.qpid.server.queue.AMQQueue;
import org.apache.qpid.server.queue.QueueEntry;
import org.apache.qpid.server.store.StoreFuture;
import org.apache.qpid.server.store.StoredMessage;
import org.apache.qpid.server.subscription.Subscription;
import org.apache.qpid.server.txn.ServerTransaction;
import org.apache.qpid.transport.DeliveryProperties;
import org.apache.qpid.transport.MessageDeliveryMode;
import org.apache.qpid.transport.MessageProperties;
import org.apache.qpid.transport.codec.BBDecoder;

class Subscription_1_0 implements Subscription
{
    private SendingLink_1_0 _link;

    private AMQQueue _queue;

    private final AtomicReference<State> _state = new AtomicReference<State>(State.SUSPENDED);

    private final QueueEntry.SubscriptionAcquiredState _owningState = new QueueEntry.SubscriptionAcquiredState(this);
    private final long _id;
    private final boolean _acquires;
    private volatile AMQQueue.Context _queueContext;
    private Map<String, Object> _properties = new ConcurrentHashMap<String, Object>();
    private ReentrantLock _stateChangeLock = new ReentrantLock();

    private boolean _noLocal;
    private FilterManager _filters;

    private long _deliveryTag = 0L;
    private StateListener _stateListener;

    private Binary _transactionId;
    private final AMQPDescribedTypeRegistry _typeRegistry = AMQPDescribedTypeRegistry.newInstance()
                                                                                     .registerTransportLayer()
                                                                                     .registerMessagingLayer()
                                                                                     .registerTransactionLayer()
                                                                                     .registerSecurityLayer();
    private SectionEncoder _sectionEncoder = new SectionEncoderImpl(_typeRegistry);

    public Subscription_1_0(final SendingLink_1_0 link, final QueueDestination destination)
    {
        this(link, destination, ((Source)link.getEndpoint().getSource()).getDistributionMode() != StdDistMode.COPY);
    }

    public Subscription_1_0(final SendingLink_1_0 link, final QueueDestination destination, boolean acquires)
    {
        _link = link;
        _queue = destination.getQueue();
        _id = getEndpoint().getLocalHandle().longValue();
        _acquires = acquires;
    }

    private SendingLinkEndpoint getEndpoint()
    {
        return _link.getEndpoint();
    }

    public LogActor getLogActor()
    {
        return null;  //TODO
    }

    public boolean isTransient()
    {
        return true;  //TODO
    }

    public AMQQueue getQueue()
    {
        return _queue;
    }

    public QueueEntry.SubscriptionAcquiredState getOwningState()
    {
        return _owningState;
    }

    public void setQueue(final AMQQueue queue, final boolean exclusive)
    {
        //TODO
    }

    public void setNoLocal(final boolean noLocal)
    {
        _noLocal = noLocal;
    }

    public long getSubscriptionID()
    {
        return _id;
    }

    public boolean isSuspended()
    {
        return !isActive();// || !getEndpoint().hasCreditToSend();

    }

    public boolean hasInterest(final QueueEntry entry)
    {
        return !(_noLocal && (entry.getMessage() instanceof Message_1_0)
                          && ((Message_1_0)entry.getMessage()).getSession() == getSession())
               && checkFilters(entry);

    }

    private boolean checkFilters(final QueueEntry entry)
    {
        return (_filters == null) || _filters.allAllow(entry);
    }

    public boolean isClosed()
    {
        return !getEndpoint().isAttached();
    }

    public boolean acquires()
    {
        return _acquires;
    }

    public boolean seesRequeues()
    {
        // TODO
        return acquires();
    }

    public void close()
    {
        getEndpoint().detach();
    }

    public void send(QueueEntry entry, boolean batch) throws AMQException
    {
        // TODO
        send(entry);
    }

    public void flushBatched()
    {
        // TODO
    }

    public void send(final QueueEntry queueEntry) throws AMQException
    {
        ServerMessage serverMessage = queueEntry.getMessage();
        Message_1_0 message;
        if(serverMessage instanceof Message_1_0)
        {
            message = (Message_1_0) serverMessage;
        }
        else
        {
            if(serverMessage instanceof AMQMessage)
            {
                message = new Message_1_0(convert08Message((AMQMessage)serverMessage));
            }
            else if(serverMessage instanceof MessageTransferMessage)
            {
                message = new Message_1_0(convert010Message((MessageTransferMessage)serverMessage));
            }
            else
            {
                return;
            }
        }

        Transfer transfer = new Transfer();
        //TODO


        List<ByteBuffer> fragments = message.getFragments();
        ByteBuffer payload;
        if(fragments.size() == 1)
        {
            payload = fragments.get(0);
        }
        else
        {
            int size = 0;
            for(ByteBuffer fragment : fragments)
            {
                size += fragment.remaining();
            }

            payload = ByteBuffer.allocate(size);

            for(ByteBuffer fragment : fragments)
            {
                payload.put(fragment.duplicate());
            }

            payload.flip();
        }

        if(queueEntry.getDeliveryCount() != 0)
        {
            payload = payload.duplicate();
            ValueHandler valueHandler = new ValueHandler(_typeRegistry);

            Header oldHeader = null;
            try
            {
                ByteBuffer encodedBuf = payload.duplicate();
                Object value = valueHandler.parse(payload);
                if(value instanceof Header)
                {
                    oldHeader = (Header) value;
                }
                else
                {
                    payload.position(0);
                }
            }
            catch (AmqpErrorException e)
            {
                //TODO
                throw new RuntimeException(e);
            }

            Header header = new Header();
            if(oldHeader != null)
            {
                header.setDurable(oldHeader.getDurable());
                header.setPriority(oldHeader.getPriority());
                header.setTtl(oldHeader.getTtl());
            }
            header.setDeliveryCount(UnsignedInteger.valueOf(queueEntry.getDeliveryCount()));
            _sectionEncoder.reset();
            _sectionEncoder.encodeObject(header);
            Binary encodedHeader = _sectionEncoder.getEncoding();

            ByteBuffer oldPayload = payload;
            payload = ByteBuffer.allocate(oldPayload.remaining() + encodedHeader.getLength());
            payload.put(encodedHeader.getArray(),encodedHeader.getArrayOffset(),encodedHeader.getLength());
            payload.put(oldPayload);
            payload.flip();
        }

        transfer.setPayload(payload);
        byte[] data = new byte[8];
        ByteBuffer.wrap(data).putLong(_deliveryTag++);
        final Binary tag = new Binary(data);

        transfer.setDeliveryTag(tag);

        synchronized(_link.getLock())
        {
            if(_link.isAttached())
            {
                if(SenderSettleMode.SETTLED.equals(getEndpoint().getSendingSettlementMode()))
                {
                    transfer.setSettled(true);
                }
                else
                {
                    UnsettledAction action = _acquires
                                             ? new DispositionAction(tag, queueEntry)
                                             : new DoNothingAction(tag, queueEntry);

                    _link.addUnsettled(tag, action, queueEntry);
                }

                if(_transactionId != null)
                {
                    TransactionalState state = new TransactionalState();
                    state.setTxnId(_transactionId);
                    transfer.setState(state);
                }
                // TODO - need to deal with failure here
                if(_acquires && _transactionId != null)
                {
                    ServerTransaction txn = _link.getTransaction(_transactionId);
                    if(txn != null)
                    {
                        txn.addPostTransactionAction(new ServerTransaction.Action(){

                            public void postCommit()
                            {
                                //To change body of implemented methods use File | Settings | File Templates.
                            }

                            public void onRollback()
                            {
                                if(queueEntry.isAcquiredBy(Subscription_1_0.this))
                                {
                                    queueEntry.release();
                                    _link.getEndpoint().updateDisposition(tag, (DeliveryState)null, true);


                                }
                            }
                        });
                    }

                }
                getSession().getConnectionModel().registerMessageDelivered(message.getSize());
                getEndpoint().transfer(transfer);
            }
            else
            {
                queueEntry.release();
            }
        }

    }

    private StoredMessage<MessageMetaData_1_0> convert010Message(final MessageTransferMessage serverMessage)
    {
        final MessageMetaData_1_0 metaData = convertMetaData(serverMessage);

        return convertServerMessage(metaData, serverMessage);

    }

    private MessageMetaData_1_0 convertMetaData(final MessageTransferMessage serverMessage)
    {
        List<Section> sections = new ArrayList<Section>(3);
        final MessageProperties msgProps = serverMessage.getHeader().getMessageProperties();
        final DeliveryProperties deliveryProps = serverMessage.getHeader().getDeliveryProperties();

        Header header = new Header();
        if(deliveryProps != null)
        {
            header.setDurable(deliveryProps.hasDeliveryMode() && deliveryProps.getDeliveryMode() == MessageDeliveryMode.PERSISTENT);
            if(deliveryProps.hasPriority())
            {
                header.setPriority(UnsignedByte.valueOf((byte)deliveryProps.getPriority().getValue()));
            }
            if(deliveryProps.hasTtl())
            {
                header.setTtl(UnsignedInteger.valueOf(deliveryProps.getTtl()));
            }
            sections.add(header);
        }

        Properties props = new Properties();
        if(msgProps != null)
        {
        //        props.setAbsoluteExpiryTime();
            if(msgProps.hasContentEncoding())
            {
                props.setContentEncoding(Symbol.valueOf(msgProps.getContentEncoding()));
            }

            if(msgProps.hasCorrelationId())
            {
                props.setCorrelationId(msgProps.getCorrelationId());
            }
        //        props.setCreationTime();
        //        props.setGroupId();
        //        props.setGroupSequence();
            if(msgProps.hasMessageId())
            {
                props.setMessageId(msgProps.getMessageId());
            }
            if(msgProps.hasReplyTo())
            {
                props.setReplyTo(msgProps.getReplyTo().getExchange()+"/"+msgProps.getReplyTo().getRoutingKey());
            }
            if(msgProps.hasContentType())
            {
                props.setContentType(Symbol.valueOf(msgProps.getContentType()));

                // Modify the content type when we are dealing with java object messages produced by the Qpid 0.x client
                if(props.getContentType() == Symbol.valueOf("application/java-object-stream"))
                {
                    props.setContentType(Symbol.valueOf("application/x-java-serialized-object"));
                }
            }
        //        props.setReplyToGroupId();
            props.setSubject(serverMessage.getRoutingKey());
        //        props.setTo();
            if(msgProps.hasUserId())
            {
                props.setUserId(new Binary(msgProps.getUserId()));
            }

            sections.add(props);

            if(msgProps.getApplicationHeaders() != null)
            {
                sections.add(new ApplicationProperties(msgProps.getApplicationHeaders()));
            }
        }
        return new MessageMetaData_1_0(sections, _sectionEncoder);
    }

    private StoredMessage<MessageMetaData_1_0> convert08Message(final AMQMessage serverMessage)
    {
        final MessageMetaData_1_0 metaData = convertMetaData(serverMessage);

        return convertServerMessage(metaData, serverMessage);


    }

    private StoredMessage<MessageMetaData_1_0> convertServerMessage(final MessageMetaData_1_0 metaData,
                                                                    final ServerMessage serverMessage)
    {
        final String mimeType = serverMessage.getMessageHeader().getMimeType();
        byte[] data = new byte[(int) serverMessage.getSize()];
        serverMessage.getContent(ByteBuffer.wrap(data), 0);

        Section bodySection = convertMessageBody(mimeType, data);

        final ByteBuffer allData = encodeConvertedMessage(metaData, bodySection);

        return new StoredMessage<MessageMetaData_1_0>()
        {
            @Override
            public MessageMetaData_1_0 getMetaData()
            {
                return metaData;
            }

            @Override
            public long getMessageNumber()
            {
                return serverMessage.getMessageNumber();
            }

            @Override
            public void addContent(int offsetInMessage, ByteBuffer src)
            {
                throw new UnsupportedOperationException();
            }

            @Override
            public int getContent(int offsetInMessage, ByteBuffer dst)
            {
                ByteBuffer buf = allData.duplicate();
                buf.position(offsetInMessage);
                buf = buf.slice();
                int size;
                if(dst.remaining()<buf.remaining())
                {
                    buf.limit(dst.remaining());
                    size = dst.remaining();
                }
                else
                {
                    size = buf.remaining();
                }
                dst.put(buf);
                return size;
            }

            @Override
            public ByteBuffer getContent(int offsetInMessage, int size)
            {
                ByteBuffer buf = allData.duplicate();
                buf.position(offsetInMessage);
                buf = buf.slice();
                if(size < buf.remaining())
                {
                    buf.limit(size);
                }
                return buf;
            }

            @Override
            public StoreFuture flushToStore()
            {
                throw new UnsupportedOperationException();
            }

            @Override
            public void remove()
            {
                serverMessage.getStoredMessage().remove();
            }
        };
    }

    private ByteBuffer encodeConvertedMessage(MessageMetaData_1_0 metaData, Section bodySection)
    {
        int headerSize = (int) metaData.getStorableSize();

        _sectionEncoder.reset();
        _sectionEncoder.encodeObject(bodySection);
        Binary dataEncoding = _sectionEncoder.getEncoding();

        final ByteBuffer allData = ByteBuffer.allocate(headerSize + dataEncoding.getLength());
        metaData.writeToBuffer(0,allData);
        allData.put(dataEncoding.getArray(),dataEncoding.getArrayOffset(),dataEncoding.getLength());
        return allData;
    }

    private static Section convertMessageBody(String mimeType, byte[] data)
    {
        if("text/plain".equals(mimeType) || "text/xml".equals(mimeType))
        {
            String text = new String(data);
            return new AmqpValue(text);
        }
        else if("jms/map-message".equals(mimeType))
        {
            TypedBytesContentReader reader = new TypedBytesContentReader(ByteBuffer.wrap(data));

            LinkedHashMap map = new LinkedHashMap();
            final int entries = reader.readIntImpl();
            for (int i = 0; i < entries; i++)
            {
                try
                {
                    String propName = reader.readStringImpl();
                    Object value = reader.readObject();
                    map.put(propName, value);
                }
                catch (EOFException e)
                {
                    throw new IllegalArgumentException(e);
                }
                catch (TypedBytesFormatException e)
                {
                    throw new IllegalArgumentException(e);
                }

            }

            return new AmqpValue(map);

        }
        else if("amqp/map".equals(mimeType))
        {
            BBDecoder decoder = new BBDecoder();
            decoder.init(ByteBuffer.wrap(data));
            return new AmqpValue(decoder.readMap());

        }
        else if("amqp/list".equals(mimeType))
        {
            BBDecoder decoder = new BBDecoder();
            decoder.init(ByteBuffer.wrap(data));
            return new AmqpValue(decoder.readList());
        }
        else if("jms/stream-message".equals(mimeType))
        {
            TypedBytesContentReader reader = new TypedBytesContentReader(ByteBuffer.wrap(data));

            List list = new ArrayList();
            while (reader.remaining() != 0)
            {
                try
                {
                    list.add(reader.readObject());
                }
                catch (TypedBytesFormatException e)
                {
                    throw new RuntimeException(e);  // TODO - Implement
                }
                catch (EOFException e)
                {
                    throw new RuntimeException(e);  // TODO - Implement
                }
            }
            return new AmqpValue(list);
        }
        else
        {
            return new Data(new Binary(data));

        }
    }

    private MessageMetaData_1_0 convertMetaData(final AMQMessage serverMessage)
    {

        List<Section> sections = new ArrayList<Section>(3);

        Header header = new Header();

        header.setDurable(serverMessage.isPersistent());

        BasicContentHeaderProperties contentHeader =
                (BasicContentHeaderProperties) serverMessage.getContentHeaderBody().getProperties();

        header.setPriority(UnsignedByte.valueOf(contentHeader.getPriority()));
        final long expiration = serverMessage.getExpiration();
        final long arrivalTime = serverMessage.getArrivalTime();

        if(expiration > arrivalTime)
        {
            header.setTtl(UnsignedInteger.valueOf(expiration - arrivalTime));
        }
        sections.add(header);


        Properties props = new Properties();

        props.setContentEncoding(Symbol.valueOf(contentHeader.getEncodingAsString()));

        props.setContentType(Symbol.valueOf(contentHeader.getContentTypeAsString()));

        // Modify the content type when we are dealing with java object messages produced by the Qpid 0.x client
        if(props.getContentType() == Symbol.valueOf("application/java-object-stream"))
        {
            props.setContentType(Symbol.valueOf("application/x-java-serialized-object"));
        }

        final AMQShortString correlationId = contentHeader.getCorrelationId();
        if(correlationId != null)
        {
            props.setCorrelationId(new Binary(correlationId.getBytes()));
        }
        //        props.setCreationTime();
        //        props.setGroupId();
        //        props.setGroupSequence();
        final AMQShortString messageId = contentHeader.getMessageId();
        if(messageId != null)
        {
            props.setMessageId(new Binary(messageId.getBytes()));
        }
        props.setReplyTo(String.valueOf(contentHeader.getReplyTo()));

        //        props.setReplyToGroupId();
        props.setSubject(serverMessage.getRoutingKey());
        //        props.setTo();
        if(contentHeader.getUserId() != null)
        {
            props.setUserId(new Binary(contentHeader.getUserId().getBytes()));
        }
        sections.add(props);

        sections.add(new ApplicationProperties(FieldTable.convertToMap(contentHeader.getHeaders())));

        return new MessageMetaData_1_0(sections, _sectionEncoder);
    }

    public void queueDeleted(final AMQQueue queue)
    {
        //TODO
        getEndpoint().setSource(null);
        getEndpoint().detach();
    }

    public boolean wouldSuspend(final QueueEntry msg)
    {
        synchronized (_link.getLock())
        {
            final boolean hasCredit = _link.isAttached() && getEndpoint().hasCreditToSend();
            if(!hasCredit && getState() == State.ACTIVE)
            {
                suspend();
            }

            return !hasCredit;
        }
    }

    public boolean trySendLock()
    {
        return _stateChangeLock.tryLock();
    }

    public void suspend()
    {
        synchronized(_link.getLock())
        {
            if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED))
            {
                _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED);
            }
        }
    }

    public void getSendLock()
    {
        _stateChangeLock.lock();
    }

    public void releaseSendLock()
    {
        _stateChangeLock.unlock();
    }

    public void releaseQueueEntry(QueueEntry queueEntryImpl)
    {
        //To change body of implemented methods use File | Settings | File Templates.
    }


    public void onDequeue(final QueueEntry queueEntry)
    {
        //TODO
    }

    public void restoreCredit(final QueueEntry queueEntry)
    {
        //TODO
    }

    public void setStateListener(final StateListener listener)
    {
        _stateListener = listener;
    }

    public State getState()
    {
        return _state.get();
    }

    public AMQQueue.Context getQueueContext()
    {
        return _queueContext;
    }

    public void setQueueContext(AMQQueue.Context queueContext)
    {
        _queueContext = queueContext;
    }


    public boolean isActive()
    {
        return getState() == State.ACTIVE;
    }

    public void set(String key, Object value)
    {
        _properties.put(key, value);
    }

    public Object get(String key)
    {
        return _properties.get(key);
    }

    public boolean isSessionTransactional()
    {
        return false;  //TODO
    }

    public void queueEmpty()
    {
        synchronized(_link.getLock())
        {
            if(_link.drained())
            {
                if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED))
                {
                    _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED);
                }
            }
        }
    }

    public void flowStateChanged()
    {
        synchronized(_link.getLock())
        {
            if(isSuspended() && getEndpoint() != null)
            {
                if(_state.compareAndSet(State.SUSPENDED, State.ACTIVE))
                {
                    _stateListener.stateChange(this, State.SUSPENDED, State.ACTIVE);
                }
                _transactionId = _link.getTransactionId();
            }
        }
    }

    public Session_1_0 getSession()
    {
        return _link.getSession();
    }

    private class DispositionAction implements UnsettledAction
    {

        private final QueueEntry _queueEntry;
        private final Binary _deliveryTag;

        public DispositionAction(Binary tag, QueueEntry queueEntry)
        {
            _deliveryTag = tag;
            _queueEntry = queueEntry;
        }

        public boolean process(DeliveryState state, final Boolean settled)
        {

            Binary transactionId = null;
            final Outcome outcome;
            // If disposition is settled this overrides the txn?
            if(state instanceof TransactionalState)
            {
                transactionId = ((TransactionalState)state).getTxnId();
                outcome = ((TransactionalState)state).getOutcome();
            }
            else if (state instanceof Outcome)
            {
                outcome = (Outcome) state;
            }
            else
            {
                outcome = null;
            }


            ServerTransaction txn = _link.getTransaction(transactionId);

            if(outcome instanceof Accepted)
            {
                txn.dequeue(_queueEntry.getQueue(), _queueEntry.getMessage(),
                        new ServerTransaction.Action()
                        {

                            public void postCommit()
                            {
                                if(_queueEntry.isAcquiredBy(Subscription_1_0.this))
                                {
                                    _queueEntry.discard();
                                }
                            }

                            public void onRollback()
                            {

                            }
                        });
                txn.addPostTransactionAction(new ServerTransaction.Action()
                    {
                        public void postCommit()
                        {
                            //_link.getEndpoint().settle(_deliveryTag);
                            _link.getEndpoint().updateDisposition(_deliveryTag, (DeliveryState)outcome, true);
                            _link.getEndpoint().sendFlowConditional();
                        }

                        public void onRollback()
                        {
                            if(Boolean.TRUE.equals(settled))
                            {
                                final Modified modified = new Modified();
                                modified.setDeliveryFailed(true);
                                _link.getEndpoint().updateDisposition(_deliveryTag, modified, true);
                                _link.getEndpoint().sendFlowConditional();
                            }
                        }
                    });
            }
            else if(outcome instanceof Released)
            {
                txn.addPostTransactionAction(new ServerTransaction.Action()
                {
                    public void postCommit()
                    {

                        _queueEntry.release();
                        _link.getEndpoint().settle(_deliveryTag);
                    }

                    public void onRollback()
                    {
                        _link.getEndpoint().settle(_deliveryTag);
                    }
                });
            }

            else if(outcome instanceof Modified)
            {
                txn.addPostTransactionAction(new ServerTransaction.Action()
                {
                    public void postCommit()
                    {

                        _queueEntry.release();
                        if(Boolean.TRUE.equals(((Modified)outcome).getDeliveryFailed()))
                        {
                            _queueEntry.incrementDeliveryCount();
                        }
                        _link.getEndpoint().settle(_deliveryTag);
                    }

                    public void onRollback()
                    {
                        if(Boolean.TRUE.equals(settled))
                        {
                            final Modified modified = new Modified();
                            modified.setDeliveryFailed(true);
                            _link.getEndpoint().updateDisposition(_deliveryTag, modified, true);
                            _link.getEndpoint().sendFlowConditional();
                        }
                    }
                });
            }

            return (transactionId == null && outcome != null);
        }
    }

    private class DoNothingAction implements UnsettledAction
    {
        public DoNothingAction(final Binary tag,
                               final QueueEntry queueEntry)
        {
        }

        public boolean process(final DeliveryState state, final Boolean settled)
        {
            Binary transactionId = null;
            Outcome outcome = null;
            // If disposition is settled this overrides the txn?
            if(state instanceof TransactionalState)
            {
                transactionId = ((TransactionalState)state).getTxnId();
                outcome = ((TransactionalState)state).getOutcome();
            }
            else if (state instanceof Outcome)
            {
                outcome = (Outcome) state;
            }
            return true;
        }
    }

    public FilterManager getFilters()
    {
        return _filters;
    }

    public void setFilters(final FilterManager filters)
    {
        _filters = filters;
    }

    @Override
    public AMQSessionModel getSessionModel()
    {
        // TODO
        return getSession();
    }

    @Override
    public long getBytesOut()
    {
        // TODO
        return 0;
    }

    @Override
    public long getMessagesOut()
    {
        // TODO
        return 0;
    }

    @Override
    public long getUnacknowledgedBytes()
    {
        // TODO
        return 0;
    }

    @Override
    public long getUnacknowledgedMessages()
    {
        // TODO
        return 0;
    }

    @Override
    public String getConsumerName()
    {
        //TODO
        return "TODO";
    }
}
