/**
 * 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.ArrayList;
import java.util.List;
import java.util.SortedMap;

import org.apache.bookkeeper.net.BookieSocketAddress;

/**
 * Represents the entries of a segment of a ledger which are stored on a single
 * bookie in the segments bookie ensemble.
 * 
 * Used for checking and recovery
 */
public class LedgerFragment {
    private final int bookieIndex;
    private final List<BookieSocketAddress> ensemble;
    private final long firstEntryId;
    private final long lastKnownEntryId;
    private final long ledgerId;
    private final DistributionSchedule schedule;
    private final boolean isLedgerClosed;

    LedgerFragment(LedgerHandle lh, long firstEntryId,
            long lastKnownEntryId, int bookieIndex) {
        this.ledgerId = lh.getId();
        this.firstEntryId = firstEntryId;
        this.lastKnownEntryId = lastKnownEntryId;
        this.bookieIndex = bookieIndex;
        this.ensemble = lh.getLedgerMetadata().getEnsemble(firstEntryId);
        this.schedule = lh.getDistributionSchedule();
        SortedMap<Long, ArrayList<BookieSocketAddress>> ensembles = lh
                .getLedgerMetadata().getEnsembles();
        this.isLedgerClosed = lh.getLedgerMetadata().isClosed()
                || !ensemble.equals(ensembles.get(ensembles.lastKey()));
    }

    /**
     * Returns true, if and only if the ledger fragment will never be modified
     * by any of the clients in future, otherwise false. i.e,
     * <ol>
     * <li>If ledger is in closed state, then no other clients can modify this
     * fragment.</li>
     * <li>If ledger is not in closed state and the current fragment is not a
     * last fragment, then no one will modify this fragment.</li>
     * </ol>
     */
    public boolean isClosed() {
        return isLedgerClosed;
    }

    long getLedgerId() {
        return ledgerId;
    }

    long getFirstEntryId() {
        return firstEntryId;
    }

    long getLastKnownEntryId() {
        return lastKnownEntryId;
    }

    /**
     * Gets the failedBookie address
     */
    public BookieSocketAddress getAddress() {
        return ensemble.get(bookieIndex);
    }
    
    /**
     * Gets the failedBookie index
     */
    public int getBookiesIndex() {
        return bookieIndex;
    }

    /**
     * Gets the first stored entry id of the fragment in failed bookie.
     * 
     * @return entryId
     */
    public long getFirstStoredEntryId() {
        long firstEntry = firstEntryId;

        for (int i = 0; i < ensemble.size() && firstEntry <= lastKnownEntryId; i++) {
            if (schedule.hasEntry(firstEntry, bookieIndex)) {
                return firstEntry;
            } else {
                firstEntry++;
            }
        }
        return LedgerHandle.INVALID_ENTRY_ID;
    }

    /**
     * Gets the last stored entry id of the fragment in failed bookie.
     * 
     * @return entryId
     */
    public long getLastStoredEntryId() {
        long lastEntry = lastKnownEntryId;
        for (int i = 0; i < ensemble.size() && lastEntry >= firstEntryId; i++) {
            if (schedule.hasEntry(lastEntry, bookieIndex)) {
                return lastEntry;
            } else {
                lastEntry--;
            }
        }
        return LedgerHandle.INVALID_ENTRY_ID;
    }

    /**
     * Gets the ensemble of fragment
     * 
     * @return the ensemble for the segment which this fragment is a part of
     */
    public List<BookieSocketAddress> getEnsemble() {
        return this.ensemble;
    }

    @Override
    public String toString() {
        return String.format("Fragment(LedgerID: %d, FirstEntryID: %d[%d], "
                + "LastKnownEntryID: %d[%d], Host: %s, Closed: %s)", ledgerId, firstEntryId,
                getFirstStoredEntryId(), lastKnownEntryId, getLastStoredEntryId(),
                getAddress(), isLedgerClosed);
    }
}