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

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.management.ManagementFactory;
import java.util.Date;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.broker.TransportConnector;
import org.apache.activemq.store.kahadb.KahaDBStore;
import org.apache.activemq.util.Wait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DurableSubscriberNonPersistentMessageTest extends TestCase {

   private final Logger LOG = LoggerFactory.getLogger(DurableSubscriberNonPersistentMessageTest.class);
   private String brokerURL;
   private String consumerBrokerURL;

   int initialMaxMsgs = 10;
   int cleanupMsgCount = 10;
   int totalMsgCount = initialMaxMsgs + cleanupMsgCount;
   int totalMsgReceived = 0;
   int sleep = 500;
   int reconnectSleep = 2000;
   int messageTimeout = 1000;
   int messageSize = 1024;

   // Note: If ttl is set 0, the default set by the broker will be used if any
   // setting a value greater than 0 will enable the producer to set the ttl on
   // the message
   long ttl = 0;

   static String clientId = "Jason";
   MBeanServer mbeanServer;

   BrokerService broker;

   @Override
   protected void setUp() throws Exception {
      super.setUp();
      broker = new BrokerService();
      TransportConnector transportConnector = broker.addConnector("tcp://localhost:0");
      KahaDBStore store = new KahaDBStore();
      store.setDirectory(new File("data"));
      broker.setPersistenceAdapter(store);
      broker.start();

      brokerURL = "failover:(" + transportConnector.getPublishableConnectString() + ")";
      consumerBrokerURL = brokerURL + "?jms.prefetchPolicy.all=100";

      mbeanServer = ManagementFactory.getPlatformMBeanServer();
   }

   @Override
   protected void tearDown() throws Exception {
      broker.stop();
      super.tearDown();
   }

   /**
    * Create the test case
    *
    * @param testName name of the test case
    */
   public DurableSubscriberNonPersistentMessageTest(String testName) {
      super(testName);
   }

   /**
    * @return the suite of tests being tested
    */
   public static Test suite() {
      return new TestSuite(DurableSubscriberNonPersistentMessageTest.class);
   }

   public void testDurableSubscriberNonPersistentMessage() {
      String interest = "TEST";

      LOG.info("Starting DurableSubscriberNonPersistentMessageTest");

      try {
         // create durable topic consumer and disconnect
         createConsumer(interest, 0);
         Thread.sleep(1000);

         // produce 15 messages to topic
         Producer producer = new Producer(brokerURL, interest, messageSize, ttl);
         producer.sendMessages(totalMsgCount);
         producer.close();
         LOG.info(totalMsgCount + " messages sent");

         // durable topic consumer will consume 10 messages and disconnect
         createConsumer(interest, initialMaxMsgs);

         Thread.sleep(reconnectSleep);

         createConsumer(interest, cleanupMsgCount);

         String brokerVersion = (String) mbeanServer.getAttribute(new ObjectName("org.apache.activemq:brokerName=localhost,type=Broker"), "BrokerVersion");

         LOG.info("Test run on: " + brokerVersion);
         final String theJmxObject = "org.apache.activemq:type=Broker,brokerName=localhost," +
            "endpoint=Consumer,destinationType=Topic,destinationName=TEST,clientId=Jason," +
            "consumerId=Durable(Jason_MyDurableTopic)";

         assertTrue("pendingQueueSize should be zero", Wait.waitFor(new Wait.Condition() {
            @Override
            public boolean isSatisified() throws Exception {
               Integer pendingQueueSize = (Integer) mbeanServer.getAttribute(new ObjectName(theJmxObject), "PendingQueueSize");
               LOG.info("pendingQueueSize = " + pendingQueueSize);
               return pendingQueueSize.intValue() == 0;
            }
         }));

         assertTrue("cursorMemoryUsage should be zero", Wait.waitFor(new Wait.Condition() {
            @Override
            public boolean isSatisified() throws Exception {
               Long cursorMemoryUsage = (Long) mbeanServer.getAttribute(new ObjectName(theJmxObject), "CursorMemoryUsage");
               LOG.info("cursorMemoryUsage = " + cursorMemoryUsage);
               return cursorMemoryUsage.longValue() == 0L;
            }
         }));

         // Not sure what the behavior should be here, if the messages
         // expired the received count shouldn't equal total message count
         assertTrue(totalMsgReceived == initialMaxMsgs + cleanupMsgCount);
      } catch (Exception e) {
         LOG.error("Exception Executing DurableSubscriberNonPersistentMessageTest: " + getStackTrace(e));
         fail("Should not throw any exceptions");
      }
   }

   // create durable topic consumer and max number of messages
   public void createConsumer(String interest, int maxMsgs) {
      int messageReceived = 0;
      int messagesNotReceived = 0;

      LOG.info("Starting DurableSubscriber");

      Consumer consumer = null;

      try {
         consumer = new Consumer(consumerBrokerURL, interest, clientId);

         for (int i = 0; i < maxMsgs; i++) {
            try {
               Message msg = consumer.getMessage(messageTimeout);
               if (msg != null) {
                  LOG.debug("Received Message: " + msg.toString());
                  messageReceived++;
                  totalMsgReceived++;
               } else {
                  LOG.debug("message " + i + " not received");
                  messagesNotReceived++;
               }

               Thread.sleep(sleep);
            } catch (InterruptedException ie) {
               LOG.debug("Exception: " + ie);
            }
         }

         consumer.close();

         LOG.info("Consumer Finished");
         LOG.info("Received " + messageReceived);
         LOG.info("Not Received " + messagesNotReceived);
      } catch (JMSException e) {
         LOG.error("Exception Executing SimpleConsumer: " + getStackTrace(e));
      }
   }

   public String getStackTrace(Throwable aThrowable) {
      final Writer result = new StringWriter();
      final PrintWriter printWriter = new PrintWriter(result);
      aThrowable.printStackTrace(printWriter);
      return result.toString();
   }

   public class Producer {

      protected ConnectionFactory factory;
      protected transient Connection connection;
      protected transient Session session;
      protected transient MessageProducer producer;
      protected static final int messageSize = 1024;

      public Producer(String brokerURL, String interest, int messageSize, long ttl) throws JMSException {

         factory = new ActiveMQConnectionFactory(brokerURL);
         connection = factory.createConnection();
         connection.start();
         session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
         producer = session.createProducer(session.createTopic(interest));
         producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
         if (ttl > 0) {
            producer.setTimeToLive(ttl);
         }
      }

      public void close() throws JMSException {
         if (connection != null) {
            connection.close();
         }
      }

      protected void sendMessage() throws JMSException {
         TextMessage textMessage = session.createTextMessage("test message");
         producer.send(textMessage);
      }

      protected void sendMessages(int count) throws JMSException {
         for (int i = 0; i < count; i++) {
            TextMessage textMessage = session.createTextMessage(createMessageText(i));
            producer.send(textMessage);
         }
      }

      private String createMessageText(int index) {
         StringBuffer buffer = new StringBuffer(messageSize);
         buffer.append("Message: " + index + " sent at: " + new Date());
         if (buffer.length() > messageSize) {
            return buffer.substring(0, messageSize);
         }
         for (int i = buffer.length(); i < messageSize; i++) {
            buffer.append(' ');
         }
         return buffer.toString();
      }

      protected void commitTransaction() throws JMSException {
         session.commit();
      }
   }

   public class Consumer {

      private final ConnectionFactory factory;
      private final ActiveMQConnection connection;
      private final Session session;
      private final MessageConsumer messageConsumer;

      public Consumer(String brokerURL, String interest, String clientId) throws JMSException {
         factory = new ActiveMQConnectionFactory(brokerURL);
         connection = (ActiveMQConnection) factory.createConnection();
         connection.setClientID(clientId);
         connection.start();
         connection.getPrefetchPolicy().setAll(15);
         session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
         Destination destination = session.createTopic(interest);
         messageConsumer = session.createDurableSubscriber((Topic) destination, "MyDurableTopic");
      }

      public void deleteAllMessages() throws JMSException {
         while (getMessage(500) != null) {
            // empty queue
         }
      }

      public Message getMessage(int timeout) throws JMSException {
         return messageConsumer.receive(timeout);
      }

      public void close() throws JMSException {
         if (messageConsumer != null) {
            messageConsumer.close();
         }
         if (session != null) {
            session.close();
         }
         if (connection != null) {
            connection.close();
         }
      }

      public Session getSession() {
         return session;
      }
   }
}
