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

import static org.apache.jackrabbit.guava.common.base.Preconditions.checkNotNull;

import org.apache.jackrabbit.guava.common.base.Predicate;
import org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.GCType;
import org.apache.jackrabbit.oak.segment.file.tar.GCGeneration;
import org.jetbrains.annotations.NotNull;

/**
 * Helper class exposing static factories for reclaimers. A reclaimer
 * is a predicate used during the cleanup phase of garbage collection
 * to decide whether a segment of a given generation is reclaimable.
 */
class Reclaimers {

    private Reclaimers() {
        // Prevent instantiation.
    }

    /**
     * Create a reclaimer for segments of old generations. Whether a segment is considered old and
     * thus reclaimable depends on the type of the most recent GC operation and the number of
     * retained generations.
     * <p>
     * In the case of {@link GCType#FULL FULL} a segment is reclaimable if its
     * {@link GCGeneration#getFullGeneration() full generation} is at least {@code retainedGenerations}
     * in the past wrt. {@code referenceGeneration} <em>or</em> if its
     * {@link GCGeneration#getGeneration() generation} is at least {@code retainedGenerations} in the
     * past wrt. {@code referenceGeneration} and it not a {@link GCGeneration#isCompacted() compacted segment}.
     * <p>
     * In the case of {@link GCType#TAIL TAIL} a segment is reclaimable if its
     * {@link GCGeneration#getGeneration() generation} is at least {@code retainedGenerations} in the
     * past wrt. {@code referenceGeneration} <em>and</em> the segment is not in the same tail as
     * segments of the {@code referenceGeneration}. A segment is in the same tail as another segment
     * if it is a {@link GCGeneration#isCompacted() compacted segment} <em>and</em> both segments have
     * the same {@code full generation}.
     *
     * @param lastGCType  type of the most recent GC operation. {@link GCType#FULL} if unknown.
     * @param referenceGeneration  generation used as reference for determining the age of other segments.
     * @param retainedGenerations  number of generations to retain.
     */
    static Predicate<GCGeneration> newOldReclaimer(
            @NotNull GCType lastGCType,
            @NotNull final GCGeneration referenceGeneration,
            int retainedGenerations) {

        switch (checkNotNull(lastGCType)) {
            case FULL:
                return newOldFullReclaimer(referenceGeneration, retainedGenerations);
            case TAIL:
                return newOldTailReclaimer(referenceGeneration, retainedGenerations);
            default:
                throw new IllegalArgumentException("Invalid gc type: " + lastGCType);
        }
    }

    private static Predicate<GCGeneration> newOldFullReclaimer(
            @NotNull final GCGeneration referenceGeneration,
            int retainedGenerations) {
        return new Predicate<GCGeneration>() {

            @Override
            public boolean apply(GCGeneration generation) {
                return isOldFull(generation) || (isOld(generation) && !generation.isCompacted());
            }

            private boolean isOld(GCGeneration generation) {
                return referenceGeneration.compareWith(generation) >= retainedGenerations;
            }

            private boolean isOldFull(GCGeneration generation) {
                return referenceGeneration.compareFullGenerationWith(generation) >= retainedGenerations;
            }

            @Override
            public String toString() {
                return String.format(
                        "(full generation older than %d.%d, with %d retained generations)",
                        referenceGeneration.getGeneration(),
                        referenceGeneration.getFullGeneration(),
                        retainedGenerations
                );
            }
        };
    }

    private static Predicate<GCGeneration> newOldTailReclaimer(
            @NotNull final GCGeneration referenceGeneration,
            int retainedGenerations) {
        return new Predicate<GCGeneration>() {

            @Override
            public boolean apply(GCGeneration generation) {
                return isOld(generation) && !sameCompactedTail(generation);
            }

            private boolean isOld(GCGeneration generation) {
                return referenceGeneration.compareWith(generation) >= retainedGenerations;
            }

            private boolean sameCompactedTail(GCGeneration generation) {
                return generation.isCompacted()
                        && generation.getFullGeneration() == referenceGeneration.getFullGeneration();
            }

            @Override
            public String toString() {
                return String.format(
                        "(generation older than %d.%d, with %d retained generations and not in the same compacted tail)",
                        referenceGeneration.getGeneration(),
                        referenceGeneration.getFullGeneration(),
                        retainedGenerations
                );
            }

        };
    }

    /**
     * Create an exact reclaimer. An exact reclaimer reclaims only segment of on single generation.
     * @param referenceGeneration  the generation to collect.
     * @return  an new instance of an exact reclaimer for segments with their generation
     *          matching {@code referenceGeneration}.
     */
    static Predicate<GCGeneration> newExactReclaimer(@NotNull final GCGeneration referenceGeneration) {
        return new Predicate<GCGeneration>() {
            @Override
            public boolean apply(GCGeneration generation) {
                return generation.equals(referenceGeneration);
            }
            @Override
            public String toString() {
                return "(generation==" + referenceGeneration + ")";
            }
        };
    }

}
