/**
 * 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 java.net.URI;
import java.util.List;

import junit.framework.Test;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.JmsMultipleBrokersTestSupport;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.broker.TransportConnector;
import org.apache.activemq.command.ActiveMQTopic;
import org.apache.activemq.network.DiscoveryNetworkConnector;
import org.apache.activemq.network.NetworkConnector;
import org.apache.activemq.util.SocketProxy;
import org.apache.activemq.util.Wait;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class DurableSubscriberWithNetworkDisconnectTest extends JmsMultipleBrokersTestSupport {

   private static final Log LOG = LogFactory.getLog(DurableSubscriberWithNetworkDisconnectTest.class);
   private static final int NETWORK_DOWN_TIME = 10000;
   private static final String HUB = "HubBroker";
   private static final String SPOKE = "SpokeBroker";
   private SocketProxy socketProxy;
   private long networkDownTimeStart;
   private long inactiveDuration = 1000;
   private long receivedMsgs = 0;
   private boolean useSocketProxy = true;
   protected static final int MESSAGE_COUNT = 200;
   public boolean useDuplexNetworkBridge = true;
   public boolean simulateStalledNetwork;
   public boolean dynamicOnly = true;
   public long networkTTL = 3;
   public boolean exponentialBackOff;
   public boolean failover = false;
   public boolean inactivity = true;

   public void initCombosForTestSendOnAReceiveOnBWithTransportDisconnect() {
      addCombinationValues("failover", new Object[]{Boolean.FALSE, Boolean.TRUE});
   }

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

      startAllBrokers();

      // Setup connection
      URI hubURI = brokers.get(HUB).broker.getVmConnectorURI();
      URI spokeURI = brokers.get(SPOKE).broker.getVmConnectorURI();
      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 = sesSpoke.createDurableSubscriber(topic, consumerName);
      remoteConsumer.setMessageListener(new MessageListener() {
         @Override
         public void onMessage(Message msg) {
            try {
               TextMessage textMsg = (TextMessage) msg;
               receivedMsgs++;
               LOG.info("Received messages (" + receivedMsgs + "): " + textMsg.getText());
            } catch (JMSException e) {
               e.printStackTrace();
            }
         }
      });

      // allow subscription information to flow back to Spoke
      sleep(1000);

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

      // Send messages
      for (int i = 0; i < MESSAGE_COUNT; i++) {
         sleep(50);
         if (i == 50 || i == 150) {
            if (simulateStalledNetwork) {
               socketProxy.pause();
            } else {
               socketProxy.close();
            }
            networkDownTimeStart = System.currentTimeMillis();
         } else if (networkDownTimeStart > 0) {
            // restart after NETWORK_DOWN_TIME seconds
            sleep(NETWORK_DOWN_TIME);
            networkDownTimeStart = 0;
            if (simulateStalledNetwork) {
               socketProxy.goOn();
            } else {
               socketProxy.reopen();
            }
         } else {
            // slow message production to allow bridge to recover and limit message duplication
            sleep(500);
         }
         Message test = sesHub.createTextMessage("test-" + i);
         localProducer.send(test);
      }

      LOG.info("waiting for messages to flow");
      Wait.waitFor(new Wait.Condition() {
         @Override
         public boolean isSatisified() throws Exception {
            return receivedMsgs >= MESSAGE_COUNT;
         }
      });

      assertTrue("At least message " + MESSAGE_COUNT +
                    " must be received, count=" + receivedMsgs, MESSAGE_COUNT <= receivedMsgs);
      brokers.get(HUB).broker.deleteAllMessages();
      brokers.get(SPOKE).broker.deleteAllMessages();
      conHub.close();
      conSpoke.close();
   }

   @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 {
      networkDownTimeStart = 0;
      inactiveDuration = 1000;
      useSocketProxy = true;
      receivedMsgs = 0;
      super.setAutoFail(true);
      super.setUp();
      final String options = "?persistent=true&useJmx=false&deleteAllMessagesOnStartup=true";
      createBroker(new URI("broker:(tcp://localhost:61617)/" + HUB + options));
      createBroker(new URI("broker:(tcp://localhost:61616)/" + SPOKE + options));
   }

   @Override
   public void tearDown() throws Exception {
      super.tearDown();
      if (socketProxy != null) {
         socketProxy.close();
      }
   }

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

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

   @Override
   protected NetworkConnector bridgeBrokers(BrokerService localBroker,
                                            BrokerService remoteBroker,
                                            boolean l_dynamicOnly,
                                            int networkTTL,
                                            boolean l_conduit,
                                            boolean l_failover) throws Exception {
      List<TransportConnector> transportConnectors = remoteBroker.getTransportConnectors();
      URI remoteURI;
      if (!transportConnectors.isEmpty()) {
         remoteURI = transportConnectors.get(0).getConnectUri();
         if (useSocketProxy) {
            socketProxy = new SocketProxy(remoteURI);
            remoteURI = socketProxy.getUrl();
         }
         String options = "";
         if (failover) {
            options = "static:(failover:(" + remoteURI;
         } else {
            options = "static:(" + remoteURI;
         }
         if (inactivity) {
            options += "?wireFormat.maxInactivityDuration=" + inactiveDuration + "&wireFormat.maxInactivityDurationInitalDelay=" + inactiveDuration + ")";
         } else {
            options += ")";
         }

         if (failover) {
            options += "?maxReconnectAttempts=0)";
         }

         options += "?useExponentialBackOff=" + exponentialBackOff;
         DiscoveryNetworkConnector connector = new DiscoveryNetworkConnector(new URI(options));
         connector.setDynamicOnly(dynamicOnly);
         connector.setNetworkTTL(networkTTL);
         localBroker.addNetworkConnector(connector);
         maxSetupTime = 2000;
         if (useDuplexNetworkBridge) {
            connector.setDuplex(true);
         }
         return connector;
      } else {
         throw new Exception("Remote broker has no registered connectors.");
      }
   }
}
