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

import java.io.IOException;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nonnull;

import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.io.sstable.AbstractRowIndexEntry;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.IScrubber;
import org.apache.cassandra.io.sstable.MetricsProviders;
import org.apache.cassandra.io.sstable.SSTable;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.utils.OutputHandler;
import org.apache.cassandra.utils.Pair;

/**
 * Provides the accessors to data on disk.
 */
public interface SSTableFormat<R extends SSTableReader, W extends SSTableWriter>
{
    String name();

    Version getLatestVersion();
    Version getVersion(String version);

    SSTableWriterFactory<W, ?> getWriterFactory();

    SSTableReaderFactory<R, ?> getReaderFactory();

    /**
     * All the components that the writter can produce when saving an sstable, as well as all the components
     * that the reader can read.
     */
    Set<Component> allComponents();


    Set<Component> primaryComponents();

    /**
     * Returns components required by offline compaction tasks - like splitting sstables.
     */
    Set<Component> batchComponents();

    /**
     * Returns the components which should be selected for upload by the sstable loader.
     */
    Set<Component> uploadComponents();

    /**
     * Returns a set of the components that can be changed after an sstable was written.
     */
    Set<Component> mutableComponents();

    /**
     * Returns a set of components that can be automatically generated when loading sstable and thus are not mandatory.
     */
    Set<Component> generatedOnLoadComponents();

    KeyCacheValueSerializer<R, ?> getKeyCacheValueSerializer();

    /**
     * Returns a new scrubber for an sstable. Note that the transaction must contain only one reader
     * and the reader must match the provided cfs.
     */
    IScrubber getScrubber(ColumnFamilyStore cfs,
                          LifecycleTransaction transaction,
                          OutputHandler outputHandler,
                          IScrubber.Options options);

    MetricsProviders getFormatSpecificMetricsProviders();

    void deleteOrphanedComponents(Descriptor descriptor, Set<Component> components);

    /**
     * Deletes the existing components of the sstables represented by the provided descriptor.
     * The method is also responsible for cleaning up the in-memory resources occupied by the stuff related to that
     * sstables, such as row key cache entries.
     */
    void delete(Descriptor descriptor);

    interface SSTableReaderFactory<R extends SSTableReader, B extends SSTableReader.Builder<R, B>>
    {
        /**
         * A simple builder which creates an instnace of {@link SSTableReader} with the provided parameters.
         * It expects that all the required resources to be opened/loaded externally by the caller.
         * <p>
         * The builder is expected to perform basic validation of the provided parameters.
         */
        SSTableReader.Builder<R, B> builder(Descriptor descriptor);

        /**
         * A builder which opens/loads all the required resources upon execution of
         * {@link SSTableReaderLoadingBuilder#build(SSTable.Owner, boolean, boolean)} and passed them to the created
         * reader instance. If the creation of {@link SSTableReader} fails, no resources should be left opened.
         * <p>
         * The builder is expected to perform basic validation of the provided parameters.
         */
        SSTableReaderLoadingBuilder<R, B> loadingBuilder(Descriptor descriptor, TableMetadataRef tableMetadataRef, Set<Component> components);

        /**
         * Retrieves a key range for the given sstable at the lowest cost - that is, without opening all sstables files
         * if possible.
         */
        Pair<DecoratedKey, DecoratedKey> readKeyRange(Descriptor descriptor, IPartitioner partitioner) throws IOException;

        Class<R> getReaderClass();
    }

    interface SSTableWriterFactory<W extends SSTableWriter, B extends SSTableWriter.Builder<W, B>>
    {
        /**
         * Returns a new builder which can create instance of {@link SSTableWriter} with the provided parameters.
         * Similarly to the loading builder, it should open the required resources when
         * the {@link SSTableWriter.Builder#build(org.apache.cassandra.db.lifecycle.ILifecycleTransaction, SSTable.Owner)} method is called.
         * It should not let the caller passing any closeable resources directly, that is, via setters.
         * If building fails, all the opened resources should be released.
         */
        B builder(Descriptor descriptor);

        /**
         * Tries to estimate the size of all the sstable files from the provided parameters.
         */
        long estimateSize(SSTableWriter.SSTableSizeParameters parameters);
    }

    class Components
    {
        public static class Types
        {
            // the base data for an sstable: the remaining components can be regenerated
            // based on the data component
            public static final Component.Type DATA = Component.Type.createSingleton("DATA", "Data.db", true, null);
            // file to hold information about uncompressed data length, chunk offsets etc.
            public static final Component.Type COMPRESSION_INFO = Component.Type.createSingleton("COMPRESSION_INFO", "CompressionInfo.db", true, null);
            // statistical metadata about the content of the sstable
            public static final Component.Type STATS = Component.Type.createSingleton("STATS", "Statistics.db", true, null);
            // serialized bloom filter for the row keys in the sstable
            public static final Component.Type FILTER = Component.Type.createSingleton("FILTER", "Filter.db", true, null);
            // holds CRC32 checksum of the data file
            public static final Component.Type DIGEST = Component.Type.createSingleton("DIGEST", "Digest.crc32", true, null);
            // holds the CRC32 for chunks in an uncompressed file.
            public static final Component.Type CRC = Component.Type.createSingleton("CRC", "CRC.db", true, null);
            // table of contents, stores the list of all components for the sstable
            public static final Component.Type TOC = Component.Type.createSingleton("TOC", "TOC.txt", false, null);
            // built-in secondary index (may exist multiple per sstable)
            public static final Component.Type SECONDARY_INDEX = Component.Type.create("SECONDARY_INDEX", "SI_.*.db", false, null);
            // custom component, used by e.g. custom compaction strategy
            public static final Component.Type CUSTOM = Component.Type.create("CUSTOM", null, true, null);
        }

        // singleton components for types that don't need ids
        public final static Component DATA = Types.DATA.getSingleton();
        public final static Component COMPRESSION_INFO = Types.COMPRESSION_INFO.getSingleton();
        public final static Component STATS = Types.STATS.getSingleton();
        public final static Component FILTER = Types.FILTER.getSingleton();
        public final static Component DIGEST = Types.DIGEST.getSingleton();
        public final static Component CRC = Types.CRC.getSingleton();
        public final static Component TOC = Types.TOC.getSingleton();
    }

    interface KeyCacheValueSerializer<R extends SSTableReader, T extends AbstractRowIndexEntry>
    {
        void skip(DataInputPlus input) throws IOException;

        T deserialize(R reader, DataInputPlus input) throws IOException;

        void serialize(T entry, DataOutputPlus output) throws IOException;
    }

    interface Factory
    {
        /**
         * Returns a name of the format. Format name must not be empty, must be unique and must consist only of lowercase letters.
         */
        String name();

        /**
         * Returns an instance of the sstable format configured with the provided options.
         * <p/>
         * The method is expected to validate the options, and throw
         * {@link org.apache.cassandra.exceptions.ConfigurationException} if the validation fails.
         *
         * @param options    overrides for the default options, can be empty, cannot be null
         */
        SSTableFormat<?, ?> getInstance(@Nonnull Map<String, String> options);
    }
}
