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

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.core.io.nio.NIOSequentialFileFactory;
import org.apache.activemq.artemis.core.journal.IOCompletion;
import org.apache.activemq.artemis.core.journal.Journal;
import org.apache.activemq.artemis.core.journal.PreparedTransactionInfo;
import org.apache.activemq.artemis.core.journal.RecordInfo;
import org.apache.activemq.artemis.core.journal.collections.AbstractHashMapPersister;
import org.apache.activemq.artemis.core.journal.collections.MapStorageManager;
import org.apache.activemq.artemis.core.journal.collections.JournalHashMap;
import org.apache.activemq.artemis.core.journal.collections.JournalHashMapProvider;
import org.apache.activemq.artemis.core.journal.impl.JournalImpl;
import org.apache.activemq.artemis.core.persistence.Persister;
import org.apache.activemq.artemis.core.persistence.impl.journal.OperationContextImpl;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.utils.RandomUtil;
import org.apache.activemq.artemis.utils.DataConstants;
import org.apache.activemq.artemis.utils.actors.OrderedExecutorFactory;
import org.junit.jupiter.api.Test;

public class JournalHashMapTest extends ActiveMQTestBase {


   static class JournalManager implements MapStorageManager {
      final Journal journal;

      JournalManager(Journal journal) {
         this.journal = journal;
      }

      @Override
      public void storeMapRecord(long id,
                                 byte recordType,
                                 Persister persister,
                                 Object record,
                                 boolean sync,
                                 IOCompletion completionCallback) throws Exception {
         journal.appendAddRecord(id, recordType, persister, record, sync, completionCallback);
      }

      @Override
      public void storeMapRecord(long id,
                                 byte recordType,
                                 Persister persister,
                                 Object record,
                                 boolean sync) throws Exception {
         journal.appendAddRecord(id, recordType, persister, record, sync);
      }

      @Override
      public void deleteMapRecord(long id, boolean sync) throws Exception {
         journal.appendDeleteRecord(id, sync);
      }

      @Override
      public void deleteMapRecordTx(long txid, long id) throws Exception {
         journal.appendDeleteRecordTransactional(txid, id);

      }
   }

   @Test
   public void testHashMap() throws Exception {
      ExecutorService service = Executors.newFixedThreadPool(10);
      runAfter(service::shutdownNow);
      OrderedExecutorFactory executorFactory = new OrderedExecutorFactory(service);

      NIOSequentialFileFactory factory = new NIOSequentialFileFactory(getTestDirfile(), 1);

      JournalImpl journal = new JournalImpl(executorFactory, 10 * 1024, 10, 10, 3, 0, 50_000, factory, "coll", "data", 1, 0);

      journal.start();
      runAfter(journal::stop);

      journal.loadInternalOnly();

      AtomicLong sequence = new AtomicLong(1);

      JournalHashMapProvider<Long, Long, Object> journalHashMapProvider = new JournalHashMapProvider(sequence::incrementAndGet, new JournalManager(journal), new LongPersister(), (byte)3, OperationContextImpl::getContext, l -> null, (e, m, f) -> {
         e.printStackTrace();
      });

      JournalHashMap<Long, Long, Object> journalHashMap = journalHashMapProvider.getMap(1);

      for (long i = 0; i < 1000; i++) {
         journalHashMap.put(i, RandomUtil.randomLong());
      }

      /// repeating to make sure the remove works fine
      for (long i = 0; i < 1000; i++) {
         journalHashMap.put(i, RandomUtil.randomLong());
      }


      journal.flush();

      journal.stop();

      journalHashMapProvider.clear();

      journal.start();


      List<RecordInfo> recordInfos = new ArrayList<>();
      List<PreparedTransactionInfo> preparedTransactions = new ArrayList<>();
      journal.load(recordInfos, preparedTransactions, (a, b, c) -> { }, true);

      List<JournalHashMap.MapRecord<Long, Long>> records = new ArrayList<>();
      recordInfos.forEach(r -> {
         assertEquals((byte)3, r.userRecordType);
         journalHashMapProvider.reload(r);
      });

      List<JournalHashMap<Long, Long, Object>>  existingLists = journalHashMapProvider.getMaps();
      assertEquals(1, existingLists.size());
      JournalHashMap<Long, Long, Object> reloadedList = existingLists.get(0);

      assertEquals(journalHashMap.size(), reloadedList.size());

      journalHashMap.forEach((a, b) -> assertEquals(b, reloadedList.get(a)));

   }


   private static class LongPersister extends AbstractHashMapPersister<Long, Long> {

      @Override
      public byte getID() {
         return 0;
      }

      @Override
      protected int getKeySize(Long key) {
         return DataConstants.SIZE_LONG;
      }

      @Override
      protected void encodeKey(ActiveMQBuffer buffer, Long key) {
         buffer.writeLong(key);

      }

      @Override
      protected Long decodeKey(ActiveMQBuffer buffer) {
         return buffer.readLong();
      }

      @Override
      protected int getValueSize(Long value) {
         return DataConstants.SIZE_LONG;
      }

      @Override
      protected void encodeValue(ActiveMQBuffer buffer, Long value) {
         buffer.writeLong(value);
      }

      @Override
      protected Long decodeValue(ActiveMQBuffer buffer, Long key) {
         return buffer.readLong();
      }
   }
}
