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

import java.io.File;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
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.core.client.impl.ServerLocatorInternal;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.junit.Assert;
import org.junit.Test;

public class BackupSyncLargeMessageTest extends BackupSyncJournalTest {

   @Override
   protected void assertMessageBody(final int i, final ClientMessage message) {
      assertLargeMessageBody(i, message);
   }

   @Override
   protected ServerLocatorInternal getServerLocator() throws Exception {
      return (ServerLocatorInternal) super.getServerLocator().setMinLargeMessageSize(MIN_LARGE_MESSAGE);
   }

   @Override
   protected void setBody(final int i, final ClientMessage message) {
      setLargeMessageBody(i, message);
   }

   // ------------------------

   @Test
   public void testDeleteLargeMessages() throws Exception {
      // 200 will increase the odds of a failure
      setNumberOfMessages(200);
      File dir = new File(backupServer.getServer().getConfiguration().getLargeMessagesDirectory());
      assertEquals("Should not have any large messages... previous test failed to clean up?", 0, getAllMessageFileIds(dir).size());
      createProducerSendSomeMessages();
      startBackupFinishSyncing();
      receiveMsgsInRange(0, getNumberOfMessages() / 2);
      finishSyncAndFailover();
      final int target = getNumberOfMessages() / 2;
      long timeout = System.currentTimeMillis() + 5000;
      while (getAllMessageFileIds(dir).size() != target && System.currentTimeMillis() < timeout) {
         Thread.sleep(50);
      }
      assertEquals("we really ought to delete these after delivery", target, getAllMessageFileIds(dir).size());
   }

   /**
    * @throws Exception
    */
   @Test
   public void testDeleteLargeMessagesDuringSync() throws Exception {
      setNumberOfMessages(200);
      File backupLMdir = new File(backupServer.getServer().getConfiguration().getLargeMessagesDirectory());
      File liveLMDir = new File(liveServer.getServer().getConfiguration().getLargeMessagesDirectory());
      assertEquals("Should not have any large messages... previous test failed to clean up?", 0, getAllMessageFileIds(backupLMdir).size());
      createProducerSendSomeMessages();

      backupServer.start();
      waitForComponent(backupServer.getServer(), 5);
      receiveMsgsInRange(0, getNumberOfMessages() / 2);

      startBackupFinishSyncing();
      Thread.sleep(500);
      liveServer.getServer().stop();
      backupServer.getServer().waitForActivation(10, TimeUnit.SECONDS);
      backupServer.stop();

      Set<Long> backupLM = getAllMessageFileIds(backupLMdir);
      Set<Long> liveLM = getAllMessageFileIds(liveLMDir);
      assertEquals("live and backup should have the same files ", liveLM, backupLM);
      assertEquals("we really ought to delete these after delivery: " + backupLM, getNumberOfMessages() / 2, backupLM.size());
      assertEquals("we really ought to delete these after delivery", getNumberOfMessages() / 2, getAllMessageFileIds(backupLMdir).size());
   }

   /**
    * LargeMessages are passed from the client to the server in chunks. Here we test the backup
    * starting the data synchronization with the live in the middle of a multiple chunks large
    * message upload from the client to the live server.
    *
    * @throws Exception
    */
   @Test
   public void testBackupStartsWhenLiveIsReceivingLargeMessage() throws Exception {
      final ClientSession session = addClientSession(sessionFactory.createSession(true, true));
      session.createQueue(new QueueConfiguration(FailoverTestBase.ADDRESS));
      final ClientProducer producer = session.createProducer(FailoverTestBase.ADDRESS);
      final ClientMessage message = session.createMessage(true);
      final int largeMessageSize = 1000 * MIN_LARGE_MESSAGE;
      message.setBodyInputStream(ActiveMQTestBase.createFakeLargeStream(largeMessageSize));

      final AtomicBoolean caughtException = new AtomicBoolean(false);
      final CountDownLatch latch = new CountDownLatch(1);
      final CountDownLatch latch2 = new CountDownLatch(1);

      Runnable r = new Runnable() {
         @Override
         public void run() {
            try {
               latch.countDown();
               producer.send(message);
               sendMessages(session, producer, 20);
               session.commit();
            } catch (ActiveMQException e) {
               e.printStackTrace();
               caughtException.set(true);
            } finally {
               latch2.countDown();
            }
         }
      };
      Executors.defaultThreadFactory().newThread(r).start();
      waitForLatch(latch);
      startBackupFinishSyncing();
      ActiveMQTestBase.waitForLatch(latch2);
      crash(session);
      assertFalse("no exceptions while sending message", caughtException.get());

      session.start();
      ClientConsumer consumer = session.createConsumer(FailoverTestBase.ADDRESS);
      ClientMessage msg = consumer.receive(2000);
      ActiveMQBuffer buffer = msg.getBodyBuffer();

      for (int j = 0; j < largeMessageSize; j++) {
         Assert.assertTrue("large msg , expecting " + largeMessageSize + " bytes, got " + j, buffer.readable());
         Assert.assertEquals("equal at " + j, ActiveMQTestBase.getSamplebyte(j), buffer.readByte());
      }
      receiveMessages(consumer, 0, 20, true);
      assertNull("there should be no more messages!", consumer.receiveImmediate());
      consumer.close();
      session.commit();
   }

   private Set<Long> getAllMessageFileIds(File dir) {
      Set<Long> idsOnBkp = new TreeSet<>();
      String[] fileList = dir.list();
      if (fileList != null) {
         for (String filename : fileList) {
            if (filename.endsWith(".msg")) {
               idsOnBkp.add(Long.valueOf(filename.split("\\.")[0]));
            }
         }
      }
      return idsOnBkp;
   }
}
