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

import java.io.IOException;

import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.ClusteringComparator;
import org.apache.cassandra.db.Slice;
import org.apache.cassandra.db.Slices;
import org.apache.cassandra.db.partitions.CachedPartition;
import org.apache.cassandra.db.partitions.Partition;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.schema.TableMetadata;

/**
 * A filter that selects a subset of the rows of a given partition by using the "clustering index".
 * <p>
 * In CQL terms, this correspond to the clustering columns selection and correspond to what
 * the storage engine can do without filtering (and without 2ndary indexes). This does not include
 * the restrictions on non-PK columns which can be found in {@link RowFilter}.
 */
public interface ClusteringIndexFilter
{
    public static final Serializer serializer = AbstractClusteringIndexFilter.serializer;

    public enum Kind
    {
        SLICE (ClusteringIndexSliceFilter.deserializer),
        NAMES (ClusteringIndexNamesFilter.deserializer);

        private static final Kind[] VALUES = values();

        protected final InternalDeserializer deserializer;

        private Kind(InternalDeserializer deserializer)
        {
            this.deserializer = deserializer;
        }

        public static Kind fromOrdinal(int ordinal)
        {
            return VALUES[ordinal];
        }
    }

    static interface InternalDeserializer
    {
        public ClusteringIndexFilter deserialize(DataInputPlus in, int version, TableMetadata metadata, boolean reversed) throws IOException;
    }

    /**
     * Whether the filter query rows in reversed clustering order or not.
     *
     * @return whether the filter query rows in reversed clustering order or not.
     */
    public boolean isReversed();

    public boolean isEmpty(ClusteringComparator comparator);

    /**
     * Returns a filter for continuing the paging of this filter given the last returned clustering prefix.
     *
     * @param comparator the comparator for the table this is a filter for.
     * @param lastReturned the last clustering that was returned for the query we are paging for. The
     * resulting filter will be such that results coming after {@code lastReturned} are returned
     * (where coming after means "greater than" if the filter is not reversed, "lesser than" otherwise;
     * futher, whether the comparison is strict or not depends on {@code inclusive}).
     * @param inclusive whether or not we want to include the {@code lastReturned} in the newly returned
     * page of results.
     *
     * @return a new filter that selects results coming after {@code lastReturned}.
     */
    public ClusteringIndexFilter forPaging(ClusteringComparator comparator, Clustering<?> lastReturned, boolean inclusive);

    /**
     * Returns whether we can guarantee that a given cached partition contains all the data selected by this filter.
     *
     * @param partition the cached partition. This method assumed that the rows of this partition contains all the table columns.
     *
     * @return whether we can guarantee that all data selected by this filter are in {@code partition}.
     */
    public boolean isFullyCoveredBy(CachedPartition partition);

    /**
     * Whether this filter selects the head of a partition (i.e. it isn't reversed and selects all rows up to a certain point).
     *
     * @return whether this filter selects the head of a partition.
     */
    public boolean isHeadFilter();

    /**
     * Whether this filter selects all the row of a partition (it's an "identity" filter).
     *
     * @return whether this filter selects all the row of a partition (it's an "identity" filter).
     */
    public boolean selectsAllPartition();

    /**
     * Whether a given row is selected by this filter.
     *
     * @param clustering the clustering of the row to test the selection of.
     *
     * @return whether the row with clustering {@code clustering} is selected by this filter.
     */
    public boolean selects(Clustering<?> clustering);

    /**
     * Returns an iterator that only returns the rows of the provided iterator that this filter selects.
     * <p>
     * This method is the "dumb" counterpart to {@link #getSlices(TableMetadata)} in that it has no way to quickly get
     * to what is actually selected, so it simply iterate over it all and filters out what shouldn't be returned. This should
     * be avoided in general.
     * Another difference with {@link #getSlices(TableMetadata)} is that this method also filter the queried
     * columns in the returned result, while the former assumes that the provided iterator has already done it.
     *
     * @param columnFilter the columns to include in the rows of the result iterator.
     * @param iterator the iterator for which we should filter rows.
     *
     * @return an iterator that only returns the rows (or rather Unfilted) from {@code iterator} that are selected by this filter.
     */
    public UnfilteredRowIterator filterNotIndexed(ColumnFilter columnFilter, UnfilteredRowIterator iterator);

    public Slices getSlices(TableMetadata metadata);

    /**
     * Given a partition, returns a row iterator for the rows of this partition that are selected by this filter.
     *
     * @param columnFilter the columns to include in the rows of the result iterator.
     * @param partition the partition containing the rows to filter.
     *
     * @return a unfiltered row iterator returning those rows (or rather Unfiltered) from {@code partition} that are selected by this filter.
     */
    public UnfilteredRowIterator getUnfilteredRowIterator(ColumnFilter columnFilter, Partition partition);

    /**
     * Whether the data selected by this filter intersects with the provided slice.
     *
     * @param comparator the comparator of the table this if a filter on.
     * @param slice the slice to check intersection with,
     *
     * @return whether the data selected by this filter intersects with {@code slice}.
     */
    public boolean intersects(ClusteringComparator comparator, Slice slice);

    public Kind kind();

    public String toString(TableMetadata metadata);
    public String toCQLString(TableMetadata metadata, RowFilter rowFilter);

    public interface Serializer
    {
        public void serialize(ClusteringIndexFilter filter, DataOutputPlus out, int version) throws IOException;
        public ClusteringIndexFilter deserialize(DataInputPlus in, int version, TableMetadata metadata) throws IOException;
        public long serializedSize(ClusteringIndexFilter filter, int version);
    }
}