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

import java.io.IOException;

import javax.jms.JMSException;
import javax.jms.TextMessage;

import junit.framework.TestCase;

import org.codehaus.activemq.broker.BrokerClient;
import org.codehaus.activemq.broker.impl.BrokerClientImpl;
import org.codehaus.activemq.filter.DestinationFilter;
import org.codehaus.activemq.filter.FilterFactoryImpl;
import org.codehaus.activemq.message.ActiveMQDestination;
import org.codehaus.activemq.message.ActiveMQMessage;
import org.codehaus.activemq.message.ActiveMQTextMessage;
import org.codehaus.activemq.message.ConsumerInfo;
import org.codehaus.activemq.service.DeadLetterPolicy;
import org.codehaus.activemq.service.MessageContainer;
import org.codehaus.activemq.service.MessageContainerManager;
import org.codehaus.activemq.service.MessageIdentity;
import org.codehaus.activemq.service.RedeliveryPolicy;
import org.codehaus.activemq.service.Subscription;
import org.codehaus.activemq.service.impl.DispatcherImpl;
import org.codehaus.activemq.service.impl.DurableQueueMessageContainerManager;
import org.codehaus.activemq.service.impl.DurableTopicMessageContainerManager;
import org.codehaus.activemq.service.impl.DurableTopicSubscription;
import org.codehaus.activemq.service.impl.DurableTopicSubscriptionContainerImpl;
import org.codehaus.activemq.store.PersistenceAdapter;
import org.codehaus.activemq.util.Callback;
import org.codehaus.activemq.util.IdGenerator;
import org.codehaus.activemq.util.TransactionTemplate;

/**
 * @version $Revision: 1.2 $
 */
public abstract class MessageStoreTestSupport extends TestCase {
    protected PersistenceAdapter persistenceAapter;
    protected MessageContainer container;
    protected Subscription subscription;
    protected int publishMessageCount = 10;
    protected int ackCount = 5;
    protected ActiveMQMessage[] messages;
    protected ActiveMQDestination destination;
    protected IdGenerator idGenerator = new IdGenerator();
    protected MessageContainerManager messageContainerManager;
    protected BrokerClient client = new BrokerClientImpl();
    protected TransactionTemplate template;

    public void testRecovery() throws Exception {
        System.out.println("Publishing: " + publishMessageCount + " messages");

        for (int i = 0; i < publishMessageCount; i++) {
            doAddMessage(i);
        }

        dumpMessageIdentities("After add");

        assertDeliveryList(0, publishMessageCount);

        // now lets ack messages
        System.out.println("Acknowledging the first: " + ackCount + " messages");
        for (int i = 0; i < ackCount; i++) {
            doAcknowledgeMessage(i);
        }

        // no more left to dispatch
        assertDeliveryList(0, 0);

        dumpMessageIdentities("After ack of first part");

        // now lets shut things down and then recover
        closeAndReopenContainer();

        assertDeliveryList(ackCount, publishMessageCount);

        dumpMessageIdentities("About to perform final ack");

        for (int i = ackCount; i < publishMessageCount; i++) {
            doAcknowledgeMessage(i);
        }
    }

    public void testRecoveryOfNewConsumerWhichHasYetToAck() throws Exception {

        assertDeliveryList(0, publishMessageCount);

        // no more left to dispatch
        assertDeliveryList(0, 0);

        // now lets shut things down and then recover
        closeAndReopenContainer();

        assertDeliveryList(0, publishMessageCount);
    }

    protected abstract void acknowledgeMessage(int i) throws JMSException;

    protected abstract PersistenceAdapter createPersistenceAdapter() throws IOException, Exception;

    protected abstract ActiveMQDestination createDestination();

    protected abstract ActiveMQMessage[] getMessagesToDispatch() throws JMSException;


    protected void doAcknowledgeMessage(final int i) throws JMSException {
        template.run(new Callback() {
            public void execute() throws Throwable {
                acknowledgeMessage(i);
            }
        });
    }

    protected void doAddMessage(int i) throws JMSException {
        final ActiveMQMessage message = getMessage(i);
        template.run(new Callback() {
            public void execute() throws Throwable {
                container.addMessage(message);
            }
        });
    }

    protected void dumpMessageIdentities(String text) throws JMSException {
        System.out.println("#### Dumping identities at: " + text);
        for (int i = 0; i < publishMessageCount; i++) {
            ActiveMQMessage message = getMessage(i);
            MessageIdentity identity = message.getJMSMessageIdentity();
            Object sequenceNo = identity.getSequenceNumber();
            String sequenceText = null;
            if (sequenceNo != null) {
                sequenceText = toStringFromSequenceNumber(sequenceNo);
            }
            System.out.println("item: " + i + " is: " + sequenceText);
        }
        System.out.println();
    }
    
    protected String toStringFromSequenceNumber(Object sequenceNo) {
    	return sequenceNo.toString();
    }

    protected String asText(byte[] data) {
        StringBuffer buffer = new StringBuffer("[ ");
        for (int i = 0; i < data.length; i++) {
            if (i > 0) {
                buffer.append(", ");
            }
            buffer.append(Byte.toString(data[i]));
        }
        buffer.append(" ]");
        return buffer.toString();
    }


    protected MessageContainer createTopicMessageContainer() throws JMSException {
        if (destination.isTopic()) {
            return persistenceAapter.createTopicMessageContainer(destination.toString());
        }
        else {
            return persistenceAapter.createQueueMessageContainer(destination.toString());
        }
    }

    protected Subscription createSubscription() throws JMSException {
        DestinationFilter filter = DestinationFilter.parseFilter(destination);
        ConsumerInfo consumerInfo = createConsumerInfo();

        // lets register the subscription with the manager
        messageContainerManager.addMessageConsumer(client, consumerInfo);

        return new DurableTopicSubscription(new DispatcherImpl(), client, consumerInfo, filter, new RedeliveryPolicy(),new DeadLetterPolicy());
    }

    protected ConsumerInfo createConsumerInfo() {
        ConsumerInfo answer = new ConsumerInfo();
        answer.setClientId(getClientID());
        answer.setConsumerId(idGenerator.generateId());
        answer.setConsumerName(getConsumerName());
        answer.setDestination(destination);
        answer.setPrefetchNumber(100);
        answer.setSessionId(idGenerator.generateId());
        answer.setStarted(true);
        return answer;
    }

    protected String getConsumerName() {
        return getName();
    }

    protected String getClientID() {
        return getClass().getName();
    }

    protected void setUp() throws Exception {
        super.setUp();
        this.messages = new ActiveMQMessage[publishMessageCount];
        this.destination = createDestination();

        this.persistenceAapter = createPersistenceAdapter();
        persistenceAapter.start();

        template = new TransactionTemplate(persistenceAapter);

        this.messageContainerManager = createMessageContainerManager();

        this.container = messageContainerManager.getContainer(this.destination.getPhysicalName());
        assertTrue("Should have created a container", container != null);

        this.subscription = createSubscription();

    }

    protected void tearDown() throws Exception {
        messageContainerManager.stop();
        persistenceAapter.stop();
        super.tearDown();
    }

    protected MessageContainerManager createMessageContainerManager() {
        if (destination.isTopic()) {
            return new DurableTopicMessageContainerManager(persistenceAapter, new DurableTopicSubscriptionContainerImpl(new RedeliveryPolicy(),new DeadLetterPolicy()), new FilterFactoryImpl(), new DispatcherImpl());
        }
        else {
            return new DurableQueueMessageContainerManager(persistenceAapter, new DurableTopicSubscriptionContainerImpl(new RedeliveryPolicy(),new DeadLetterPolicy()), new FilterFactoryImpl(), new DispatcherImpl());
        }
    }

    protected void assertDeliveryList(final int startIndex, final int lastIndex) throws JMSException {
        template.run(new Callback() {
            public void execute() throws Throwable {
                ActiveMQMessage[] messagesToDispatch = getMessagesToDispatch();
                int count = lastIndex - startIndex;
                assertTrue("Not enough messages available to dispatch. Expected: " + count
                        + " messages but was: " + messagesToDispatch.length, messagesToDispatch.length >= count);

                for (int i = 0; i < count; i++) {
                    ActiveMQMessage expected = getMessage(i + startIndex);
                    ActiveMQMessage actual = messagesToDispatch[i];
                    assertMessagesEqual("Dispatched message at index: " + i, expected, actual);
                }
            }
        });
    }

    protected void assertMessagesEqual(String description, ActiveMQMessage expected, ActiveMQMessage actual) throws JMSException {
        assertEquals("MessageText compare. " + description, ((TextMessage) expected).getText(), ((TextMessage) actual).getText());
        assertEquals("MessageID compare. " + description + " expected: " + expected + " actual: " + actual, expected.getJMSMessageID(), actual.getJMSMessageID());
        assertEquals(description, expected, actual);
    }

    protected ActiveMQMessage getMessage(int i) throws JMSException {
        if (messages[i] == null) {
            messages[i] = createMessage(i);
        }
        return messages[i];
    }

    protected ActiveMQMessage createMessage(int i) throws JMSException {
        ActiveMQTextMessage answer = new ActiveMQTextMessage();
        answer.setJMSMessageID(idGenerator.generateId());
        answer.setJMSClientID(getClientID());
        answer.setJMSDestination(destination);
        answer.setText("message index: " + i);
        return answer;
    }

    protected void closeAndReopenContainer() throws Exception {
        subscription.clear();

        messageContainerManager.stop();
        persistenceAapter.stop();

        persistenceAapter = createPersistenceAdapter();
        persistenceAapter.start();

        template = new TransactionTemplate(persistenceAapter);

        this.messageContainerManager = createMessageContainerManager();

        container = messageContainerManager.getContainer(destination.getPhysicalName());

        this.subscription = createSubscription();

        template.run(new Callback() {
            public void execute() throws Throwable {
                recover();
            }
        });
    }

    protected void recover() throws JMSException {
    }

    protected String getSubject() {
        return getClass().getName() + "." + getName();
    }
}
