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

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.artemis.core.config.Configuration;
import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
import org.apache.activemq.artemis.jms.server.config.impl.JMSConfigurationImpl;
import org.apache.activemq.artemis.jms.server.embedded.EmbeddedJMS;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ConsumerControl;
import org.apache.activemq.command.ExceptionResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 */
public class ZeroPrefetchConsumerTest extends EmbeddedBrokerTestSupport {

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

   protected Connection connection;
   protected Queue queue;
   protected Queue brokerZeroQueue = new ActiveMQQueue("brokerZeroConfig");

   public void testCannotUseMessageListener() throws Exception {
      Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
      MessageConsumer consumer = session.createConsumer(queue);

      try {
         consumer.setMessageListener(msg -> {});
         fail("Should have thrown JMSException as we cannot use MessageListener with zero prefetch");
      } catch (JMSException e) {
         LOG.info("Received expected exception : " + e);
      }
   }

   public void testPullConsumerWorks() throws Exception {
      Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

      MessageProducer producer = session.createProducer(queue);
      producer.send(session.createTextMessage("Hello World!"));

      // now lets receive it
      MessageConsumer consumer = session.createConsumer(queue);
      Message answer = consumer.receive(5000);
      assertNotNull("Should have received a message!", answer);
      // check if method will return at all and will return a null
      answer = consumer.receive(1);
      assertNull("Should have not received a message!", answer);
      answer = consumer.receiveNoWait();
      assertNull("Should have not received a message!", answer);
   }

   public void testIdleConsumer() throws Exception {
      doTestIdleConsumer(false);
   }

   public void testIdleConsumerTranscated() throws Exception {
      doTestIdleConsumer(true);
   }

   private void doTestIdleConsumer(boolean transacted) throws Exception {
      Session session = connection.createSession(transacted, Session.AUTO_ACKNOWLEDGE);

      MessageProducer producer = session.createProducer(queue);
      producer.send(session.createTextMessage("Msg1"));
      producer.send(session.createTextMessage("Msg2"));
      if (transacted) {
         session.commit();
      }
      // now lets receive it
      MessageConsumer consumer = session.createConsumer(queue);

      session.createConsumer(queue);
      TextMessage answer = (TextMessage) consumer.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg1");
      if (transacted) {
         session.commit();
      }
      // this call would return null if prefetchSize > 0
      answer = (TextMessage) consumer.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg2");
      if (transacted) {
         session.commit();
      }
      answer = (TextMessage) consumer.receiveNoWait();
      assertNull("Should have not received a message!", answer);
   }

   public void testRecvRecvCommit() throws Exception {
      doTestRecvRecvCommit(false);
   }

   public void testRecvRecvCommitTranscated() throws Exception {
      doTestRecvRecvCommit(true);
   }

   private void doTestRecvRecvCommit(boolean transacted) throws Exception {
      Session session = connection.createSession(transacted, Session.AUTO_ACKNOWLEDGE);

      MessageProducer producer = session.createProducer(queue);
      producer.send(session.createTextMessage("Msg1"));
      producer.send(session.createTextMessage("Msg2"));
      if (transacted) {
         session.commit();
      }
      // now lets receive it
      MessageConsumer consumer = session.createConsumer(queue);
      TextMessage answer = (TextMessage) consumer.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg1");
      answer = (TextMessage) consumer.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg2");
      if (transacted) {
         session.commit();
      }
      answer = (TextMessage) consumer.receiveNoWait();
      assertNull("Should have not received a message!", answer);
   }

   public void testTwoConsumers() throws Exception {
      Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

      MessageProducer producer = session.createProducer(queue);
      producer.send(session.createTextMessage("Msg1"));
      producer.send(session.createTextMessage("Msg2"));

      // now lets receive it
      MessageConsumer consumer1 = session.createConsumer(queue);
      MessageConsumer consumer2 = session.createConsumer(queue);
      TextMessage answer = (TextMessage) consumer1.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg1");
      answer = (TextMessage) consumer2.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg2");

      answer = (TextMessage) consumer2.receiveNoWait();
      assertNull("Should have not received a message!", answer);
   }

   // https://issues.apache.org/activemq/browse/AMQ-2567
   public void testManyMessageConsumer() throws Exception {
      doTestManyMessageConsumer(true);
   }

   public void testManyMessageConsumerNoTransaction() throws Exception {
      doTestManyMessageConsumer(false);
   }

   private void doTestManyMessageConsumer(boolean transacted) throws Exception {
      Session session = connection.createSession(transacted, transacted ? Session.SESSION_TRANSACTED : Session.AUTO_ACKNOWLEDGE);

      MessageProducer producer = session.createProducer(queue);
      producer.send(session.createTextMessage("Msg1"));
      producer.send(session.createTextMessage("Msg2"));
      producer.send(session.createTextMessage("Msg3"));
      producer.send(session.createTextMessage("Msg4"));
      producer.send(session.createTextMessage("Msg5"));
      producer.send(session.createTextMessage("Msg6"));
      producer.send(session.createTextMessage("Msg7"));
      producer.send(session.createTextMessage("Msg8"));
      if (transacted) {
         session.commit();
      }
      // now lets receive it
      MessageConsumer consumer = session.createConsumer(queue);

      MessageConsumer consumer2 = session.createConsumer(queue);
      TextMessage answer = (TextMessage) consumer.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg1");
      if (transacted) {
         session.commit();
      }
      answer = (TextMessage) consumer.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg2");
      if (transacted) {
         session.commit();
      }
      answer = (TextMessage) consumer.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg3");
      if (transacted) {
         session.commit();
      }
      // this call would return null if prefetchSize > 0
      answer = (TextMessage) consumer.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg4");
      if (transacted) {
         session.commit();
      }
      // Now using other consumer
      // this call should return the next message (Msg5) still left on the queue
      answer = (TextMessage) consumer2.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg5");
      if (transacted) {
         session.commit();
      }
      // Now using other consumer
      // this call should return the next message still left on the queue
      answer = (TextMessage) consumer.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg6");
      // read one more message without commit
      // this call should return the next message still left on the queue
      answer = (TextMessage) consumer.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg7");
      if (transacted) {
         session.commit();
      }
      // Now using other consumer
      // this call should return the next message (Msg5) still left on the queue
      answer = (TextMessage) consumer2.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg8");
      if (transacted) {
         session.commit();
      }
      answer = (TextMessage) consumer.receiveNoWait();
      assertNull("Should have not received a message!", answer);
   }

   public void testManyMessageConsumerWithSend() throws Exception {
      doTestManyMessageConsumerWithSend(true);
   }

   public void testManyMessageConsumerWithTxSendPrioritySupport() throws Exception {
      ((ActiveMQConnection) connection).setMessagePrioritySupported(true);
      doTestManyMessageConsumerWithSend(true);
   }

   public void testManyMessageConsumerWithSendNoTransaction() throws Exception {
      doTestManyMessageConsumerWithSend(false);
   }

   private void doTestManyMessageConsumerWithSend(boolean transacted) throws Exception {
      Session session = connection.createSession(transacted, transacted ? Session.SESSION_TRANSACTED : Session.AUTO_ACKNOWLEDGE);

      MessageProducer producer = session.createProducer(queue);
      producer.send(session.createTextMessage("Msg1"));
      producer.send(session.createTextMessage("Msg2"));
      producer.send(session.createTextMessage("Msg3"));
      producer.send(session.createTextMessage("Msg4"));
      producer.send(session.createTextMessage("Msg5"));
      producer.send(session.createTextMessage("Msg6"));
      producer.send(session.createTextMessage("Msg7"));
      producer.send(session.createTextMessage("Msg8"));
      if (transacted) {
         session.commit();
      }
      // now lets receive it
      MessageConsumer consumer = session.createConsumer(queue);

      MessageConsumer consumer2 = session.createConsumer(queue);
      TextMessage answer = (TextMessage) consumer.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg1");
      if (transacted) {
         session.commit();
      }
      answer = (TextMessage) consumer.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg2");
      if (transacted) {
         session.commit();
      }
      answer = (TextMessage) consumer.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg3");
      if (transacted) {
         session.commit();
      }
      // Now using other consumer take 2
      answer = (TextMessage) consumer2.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg4");
      answer = (TextMessage) consumer2.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg5");

      // ensure prefetch extension ok by sending another that could get dispatched
      producer.send(session.createTextMessage("Msg9"));
      if (transacted) {
         session.commit();
      }

      answer = (TextMessage) consumer.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg6");
      // read one more message without commit
      // and using other consumer
      answer = (TextMessage) consumer2.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg7");
      if (transacted) {
         session.commit();
      }

      answer = (TextMessage) consumer2.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg8");
      if (transacted) {
         session.commit();
      }

      answer = (TextMessage) consumer.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg9");
      if (transacted) {
         session.commit();
      }
      answer = (TextMessage) consumer.receiveNoWait();
      assertNull("Should have not received a message!", answer);
   }

   // https://issues.apache.org/jira/browse/AMQ-4224
   public void testBrokerZeroPrefetchConfig() throws Exception {
      Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

      MessageProducer producer = session.createProducer(brokerZeroQueue);
      producer.send(session.createTextMessage("Msg1"));
      // now lets receive it
      MessageConsumer consumer = session.createConsumer(brokerZeroQueue);

      TextMessage answer = (TextMessage) consumer.receive(5000);
      assertEquals("Should have received a message!", answer.getText(), "Msg1");
   }

   // https://issues.apache.org/jira/browse/AMQ-4234
   // https://issues.apache.org/jira/browse/AMQ-4235
   public void testBrokerZeroPrefetchConfigWithConsumerControl() throws Exception {
      Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

      ActiveMQMessageConsumer consumer = (ActiveMQMessageConsumer) session.createConsumer(brokerZeroQueue);
      assertEquals("broker config prefetch in effect", 0, consumer.info.getCurrentPrefetchSize());

      // verify sub view broker
      // I comment out this because it checks broker internal
      // which doesn't apply to artemis broker.
      //Subscription sub = broker.getRegionBroker().getDestinationMap().get(ActiveMQDestination.transform(brokerZeroQueue)).getConsumers().get(0);
      //assertEquals("broker sub prefetch is correct", 0, sub.getConsumerInfo().getCurrentPrefetchSize());

      // manipulate Prefetch (like failover and stomp)
      ConsumerControl consumerControl = new ConsumerControl();
      consumerControl.setConsumerId(consumer.info.getConsumerId());
      consumerControl.setDestination(ActiveMQDestination.transform(brokerZeroQueue));
      consumerControl.setPrefetch(1000); // default for a q

      Object reply = ((ActiveMQConnection) connection).getTransport().request(consumerControl);
      assertTrue("good request", !(reply instanceof ExceptionResponse));
      assertEquals("broker config prefetch in effect", 0, consumer.info.getCurrentPrefetchSize());
   }

   @Override
   public EmbeddedJMS createArtemisBroker() throws Exception {
      Configuration config0 = createConfig("localhost", 0);
      String coreQueueAddress = brokerZeroQueue.getQueueName();
      AddressSettings addrSettings = new AddressSettings();
      addrSettings.setQueuePrefetch(0);
      config0.getAddressesSettings().put(coreQueueAddress, addrSettings);
      EmbeddedJMS newbroker = new EmbeddedJMS().setConfiguration(config0).setJmsConfiguration(new JMSConfigurationImpl());
      return newbroker;
   }

   @Override
   protected void setUp() throws Exception {
      disableWrapper = true;
      bindAddress = "tcp://localhost:0";
      super.setUp();

      connection = createConnection();
      connection.start();
      queue = createQueue();
   }

   @Override
   protected void startBroker() throws Exception {
      super.startBroker();
      bindAddress = newURI("localhost", 0);
   }

   @Override
   protected void tearDown() throws Exception {
      BrokerService.disableWrapper = false;
      connection.close();
      super.tearDown();
   }

   protected Queue createQueue() {
      return new ActiveMQQueue(getDestinationString() + "?consumer.prefetchSize=0");
   }

}
