/**
 * 
 * Copyright 2004 Protique Ltd
 * 
 * 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.codehaus.activemq.service.impl;

import java.beans.DesignMode;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.activemq.message.ActiveMQMessage;
import org.codehaus.activemq.message.MessageAck;
import org.codehaus.activemq.service.DeadLetterPolicy;
import org.codehaus.activemq.service.MessageContainerAdmin;
import org.codehaus.activemq.service.MessageContainerManager;
import org.codehaus.activemq.service.MessageIdentity;
import org.codehaus.activemq.service.QueueList;
import org.codehaus.activemq.service.QueueListEntry;
import org.codehaus.activemq.service.QueueMessageContainer;
import org.codehaus.activemq.store.MessageStore;
import org.codehaus.activemq.store.PersistenceAdapter;
import org.codehaus.activemq.util.Callback;
import org.codehaus.activemq.util.TransactionTemplate;

import javax.jms.JMSException;
import javax.jms.MessageConsumer;

/**
 * A default implemenation of a Durable Queue based
 * {@link org.codehaus.activemq.service.MessageContainer}
 * which acts as an adapter between the {@link org.codehaus.activemq.service.MessageContainerManager}
 * requirements and those of the persistent {@link MessageStore} implementations.
 *
 * @version $Revision: 1.4 $
 */
public class DurableQueueMessageContainer implements QueueMessageContainer, MessageContainerAdmin {
    private static final Log log = LogFactory.getLog(DurableQueueMessageContainer.class);

    private MessageStore messageStore;
    private String destinationName;
    private boolean deadLetterQueue;

    /**
     * messages to be delivered
     */
    private QueueList messagesToBeDelivered;
    /**
     * messages that have been delivered but not acknowledged
     */
    private QueueList deliveredMessages;
    private PersistenceAdapter persistenceAdapter;
    private TransactionTemplate transactionTemplate;

    public DurableQueueMessageContainer(PersistenceAdapter persistenceAdapter, MessageStore messageStore, String destinationName) {
        this(persistenceAdapter, messageStore, destinationName, new DefaultQueueList(), new DefaultQueueList());
    }

    public DurableQueueMessageContainer(PersistenceAdapter persistenceAdapter, MessageStore messageStore, String destinationName, QueueList messagesToBeDelivered, QueueList deliveredMessages) {
        this.persistenceAdapter = persistenceAdapter;
        this.messageStore = messageStore;
        this.destinationName = destinationName;
        this.messagesToBeDelivered = messagesToBeDelivered;
        this.deliveredMessages = deliveredMessages;
        this.transactionTemplate = new TransactionTemplate(persistenceAdapter);
        this.deadLetterQueue = destinationName.startsWith(DeadLetterPolicy.DEAD_LETTER_PREFIX);
    }

    public String getDestinationName() {
        return destinationName;
    }

    public MessageIdentity addMessage(ActiveMQMessage message) throws JMSException {
        MessageIdentity answer = messageStore.addMessage(message);
        synchronized( this ) {
        	messagesToBeDelivered.add(answer);
        }
        return answer;

    }

    public synchronized void delete(MessageIdentity messageID, MessageAck ack) throws JMSException {
        
        // lets find the cached identity as it has the sequence number
        // attached to it
    	MessageIdentity storedIdentity=null;

    	synchronized( this ) {
	        QueueListEntry entry = deliveredMessages.getFirstEntry();
	        while (entry != null) {
	            MessageIdentity identity = (MessageIdentity) entry.getElement();
	            if (messageID.equals(identity)) {
	                deliveredMessages.remove(entry);
	                storedIdentity=identity;
	                break;
	            }
	            entry = deliveredMessages.getNextEntry(entry);
	        }
	        
	        if (storedIdentity==null) {
	            // maybe the messages have not been delivered yet
	            // as we are recovering from a previous transaction log
	            entry = messagesToBeDelivered.getFirstEntry();
	            while (entry != null) {
	                MessageIdentity identity = (MessageIdentity) entry.getElement();
	                if (messageID.equals(identity)) {
	                    messagesToBeDelivered.remove(entry);
	                    storedIdentity=identity;
	                    break;
	                }
	                entry = messagesToBeDelivered.getNextEntry(entry);
	            }
	        }
    	}
    	
        if (storedIdentity==null) {
            log.error("Attempt to acknowledge unknown messageID: " + messageID);
        } else {
            messageStore.removeMessage(storedIdentity, ack);        	
        }
        
    }

    public ActiveMQMessage getMessage(MessageIdentity messageID) throws JMSException {
        return messageStore.getMessage(messageID);
    }


    public boolean containsMessage(MessageIdentity messageIdentity) throws JMSException {
        /** TODO: make more optimal implementation */
        return getMessage(messageIdentity) != null;
    }

    /**
     * Does nothing since when we receive an acknowledgement on a queue
     * we can delete the message
     *
     * @param messageIdentity
     */
    public void registerMessageInterest(MessageIdentity messageIdentity) {
    }

    /**
     * Does nothing since when we receive an acknowledgement on a queue
     * we can delete the message
     *
     * @param messageIdentity
     * @param ack
     */
    public void unregisterMessageInterest(MessageIdentity messageIdentity, MessageAck ack) {
    }

    public ActiveMQMessage poll() throws JMSException {
        ActiveMQMessage message = null;
        MessageIdentity messageIdentity=null;
    	synchronized( this ) {
	        messageIdentity = (MessageIdentity) messagesToBeDelivered.removeFirst();
	        if (messageIdentity != null) {
	            deliveredMessages.add(messageIdentity);
	        }
    	}
        if (messageIdentity != null) {
            message = messageStore.getMessage(messageIdentity);
        }
        return message;
    }

    public ActiveMQMessage peekNext(MessageIdentity messageID) throws JMSException {
    	ActiveMQMessage answer = null;
    	MessageIdentity identity = null;
    	synchronized( this ) {
            if (messageID == null) {
            	identity = (MessageIdentity) messagesToBeDelivered.getFirst();
            }
            else {
                int index = messagesToBeDelivered.indexOf(messageID);
                if (index >= 0 && (index + 1) < messagesToBeDelivered.size()) {
                	identity = (MessageIdentity) messagesToBeDelivered.get(index + 1);
                }
            }
            
    	}
        if (identity != null) {
            answer = messageStore.getMessage(identity);
        }
        return answer;
    }


    public synchronized void returnMessage(MessageIdentity messageIdentity) throws JMSException {
        boolean result = deliveredMessages.remove(messageIdentity);
        messagesToBeDelivered.addFirst(messageIdentity);
    }

    /**
     * called to reset dispatch pointers if a new Message Consumer joins
     *
     * @throws javax.jms.JMSException
     */
    public synchronized void reset() throws JMSException {
        //new Message Consumer - move all filtered/undispatched messages to front of queue
        int count = 0;
        MessageIdentity messageIdentity = (MessageIdentity) deliveredMessages.removeFirst();
        while (messageIdentity != null) {
            messagesToBeDelivered.add(count++, messageIdentity);
            messageIdentity = (MessageIdentity) deliveredMessages.removeFirst();
        }
    }

    public synchronized void start() throws JMSException {
        final QueueMessageContainer container = this;
        transactionTemplate.run(new Callback() {
            public void execute() throws Throwable {
                messageStore.start();
                messageStore.recover(container);
            }
        });

    }

    public synchronized void recoverMessageToBeDelivered(MessageIdentity messageIdentity) throws JMSException {
        messagesToBeDelivered.add(messageIdentity);
    }

    public void stop() throws JMSException {
        messageStore.stop();
    }

    /**
     * @see org.codehaus.activemq.service.MessageContainer#getMessageContainerAdmin()
     */
    public MessageContainerAdmin getMessageContainerAdmin() {
        return this;
    }

    /**
     * @see org.codehaus.activemq.service.MessageContainerAdmin#empty()
     */
    public void empty() throws JMSException {
        messageStore.removeAllMessages();
    }

    /**
     * @see org.codehaus.activemq.service.QueueMessageContainer#isDeadLetterQueue()
     */
    public boolean isDeadLetterQueue() {
        return deadLetterQueue;
    }

    /**
     * @see org.codehaus.activemq.service.QueueMessageContainer#setDeadLetterQueue(boolean)
     */
    public void setDeadLetterQueue(boolean value) {
        deadLetterQueue = value;
        
    }
    
}
