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

import java.util.BitSet;
import java.util.Map;
import org.apache.bookkeeper.net.BookieId;

/**
 * This interface determins how entries are distributed among bookies.
 *
 * <p>Every entry gets replicated to some number of replicas. The first replica for
 * an entry is given a replicaIndex of 0, and so on. To distribute write load,
 * not all entries go to all bookies. Given an entry-id and replica index, an
 * {@link DistributionSchedule} determines which bookie that replica should go
 * to.
 */

public interface DistributionSchedule {

    /**
     * A write set represents the set of bookies to which
     * a request will be written.
     * The set consists of a list of indices which can be
     * used to lookup the bookie in the ensemble.
     */
    interface WriteSet {
        /**
         * The number of indexes in the write set.
         */
        int size();

        /**
         * Whether the set contains the given index.
         */
        boolean contains(int i);

        /**
         * Get the index at index i.
         */
        int get(int i);

        /**
         * Set the index at index i.
         * @return the previous value at that index.
         */
        int set(int i, int index);

        /**
         * Sort the indices.
         */
        void sort();

        /**
         * Index of a specified bookie index.
         * -1 if not found.
         */
        int indexOf(int index);

        /**
         * If we want a write set to cover all bookies in an ensemble
         * of size X, then all of the index from 0..X must exist in the
         * write set. This method appends those which are missing to the
         * end of the write set.
         */
        void addMissingIndices(int maxIndex);

        /**
         * Move an index from one position to another,
         * shifting the other indices accordingly.
         */
        void moveAndShift(int from, int to);

        /**
         * Recycle write set object when not in use.
         */
        void recycle();

        /**
         * Make a deep copy of this write set.
         */
        WriteSet copy();
    }

    WriteSet NULL_WRITE_SET = new WriteSet() {
            @Override
            public int size() {
                return 0;
            }
            @Override
            public boolean contains(int i) {
                return false;
            }
            @Override
            public int get(int i) {
                throw new ArrayIndexOutOfBoundsException();
            }
            @Override
            public int set(int i, int index) {
                throw new ArrayIndexOutOfBoundsException();
            }
            @Override
            public void sort() {}
            @Override
            public int indexOf(int index) {
                return -1;
            }
            @Override
            public void addMissingIndices(int maxIndex) {
                throw new ArrayIndexOutOfBoundsException();
            }
            @Override
            public void moveAndShift(int from, int to) {
                throw new ArrayIndexOutOfBoundsException();
            }
            @Override
            public void recycle() {}
            @Override
            public WriteSet copy() {
                return this;
            }
        };

    int getWriteQuorumSize();

    /**
     * Return the set of bookie indices to send the message to.
     */
    WriteSet getWriteSet(long entryId);

    /**
     * Return the WriteSet bookie index for a given and index
     * in the WriteSet.
     *
     * @param entryId
     * @param writeSetIndex
     * @return
     */
    int getWriteSetBookieIndex(long entryId, int writeSetIndex);

    /**
     * Return the set of bookies indices to send the messages to the whole ensemble.
     *
     * @param entryId entry id used to calculate the ensemble.
     * @return the set of bookies indices to send the request.
     */
    WriteSet getEnsembleSet(long entryId);

    /**
     * An ack set represents the set of bookies from which
     * a response must be received so that an entry can be
     * considered to be replicated on a quorum.
     */
    interface AckSet {
        /**
         * Add a bookie response and check if quorum has been met.
         * @return true if quorum has been met, false otherwise
         */
        boolean completeBookieAndCheck(int bookieIndexHeardFrom);

        /**
         * Received failure response from a bookie and check if ack quorum
         * will be broken.
         *
         * @param bookieIndexHeardFrom
         *          bookie index that failed.
         * @param address
         *          bookie address
         * @return true if ack quorum is broken, false otherwise.
         */
        boolean failBookieAndCheck(int bookieIndexHeardFrom, BookieId address);

        /**
         * Return the list of bookies that already failed.
         *
         * @return the list of bookies that already failed.
         */
        Map<Integer, BookieId> getFailedBookies();

        /**
         * Invalidate a previous bookie response.
         * Used for reissuing write requests.
         */
        boolean removeBookieAndCheck(int bookie);

        /**
         * Recycle this ack set when not used anymore.
         */
        void recycle();
    }

    /**
     * Returns an ackset object, responses should be checked against this.
     */
    AckSet getAckSet();

    /**
     * Returns an ackset object useful to wait for all bookies in the ensemble,
     * responses should be checked against this.
     */
    AckSet getEnsembleAckSet();

    /**
     * Interface to keep track of which bookies in an ensemble, an action
     * has been performed for.
     */
    interface QuorumCoverageSet {
        /**
         * Add a bookie to the result set.
         *
         * @param bookieIndexHeardFrom Bookie we've just heard from
         */
        void addBookie(int bookieIndexHeardFrom, int rc);

        /**
         * check if all quorum in the set have had the action performed for it.
         *
         * @return whether all quorums have been covered
         */
        boolean checkCovered();
    }

    QuorumCoverageSet getCoverageSet();

    /**
     * Whether entry presents on given bookie index.
     *
     * @param entryId
     *            - entryId to check the presence on given bookie index
     * @param bookieIndex
     *            - bookie index on which it need to check the possible presence
     *            of the entry
     * @return true if it has entry otherwise false.
     */
    boolean hasEntry(long entryId, int bookieIndex);

    /**
     * Get the bitset representing the entries from entry 'startEntryId' to
     * 'lastEntryId', that would be striped to the bookie with index -
     * bookieIndex. Value of the bit with the 'bitIndex+n', indicate whether
     * entry with entryid 'startEntryId+n' is striped to this bookie or not.
     *
     * @param bookieIndex
     *            index of the bookie in the ensemble starting with 0
     * @param startEntryId
     *            starting entryid
     * @param lastEntryId
     *            last entryid
     * @return the bitset representing the entries that would be striped to the
     *         bookie
     */
    BitSet getEntriesStripedToTheBookie(int bookieIndex, long startEntryId, long lastEntryId);
}
