/**
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.extras.benchmarks.journal.gcfree;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import org.apache.activemq.artemis.core.io.SequentialFile;
import org.apache.activemq.artemis.core.io.SequentialFileFactory;
import org.apache.activemq.artemis.core.journal.impl.JournalFile;
import org.apache.activemq.artemis.core.journal.impl.JournalImpl;

final class GcFreeJournal extends JournalImpl {

   private final AddJournalRecordEncoder addJournalRecordEncoder = new AddJournalRecordEncoder();
   //TODO replace with thread local pools if not single threaded!
   private ByteBuffer journalRecordBytes = null;

   GcFreeJournal(final int fileSize,
                 final int minFiles,
                 final int poolSize,
                 final int compactMinFiles,
                 final int compactPercentage,
                 final SequentialFileFactory fileFactory,
                 final String filePrefix,
                 final String fileExtension,
                 final int maxAIO) {
      super(fileSize, minFiles, poolSize, compactMinFiles, compactPercentage, fileFactory, filePrefix, fileExtension, maxAIO, 0);
   }

   public static int align(final int value, final int alignment) {
      return (value + (alignment - 1)) & ~(alignment - 1);
   }

   public void appendAddRecord(final long id,
                               final int recordType,
                               final ByteBuffer encodedRecord,
                               final int offset,
                               final int length,
                               final boolean sync) throws Exception {
      final int expectedLength = JournalRecordHeader.BYTES + AddJournalRecordEncoder.expectedSize(length);
      final int alignedLength = align(expectedLength, 8);
      switchFileIfNecessary(alignedLength);
      final JournalFile currentFile = getCurrentFile();
      final int fileId = currentFile.getRecordID();
      if (this.journalRecordBytes == null || this.journalRecordBytes.capacity() < alignedLength) {
         final int newPooledLength = align(alignedLength, 4096);
         //TODO ADD LIMITS OR WARNS IN CASE OF TOO MUCH BIGGER SIZE
         this.journalRecordBytes = ByteBuffer.allocateDirect(newPooledLength);
         this.journalRecordBytes.order(ByteOrder.nativeOrder());
      }
      final long journalRecordHeader = JournalRecordHeader.makeHeader(JournalRecordTypes.ADD_JOURNAL, expectedLength);
      this.journalRecordBytes.putLong(0, journalRecordHeader);
      //use natural stride while encoding: FileId<CompactCount<Id<RecordType<RecordBytes
      this.addJournalRecordEncoder.on(this.journalRecordBytes, JournalRecordHeader.BYTES).fileId(fileId).compactCount(0).id(id).recordType(recordType).record(encodedRecord, offset, length);
      final SequentialFile sequentialFile = currentFile.getFile();
      try {
         this.journalRecordBytes.limit(alignedLength);
         sequentialFile.writeDirect(this.journalRecordBytes, sync);
      } finally {
         this.journalRecordBytes.clear();
      }
      //TODO AVOID INDEXING WITH CONCURRENT MAP!
   }

}
