/*
 * 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.artemis.tests.integration.cluster.reattach;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;

import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.api.core.client.ClientConsumer;
import org.apache.activemq.artemis.api.core.client.ClientMessage;
import org.apache.activemq.artemis.api.core.client.ClientProducer;
import org.apache.activemq.artemis.api.core.client.ClientSession;
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
import org.apache.activemq.artemis.api.core.client.MessageHandler;
import org.apache.activemq.artemis.api.core.client.ServerLocator;
import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal;
import org.apache.activemq.artemis.core.protocol.core.impl.RemotingConnectionImpl;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.jms.client.ActiveMQTextMessage;
import org.apache.activemq.artemis.tests.integration.IntegrationTestLogger;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.junit.Assert;
import org.junit.Test;

public class OrderReattachTest extends ActiveMQTestBase {
   // Constants -----------------------------------------------------

   final SimpleString ADDRESS = new SimpleString("address");

   // Attributes ----------------------------------------------------
   private final IntegrationTestLogger log = IntegrationTestLogger.LOGGER;

   private ActiveMQServer server;

   // Static --------------------------------------------------------

   // Constructors --------------------------------------------------

   // Public --------------------------------------------------------

   @Test
   public void testOrderOnSendInVM() throws Throwable {
      doTestOrderOnSend(false);
   }

   public void doTestOrderOnSend(final boolean isNetty) throws Throwable {
      server = createServer(false, isNetty);

      server.start();
      ServerLocator locator = createFactory(isNetty).setReconnectAttempts(-1).setConfirmationWindowSize(1024 * 1024).setBlockOnNonDurableSend(false).setBlockOnAcknowledge(false);

      ClientSessionFactory sf = createSessionFactory(locator);

      final ClientSession session = sf.createSession(false, true, true);

      final LinkedBlockingDeque<Boolean> failureQueue = new LinkedBlockingDeque<>();

      final CountDownLatch ready = new CountDownLatch(1);

      // this test will use a queue. Whenever the test wants a failure.. it can just send TRUE to failureQueue
      // This Thread will be reading the queue
      Thread failer = new Thread() {
         @Override
         public void run() {
            ready.countDown();
            while (true) {
               try {
                  Boolean poll = false;
                  try {
                     poll = failureQueue.poll(60, TimeUnit.SECONDS);
                  } catch (InterruptedException e) {
                     e.printStackTrace();
                     break;
                  }

                  Thread.sleep(1);

                  final RemotingConnectionImpl conn = (RemotingConnectionImpl) ((ClientSessionInternal) session).getConnection();

                  // True means... fail session
                  if (poll) {
                     conn.fail(new ActiveMQNotConnectedException("poop"));
                  } else {
                     // false means... finish thread
                     break;
                  }
               } catch (Exception e) {
                  e.printStackTrace();
               }
            }
         }
      };

      failer.start();

      ready.await();

      try {
         doSend2(1, sf, failureQueue);
      } finally {
         try {
            session.close();
         } catch (Exception e) {
            e.printStackTrace();
         }

         try {
            locator.close();
         } catch (Exception e) {
            //
         }
         try {
            sf.close();
         } catch (Exception e) {
            e.printStackTrace();
         }

         failureQueue.put(false);

         failer.join();
      }

   }

   public void doSend2(final int order,
                       final ClientSessionFactory sf,
                       final LinkedBlockingDeque<Boolean> failureQueue) throws Exception {
      ClientSession s = sf.createSession(false, false, false);

      final int numMessages = 500;

      final int numSessions = 10;

      Set<ClientConsumer> consumers = new HashSet<>();
      Set<ClientSession> sessions = new HashSet<>();

      for (int i = 0; i < numSessions; i++) {
         SimpleString subName = new SimpleString("sub" + i);

         // failureQueue.push(true);

         ClientSession sessConsume = sf.createSession(false, true, true);

         sessConsume.createQueue(ADDRESS, RoutingType.MULTICAST, subName, null, false);

         ClientConsumer consumer = sessConsume.createConsumer(subName);

         consumers.add(consumer);

         sessions.add(sessConsume);
      }

      ClientSession sessSend = sf.createSession(false, true, true);

      ClientProducer producer = sessSend.createProducer(ADDRESS);

      for (int i = 0; i < numMessages; i++) {
         ClientMessage message = sessSend.createMessage(ActiveMQTextMessage.TYPE, false, 0, System.currentTimeMillis(), (byte) 1);

         if (i % 10 == 0) {
            // failureQueue.push(true);
         }
         message.putIntProperty(new SimpleString("count"), i);
         producer.send(message);
      }

      for (ClientSession session : sessions) {
         session.start();
      }

      class MyHandler implements MessageHandler {

         final CountDownLatch latch = new CountDownLatch(1);

         int count;

         Exception failure;

         @Override
         public void onMessage(final ClientMessage message) {
            if (count >= numMessages) {
               failure = new Exception("too many messages");
               latch.countDown();
            }

            if (message.getIntProperty("count") != count) {
               failure = new Exception("counter " + count + " was not as expected (" + message.getIntProperty("count") + ")");
               log.warn("Failure on receiving message ", failure);
               failure.printStackTrace();
               latch.countDown();
            }

            count++;

            if (count % 100 == 0) {
               failureQueue.push(true);
            }

            if (count == numMessages) {
               latch.countDown();
            }
         }
      }

      Set<MyHandler> handlers = new HashSet<>();

      for (ClientConsumer consumer : consumers) {
         MyHandler handler = new MyHandler();

         consumer.setMessageHandler(handler);

         handlers.add(handler);
      }

      for (MyHandler handler : handlers) {
         boolean ok = handler.latch.await(60000, TimeUnit.MILLISECONDS);

         Assert.assertTrue(ok);

         if (handler.failure != null) {
            throw handler.failure;
         }
      }

      // failureQueue.push(true);

      sessSend.close();

      for (ClientSession session : sessions) {
         // failureQueue.push(true);
         session.close();
      }

      for (int i = 0; i < numSessions; i++) {

         failureQueue.push(true);

         SimpleString subName = new SimpleString("sub" + i);

         s.deleteQueue(subName);
      }

      s.close();
   }
}
