/*
 * 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.unit.core.journal.impl;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQBuffers;
import org.apache.activemq.artemis.core.io.SequentialFile;
import org.apache.activemq.artemis.core.io.SequentialFileFactory;
import org.apache.activemq.artemis.nativo.jlibaio.LibaioContext;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public abstract class SequentialFileFactoryTestBase extends ActiveMQTestBase {

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

      factory = createFactory(getTestDir());

      factory.start();
   }

   @Override
   @AfterEach
   public void tearDown() throws Exception {
      factory.stop();

      factory = null;

      ActiveMQTestBase.forceGC();

      assertEquals(0, LibaioContext.getTotalMaxIO());

      super.tearDown();
   }

   protected abstract SequentialFileFactory createFactory(String folder);

   protected SequentialFileFactory factory;

   @Test
   public void listFilesOnNonExistentFolder() throws Exception {
      SequentialFileFactory fileFactory = createFactory("./target/dontexist");
      List<String> list = fileFactory.listFiles("tmp");
      assertNotNull(list);
      assertEquals(0, list.size());
   }

   @Test
   public void testCreateAndListFiles() throws Exception {
      List<String> expectedFiles = new ArrayList<>();

      final int numFiles = 10;

      for (int i = 0; i < numFiles; i++) {
         String fileName = UUID.randomUUID().toString() + ".amq";

         expectedFiles.add(fileName);

         SequentialFile sf = factory.createSequentialFile(fileName);

         sf.open();

         assertEquals(fileName, sf.getFileName());

         sf.close();
      }

      // Create a couple with a different extension - they shouldn't be picked
      // up

      SequentialFile sf1 = factory.createSequentialFile("different.file");
      sf1.open();

      SequentialFile sf2 = factory.createSequentialFile("different.cheese");
      sf2.open();

      List<String> fileNames = factory.listFiles("amq");

      assertEquals(expectedFiles.size(), fileNames.size());

      for (String fileName : expectedFiles) {
         assertTrue(fileNames.contains(fileName));
      }

      fileNames = factory.listFiles("file");

      assertEquals(1, fileNames.size());

      assertTrue(fileNames.contains("different.file"));

      fileNames = factory.listFiles("cheese");

      assertEquals(1, fileNames.size());

      assertTrue(fileNames.contains("different.cheese"));

      sf1.close();
      sf2.close();
   }

   @Test
   public void testFill() throws Exception {
      SequentialFile sf = factory.createSequentialFile("fill.amq");

      sf.open();

      try {

         checkFill(factory, sf, 2048);

         checkFill(factory, sf, 512);

         checkFill(factory, sf, 512 * 4);
      } finally {
         sf.close();
      }
   }

   @Test
   public void testDelete() throws Exception {
      SequentialFile sf = factory.createSequentialFile("delete-me.amq");

      sf.open();

      SequentialFile sf2 = factory.createSequentialFile("delete-me2.amq");

      sf2.open();

      List<String> fileNames = factory.listFiles("amq");

      assertEquals(2, fileNames.size());

      assertTrue(fileNames.contains("delete-me.amq"));

      assertTrue(fileNames.contains("delete-me2.amq"));

      sf.delete();

      fileNames = factory.listFiles("amq");

      assertEquals(1, fileNames.size());

      assertTrue(fileNames.contains("delete-me2.amq"));

      sf2.close();

   }

   @Test
   public void testRename() throws Exception {
      SequentialFile sf = factory.createSequentialFile("test1.amq");

      sf.open();

      List<String> fileNames = factory.listFiles("amq");

      assertEquals(1, fileNames.size());

      assertTrue(fileNames.contains("test1.amq"));

      sf.renameTo("test1.cmp");

      fileNames = factory.listFiles("cmp");

      assertEquals(1, fileNames.size());

      assertTrue(fileNames.contains("test1.cmp"));

      sf.delete();

      fileNames = factory.listFiles("amq");

      assertEquals(0, fileNames.size());

      fileNames = factory.listFiles("cmp");

      assertEquals(0, fileNames.size());

   }

   @Test
   public void testWriteandRead() throws Exception {
      SequentialFile sf = factory.createSequentialFile("write.amq");

      sf.open();

      String s1 = "aardvark";
      byte[] bytes1 = s1.getBytes(StandardCharsets.UTF_8);
      ActiveMQBuffer bb1 = wrapBuffer(bytes1);

      String s2 = "hippopotamus";
      byte[] bytes2 = s2.getBytes(StandardCharsets.UTF_8);
      ActiveMQBuffer bb2 = wrapBuffer(bytes2);

      String s3 = "echidna";
      byte[] bytes3 = s3.getBytes(StandardCharsets.UTF_8);
      ActiveMQBuffer bb3 = wrapBuffer(bytes3);

      long initialPos = sf.position();
      sf.write(bb1, true);
      long bytesWritten = sf.position() - initialPos;

      assertEquals(calculateRecordSize(bytes1.length, factory.getAlignment()), bytesWritten);

      initialPos = sf.position();
      sf.write(bb2, true);
      bytesWritten = sf.position() - initialPos;

      assertEquals(calculateRecordSize(bytes2.length, factory.getAlignment()), bytesWritten);

      initialPos = sf.position();
      sf.write(bb3, true);
      bytesWritten = sf.position() - initialPos;

      assertEquals(calculateRecordSize(bytes3.length, factory.getAlignment()), bytesWritten);

      sf.position(0);

      ByteBuffer rb1 = factory.newBuffer(bytes1.length);
      ByteBuffer rb2 = factory.newBuffer(bytes2.length);
      ByteBuffer rb3 = factory.newBuffer(bytes3.length);

      int bytesRead = sf.read(rb1);
      assertEquals(calculateRecordSize(bytes1.length, factory.getAlignment()), bytesRead);

      for (int i = 0; i < bytes1.length; i++) {
         assertEquals(bytes1[i], rb1.get(i));
      }

      bytesRead = sf.read(rb2);
      assertEquals(calculateRecordSize(bytes2.length, factory.getAlignment()), bytesRead);
      for (int i = 0; i < bytes2.length; i++) {
         assertEquals(bytes2[i], rb2.get(i));
      }

      bytesRead = sf.read(rb3);
      assertEquals(calculateRecordSize(bytes3.length, factory.getAlignment()), bytesRead);
      for (int i = 0; i < bytes3.length; i++) {
         assertEquals(bytes3[i], rb3.get(i));
      }

      sf.close();

   }

   @Test
   public void testPosition() throws Exception {
      SequentialFile sf = factory.createSequentialFile("position.amq");

      sf.open();

      try {

         sf.fill(3 * 512);

         String s1 = "orange";
         byte[] bytes1 = s1.getBytes(StandardCharsets.UTF_8);

         byte[] bytes2 = s1.getBytes(StandardCharsets.UTF_8);

         String s3 = "lemon";
         byte[] bytes3 = s3.getBytes(StandardCharsets.UTF_8);

         long initialPos = sf.position();
         sf.write(wrapBuffer(bytes1), true);
         long bytesWritten = sf.position() - initialPos;

         assertEquals(calculateRecordSize(bytes1.length, factory.getAlignment()), bytesWritten);

         initialPos = sf.position();
         sf.write(wrapBuffer(bytes2), true);
         bytesWritten = sf.position() - initialPos;

         assertEquals(calculateRecordSize(bytes2.length, factory.getAlignment()), bytesWritten);

         initialPos = sf.position();
         sf.write(wrapBuffer(bytes3), true);
         bytesWritten = sf.position() - initialPos;

         assertEquals(calculateRecordSize(bytes3.length, factory.getAlignment()), bytesWritten);

         byte[] rbytes1 = new byte[bytes1.length];

         byte[] rbytes2 = new byte[bytes2.length];

         byte[] rbytes3 = new byte[bytes3.length];

         ByteBuffer rb1 = factory.newBuffer(rbytes1.length);
         ByteBuffer rb2 = factory.newBuffer(rbytes2.length);
         ByteBuffer rb3 = factory.newBuffer(rbytes3.length);

         sf.position(calculateRecordSize(bytes1.length, factory.getAlignment()) + calculateRecordSize(bytes2.length, factory.getAlignment()));

         int bytesRead = sf.read(rb3);
         assertEquals(rb3.limit(), bytesRead);
         rb3.rewind();
         rb3.get(rbytes3);
         ActiveMQTestBase.assertEqualsByteArrays(bytes3, rbytes3);

         sf.position(rb1.limit());

         bytesRead = sf.read(rb2);
         assertEquals(rb2.limit(), bytesRead);
         rb2.get(rbytes2);
         ActiveMQTestBase.assertEqualsByteArrays(bytes2, rbytes2);

         sf.position(0);

         bytesRead = sf.read(rb1);
         assertEquals(rb1.limit(), bytesRead);
         rb1.get(rbytes1);

         ActiveMQTestBase.assertEqualsByteArrays(bytes1, rbytes1);

      } finally {
         try {
            sf.close();
         } catch (Exception ignored) {
         }
      }
   }

   @Test
   public void testOpenClose() throws Exception {
      SequentialFile sf = factory.createSequentialFile("openclose.amq");

      sf.open();

      sf.fill(512);

      String s1 = "cheesecake";
      byte[] bytes1 = s1.getBytes(StandardCharsets.UTF_8);

      long initialPos = sf.position();
      sf.write(wrapBuffer(bytes1), true);
      long bytesWritten = sf.position() - initialPos;

      assertEquals(calculateRecordSize(bytes1.length, factory.getAlignment()), bytesWritten);

      sf.close();

      try {

         sf.write(wrapBuffer(bytes1), true);

         fail("Should throw exception");
      } catch (Exception e) {
      }

      sf.open();

      sf.write(wrapBuffer(bytes1), true);

      sf.close();
   }

   private ActiveMQBuffer wrapBuffer(final byte[] bytes) {
      return ActiveMQBuffers.wrappedBuffer(bytes);
   }

   protected void checkFill(final SequentialFileFactory factory, final SequentialFile file, final int size) throws Exception {
      file.fill(size);

      file.close();

      file.open();

      file.position(0);

      ByteBuffer bb = factory.newBuffer(size);

      int bytesRead = file.read(bb);

      assertEquals(calculateRecordSize(size, factory.getAlignment()), bytesRead);

      for (int i = 0; i < size; i++) {
         // log.debug(" i is {}", i);
         assertEquals(0, bb.get(i));
      }

   }

}
