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

import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
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 junit.framework.TestCase;

import junit.textui.TestRunner;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.network.NetworkConnector;
import org.apache.activemq.xbean.BrokerFactoryBean;
import org.apache.xbean.spring.context.ClassPathXmlApplicationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;

public class NetworkedSyncTest extends TestCase {

   // constants
   public static final int MESSAGE_COUNT = 10000; //100000;
   public final static String config = "org/apache/activemq/perf/networkSync.xml";
   public final static String broker1URL = "tcp://localhost:61616";
   public final static String broker2URL = "tcp://localhost:62616";
   private final String networkConnectorURL = "static://(" + broker2URL + ")";
   private static final Logger LOG = LoggerFactory.getLogger(NetworkedSyncTest.class);
   BrokerService broker1 = null;
   BrokerService broker2 = null;
   NetworkConnector connector = null;

   /**
    * @param name
    */
   public NetworkedSyncTest(String name) {
      super(name);
      LOG.info("Testcase started.");
   }

   public static void main(String args[]) {
      TestRunner.run(NetworkedSyncTest.class);
   }

   /**
    * @throws java.lang.Exception
    */
   @Override
   protected void setUp() throws Exception {
      LOG.info("setUp() called.");
      ClassPathXmlApplicationContext context1 = null;
      BrokerFactoryBean brokerFactory = new BrokerFactoryBean(new ClassPathResource(config));
      assertNotNull(brokerFactory);

        /* start up first broker instance */
      try {
         // resolve broker1
         Thread.currentThread().setContextClassLoader(NetworkedSyncTest.class.getClassLoader());
         context1 = new ClassPathXmlApplicationContext(config);
         broker1 = (BrokerService) context1.getBean("broker1");

         // start the broker
         if (!broker1.isStarted()) {
            LOG.info("Broker broker1 not yet started. Kicking it off now.");
            broker1.start();
         }
         else {
            LOG.info("Broker broker1 already started. Not kicking it off a second time.");
            broker1.waitUntilStopped();
         }
      }
      catch (Exception e) {
         LOG.error("Error: " + e.getMessage());
         throw e;
         // brokerService.stop();
      }

        /* start up second broker instance */
      try {
         Thread.currentThread().setContextClassLoader(NetworkedSyncTest.class.getClassLoader());
         context1 = new ClassPathXmlApplicationContext(config);
         broker2 = (BrokerService) context1.getBean("broker2");

         // start the broker
         if (!broker2.isStarted()) {
            LOG.info("Broker broker2 not yet started. Kicking it off now.");
            broker2.start();
         }
         else {
            LOG.info("Broker broker2 already started. Not kicking it off a second time.");
            broker2.waitUntilStopped();
         }
      }
      catch (Exception e) {
         LOG.error("Error: " + e.getMessage());
         throw e;
      }

      // setup network connector from broker1 to broker2
      connector = broker1.addNetworkConnector(networkConnectorURL);
      connector.setBrokerName(broker1.getBrokerName());
      connector.setDuplex(true);
      connector.start();
      LOG.info("Network connector created.");
   }

   /**
    * @throws java.lang.Exception
    */
   @Override
   protected void tearDown() throws Exception {

      LOG.info("tearDown() called.");

      if (broker1 != null && broker1.isStarted()) {
         LOG.info("Broker1 still running, stopping it now.");
         broker1.stop();
      }
      else {
         LOG.info("Broker1 not running, nothing to shutdown.");
      }
      if (broker2 != null && broker2.isStarted()) {
         LOG.info("Broker2 still running, stopping it now.");
         broker2.stop();
      }
      else {
         LOG.info("Broker2 not running, nothing to shutdown.");
      }

   }

   public void testMessageExchange() throws Exception {
      LOG.info("testMessageExchange() called.");

      long start = System.currentTimeMillis();

      // create producer and consumer threads
      Thread producer = new Thread(new Producer());
      Thread consumer = new Thread(new Consumer());
      // start threads
      consumer.start();
      Thread.sleep(2000);
      producer.start();

      // wait for threads to finish
      producer.join();
      consumer.join();
      long end = System.currentTimeMillis();

      System.out.println("Duration: " + (end - start));
   }
}

/**
 * Message producer running as a separate thread, connecting to broker1
 *
 * @author tmielke
 */
class Producer implements Runnable {

   private static final Logger LOG = LoggerFactory.getLogger(Producer.class);

   /**
    * connect to broker and constantly send messages
    */
   @Override
   public void run() {

      Connection connection = null;
      Session session = null;
      MessageProducer producer = null;

      try {
         ActiveMQConnectionFactory amq = new ActiveMQConnectionFactory(NetworkedSyncTest.broker1URL);
         connection = amq.createConnection();

         connection.setExceptionListener(new javax.jms.ExceptionListener() {
            @Override
            public void onException(javax.jms.JMSException e) {
               e.printStackTrace();
            }
         });

         connection.start();
         session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
         Topic destination = session.createTopic("TEST.FOO");

         producer = session.createProducer(destination);
         producer.setDeliveryMode(DeliveryMode.PERSISTENT);

         long counter = 0;

         // Create and send message
         for (int i = 0; i < NetworkedSyncTest.MESSAGE_COUNT; i++) {

            String text = "Hello world! From: " + Thread.currentThread().getName() + " : " + this.hashCode() + ":" + counter;
            TextMessage message = session.createTextMessage(text);
            producer.send(message);
            counter++;

            if ((counter % 1000) == 0)
               LOG.info("sent " + counter + " messages");

         }
      }
      catch (Exception ex) {
         LOG.error(ex.toString());
         return;
      }
      finally {
         try {
            if (producer != null)
               producer.close();
            if (session != null)
               session.close();
            if (connection != null)
               connection.close();
         }
         catch (Exception e) {
            LOG.error("Problem closing down JMS objects: " + e);
         }
      }
   }
}

/*
 * * Message consumer running as a separate thread, connecting to broker2
 * @author tmielke
 *
 */
class Consumer implements Runnable {

   private static final Logger LOG = LoggerFactory.getLogger(Consumer.class);
   ;

   /**
    * connect to broker and receive messages
    */
   @Override
   public void run() {
      Connection connection = null;
      Session session = null;
      MessageConsumer consumer = null;

      try {
         ActiveMQConnectionFactory amq = new ActiveMQConnectionFactory(NetworkedSyncTest.broker2URL);
         connection = amq.createConnection();
         // need to set clientID when using durable subscription.
         connection.setClientID("tmielke");

         connection.setExceptionListener(new javax.jms.ExceptionListener() {
            @Override
            public void onException(javax.jms.JMSException e) {
               e.printStackTrace();
            }
         });

         connection.start();
         session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
         Destination destination = session.createTopic("TEST.FOO");
         consumer = session.createDurableSubscriber((Topic) destination, "tmielke");

         long counter = 0;
         // Wait for a message
         for (int i = 0; i < NetworkedSyncTest.MESSAGE_COUNT; i++) {
            Message message2 = consumer.receive();
            if (message2 instanceof TextMessage) {
               TextMessage textMessage = (TextMessage) message2;
               textMessage.getText();
               // logger.info("Received: " + text);
            }
            else {
               LOG.error("Received message of unsupported type. Expecting TextMessage. " + message2);
            }
            counter++;
            if ((counter % 1000) == 0)
               LOG.info("received " + counter + " messages");

         }
      }
      catch (Exception e) {
         LOG.error("Error in Consumer: " + e);
         return;
      }
      finally {
         try {
            if (consumer != null)
               consumer.close();
            if (session != null)
               session.close();
            if (connection != null)
               connection.close();
         }
         catch (Exception ex) {
            LOG.error("Error closing down JMS objects: " + ex);
         }
      }
   }
}
