/*
 * Copyright (C) 2014 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 android.hardware.camera2.legacy;

import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.utils.SubmitInfo;
import android.util.Log;

import java.util.ArrayDeque;
import java.util.List;

/**
 * A queue of bursts of requests.
 *
 * <p>This queue maintains the count of frames that have been produced, and is thread safe.</p>
 */
public class RequestQueue {
    private static final String TAG = "RequestQueue";

    public static final long INVALID_FRAME = -1;

    private BurstHolder mRepeatingRequest = null;
    private final ArrayDeque<BurstHolder> mRequestQueue = new ArrayDeque<BurstHolder>();

    private long mCurrentFrameNumber = 0;
    private long mCurrentRepeatingFrameNumber = INVALID_FRAME;
    private int mCurrentRequestId = 0;
    private final List<Long> mJpegSurfaceIds;

    public final class RequestQueueEntry {
        private final BurstHolder mBurstHolder;
        private final Long mFrameNumber;
        private final boolean mQueueEmpty;

        public BurstHolder getBurstHolder() {
            return mBurstHolder;
        }
        public Long getFrameNumber() {
            return mFrameNumber;
        }
        public boolean isQueueEmpty() {
            return mQueueEmpty;
        }

        public RequestQueueEntry(BurstHolder burstHolder, Long frameNumber, boolean queueEmpty) {
            mBurstHolder = burstHolder;
            mFrameNumber = frameNumber;
            mQueueEmpty = queueEmpty;
        }
    }

    public RequestQueue(List<Long> jpegSurfaceIds) {
        mJpegSurfaceIds = jpegSurfaceIds;
    }

    /**
     * Return and remove the next burst on the queue.
     *
     * <p>If a repeating burst is returned, it will not be removed.</p>
     *
     * @return an entry containing the next burst, the current frame number, and flag about whether
     * request queue becomes empty. Null if no burst exists.
     */
    public synchronized RequestQueueEntry getNext() {
        BurstHolder next = mRequestQueue.poll();
        boolean queueEmptied = (next != null && mRequestQueue.size() == 0);
        if (next == null && mRepeatingRequest != null) {
            next = mRepeatingRequest;
            mCurrentRepeatingFrameNumber = mCurrentFrameNumber +
                    next.getNumberOfRequests();
        }

        if (next == null) {
            return null;
        }

        RequestQueueEntry ret =  new RequestQueueEntry(next, mCurrentFrameNumber, queueEmptied);
        mCurrentFrameNumber += next.getNumberOfRequests();
        return ret;
    }

    /**
     * Cancel a repeating request.
     *
     * @param requestId the id of the repeating request to cancel.
     * @return the last frame to be returned from the HAL for the given repeating request, or
     *          {@code INVALID_FRAME} if none exists.
     */
    public synchronized long stopRepeating(int requestId) {
        long ret = INVALID_FRAME;
        if (mRepeatingRequest != null && mRepeatingRequest.getRequestId() == requestId) {
            mRepeatingRequest = null;
            ret = (mCurrentRepeatingFrameNumber == INVALID_FRAME) ? INVALID_FRAME :
                    mCurrentRepeatingFrameNumber - 1;
            mCurrentRepeatingFrameNumber = INVALID_FRAME;
            Log.i(TAG, "Repeating capture request cancelled.");
        } else {
            Log.e(TAG, "cancel failed: no repeating request exists for request id: " + requestId);
        }
        return ret;
    }

    /**
     * Cancel a repeating request.
     *
     * @return the last frame to be returned from the HAL for the given repeating request, or
     *          {@code INVALID_FRAME} if none exists.
     */
    public synchronized long stopRepeating() {
        if (mRepeatingRequest == null) {
            Log.e(TAG, "cancel failed: no repeating request exists.");
            return INVALID_FRAME;
        }
        return stopRepeating(mRepeatingRequest.getRequestId());
    }

    /**
     * Add a the given burst to the queue.
     *
     * <p>If the burst is repeating, replace the current repeating burst.</p>
     *
     * @param requests the burst of requests to add to the queue.
     * @param repeating true if the burst is repeating.
     * @return the submission info, including the new request id, and the last frame number, which
     *   contains either the frame number of the last frame that will be returned for this request,
     *   or the frame number of the last frame that will be returned for the current repeating
     *   request if this burst is set to be repeating.
     */
    public synchronized SubmitInfo submit(CaptureRequest[] requests, boolean repeating) {
        int requestId = mCurrentRequestId++;
        BurstHolder burst = new BurstHolder(requestId, repeating, requests, mJpegSurfaceIds);
        long lastFrame = INVALID_FRAME;
        if (burst.isRepeating()) {
            Log.i(TAG, "Repeating capture request set.");
            if (mRepeatingRequest != null) {
                lastFrame = (mCurrentRepeatingFrameNumber == INVALID_FRAME) ? INVALID_FRAME :
                        mCurrentRepeatingFrameNumber - 1;
            }
            mCurrentRepeatingFrameNumber = INVALID_FRAME;
            mRepeatingRequest = burst;
        } else {
            mRequestQueue.offer(burst);
            lastFrame = calculateLastFrame(burst.getRequestId());
        }
        SubmitInfo info = new SubmitInfo(requestId, lastFrame);
        return info;
    }

    private long calculateLastFrame(int requestId) {
        long total = mCurrentFrameNumber;
        for (BurstHolder b : mRequestQueue) {
            total += b.getNumberOfRequests();
            if (b.getRequestId() == requestId) {
                return total - 1;
            }
        }
        throw new IllegalStateException(
                "At least one request must be in the queue to calculate frame number");
    }

}
