/**
 * 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.activemq.command;

import org.apache.activemq.state.CommandVisitor;

/**
 * @openwire:marshaller code="22"
 * 
 */
public class MessageAck extends BaseCommand {

    public static final byte DATA_STRUCTURE_TYPE = CommandTypes.MESSAGE_ACK;

    /**
     * Used to let the broker know that the message has been delivered to the
     * client. Message will still be retained until an standard ack is received.
     * This is used get the broker to send more messages past prefetch limits
     * when an standard ack has not been sent.
     */
    public static final byte DELIVERED_ACK_TYPE = 0;

    /**
     * The standard ack case where a client wants the message to be discarded.
     */
    public static final byte STANDARD_ACK_TYPE = 2;

    /**
     * In case the client want's to explicitly let the broker know that a
     * message was not processed and the message was considered a poison
     * message.
     */
    public static final byte POISON_ACK_TYPE = 1;

    /**
     * In case the client want's to explicitly let the broker know that a
     * message was not processed and it was re-delivered to the consumer
     * but it was not yet considered to be a poison message.  The messageCount 
     * field will hold the number of times the message was re-delivered. 
     */
    public static final byte REDELIVERED_ACK_TYPE = 3;
    
    /**
     * The  ack case where a client wants only an individual message to be discarded.
     */
    public static final byte INDIVIDUAL_ACK_TYPE = 4;

/**
     * The ack case where a durable topic subscription does not match a selector.
     */
    public static final byte UNMATCHED_ACK_TYPE = 5;

    /**
     * the case where a consumer does not dispatch because message has expired inflight
     */
    public static final byte EXPIRED_ACK_TYPE = 6;

    protected byte ackType;
    protected ConsumerId consumerId;
    protected MessageId firstMessageId;
    protected MessageId lastMessageId;
    protected ActiveMQDestination destination;
    protected TransactionId transactionId;
    protected int messageCount;
    protected Throwable poisonCause;

    protected transient String consumerKey;

    public MessageAck() {
    }

    public MessageAck(MessageDispatch md, byte ackType, int messageCount) {
        this.ackType = ackType;
        this.consumerId = md.getConsumerId();
        this.destination = md.getDestination();
        this.lastMessageId = md.getMessage().getMessageId();
        this.messageCount = messageCount;
    }

    public MessageAck(Message message, byte ackType, int messageCount) {
        this.ackType = ackType;
        this.destination = message.getDestination();
        this.lastMessageId = message.getMessageId();
        this.messageCount = messageCount;
    }

    public void copy(MessageAck copy) {
        super.copy(copy);
        copy.firstMessageId = firstMessageId;
        copy.lastMessageId = lastMessageId;
        copy.destination = destination;
        copy.transactionId = transactionId;
        copy.ackType = ackType;
        copy.consumerId = consumerId;
    }

    public byte getDataStructureType() {
        return DATA_STRUCTURE_TYPE;
    }

    public boolean isMessageAck() {
        return true;
    }

    public boolean isPoisonAck() {
        return ackType == POISON_ACK_TYPE;
    }

    public boolean isStandardAck() {
        return ackType == STANDARD_ACK_TYPE;
    }

    public boolean isDeliveredAck() {
        return ackType == DELIVERED_ACK_TYPE;
    }
    
    public boolean isRedeliveredAck() {
        return ackType == REDELIVERED_ACK_TYPE;
    }
    
    public boolean isIndividualAck() {
        return ackType == INDIVIDUAL_ACK_TYPE;
    }

    public boolean isUnmatchedAck() {
        return ackType == UNMATCHED_ACK_TYPE;
    }

    public boolean isExpiredAck() {
        return ackType == EXPIRED_ACK_TYPE;
    }

    /**
     * @openwire:property version=1 cache=true
     */
    public ActiveMQDestination getDestination() {
        return destination;
    }

    public void setDestination(ActiveMQDestination destination) {
        this.destination = destination;
    }

    /**
     * @openwire:property version=1 cache=true
     */
    public TransactionId getTransactionId() {
        return transactionId;
    }

    public void setTransactionId(TransactionId transactionId) {
        this.transactionId = transactionId;
    }

    public boolean isInTransaction() {
        return transactionId != null;
    }

    /**
     * @openwire:property version=1 cache=true
     */
    public ConsumerId getConsumerId() {
        return consumerId;
    }

    public void setConsumerId(ConsumerId consumerId) {
        this.consumerId = consumerId;
    }

    /**
     * @openwire:property version=1
     */
    public byte getAckType() {
        return ackType;
    }

    public void setAckType(byte ackType) {
        this.ackType = ackType;
    }

    /**
     * @openwire:property version=1
     */
    public MessageId getFirstMessageId() {
        return firstMessageId;
    }

    public void setFirstMessageId(MessageId firstMessageId) {
        this.firstMessageId = firstMessageId;
    }

    /**
     * @openwire:property version=1
     */
    public MessageId getLastMessageId() {
        return lastMessageId;
    }

    public void setLastMessageId(MessageId lastMessageId) {
        this.lastMessageId = lastMessageId;
    }

    /**
     * The number of messages being acknowledged in the range.
     * 
     * @openwire:property version=1
     */
    public int getMessageCount() {
        return messageCount;
    }

    public void setMessageCount(int messageCount) {
        this.messageCount = messageCount;
    }

    /**
     * The cause of a poison ack, if a message listener
     * throws an exception it will be recorded here
     *
     * @openwire:property version=7
     */
    public Throwable getPoisonCause() {
        return poisonCause;
    }

    public void setPoisonCause(Throwable poisonCause) {
        this.poisonCause = poisonCause;
    }

    public Response visit(CommandVisitor visitor) throws Exception {
        return visitor.processMessageAck(this);
    }

    /**
     * A helper method to allow a single message ID to be acknowledged
     */
    public void setMessageID(MessageId messageID) {
        setFirstMessageId(messageID);
        setLastMessageId(messageID);
        setMessageCount(1);
    }

}
