/*
 * 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.jackrabbit.oak.plugins.document;

import javax.management.openmbean.CompositeData;

import org.apache.jackrabbit.api.stats.TimeSeries;
import org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.VersionGCStats;
import org.apache.jackrabbit.oak.stats.CounterStats;
import org.apache.jackrabbit.oak.stats.MeterStats;
import org.apache.jackrabbit.oak.stats.StatisticsProvider;
import org.apache.jackrabbit.oak.stats.TimerStats;
import org.apache.jackrabbit.stats.TimeSeriesStatsUtil;

import static java.util.concurrent.TimeUnit.MICROSECONDS;
import static org.apache.jackrabbit.oak.stats.StatsOptions.DEFAULT;
import static org.apache.jackrabbit.oak.stats.StatsOptions.METRICS_ONLY;

/**
 * DocumentNodeStore revision garbage collection statistics.
 */
class RevisionGCStats implements RevisionGCStatsCollector, RevisionGCStatsMBean {

    static final String RGC = "RevisionGC";
    static final String READ_DOC = "READ_DOC";
    static final String DELETE_DOC = "DELETE_DOC";
    static final String DELETE_LEAF_DOC = "DELETE_LEAF_DOC";
    static final String DELETE_SPLIT_DOC = "DELETE_SPLIT_DOC";
    static final String DELETE_INT_SPLIT_DOC = "DELETE_INT_SPLIT_DOC";
    static final String RESET_DELETED_FLAG = "RESET_DELETED_FLAG";

    static final String ACTIVE_TIMER = "ACTIVE_TIMER";
    static final String READ_DOC_TIMER = "READ_DOC_TIMER";
    static final String CHECK_DELETED_TIMER = "CHECK_DELETED_TIMER";
    static final String SORT_IDS_TIMER = "SORT_IDS_TIMER";
    static final String RESET_DELETED_FLAG_TIMER = "RESET_DELETED_FLAG_TIMER";
    static final String DELETE_DOC_TIMER = "DELETE_DOC_TIMER";
    static final String DELETE_SPLIT_DOC_TIMER = "DELETE_SPLIT_DOC_TIMER";

    static final String COUNTER = "COUNTER";
    static final String FAILURE_COUNTER = "FAILURE";

    private final StatisticsProvider provider;

    private final MeterStats readDoc;
    private final MeterStats deletedDoc;
    private final MeterStats deletedLeafDoc;
    private final MeterStats deletedSplitDoc;
    private final MeterStats deletedIntSplitDoc;
    private final MeterStats resetDeletedFlag;

    private final TimerStats activeTimer;
    private final TimerStats readDocTimer;
    private final TimerStats checkDeletedTimer;
    private final TimerStats sortIdsTimer;
    private final TimerStats resetDeletedFlagTimer;
    private final TimerStats deletedDocTimer;
    private final TimerStats deletedSplitDocTimer;

    private final CounterStats counter;
    private final CounterStats failureCounter;

    RevisionGCStats(StatisticsProvider provider) {
        this.provider = provider;

        readDoc = meter(provider, READ_DOC);
        deletedDoc = meter(provider, DELETE_DOC);
        deletedLeafDoc = meter(provider, DELETE_LEAF_DOC);
        deletedSplitDoc = meter(provider, DELETE_SPLIT_DOC);
        deletedIntSplitDoc = meter(provider, DELETE_INT_SPLIT_DOC);
        resetDeletedFlag = meter(provider, RESET_DELETED_FLAG);

        activeTimer = timer(provider, ACTIVE_TIMER);
        readDocTimer = timer(provider, READ_DOC_TIMER);
        checkDeletedTimer = timer(provider, CHECK_DELETED_TIMER);
        sortIdsTimer = timer(provider, SORT_IDS_TIMER);
        resetDeletedFlagTimer = timer(provider, RESET_DELETED_FLAG_TIMER);
        deletedDocTimer = timer(provider, DELETE_DOC_TIMER);
        deletedSplitDocTimer = timer(provider, DELETE_SPLIT_DOC_TIMER);

        counter = counter(provider, COUNTER);
        failureCounter = counter(provider, FAILURE_COUNTER);
    }

    //---------------------< RevisionGCStatsCollector >-------------------------

    @Override
    public void documentRead() {
        readDoc.mark();
    }

    @Override
    public void documentsDeleted(long numDocs) {
        deletedDoc.mark(numDocs);

    }

    @Override
    public void leafDocumentsDeleted(long numDocs) {
        deletedDoc.mark(numDocs);
        deletedLeafDoc.mark(numDocs);
    }

    @Override
    public void splitDocumentsDeleted(long numDocs) {
        deletedDoc.mark(numDocs);
        deletedSplitDoc.mark(numDocs);

    }

    @Override
    public void intermediateSplitDocumentsDeleted(long numDocs) {
        deletedIntSplitDoc.mark(numDocs);
    }

    @Override
    public void deletedOnceFlagReset() {
        resetDeletedFlag.mark();
    }

    @Override
    public void started() {
        counter.inc();
    }

    @Override
    public void finished(VersionGCStats stats) {
        activeTimer.update(stats.active.elapsed(MICROSECONDS), MICROSECONDS);
        readDocTimer.update(stats.collectDeletedDocsElapsed, MICROSECONDS);
        checkDeletedTimer.update(stats.checkDeletedDocsElapsed, MICROSECONDS);
        deletedDocTimer.update(stats.deleteDeletedDocsElapsed, MICROSECONDS);
        deletedSplitDocTimer.update(stats.collectAndDeleteSplitDocsElapsed, MICROSECONDS);
        sortIdsTimer.update(stats.sortDocIdsElapsed, MICROSECONDS);
        resetDeletedFlagTimer.update(stats.updateResurrectedDocumentsElapsed, MICROSECONDS);
        if (!stats.success) {
            failureCounter.inc();
        }
    }


    //------------------------< RevisionGCStatsMBean >--------------------------

    @Override
    public long getReadDocCount() {
        return readDoc.getCount();
    }

    @Override
    public long getDeletedDocCount() {
        return deletedDoc.getCount();
    }

    @Override
    public long getDeletedLeafDocCount() {
        return deletedLeafDoc.getCount();
    }

    @Override
    public long getDeletedSplitDocCount() {
        return deletedSplitDoc.getCount();
    }

    @Override
    public long getDeletedIntSplitDocCount() {
        return deletedIntSplitDoc.getCount();
    }

    @Override
    public long getResetDeletedFlagCount() {
        return resetDeletedFlag.getCount();
    }

    @Override
    public CompositeData getReadDocHistory() {
        return getTimeSeriesData(READ_DOC,
                "Documents read by RevisionGC");
    }

    @Override
    public CompositeData getDeletedDocHistory() {
        return getTimeSeriesData(DELETE_DOC,
                "Documents deleted by RevisionGC");
    }

    @Override
    public CompositeData getDeletedLeafDocHistory() {
        return getTimeSeriesData(DELETE_LEAF_DOC,
                "Leaf documents deleted by RevisionGC");
    }

    @Override
    public CompositeData getDeletedSplitDocHistory() {
        return getTimeSeriesData(DELETE_SPLIT_DOC,
                "Split documents deleted by RevisionGC");
    }

    @Override
    public CompositeData getDeletedIntSplitDocHistory() {
        return getTimeSeriesData(DELETE_INT_SPLIT_DOC,
                "Intermediate split documents deleted by RevisionGC");
    }

    @Override
    public CompositeData getResetDeletedFlagHistory() {
        return getTimeSeriesData(RESET_DELETED_FLAG,
                "Deleted once flags reset by RevisionGC");
    }


    //----------------------------< internal >----------------------------------

    private static MeterStats meter(StatisticsProvider provider,
                                    String name) {
        return provider.getMeter(qualifiedName(name), DEFAULT);
    }

    private static TimerStats timer(StatisticsProvider provider,
                                    String name) {
        return provider.getTimer(qualifiedName(name), METRICS_ONLY);
    }

    private static CounterStats counter(StatisticsProvider provider,
                                        String name) {
        return provider.getCounterStats(qualifiedName(name), METRICS_ONLY);
    }

    private static String qualifiedName(String metricName) {
        return RGC + "." + metricName;
    }

    private CompositeData getTimeSeriesData(String name, String desc) {
        return TimeSeriesStatsUtil.asCompositeData(getTimeSeries(name), desc);
    }

    private TimeSeries getTimeSeries(String name) {
        return provider.getStats().getTimeSeries(qualifiedName(name), true);
    }
}
