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

import javax.jms.Destination;
import javax.jms.MessageConsumer;
import javax.jms.QueueBrowser;

import org.apache.activemq.JmsMultipleBrokersTestSupport;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.broker.region.QueueSubscription;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ActiveMQTextMessage;
import org.apache.activemq.network.NetworkConnector;
import org.apache.activemq.util.MessageIdList;
import org.apache.activemq.util.Wait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;

public class BrowseOverNetworkTest extends JmsMultipleBrokersTestSupport {

   private static final Logger LOG = LoggerFactory.getLogger(QueueSubscription.class);
   protected static final int MESSAGE_COUNT = 10;

   public void testBrowse() throws Exception {
      createBroker(new URI("broker:(tcp://localhost:61617)/BrokerB?persistent=false&useJmx=false"));
      createBroker(new URI("broker:(tcp://localhost:61616)/BrokerA?persistent=false&useJmx=false"));

      bridgeBrokers("BrokerA", "BrokerB");

      startAllBrokers();

      Destination dest = createDestination("TEST.FOO", false);

      sendMessages("BrokerA", dest, MESSAGE_COUNT);

      Thread.sleep(1000);

      int browsed = browseMessages("BrokerB", dest);

      Thread.sleep(1000);

      MessageConsumer clientA = createConsumer("BrokerA", dest);
      MessageIdList msgsA = getConsumerMessages("BrokerA", clientA);
      msgsA.waitForMessagesToArrive(MESSAGE_COUNT);

      Thread.sleep(1000);
      MessageConsumer clientB = createConsumer("BrokerB", dest);
      MessageIdList msgsB = getConsumerMessages("BrokerB", clientB);
      msgsB.waitForMessagesToArrive(MESSAGE_COUNT);

      LOG.info("A+B: " + msgsA.getMessageCount() + "+" + msgsB.getMessageCount());
      assertEquals("Consumer on Broker A, should've consumed all messages", MESSAGE_COUNT, msgsA.getMessageCount());
      assertEquals("Broker B shouldn't get any messages", 0, browsed);
   }

   public void testConsumerInfo() throws Exception {
      createBroker(new ClassPathResource("org/apache/activemq/usecases/browse-broker1.xml"));
      createBroker(new ClassPathResource("org/apache/activemq/usecases/browse-broker2.xml"));

      startAllBrokers();

      brokers.get("broker1").broker.waitUntilStarted();

      Destination dest = createDestination("QUEUE.A,QUEUE.B", false);

      int broker1 = browseMessages("broker1", dest);
      assertEquals("Browsed a message on an empty queue", 0, broker1);
      Thread.sleep(1000);
      int broker2 = browseMessages("broker2", dest);
      assertEquals("Browsed a message on an empty queue", 0, broker2);

   }

   public class Browser extends Thread {

      String broker;
      Destination dest;
      int totalCount;
      final int expect;
      QueueBrowser browser = null;
      MessageConsumer consumer = null;
      boolean consume = false;

      public Browser(String broker, Destination dest, int expect) {
         this.broker = broker;
         this.dest = dest;
         this.expect = expect;
      }

      @Override
      public void run() {
         int retries = 0;
         while (retries++ < 20 && totalCount != expect) {
            try {
               QueueBrowser browser = createBrowser(broker, dest);
               int count = browseMessages(browser, broker);
               if (consume) {
                  if (count != 0) {
                     MessageConsumer consumer = createSyncConsumer(broker, dest);
                     totalCount += count;
                     for (int i = 0; i < count; i++) {
                        ActiveMQTextMessage message = (ActiveMQTextMessage) consumer.receive(1000);
                        if (message == null)
                           break;
                        LOG.info(broker + " consumer: " + message.getText() + " " + message.getDestination() + " " + message.getMessageId() + " " + Arrays.toString(message.getBrokerPath()));
                     }
                  }
               }
               else {
                  totalCount = count;
               }
               LOG.info("browser '" + broker + "' browsed " + totalCount);

               Thread.sleep(1000);
            }
            catch (Exception e) {
               LOG.info("Exception browsing " + e, e);
            }
            finally {
               try {
                  if (browser != null) {
                     browser.close();
                  }
                  if (consumer != null) {
                     consumer.close();
                  }
               }
               catch (Exception e) {
                  LOG.info("Exception closing browser " + e, e);
               }
            }
         }
      }

      public int getTotalCount() {
         return totalCount;
      }
   }

   protected NetworkConnector bridgeBrokersWithIncludedDestination(String localBrokerName,
                                                                   String remoteBrokerName,
                                                                   ActiveMQDestination included,
                                                                   ActiveMQDestination excluded) throws Exception {
      NetworkConnector nc = bridgeBrokers(localBrokerName, remoteBrokerName, false, 4, true);
      nc.addStaticallyIncludedDestination(included);
      if (excluded != null) {
         nc.addExcludedDestination(excluded);
      }
      nc.setPrefetchSize(1);
      return nc;
   }

   public void testAMQ3020() throws Exception {
      createBroker(new ClassPathResource("org/apache/activemq/usecases/browse-broker1A.xml"));
      createBroker(new ClassPathResource("org/apache/activemq/usecases/browse-broker1B.xml"));
      createBroker(new ClassPathResource("org/apache/activemq/usecases/browse-broker2A.xml"));
      createBroker(new ClassPathResource("org/apache/activemq/usecases/browse-broker2B.xml"));
      createBroker(new ClassPathResource("org/apache/activemq/usecases/browse-broker3A.xml"));
      createBroker(new ClassPathResource("org/apache/activemq/usecases/browse-broker3B.xml"));

      brokers.get("broker-1A").broker.waitUntilStarted();
      brokers.get("broker-2A").broker.waitUntilStarted();
      brokers.get("broker-3A").broker.waitUntilStarted();

      for (BrokerItem brokerItem : brokers.values()) {
         final BrokerService broker = brokerItem.broker;
         waitForBridgeFormation(broker, 1, 0);
         waitForBridgeFormation(broker, 1, 1);
         waitForBridgeFormation(broker, 1, 2);
         waitForBridgeFormation(broker, 1, 3);
         waitForBridgeFormation(broker, 1, 4);
      }

      Destination composite = createDestination("PROD.FUSESOURCE.3.A,PROD.FUSESOURCE.3.B", false);

      final Browser browser1 = new Browser("broker-3A", composite, MESSAGE_COUNT);
      browser1.start();

      final Browser browser2 = new Browser("broker-3B", composite, MESSAGE_COUNT);
      browser2.start();

      LOG.info("Sending messages to broker-1A");
      sendMessages("broker-1A", composite, MESSAGE_COUNT);
      LOG.info("Message sent to broker-1A");

      Wait.waitFor(new Wait.Condition() {
         @Override
         public boolean isSatisified() throws Exception {
            return browser1.getTotalCount() == MESSAGE_COUNT;
         }
      });
      Wait.waitFor(new Wait.Condition() {
         @Override
         public boolean isSatisified() throws Exception {
            return browser2.getTotalCount() == MESSAGE_COUNT;
         }
      });

      browser1.join();
      browser2.join();

      LOG.info("broker-3A browsed " + browser1.getTotalCount());
      LOG.info("broker-3B browsed " + browser2.getTotalCount());

      assertEquals(MESSAGE_COUNT * 2, browser1.getTotalCount() + browser2.getTotalCount());

   }

   protected int browseMessages(QueueBrowser browser, String name) throws Exception {
      Enumeration<?> msgs = browser.getEnumeration();
      int browsedMessage = 0;
      while (msgs.hasMoreElements()) {
         browsedMessage++;
         ActiveMQTextMessage message = (ActiveMQTextMessage) msgs.nextElement();
         LOG.info(name + " browsed: " + message.getText() + " " + message.getDestination() + " " + message.getMessageId() + " " + Arrays.toString(message.getBrokerPath()));
      }
      return browsedMessage;
   }

   protected int browseMessages(String broker, Destination dest) throws Exception {
      QueueBrowser browser = createBrowser(broker, dest);
      int browsedMessage = browseMessages(browser, "browser");
      browser.close();
      return browsedMessage;
   }

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

}
