/**
 * 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 jakarta.jms.Connection;
import jakarta.jms.DeliveryMode;
import jakarta.jms.Destination;
import jakarta.jms.Message;
import jakarta.jms.MessageConsumer;
import jakarta.jms.MessageProducer;
import jakarta.jms.Session;
import jakarta.jms.TextMessage;
import jakarta.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 jakarta.jms.ExceptionListener() {
                @Override
                public void onException(jakarta.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 jakarta.jms.ExceptionListener() {
                @Override
                public void onException(jakarta.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);
            }
        }
    }
}
