/**
 * 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.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.Date;
import java.util.Vector;

import junit.framework.Test;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQPrefetchPolicy;
import org.apache.activemq.broker.BrokerFactory;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.broker.jmx.DurableSubscriptionViewMBean;
import org.apache.activemq.command.ActiveMQTopic;
import org.apache.activemq.util.Wait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TopicDurableConnectStatsTest extends org.apache.activemq.TestSupport {

   private static final Logger LOG = LoggerFactory.getLogger(TopicDurableConnectStatsTest.class);
   private BrokerService broker;
   private ActiveMQTopic topic;
   private final Vector<Throwable> exceptions = new Vector<>();
   private final int messageSize = 4000;
   protected MBeanServerConnection mbeanServer;
   protected String domain = "org.apache.activemq";
   private ActiveMQConnectionFactory connectionFactory = null;
   final int numMessages = 20;

   private static Session session2 = null;

   @Override
   protected ActiveMQConnectionFactory createConnectionFactory() throws Exception {

      connectionFactory = new ActiveMQConnectionFactory("vm://" + getName(true));

      ActiveMQPrefetchPolicy prefetchPolicy = new ActiveMQPrefetchPolicy();
      prefetchPolicy.setAll(10);
      connectionFactory.setPrefetchPolicy(prefetchPolicy);

      connectionFactory.setWatchTopicAdvisories(false);
      return connectionFactory;
   }

   @Override
   protected Connection createConnection() throws Exception {
      return createConnection("cliName");
   }

   protected Connection createConnection(String name) throws Exception {
      Connection con = super.createConnection();
      con.setClientID(name);
      con.start();
      return con;
   }

   public static Test suite() {
      return suite(TopicDurableConnectStatsTest.class);
   }

   @Override
   protected void setUp() throws Exception {
      exceptions.clear();
      topic = (ActiveMQTopic) createDestination();

      createBroker();
      mbeanServer = ManagementFactory.getPlatformMBeanServer();
      super.setUp();
   }

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

   private void createBroker() throws Exception {
      createBroker(true);
   }

   private void createBroker(boolean deleteAllMessages) throws Exception {
      broker = BrokerFactory.createBroker("broker:(vm://" + getName(true) + ")");
      broker.setBrokerName(getName(true));
      broker.setDeleteAllMessagesOnStartup(deleteAllMessages);
      broker.setAdvisorySupport(false);
      broker.addConnector("tcp://0.0.0.0:0");

      setDefaultPersistenceAdapter(broker);
      broker.start();
   }

   private void destroyBroker() throws Exception {
      if (broker != null)
         broker.stop();
   }

   protected ObjectName assertRegisteredObjectName(String name) throws MalformedObjectNameException, NullPointerException {
      ObjectName objectName = new ObjectName(name);

      LOG.info("** Looking for " + name);
      try {
         if (mbeanServer.isRegistered(objectName)) {
            LOG.info("Bean Registered: " + objectName);
         } else {
            LOG.info("Couldn't find Mbean! " + objectName);

         }
      } catch (IOException e) {
         e.printStackTrace();
      }
      return objectName;
   }

   public void testPendingTopicStat() throws Exception {

      Connection consumerCon = createConnection("cliId1");
      Session consumerSession = consumerCon.createSession(true, Session.AUTO_ACKNOWLEDGE);
      MessageConsumer consumer1 = consumerSession.createDurableSubscriber(topic, "SubsId", "filter = 'true'", true);
      assertNotNull(consumer1);

      DurableSubscriptionViewMBean subscriber1 = null;

      ObjectName query = new ObjectName(domain + ":type=Broker,brokerName=" + getName(true) + ",destinationType=Topic,destinationName=" + topic.getTopicName() + ",endpoint=Consumer,clientId=cliId1,consumerId=*");

      java.util.Set<ObjectName> set = mbeanServer.queryNames(query, null);

      ObjectName subscriberObjName1 = set.iterator().next();
      subscriber1 = MBeanServerInvocationHandler.newProxyInstance(mbeanServer, subscriberObjName1, DurableSubscriptionViewMBean.class, true);

      LOG.info("Beginning Pending Queue Size count: " + subscriber1.getPendingQueueSize());
      LOG.info("Prefetch Limit: " + subscriber1.getPrefetchSize());

      assertEquals("no pending", 0, subscriber1.getPendingQueueSize());
      assertEquals("Prefetch Limit ", 10, subscriber1.getPrefetchSize());

      Connection producerCon = createConnection("x");
      Session producerSessions = producerCon.createSession(true, Session.AUTO_ACKNOWLEDGE);

      MessageProducer producer = producerSessions.createProducer(topic);
      producer.setDeliveryMode(DeliveryMode.PERSISTENT);

      int i = 0;
      for (; i < numMessages; i++) {

         if (i == 15) {
            // kill consumer

            LOG.info("Killing consumer at 15");
            consumerSession.close();
            consumerCon.close();
         }

         TextMessage message = producerSessions.createTextMessage(createMessageText(i));
         message.setJMSExpiration(0);
         message.setStringProperty("filter", "true");
         producer.send(topic, message);
         producerSessions.commit();

      }
      LOG.info("Sent " + i + " messages in total");
      producerCon.close();

      LOG.info("Pending Queue Size count: " + subscriber1.getPendingQueueSize());
      assertEquals("pending as expected", 20, subscriber1.getPendingQueueSize());

      LOG.info("Re-connect client and consume messages");
      Connection con2 = createConnection("cliId1");
      session2 = con2.createSession(true, Session.AUTO_ACKNOWLEDGE);
      MessageConsumer consumer2 = session2.createDurableSubscriber(topic, "SubsId", "filter = 'true'", true);

      final Listener listener = new Listener();
      consumer2.setMessageListener(listener);

      assertTrue("received all sent", Wait.waitFor(new Wait.Condition() {
         @Override
         public boolean isSatisified() throws Exception {
            return numMessages == listener.count;
         }
      }));

      LOG.info("Received: " + listener.count);

      int pq = subscriber1.getPendingQueueSize();
      LOG.info("Pending Queue Size count: " + pq);
      assertEquals("Pending queue after consumed", 0, pq);

      session2.close();
      con2.close();
      LOG.info("FINAL Pending Queue Size count (after consumer close): " + subscriber1.getPendingQueueSize());
   }

   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();
   }

   public static class Listener implements MessageListener {

      int count = 0;
      String id = null;

      Listener() {
      }

      @Override
      public void onMessage(Message message) {
         count++;
         try {
            session2.commit();
         } catch (JMSException e1) {
            e1.printStackTrace();
         }

         if (id != null) {
            try {
               LOG.info(id + ", " + message.getJMSMessageID());
            } catch (Exception ignored) {
            }
         }

         try {
            Thread.sleep(2);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }
}


