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

import com.google.common.annotations.VisibleForTesting;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

/**
 * Log record with {@link DLSN} and <code>SequenceId</code>.
 *
 * <h3>Sequence Numbers</h3>
 *
 * <p>A log record will be assigned with an unique system generated sequence number {@link DLSN} when it is
 * written to a log. At the mean time, a 64-bits long number is assigned to the record indicates its position
 * within a log, which is called <code>SequenceId</code>. Besides {@link DLSN} and <code>SequenceID</code>,
 * application can assign its own sequence number (called <code>TransactionID</code>) to the log record while
 * writing it.
 *
 * <h4>Transaction ID</h4>
 *
 * <p>Transaction ID is a positive 64-bits long number that is assigned by the application. It is a very helpful
 * field when application wants to organize the records and position the readers using their own sequencing method.
 * A typical use case of <code>TransactionID</code> is DistributedLog Write Proxy. It assigns the non-decreasing
 * timestamps to log records, which the timestamps could be used as `physical time` to implement `TTL` in a strong
 * consistent database to achieve consistent `TTL` over replicas.
 *
 * <h4>DLSN</h4>
 *
 * <p>DistributedLog Sequence Number (<i>DLSN</i>) is the sequence number generated during written time.
 * It is comparable and could be used to figure out the order between records. The DLSN is comprised with 3 components.
 * They are <i>Log Segment Sequence Number</i>, <i>Entry Id</i> and <i>Slot Id</i>. (See {@link DLSN} for more details).
 * The DLSN is usually used for comparison, positioning and truncation.
 *
 * <h4>Sequence ID</h4>
 *
 * <p>Sequence ID is introduced to address the drawback of <code>DLSN</code>, in favor of answering questions like
 * `how many records written between two DLSNs`. It is a 64-bits monotonic increasing number (starting from zero).
 * Sequence ids are only accessible by readers. That means writers don't know the sequence ids of records after they
 * wrote them.
 */
public class LogRecordWithDLSN extends LogRecord {
    private final DLSN dlsn;
    private final long startSequenceIdOfCurrentSegment;

    /**
     * This empty constructor can only be called from Reader#readOp.
     */
    LogRecordWithDLSN(DLSN dlsn, long startSequenceIdOfCurrentSegment) {
        super();
        this.dlsn = dlsn;
        this.startSequenceIdOfCurrentSegment = startSequenceIdOfCurrentSegment;
    }

    @VisibleForTesting
    public LogRecordWithDLSN(DLSN dlsn, long txid, byte[] data, long startSequenceIdOfCurrentSegment) {
        super(txid, data);
        this.dlsn = dlsn;
        this.startSequenceIdOfCurrentSegment = startSequenceIdOfCurrentSegment;
    }

    @VisibleForTesting
    public LogRecordWithDLSN(DLSN dlsn, long txid, ByteBuf buffer, long startSequenceIdOfCurrentSegment) {
        // Need to make a copy since the passed payload is using a ref-count buffer that we don't know when could
        // release, since the record is passed to the user. Also, the passed ByteBuf is coming from network and is
        // backed by a direct buffer which we could not expose as a byte[]
        super(txid, Unpooled.copiedBuffer(buffer));
        this.dlsn = dlsn;
        this.startSequenceIdOfCurrentSegment = startSequenceIdOfCurrentSegment;
    }

    long getStartSequenceIdOfCurrentSegment() {
        return startSequenceIdOfCurrentSegment;
    }

    /**
     * Get the sequence id of the record in the log.
     *
     * @return sequence id of the record in the log.
     */
    public long getSequenceId() {
        return startSequenceIdOfCurrentSegment + getPositionWithinLogSegment() - 1;
    }

    /**
     * Get the DLSN of the record in the log.
     *
     * @return DLSN of the record in the log.
     */
    public DLSN getDlsn() {
        return dlsn;
    }

    @Override
    public String toString() {
        return "LogRecordWithDLSN{"
            + "dlsn=" + dlsn
            + ", txid=" + getTransactionId()
            + ", position=" + getPositionWithinLogSegment()
            + ", isControl=" + isControl()
            + ", isEndOfStream=" + isEndOfStream()
            + '}';
    }
}
