/*
 * 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.ignite.internal.processors.cache.persistence.wal.aware;

import org.apache.ignite.internal.IgniteInterruptedCheckedException;

/**
 * Manages last archived index, allows to emulate archivation in no-archiver mode. Monitor which is notified each time
 * WAL segment is archived.
 *
 * Class for inner usage.
 */
class SegmentArchivedStorage extends SegmentObservable {
    /** Segment lock storage: Protects WAL work segments from moving. */
    private final SegmentLockStorage segmentLockStorage;

    /** Flag of interrupt waiting on this object. */
    private volatile boolean interrupted;

    /**
     * Last archived file absolute index, 0-based. Write is quarded by {@code this}. Negative value indicates there are
     * no segments archived.
     */
    private volatile long lastAbsArchivedIdx = -1;

    /**
     * @param segmentLockStorage Protects WAL work segments from moving.
     */
    SegmentArchivedStorage(SegmentLockStorage segmentLockStorage) {
        this.segmentLockStorage = segmentLockStorage;
    }

    /**
     * @return Last archived segment absolute index.
     */
    long lastArchivedAbsoluteIndex() {
        return lastAbsArchivedIdx;
    }

    /**
     * @param lastAbsArchivedIdx New value of last archived segment index.
     */
    void setLastArchivedAbsoluteIndex(long lastAbsArchivedIdx) {
        synchronized (this) {
            this.lastAbsArchivedIdx = lastAbsArchivedIdx;

            notifyAll();
        }

        notifyObservers(lastAbsArchivedIdx);
    }

    /**
     * Method will wait archivation of particular WAL segment index.
     *
     * @param awaitIdx absolute index {@link #lastArchivedAbsoluteIndex()} to become true.
     * @throws IgniteInterruptedCheckedException if interrupted.
     */
    synchronized void awaitSegmentArchived(long awaitIdx) throws IgniteInterruptedCheckedException {
        while (lastArchivedAbsoluteIndex() < awaitIdx && !interrupted) {
            try {
                wait(2000);
            }
            catch (InterruptedException e) {
                throw new IgniteInterruptedCheckedException(e);
            }
        }

        checkInterrupted();
    }

    /**
     * Mark segment as moved to archive under lock.
     *
     * @param toArchive Segment which was should be moved to archive.
     * @throws IgniteInterruptedCheckedException if interrupted during waiting.
     */
    synchronized void markAsMovedToArchive(long toArchive) throws IgniteInterruptedCheckedException {
        try {
            while (!segmentLockStorage.minLockIndex(toArchive) && !interrupted)
                wait();
        }
        catch (InterruptedException e) {
            throw new IgniteInterruptedCheckedException(e);
        }

        //Ignore interrupted flag and force set new value. - legacy logic.
        //checkInterrupted();

        setLastArchivedAbsoluteIndex(toArchive);
    }

    /**
     * Interrupt waiting on this object.
     */
    synchronized void interrupt() {
        interrupted = true;

        notifyAll();
    }

    /**
     * Resets interrupted flag.
     */
    void reset() {
        interrupted = false;
    }

    /**
     * Check for interrupt flag was set.
     */
    private void checkInterrupted() throws IgniteInterruptedCheckedException {
        if (interrupted)
            throw new IgniteInterruptedCheckedException("Interrupt waiting of change archived idx");
    }

    /**
     * Callback for waking up waiters of this object when unlocked happened.
     */
    synchronized void onSegmentUnlocked(long segmentId) {
        notifyAll();
    }
}
