/**
 * 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.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.management.ObjectName;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.Set;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.JmsMultipleBrokersTestSupport;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.broker.region.policy.PolicyEntry;
import org.apache.activemq.broker.region.policy.PolicyMap;
import org.apache.activemq.command.ActiveMQTopic;
import org.apache.activemq.network.NetworkConnector;
import org.apache.activemq.util.Wait;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import static org.junit.Assume.assumeNotNull;

public class DurableSubscriberWithNetworkRestartTest extends JmsMultipleBrokersTestSupport {

   private static final Log LOG = LogFactory.getLog(DurableSubscriberWithNetworkRestartTest.class);
   private static final String HUB = "HubBroker";
   private static final String SPOKE = "SpokeBroker";
   protected static final int MESSAGE_COUNT = 10;
   public boolean dynamicOnly = false;

   public void testSendOnAReceiveOnBWithTransportDisconnectDynamicOnly() throws Exception {
      dynamicOnly = true;
      try {
         testSendOnAReceiveOnBWithTransportDisconnect();
      } finally {
         dynamicOnly = false;
      }
   }

   public void testSendOnAReceiveOnBWithTransportDisconnect() throws Exception {
      bridge(SPOKE, HUB);
      startAllBrokers();

      verifyDuplexBridgeMbean();

      // Setup connection
      URI hubURI = brokers.get(HUB).broker.getTransportConnectors().get(0).getPublishableConnectURI();
      URI spokeURI = brokers.get(SPOKE).broker.getTransportConnectors().get(0).getPublishableConnectURI();
      ActiveMQConnectionFactory facHub = new ActiveMQConnectionFactory(hubURI);
      ActiveMQConnectionFactory facSpoke = new ActiveMQConnectionFactory(spokeURI);
      Connection conHub = facHub.createConnection();
      Connection conSpoke = facSpoke.createConnection();
      conHub.setClientID("clientHUB");
      conSpoke.setClientID("clientSPOKE");
      conHub.start();
      conSpoke.start();
      Session sesHub = conHub.createSession(false, Session.AUTO_ACKNOWLEDGE);
      Session sesSpoke = conSpoke.createSession(false, Session.AUTO_ACKNOWLEDGE);

      ActiveMQTopic topic = new ActiveMQTopic("TEST.FOO");
      String consumerName = "consumerName";

      // Setup consumers
      MessageConsumer remoteConsumer = sesHub.createDurableSubscriber(topic, consumerName);
      sleep(1000);
      remoteConsumer.close();

      // Setup producer
      MessageProducer localProducer = sesSpoke.createProducer(topic);
      localProducer.setDeliveryMode(DeliveryMode.PERSISTENT);

      final String payloadString = new String(new byte[10 * 1024]);
      // Send messages
      for (int i = 0; i < MESSAGE_COUNT; i++) {
         Message test = sesSpoke.createTextMessage("test-" + i);
         test.setStringProperty("payload", payloadString);
         localProducer.send(test);
      }
      localProducer.close();

      final String options = "?persistent=true&useJmx=true&deleteAllMessagesOnStartup=false";
      for (int i = 0; i < 2; i++) {
         brokers.get(SPOKE).broker.stop();
         sleep(1000);
         createBroker(new URI("broker:(tcp://localhost:61616)/" + SPOKE + options));
         bridge(SPOKE, HUB);
         brokers.get(SPOKE).broker.start();
         LOG.info("restarted spoke..:" + i);

         assertTrue("got mbeans on restart", Wait.waitFor(new Wait.Condition() {
            @Override
            public boolean isSatisified() throws Exception {
               return countMbeans(brokers.get(HUB).broker, "networkBridge", 20000) == (dynamicOnly ? 1 : 2);
            }
         }));
      }
   }

   private void verifyDuplexBridgeMbean() throws Exception {
      assertEquals(1, countMbeans(brokers.get(HUB).broker, "networkBridge", 5000));
   }

   private int countMbeans(BrokerService broker, String type, int timeout) throws Exception {
      final long expiryTime = System.currentTimeMillis() + timeout;

      if (!type.contains("=")) {
         type = type + "=*";
      }

      final ObjectName beanName = new ObjectName("org.apache.activemq:type=Broker,brokerName=" + broker.getBrokerName() + "," + type + ",*");
      Set<ObjectName> mbeans = null;
      int count = 0;
      do {
         if (timeout > 0) {
            Thread.sleep(100);
         }

         mbeans = broker.getManagementContext().queryNames(beanName, null);
         if (mbeans != null) {
            count = mbeans.size();
            LOG.info("Found: " + count + ", matching type: " + type);
            for (ObjectName objectName : mbeans) {
               LOG.info("" + objectName);
            }
            //} else {
            //logAllMbeans(broker);
         }
      } while ((mbeans == null || mbeans.isEmpty()) && expiryTime > System.currentTimeMillis());

      // If port 1099 is in use when the Broker starts, starting the jmx connector
      // will fail.  So, if we have no mbsc to query, skip the test.
      if (timeout > 0) {
         assumeNotNull(mbeans);
      }

      return count;

   }

   private void logAllMbeans(BrokerService broker) throws MalformedURLException {
      try {
         // trace all existing MBeans
         Set<ObjectName> all = broker.getManagementContext().queryNames(null, null);
         LOG.info("Total MBean count=" + all.size());
         for (ObjectName on : all) {
            LOG.info(on);
         }
      } catch (Exception ignored) {
         LOG.warn("getMBeanServer ex: " + ignored);
      }
   }

   public NetworkConnector bridge(String from, String to) throws Exception {
      NetworkConnector networkConnector = bridgeBrokers(from, to, dynamicOnly, -1, true);
      networkConnector.setSuppressDuplicateQueueSubscriptions(true);
      networkConnector.setDecreaseNetworkConsumerPriority(true);
      networkConnector.setConsumerTTL(1);
      networkConnector.setDuplex(true);
      return networkConnector;
   }

   @Override
   protected void startAllBrokers() throws Exception {
      // Ensure HUB is started first so bridge will be active from the get go
      BrokerItem brokerItem = brokers.get(HUB);
      brokerItem.broker.start();
      brokerItem = brokers.get(SPOKE);
      brokerItem.broker.start();
      sleep(600);
   }

   @Override
   public void setUp() throws Exception {
      super.setAutoFail(false);
      super.setUp();
      createBrokers(true);
   }

   private void createBrokers(boolean del) throws Exception {
      final String options = "?persistent=true&useJmx=true&deleteAllMessagesOnStartup=" + del;
      createBroker(new URI("broker:(tcp://localhost:61617)/" + HUB + options));
      createBroker(new URI("broker:(tcp://localhost:61616)/" + SPOKE + options));
   }

   @Override
   protected void configureBroker(BrokerService broker) {
      broker.setKeepDurableSubsActive(false);
      broker.getManagementContext().setCreateConnector(false);
      PolicyMap defaultPolcyMap = new PolicyMap();
      PolicyEntry defaultPolicy = new PolicyEntry();
      //defaultPolicy.setUseCache(false);
      if (broker.getBrokerName().equals(HUB)) {
         defaultPolicy.setStoreUsageHighWaterMark(2);
         broker.getSystemUsage().getStoreUsage().setLimit(1 * 1024 * 1024);
      }
      defaultPolcyMap.setDefaultEntry(defaultPolicy);
      broker.setDestinationPolicy(defaultPolcyMap);
      broker.getSystemUsage().getMemoryUsage().setLimit(100 * 1024 * 1024);
   }

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

   private void sleep(int milliSecondTime) {
      try {
         Thread.sleep(milliSecondTime);
      } catch (InterruptedException igonred) {
      }
   }
}
