/*
 * Copyright (C) 2016 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.
 */

#ifndef ANDROID_HARDWARE_CAMERA_DEVICE_V3_2_CAMERADEVICE3SESSION_H
#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_2_CAMERADEVICE3SESSION_H

#include <android/hardware/camera/device/3.2/ICameraDevice.h>
#include <android/hardware/camera/device/3.2/ICameraDeviceSession.h>
#include <fmq/MessageQueue.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>
#include <include/convert.h>
#include <deque>
#include <map>
#include <unordered_map>
#include "CameraMetadata.h"
#include "HandleImporter.h"
#include "hardware/camera3.h"
#include "hardware/camera_common.h"
#include "utils/Mutex.h"

namespace android {
namespace hardware {
namespace camera {
namespace device {
namespace V3_2 {
namespace implementation {

using ::android::hardware::camera::device::V3_2::CaptureRequest;
using ::android::hardware::camera::device::V3_2::HalStreamConfiguration;
using ::android::hardware::camera::device::V3_2::StreamConfiguration;
using ::android::hardware::camera::device::V3_2::ICameraDeviceSession;
using ::android::hardware::camera::common::V1_0::Status;
using ::android::hardware::camera::common::V1_0::helper::HandleImporter;
using ::android::hardware::kSynchronizedReadWrite;
using ::android::hardware::MessageQueue;
using ::android::hardware::MQDescriptorSync;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::hardware::hidl_vec;
using ::android::hardware::hidl_string;
using ::android::sp;
using ::android::Mutex;

struct Camera3Stream;

/**
 * Function pointer types with C calling convention to
 * use for HAL callback functions.
 */
extern "C" {
    typedef void (callbacks_process_capture_result_t)(
        const struct camera3_callback_ops *,
        const camera3_capture_result_t *);

    typedef void (callbacks_notify_t)(
        const struct camera3_callback_ops *,
        const camera3_notify_msg_t *);
}

struct CameraDeviceSession : public virtual RefBase, protected camera3_callback_ops  {

    CameraDeviceSession(camera3_device_t*,
                        const camera_metadata_t* deviceInfo,
                        const sp<ICameraDeviceCallback>&);
    virtual ~CameraDeviceSession();
    // Call by CameraDevice to dump active device states
    void dumpState(const native_handle_t* fd);
    // Caller must use this method to check if CameraDeviceSession ctor failed
    bool isInitFailed() { return mInitFail; }
    // Used by CameraDevice to signal external camera disconnected
    void disconnect();
    bool isClosed();

    // Retrieve the HIDL interface, split into its own class to avoid inheritance issues when
    // dealing with minor version revs and simultaneous implementation and interface inheritance
    virtual sp<ICameraDeviceSession> getInterface() {
        return new TrampolineSessionInterface_3_2(this);
    }

protected:

    // Methods from ::android::hardware::camera::device::V3_2::ICameraDeviceSession follow

    Return<void> constructDefaultRequestSettings(
            RequestTemplate type,
            ICameraDeviceSession::constructDefaultRequestSettings_cb _hidl_cb);
    Return<void> configureStreams(
            const StreamConfiguration& requestedConfiguration,
            ICameraDeviceSession::configureStreams_cb _hidl_cb);
    Return<void> getCaptureRequestMetadataQueue(
        ICameraDeviceSession::getCaptureRequestMetadataQueue_cb _hidl_cb);
    Return<void> getCaptureResultMetadataQueue(
        ICameraDeviceSession::getCaptureResultMetadataQueue_cb _hidl_cb);
    Return<void> processCaptureRequest(
            const hidl_vec<CaptureRequest>& requests,
            const hidl_vec<BufferCache>& cachesToRemove,
            ICameraDeviceSession::processCaptureRequest_cb _hidl_cb);
    Return<Status> flush();
    Return<void> close();

    // Helper methods
    Status constructDefaultRequestSettingsRaw(int type, CameraMetadata *outMetadata);

    bool preProcessConfigurationLocked(const StreamConfiguration& requestedConfiguration,
            camera3_stream_configuration_t *stream_list /*out*/,
            hidl_vec<camera3_stream_t*> *streams /*out*/);
    void postProcessConfigurationLocked(const StreamConfiguration& requestedConfiguration);

    void postProcessConfigurationFailureLocked(const StreamConfiguration& requestedConfiguration);

protected:

    // protecting mClosed/mDisconnected/mInitFail
    mutable Mutex mStateLock;
    // device is closed either
    //    - closed by user
    //    - init failed
    //    - camera disconnected
    bool mClosed = false;

    // Set by CameraDevice (when external camera is disconnected)
    bool mDisconnected = false;

    struct AETriggerCancelOverride {
        bool applyAeLock;
        uint8_t aeLock;
        bool applyAePrecaptureTrigger;
        uint8_t aePrecaptureTrigger;
    };

    camera3_device_t* mDevice;
    const uint32_t mDeviceVersion;
    const bool mFreeBufEarly;
    bool mIsAELockAvailable;
    bool mDerivePostRawSensKey;
    uint32_t mNumPartialResults;
    // Stream ID -> Camera3Stream cache
    std::map<int, Camera3Stream> mStreamMap;

    mutable Mutex mInflightLock; // protecting mInflightBuffers and mCirculatingBuffers
    // (streamID, frameNumber) -> inflight buffer cache
    std::map<std::pair<int, uint32_t>, camera3_stream_buffer_t>  mInflightBuffers;

    // (frameNumber, AETriggerOverride) -> inflight request AETriggerOverrides
    std::map<uint32_t, AETriggerCancelOverride> mInflightAETriggerOverrides;
    ::android::hardware::camera::common::V1_0::helper::CameraMetadata mOverridenResult;
    std::map<uint32_t, bool> mInflightRawBoostPresent;
    ::android::hardware::camera::common::V1_0::helper::CameraMetadata mOverridenRequest;

    static const uint64_t BUFFER_ID_NO_BUFFER = 0;
    // buffers currently ciculating between HAL and camera service
    // key: bufferId sent via HIDL interface
    // value: imported buffer_handle_t
    // Buffer will be imported during process_capture_request and will be freed
    // when the its stream is deleted or camera device session is closed
    typedef std::unordered_map<uint64_t, buffer_handle_t> CirculatingBuffers;
    // Stream ID -> circulating buffers map
    std::map<int, CirculatingBuffers> mCirculatingBuffers;

    static HandleImporter sHandleImporter;
    static buffer_handle_t sEmptyBuffer;

    bool mInitFail;
    bool mFirstRequest = false;

    common::V1_0::helper::CameraMetadata mDeviceInfo;

    using RequestMetadataQueue = MessageQueue<uint8_t, kSynchronizedReadWrite>;
    std::unique_ptr<RequestMetadataQueue> mRequestMetadataQueue;
    using ResultMetadataQueue = MessageQueue<uint8_t, kSynchronizedReadWrite>;
    std::shared_ptr<ResultMetadataQueue> mResultMetadataQueue;

    class ResultBatcher {
    public:
        ResultBatcher(const sp<ICameraDeviceCallback>& callback);
        void setNumPartialResults(uint32_t n);
        void setBatchedStreams(const std::vector<int>& streamsToBatch);
        void setResultMetadataQueue(std::shared_ptr<ResultMetadataQueue> q);

        void registerBatch(uint32_t frameNumber, uint32_t batchSize);
        void notify(NotifyMsg& msg);
        void processCaptureResult(CaptureResult& result);

    protected:
        struct InflightBatch {
            // Protect access to entire struct. Acquire this lock before read/write any data or
            // calling any methods. processCaptureResult and notify will compete for this lock
            // HIDL IPCs might be issued while the lock is held
            Mutex mLock;

            bool allDelivered() const;

            uint32_t mFirstFrame;
            uint32_t mLastFrame;
            uint32_t mBatchSize;

            bool mShutterDelivered = false;
            std::vector<NotifyMsg> mShutterMsgs;

            struct BufferBatch {
                BufferBatch(uint32_t batchSize) {
                    mBuffers.reserve(batchSize);
                }
                bool mDelivered = false;
                // This currently assumes every batched request will output to the batched stream
                // and since HAL must always send buffers in order, no frameNumber tracking is
                // needed
                std::vector<StreamBuffer> mBuffers;
            };
            // Stream ID -> VideoBatch
            std::unordered_map<int, BufferBatch> mBatchBufs;

            struct MetadataBatch {
                //                   (frameNumber, metadata)
                std::vector<std::pair<uint32_t, CameraMetadata>> mMds;
            };
            // Partial result IDs that has been delivered to framework
            uint32_t mNumPartialResults;
            uint32_t mPartialResultProgress = 0;
            // partialResult -> MetadataBatch
            std::map<uint32_t, MetadataBatch> mResultMds;

            // Set to true when batch is removed from mInflightBatches
            // processCaptureResult and notify must check this flag after acquiring mLock to make
            // sure this batch isn't removed while waiting for mLock
            bool mRemoved = false;
        };


        // Get the batch index and pointer to InflightBatch (nullptrt if the frame is not batched)
        // Caller must acquire the InflightBatch::mLock before accessing the InflightBatch
        // It's possible that the InflightBatch is removed from mInflightBatches before the
        // InflightBatch::mLock is acquired (most likely caused by an error notification), so
        // caller must check InflightBatch::mRemoved flag after the lock is acquried.
        // This method will hold ResultBatcher::mLock briefly
        std::pair<int, std::shared_ptr<InflightBatch>> getBatch(uint32_t frameNumber);

        static const int NOT_BATCHED = -1;

        // move/push function avoids "hidl_handle& operator=(hidl_handle&)", which clones native
        // handle
        void moveStreamBuffer(StreamBuffer&& src, StreamBuffer& dst);
        void pushStreamBuffer(StreamBuffer&& src, std::vector<StreamBuffer>& dst);

        void sendBatchMetadataLocked(
                std::shared_ptr<InflightBatch> batch, uint32_t lastPartialResultIdx);

        // Check if the first batch in mInflightBatches is ready to be removed, and remove it if so
        // This method will hold ResultBatcher::mLock briefly
        void checkAndRemoveFirstBatch();

        // The following sendXXXX methods must be called while the InflightBatch::mLock is locked
        // HIDL IPC methods will be called during these methods.
        void sendBatchShutterCbsLocked(std::shared_ptr<InflightBatch> batch);
        // send buffers for all batched streams
        void sendBatchBuffersLocked(std::shared_ptr<InflightBatch> batch);
        // send buffers for specified streams
        void sendBatchBuffersLocked(
                std::shared_ptr<InflightBatch> batch, const std::vector<int>& streams);
       // End of sendXXXX methods

        // helper methods
        void freeReleaseFences(hidl_vec<CaptureResult>&);
        void notifySingleMsg(NotifyMsg& msg);
        void processOneCaptureResult(CaptureResult& result);
        void invokeProcessCaptureResultCallback(hidl_vec<CaptureResult> &results, bool tryWriteFmq);

        // Protect access to mInflightBatches, mNumPartialResults and mStreamsToBatch
        // processCaptureRequest, processCaptureResult, notify will compete for this lock
        // Do NOT issue HIDL IPCs while holding this lock (except when HAL reports error)
        mutable Mutex mLock;
        std::deque<std::shared_ptr<InflightBatch>> mInflightBatches;
        uint32_t mNumPartialResults;
        std::vector<int> mStreamsToBatch;
        const sp<ICameraDeviceCallback> mCallback;
        std::shared_ptr<ResultMetadataQueue> mResultMetadataQueue;

        // Protect against invokeProcessCaptureResultCallback()
        Mutex mProcessCaptureResultLock;

    } mResultBatcher;

    std::vector<int> mVideoStreamIds;

    bool initialize();

    static bool shouldFreeBufEarly();

    Status initStatus() const;

    // Validate and import request's input buffer and acquire fence
    virtual Status importRequest(
            const CaptureRequest& request,
            hidl_vec<buffer_handle_t*>& allBufPtrs,
            hidl_vec<int>& allFences);

    Status importRequestImpl(
            const CaptureRequest& request,
            hidl_vec<buffer_handle_t*>& allBufPtrs,
            hidl_vec<int>& allFences,
            // Optional argument for ICameraDeviceSession@3.5 impl
            bool allowEmptyBuf = false);

    Status importBuffer(int32_t streamId,
            uint64_t bufId, buffer_handle_t buf,
            /*out*/buffer_handle_t** outBufPtr,
            bool allowEmptyBuf);

    static void cleanupInflightFences(
            hidl_vec<int>& allFences, size_t numFences);

    void cleanupBuffersLocked(int id);

    void updateBufferCaches(const hidl_vec<BufferCache>& cachesToRemove);

    android_dataspace mapToLegacyDataspace(
            android_dataspace dataSpace) const;

    bool handleAePrecaptureCancelRequestLocked(
            const camera3_capture_request_t &halRequest,
            android::hardware::camera::common::V1_0::helper::CameraMetadata *settings /*out*/,
            AETriggerCancelOverride *override /*out*/);

    void overrideResultForPrecaptureCancelLocked(
            const AETriggerCancelOverride &aeTriggerCancelOverride,
            ::android::hardware::camera::common::V1_0::helper::CameraMetadata *settings /*out*/);

    Status processOneCaptureRequest(const CaptureRequest& request);
    /**
     * Static callback forwarding methods from HAL to instance
     */
    static callbacks_process_capture_result_t sProcessCaptureResult;
    static callbacks_notify_t sNotify;

    // By default camera service uses frameNumber/streamId pair to retrieve the buffer that
    // was sent to HAL. Override this implementation if HAL is using buffers from buffer management
    // APIs to send output buffer.
    virtual uint64_t getCapResultBufferId(const buffer_handle_t& buf, int streamId);

    status_t constructCaptureResult(CaptureResult& result,
                                const camera3_capture_result *hal_result);

    // Static helper method to copy/shrink capture result metadata sent by HAL
    // Temporarily allocated metadata copy will be hold in mds
    static void sShrinkCaptureResult(
            camera3_capture_result* dst, const camera3_capture_result* src,
            std::vector<::android::hardware::camera::common::V1_0::helper::CameraMetadata>* mds,
            std::vector<const camera_metadata_t*>* physCamMdArray,
            bool handlePhysCam);
    static bool sShouldShrink(const camera_metadata_t* md);
    static camera_metadata_t* sCreateCompactCopy(const camera_metadata_t* src);

private:

    struct TrampolineSessionInterface_3_2 : public ICameraDeviceSession {
        TrampolineSessionInterface_3_2(sp<CameraDeviceSession> parent) :
                mParent(parent) {}

        virtual Return<void> constructDefaultRequestSettings(
                V3_2::RequestTemplate type,
                V3_2::ICameraDeviceSession::constructDefaultRequestSettings_cb _hidl_cb) override {
            return mParent->constructDefaultRequestSettings(type, _hidl_cb);
        }

        virtual Return<void> configureStreams(
                const V3_2::StreamConfiguration& requestedConfiguration,
                V3_2::ICameraDeviceSession::configureStreams_cb _hidl_cb) override {
            return mParent->configureStreams(requestedConfiguration, _hidl_cb);
        }

        virtual Return<void> processCaptureRequest(const hidl_vec<V3_2::CaptureRequest>& requests,
                const hidl_vec<V3_2::BufferCache>& cachesToRemove,
                V3_2::ICameraDeviceSession::processCaptureRequest_cb _hidl_cb) override {
            return mParent->processCaptureRequest(requests, cachesToRemove, _hidl_cb);
        }

        virtual Return<void> getCaptureRequestMetadataQueue(
                V3_2::ICameraDeviceSession::getCaptureRequestMetadataQueue_cb _hidl_cb) override  {
            return mParent->getCaptureRequestMetadataQueue(_hidl_cb);
        }

        virtual Return<void> getCaptureResultMetadataQueue(
                V3_2::ICameraDeviceSession::getCaptureResultMetadataQueue_cb _hidl_cb) override  {
            return mParent->getCaptureResultMetadataQueue(_hidl_cb);
        }

        virtual Return<Status> flush() override {
            return mParent->flush();
        }

        virtual Return<void> close() override {
            return mParent->close();
        }

    private:
        sp<CameraDeviceSession> mParent;
    };
};

}  // namespace implementation
}  // namespace V3_2
}  // namespace device
}  // namespace camera
}  // namespace hardware
}  // namespace android

#endif  // ANDROID_HARDWARE_CAMERA_DEVICE_V3_2_CAMERADEVICE3SESSION_H
