/**
 * 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.Destination;
import javax.jms.MessageConsumer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.apache.activemq.JmsMultipleBrokersTestSupport;
import org.apache.activemq.util.MessageIdList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 */
public class MultiBrokersMultiClientsTest extends JmsMultipleBrokersTestSupport implements UncaughtExceptionHandler {

   public static final int BROKER_COUNT = 6; // number of brokers to network
   public static final int CONSUMER_COUNT = 25; // consumers per broker
   public static final int PRODUCER_COUNT = 3; // producers per broker
   public static final int MESSAGE_COUNT = 20; // messages per producer

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

   protected Map<String, MessageConsumer> consumerMap;
   final Map<Thread, Throwable> unhandeledExceptions = new HashMap<>();

   public void testTopicAllConnected() throws Exception {
      bridgeAllBrokers();
      startAllBrokers();
      waitForBridgeFormation();

      // Setup topic destination
      Destination dest = createDestination("TEST.FOO", true);

      CountDownLatch latch = new CountDownLatch(BROKER_COUNT * PRODUCER_COUNT * BROKER_COUNT * CONSUMER_COUNT * MESSAGE_COUNT);

      // Setup consumers
      for (int i = 1; i <= BROKER_COUNT; i++) {
         for (int j = 0; j < CONSUMER_COUNT; j++) {
            consumerMap.put("Consumer:" + i + ":" + j, createConsumer("Broker" + i, dest, latch));
         }
      }

      // wait for consumers to get propagated
      for (int i = 1; i <= BROKER_COUNT; i++) {
         // all consumers on the remote brokers look like 1 consumer to the local broker.
         assertConsumersConnect("Broker" + i, dest, (BROKER_COUNT - 1) + CONSUMER_COUNT, 65000);
      }

      // Send messages
      for (int i = 1; i <= BROKER_COUNT; i++) {
         for (int j = 0; j < PRODUCER_COUNT; j++) {
            sendMessages("Broker" + i, dest, MESSAGE_COUNT);
         }
      }

      assertTrue("Missing " + latch.getCount() + " messages", latch.await(45, TimeUnit.SECONDS));

      // Get message count
      for (int i = 1; i <= BROKER_COUNT; i++) {
         for (int j = 0; j < CONSUMER_COUNT; j++) {
            MessageIdList msgs = getConsumerMessages("Broker" + i, consumerMap.get("Consumer:" + i + ":" + j));
            assertEquals(BROKER_COUNT * PRODUCER_COUNT * MESSAGE_COUNT, msgs.getMessageCount());
         }
      }

      assertNoUnhandeledExceptions();
   }

   private void assertNoUnhandeledExceptions() {
      for (Entry<Thread, Throwable> e : unhandeledExceptions.entrySet()) {
         LOG.error("Thread:" + e.getKey() + " Had unexpected: " + e.getValue());
      }
      assertTrue("There are no unhandelled exceptions, see: log for detail on: " + unhandeledExceptions, unhandeledExceptions.isEmpty());
   }

   public void testQueueAllConnected() throws Exception {
      bridgeAllBrokers();
      startAllBrokers();
      this.waitForBridgeFormation();

      // Setup topic destination
      Destination dest = createDestination("TEST.FOO", false);

      CountDownLatch latch = new CountDownLatch(BROKER_COUNT * PRODUCER_COUNT * MESSAGE_COUNT);

      // Setup consumers
      for (int i = 1; i <= BROKER_COUNT; i++) {
         for (int j = 0; j < CONSUMER_COUNT; j++) {
            consumerMap.put("Consumer:" + i + ":" + j, createConsumer("Broker" + i, dest, latch));
         }
      }

      // wait for consumers to get propagated
      for (int i = 1; i <= BROKER_COUNT; i++) {
         // all consumers on the remote brokers look like 1 consumer to the local broker.
         assertConsumersConnect("Broker" + i, dest, (BROKER_COUNT - 1) + CONSUMER_COUNT, 65000);
      }

      // Send messages
      for (int i = 1; i <= BROKER_COUNT; i++) {
         for (int j = 0; j < PRODUCER_COUNT; j++) {
            sendMessages("Broker" + i, dest, MESSAGE_COUNT);
         }
      }

      // Wait for messages to be delivered
      assertTrue("Missing " + latch.getCount() + " messages", latch.await(45, TimeUnit.SECONDS));

      // Get message count
      int totalMsg = 0;
      for (int i = 1; i <= BROKER_COUNT; i++) {
         for (int j = 0; j < CONSUMER_COUNT; j++) {
            MessageIdList msgs = getConsumerMessages("Broker" + i, consumerMap.get("Consumer:" + i + ":" + j));
            totalMsg += msgs.getMessageCount();
         }
      }
      assertEquals(BROKER_COUNT * PRODUCER_COUNT * MESSAGE_COUNT, totalMsg);

      assertNoUnhandeledExceptions();
   }

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

      unhandeledExceptions.clear();
      Thread.setDefaultUncaughtExceptionHandler(this);

      // Setup n brokers
      for (int i = 1; i <= BROKER_COUNT; i++) {
         createBroker(new URI("broker:()/Broker" + i + "?persistent=false&useJmx=false"));
      }

      consumerMap = new HashMap<>();
   }

   @Override
   public void uncaughtException(Thread t, Throwable e) {
      synchronized (unhandeledExceptions) {
         unhandeledExceptions.put(t, e);
      }
   }
}
