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

import static org.apache.bookkeeper.client.BookKeeperClientStats.CATEGORY_CLIENT;
import static org.apache.bookkeeper.client.BookKeeperClientStats.CLIENT_SCOPE;

import org.apache.bookkeeper.client.BookKeeperClientStats;
import org.apache.bookkeeper.stats.Counter;
import org.apache.bookkeeper.stats.Gauge;
import org.apache.bookkeeper.stats.OpStatsLogger;
import org.apache.bookkeeper.stats.StatsLogger;
import org.apache.bookkeeper.stats.annotations.StatsDoc;

/**
 * The default implementation of {@link BookKeeperClientStats}.
 */
@StatsDoc(
    name = CLIENT_SCOPE,
    category = CATEGORY_CLIENT,
    help = "BookKeeper client stats"
)
public class BookKeeperClientStatsImpl implements BookKeeperClientStats {
    private final StatsLogger stats;
    @StatsDoc(
        name = CREATE_OP,
        help = "operation stats of creating ledgers"
    )
    private final OpStatsLogger createOpLogger;
    @StatsDoc(
        name = DELETE_OP,
        help = "operation stats of deleting ledgers"
    )
    private final OpStatsLogger deleteOpLogger;
    @StatsDoc(
        name = OPEN_OP,
        help = "operation stats of opening ledgers"
    )
    private final OpStatsLogger openOpLogger;
    @StatsDoc(
        name = RECOVER_OP,
        help = "operation stats of recovering ledgers"
    )
    private final OpStatsLogger recoverOpLogger;
    @StatsDoc(
        name = READ_OP,
        help = "operation stats of reading entries requests"
    )
    private final OpStatsLogger readOpLogger;
    @StatsDoc(
        name = READ_OP_DM,
        help = "the number of read entries hitting DigestMismatch errors"
    )
    private final Counter readOpDmCounter;
    @StatsDoc(
        name = READ_LAST_CONFIRMED_AND_ENTRY,
        help = "operation stats of read_last_confirmed_and_entry requests"
    )
    private final OpStatsLogger readLacAndEntryOpLogger;
    @StatsDoc(
        name = READ_LAST_CONFIRMED_AND_ENTRY_RESPONSE,
        help = "operation stats of read_last_confirmed_and_entry responses"
    )
    private final OpStatsLogger readLacAndEntryRespLogger;
    @StatsDoc(
        name = ADD_OP,
        help = "operation stats of adding entries requests"
    )
    private final OpStatsLogger addOpLogger;
    @StatsDoc(
        name = FORCE_OP,
        help = "operation stats of force requests"
    )
    private final OpStatsLogger forceOpLogger;
    @StatsDoc(
        name = ADD_OP_UR,
        help = "the number of add entries under replication"
    )
    private final Counter addOpUrCounter;
    @StatsDoc(
        name = WRITE_LAC_OP,
        help = "operation stats of write_lac requests"
    )
    private final OpStatsLogger writeLacOpLogger;
    @StatsDoc(
        name = READ_LAC_OP,
        help = "operation stats of read_lac requests"
    )
    private final OpStatsLogger readLacOpLogger;
    @StatsDoc(
        name = LEDGER_RECOVER_ADD_ENTRIES,
        help = "the distribution of entries written in ledger recovery requests"
    )
    private final OpStatsLogger recoverAddEntriesStats;
    @StatsDoc(
        name = LEDGER_RECOVER_READ_ENTRIES,
        help = "the distribution of entries read in ledger recovery requests"
    )
    private final OpStatsLogger recoverReadEntriesStats;

    @StatsDoc(
        name = ENSEMBLE_CHANGES,
        help = "The number of ensemble changes"
    )
    private final Counter ensembleChangeCounter;
    @StatsDoc(
        name = LAC_UPDATE_HITS,
        help = "The number of successful lac updates on piggybacked responses"
    )
    private final Counter lacUpdateHitsCounter;
    @StatsDoc(
        name = LAC_UPDATE_MISSES,
        help = "The number of unsuccessful lac updates on piggybacked responses"
    )
    private final Counter lacUpdateMissesCounter;
    @StatsDoc(
        name = CLIENT_CHANNEL_WRITE_WAIT,
        help = " The latency distribution of waiting time on channel being writable"
    )
    private final OpStatsLogger clientChannelWriteWaitStats;
    @StatsDoc(
        name = SPECULATIVE_READ_COUNT,
        help = "The number of speculative read requests"
    )
    private final Counter speculativeReadCounter;

    @StatsDoc(
        name = WRITE_DELAYED_DUE_TO_NOT_ENOUGH_FAULT_DOMAINS_LATENCY,
        help = "The delay in write completion because min number of fault domains was not reached"
    )
    private final OpStatsLogger writeDelayedDueToNotEnoughFaultDomainsLatency;

    @StatsDoc(
        name = WRITE_DELAYED_DUE_TO_NOT_ENOUGH_FAULT_DOMAINS,
        help = "The number of times write completion was delayed because min number of fault domains was not reached"
    )
    private final Counter writeDelayedDueToNotEnoughFaultDomains;

    @StatsDoc(
        name = WRITE_TIMED_OUT_DUE_TO_NOT_ENOUGH_FAULT_DOMAINS,
        help = "The number of times write completion timed out because min number of fault domains was not reached"
    )
    private final Counter writeTimedOutDueToNotEnoughFaultDomains;


    public BookKeeperClientStatsImpl(StatsLogger stats) {
        this.stats = stats;
        this.createOpLogger = stats.getOpStatsLogger(CREATE_OP);
        this.deleteOpLogger = stats.getOpStatsLogger(DELETE_OP);
        this.openOpLogger = stats.getOpStatsLogger(OPEN_OP);
        this.recoverOpLogger = stats.getOpStatsLogger(RECOVER_OP);
        this.readOpLogger = stats.getOpStatsLogger(READ_OP);
        this.readOpDmCounter = stats.getCounter(READ_OP_DM);
        this.readLacAndEntryOpLogger = stats.getOpStatsLogger(READ_LAST_CONFIRMED_AND_ENTRY);
        this.readLacAndEntryRespLogger = stats.getOpStatsLogger(READ_LAST_CONFIRMED_AND_ENTRY_RESPONSE);
        this.addOpLogger = stats.getOpStatsLogger(ADD_OP);
        this.forceOpLogger = stats.getOpStatsLogger(FORCE_OP);
        this.addOpUrCounter = stats.getCounter(ADD_OP_UR);
        this.writeLacOpLogger = stats.getOpStatsLogger(WRITE_LAC_OP);
        this.readLacOpLogger = stats.getOpStatsLogger(READ_LAC_OP);
        this.recoverAddEntriesStats = stats.getOpStatsLogger(LEDGER_RECOVER_ADD_ENTRIES);
        this.recoverReadEntriesStats = stats.getOpStatsLogger(LEDGER_RECOVER_READ_ENTRIES);

        this.ensembleChangeCounter = stats.getCounter(ENSEMBLE_CHANGES);
        this.lacUpdateHitsCounter = stats.getCounter(LAC_UPDATE_HITS);
        this.lacUpdateMissesCounter = stats.getCounter(LAC_UPDATE_MISSES);
        this.clientChannelWriteWaitStats = stats.getOpStatsLogger(CLIENT_CHANNEL_WRITE_WAIT);

        speculativeReadCounter = stats.getCounter(SPECULATIVE_READ_COUNT);

        this.writeDelayedDueToNotEnoughFaultDomainsLatency =
                stats.getOpStatsLogger(WRITE_DELAYED_DUE_TO_NOT_ENOUGH_FAULT_DOMAINS_LATENCY);
        this.writeDelayedDueToNotEnoughFaultDomains = stats.getCounter(WRITE_DELAYED_DUE_TO_NOT_ENOUGH_FAULT_DOMAINS);
        this.writeTimedOutDueToNotEnoughFaultDomains =
                stats.getCounter(WRITE_TIMED_OUT_DUE_TO_NOT_ENOUGH_FAULT_DOMAINS);
    }

    @Override
    public OpStatsLogger getCreateOpLogger() {
        return createOpLogger;
    }
    @Override
    public OpStatsLogger getOpenOpLogger() {
        return openOpLogger;
    }
    @Override
    public OpStatsLogger getDeleteOpLogger() {
        return deleteOpLogger;
    }
    @Override
    public OpStatsLogger getRecoverOpLogger() {
        return recoverOpLogger;
    }
    @Override
    public OpStatsLogger getReadOpLogger() {
        return readOpLogger;
    }
    @Override
    public OpStatsLogger getReadLacAndEntryOpLogger() {
        return readLacAndEntryOpLogger;
    }
    @Override
    public OpStatsLogger getReadLacAndEntryRespLogger() {
        return readLacAndEntryRespLogger;
    }
    @Override
    public OpStatsLogger getAddOpLogger() {
        return addOpLogger;
    }
    @Override
    public OpStatsLogger getForceOpLogger() {
        return forceOpLogger;
    }
    @Override
    public OpStatsLogger getWriteLacOpLogger() {
        return writeLacOpLogger;
    }
    @Override
    public OpStatsLogger getReadLacOpLogger() {
        return readLacOpLogger;
    }
    @Override
    public OpStatsLogger getRecoverAddCountLogger() {
        return recoverAddEntriesStats;
    }
    @Override
    public OpStatsLogger getRecoverReadCountLogger() {
        return recoverReadEntriesStats;
    }
    @Override
    public Counter getReadOpDmCounter() {
        return readOpDmCounter;
    }
    @Override
    public Counter getAddOpUrCounter() {
        return addOpUrCounter;
    }
    @Override
    public Counter getSpeculativeReadCounter() {
        return speculativeReadCounter;
    }
    @Override
    public Counter getEnsembleChangeCounter() {
        return ensembleChangeCounter;
    }
    @Override
    public Counter getLacUpdateHitsCounter() {
        return lacUpdateHitsCounter;
    }
    @Override
    public Counter getLacUpdateMissesCounter() {
        return lacUpdateMissesCounter;
    }
    @Override
    public OpStatsLogger getClientChannelWriteWaitLogger() {
        return clientChannelWriteWaitStats;
    }
    @Override
    public Counter getEnsembleBookieDistributionCounter(String bookie) {
        return stats.scopeLabel(BOOKIE_LABEL, bookie).getCounter(LEDGER_ENSEMBLE_BOOKIE_DISTRIBUTION);
    }
    @Override
    public OpStatsLogger getWriteDelayedDueToNotEnoughFaultDomainsLatency() {
        return writeDelayedDueToNotEnoughFaultDomainsLatency;
    }
    @Override
    public Counter getWriteDelayedDueToNotEnoughFaultDomains() {
        return writeDelayedDueToNotEnoughFaultDomains;
    }
    @Override
    public Counter getWriteTimedOutDueToNotEnoughFaultDomains() {
        return writeTimedOutDueToNotEnoughFaultDomains;
    }
    @Override
    public void registerPendingAddsGauge(Gauge<Integer> gauge) {
        stats.registerGauge(PENDING_ADDS, gauge);
    }
}
