/*
 * 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.cassandra.io.sstable;

import java.io.IOException;

import org.apache.cassandra.db.ClusteringPrefix;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.RowIndexEntry;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.io.ISerializer;
import org.apache.cassandra.io.sstable.format.Version;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.utils.ObjectSizes;

/**
 * {@code IndexInfo} is embedded in the indexed version of {@link RowIndexEntry}.
 * Each instance roughly covers a range of {@link org.apache.cassandra.config.Config#column_index_size_in_kb column_index_size_in_kb} kB
 * and contains the first and last clustering value (or slice bound), its offset in the data file and width in the data file.
 * <p>
 * Each {@code IndexInfo} object is serialized as follows.
 * </p>
 * <p>
 * Serialization format changed in 3.0. First, the {@code endOpenMarker} has been introduced.
 * Second, the <i>order</i> of the fields in serialized representation changed to allow future
 * optimizations to access {@code offset} and {@code width} fields directly without skipping
 * {@code firstName}/{@code lastName}.
 * </p>
 * <p>
 * {@code
 *    (*) IndexInfo.firstName (ClusteringPrefix serializer, either Clustering.serializer.serialize or Slice.Bound.serializer.serialize)
 *    (*) IndexInfo.lastName (ClusteringPrefix serializer, either Clustering.serializer.serialize or Slice.Bound.serializer.serialize)
 * (long) IndexInfo.offset
 * (long) IndexInfo.width
 * (bool) IndexInfo.endOpenMarker != null              (if 3.0)
 *  (int) IndexInfo.endOpenMarker.localDeletionTime    (if 3.0 && IndexInfo.endOpenMarker != null)
 * (long) IndexInfo.endOpenMarker.markedForDeletionAt  (if 3.0 && IndexInfo.endOpenMarker != null)
 * }
 * </p>
 */
public class IndexInfo
{
    private static final long EMPTY_SIZE = ObjectSizes.measure(new IndexInfo(null, null, 0, 0, null));

    public final long offset;
    public final long width;
    public final ClusteringPrefix firstName;
    public final ClusteringPrefix lastName;

    // If at the end of the index block there is an open range tombstone marker, this marker
    // deletion infos. null otherwise.
    public final DeletionTime endOpenMarker;

    public IndexInfo(ClusteringPrefix firstName,
                     ClusteringPrefix lastName,
                     long offset,
                     long width,
                     DeletionTime endOpenMarker)
    {
        this.firstName = firstName;
        this.lastName = lastName;
        this.offset = offset;
        this.width = width;
        this.endOpenMarker = endOpenMarker;
    }

    public static class Serializer implements ISerializer<IndexInfo>
    {
        // This is the default index size that we use to delta-encode width when serializing so we get better vint-encoding.
        // This is imperfect as user can change the index size and ideally we would save the index size used with each index file
        // to use as base. However, that's a bit more involved a change that we want for now and very seldom do use change the index
        // size so using the default is almost surely better than using no base at all.
        public static final long WIDTH_BASE = 64 * 1024;

        private final ISerializer<ClusteringPrefix> clusteringSerializer;
        private final Version version;

        public Serializer(Version version, ISerializer<ClusteringPrefix> clusteringSerializer)
        {
            this.clusteringSerializer = clusteringSerializer;
            this.version = version;
        }

        public void serialize(IndexInfo info, DataOutputPlus out) throws IOException
        {
            assert version.storeRows() : "We read old index files but we should never write them";

            clusteringSerializer.serialize(info.firstName, out);
            clusteringSerializer.serialize(info.lastName, out);
            out.writeUnsignedVInt(info.offset);
            out.writeVInt(info.width - WIDTH_BASE);

            out.writeBoolean(info.endOpenMarker != null);
            if (info.endOpenMarker != null)
                DeletionTime.serializer.serialize(info.endOpenMarker, out);
        }

        public void skip(DataInputPlus in) throws IOException
        {
            clusteringSerializer.skip(in);
            clusteringSerializer.skip(in);
            if (version.storeRows())
            {
                in.readUnsignedVInt();
                in.readVInt();
                if (in.readBoolean())
                    DeletionTime.serializer.skip(in);
            }
            else
            {
                in.skipBytes(TypeSizes.sizeof(0L));
                in.skipBytes(TypeSizes.sizeof(0L));
            }
        }

        public IndexInfo deserialize(DataInputPlus in) throws IOException
        {
            ClusteringPrefix firstName = clusteringSerializer.deserialize(in);
            ClusteringPrefix lastName = clusteringSerializer.deserialize(in);
            long offset;
            long width;
            DeletionTime endOpenMarker = null;
            if (version.storeRows())
            {
                offset = in.readUnsignedVInt();
                width = in.readVInt() + WIDTH_BASE;
                if (in.readBoolean())
                    endOpenMarker = DeletionTime.serializer.deserialize(in);
            }
            else
            {
                offset = in.readLong();
                width = in.readLong();
            }
            return new IndexInfo(firstName, lastName, offset, width, endOpenMarker);
        }

        public long serializedSize(IndexInfo info)
        {
            assert version.storeRows() : "We read old index files but we should never write them";

            long size = clusteringSerializer.serializedSize(info.firstName)
                        + clusteringSerializer.serializedSize(info.lastName)
                        + TypeSizes.sizeofUnsignedVInt(info.offset)
                        + TypeSizes.sizeofVInt(info.width - WIDTH_BASE)
                        + TypeSizes.sizeof(info.endOpenMarker != null);

            if (info.endOpenMarker != null)
                size += DeletionTime.serializer.serializedSize(info.endOpenMarker);
            return size;
        }
    }

    public long unsharedHeapSize()
    {
        return EMPTY_SIZE
             + firstName.unsharedHeapSize()
             + lastName.unsharedHeapSize()
             + (endOpenMarker == null ? 0 : endOpenMarker.unsharedHeapSize());
    }
}

@@ -1775,28 +1775,22 @@
  3.0
-. First,
+;
  the %7B@
-code 
+link #
 endO
@@ -1823,235 +1823,63 @@
 uced
-.%0A * Second, the %3Ci%3Eorder%3C/i%3E of the fields in serialized representation changed to allow future%0A * optimizations to access %7B@code offset%7D and %7B@code width%7D fields directly without skipping%0A * %7B@code firstName%7D/%7B@code lastName%7D
+ and integer fields are%0A * stored using varint encoding
 .%0A *

fix class comment in IndexInfo