/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed 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 com.android.camera.burst;

import androidx.collection.LongSparseArray;

import com.android.camera.async.SafeCloseable;
import com.android.camera.one.v2.camera2proxy.ImageProxy;

import java.util.ArrayList;
import java.util.List;

/**
 * A RingBuffer that is used during burst capture. It takes a
 * {@link EvictionHandler} instance and uses it to evict frames when the ring
 * buffer runs out of capacity.
 */
class RingBuffer<T extends ImageProxy> implements SafeCloseable {
    private final int mMaxCapacity;
    private final EvictionHandler mEvictionHandler;
    private final LongSparseArray<T> mImages = new LongSparseArray<>();

    /**
     * Create a new ring buffer instance.
     *
     * @param maxCapacity the maximum number of images in the ring buffer.
     * @param evictionHandler
     */
    public RingBuffer(int maxCapacity, EvictionHandler evictionHandler) {
        mMaxCapacity = maxCapacity;
        mEvictionHandler = evictionHandler;
    }

    /**
     * Insert an image in the ring buffer, evicting any frames if necessary.
     *
     * @param image the image to be inserted.
     */
    public synchronized void insertImage(T image) {
        long timestamp = image.getTimestamp();
        if (mImages.get(timestamp) != null) {
            image.close();
            return;
        }
        // Add image to ring buffer so it can be closed in case eviction
        // handler throws.
        addImage(image);
        mEvictionHandler.onFrameInserted(timestamp);
        if (mImages.size() > mMaxCapacity) {
            long selectFrameToDrop = mEvictionHandler.selectFrameToDrop();
            removeAndCloseImage(selectFrameToDrop);
            mEvictionHandler.onFrameDropped(selectFrameToDrop);
        }
    }

    /**
     * Returns all images present in the ring buffer.
     */
    public synchronized List<T> getAndRemoveAllImages() {
        List<T> allImages = new ArrayList<>(mImages.size());
        for (int i = 0; i < mImages.size(); i++) {
            allImages.add(mImages.valueAt(i));
        }
        mImages.clear();
        return allImages;
    }

    /**
     * Closes the ring buffer and any images in the ring buffer.
     */
    @Override
    public synchronized void close() {
        for (int i = 0; i < mImages.size(); i++) {
            mImages.valueAt(i).close();
        }
        mImages.clear();
    }

    private synchronized void removeAndCloseImage(long timestampNs) {
        mImages.get(timestampNs).close();
        mImages.remove(timestampNs);
    }

    private synchronized void addImage(T image) {
        mImages.put(image.getTimestamp(), image);
    }
}
