/*
 * Copyright (C) 2022 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 HARDWARE_INTERFACES_CAMERA_DEVICE_DEFAULT_EXTERNALCAMERADEVICESESSION_H_
#define HARDWARE_INTERFACES_CAMERA_DEVICE_DEFAULT_EXTERNALCAMERADEVICESESSION_H_

#include <ExternalCameraUtils.h>
#include <SimpleThread.h>
#include <aidl/android/hardware/camera/common/Status.h>
#include <aidl/android/hardware/camera/device/BnCameraDeviceSession.h>
#include <aidl/android/hardware/camera/device/BufferRequest.h>
#include <aidl/android/hardware/camera/device/Stream.h>
#include <android-base/unique_fd.h>
#include <android/hardware/graphics/mapper/2.0/IMapper.h>
#include <android/hardware/graphics/mapper/3.0/IMapper.h>
#include <android/hardware/graphics/mapper/4.0/IMapper.h>
#include <fmq/AidlMessageQueue.h>
#include <utils/Thread.h>
#include <deque>
#include <list>

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

using ::aidl::android::hardware::camera::common::Status;
using ::aidl::android::hardware::camera::device::BnCameraDeviceSession;
using ::aidl::android::hardware::camera::device::BufferCache;
using ::aidl::android::hardware::camera::device::BufferRequest;
using ::aidl::android::hardware::camera::device::CameraMetadata;
using ::aidl::android::hardware::camera::device::CameraOfflineSessionInfo;
using ::aidl::android::hardware::camera::device::CaptureRequest;
using ::aidl::android::hardware::camera::device::HalStream;
using ::aidl::android::hardware::camera::device::ICameraDeviceCallback;
using ::aidl::android::hardware::camera::device::ICameraOfflineSession;
using ::aidl::android::hardware::camera::device::RequestTemplate;
using ::aidl::android::hardware::camera::device::Stream;
using ::aidl::android::hardware::camera::device::StreamConfiguration;
using ::aidl::android::hardware::common::fmq::MQDescriptor;
using ::aidl::android::hardware::common::fmq::SynchronizedReadWrite;
using ::android::AidlMessageQueue;
using ::android::base::unique_fd;
using ::android::hardware::camera::common::helper::SimpleThread;
using ::android::hardware::camera::external::common::ExternalCameraConfig;
using ::android::hardware::camera::external::common::SizeHasher;
using ::android::hardware::graphics::mapper::V2_0::YCbCrLayout;
using ::ndk::ScopedAStatus;

class ExternalCameraDeviceSession : public BnCameraDeviceSession, public OutputThreadInterface {
  public:
    ExternalCameraDeviceSession(const std::shared_ptr<ICameraDeviceCallback>&,
                                const ExternalCameraConfig& cfg,
                                const std::vector<SupportedV4L2Format>& sortedFormats,
                                const CroppingType& croppingType,
                                const common::V1_0::helper::CameraMetadata& chars,
                                const std::string& cameraId, unique_fd v4l2Fd);
    ~ExternalCameraDeviceSession() override;

    // Caller must use this method to check if CameraDeviceSession ctor failed
    bool isInitFailed();
    bool isClosed();

    ScopedAStatus close() override;

    ScopedAStatus configureStreams(const StreamConfiguration& in_requestedConfiguration,
                                   std::vector<HalStream>* _aidl_return) override;
    ScopedAStatus constructDefaultRequestSettings(RequestTemplate in_type,
                                                  CameraMetadata* _aidl_return) override;
    ScopedAStatus flush() override;
    ScopedAStatus getCaptureRequestMetadataQueue(
            MQDescriptor<int8_t, SynchronizedReadWrite>* _aidl_return) override;
    ScopedAStatus getCaptureResultMetadataQueue(
            MQDescriptor<int8_t, SynchronizedReadWrite>* _aidl_return) override;
    ScopedAStatus isReconfigurationRequired(const CameraMetadata& in_oldSessionParams,
                                            const CameraMetadata& in_newSessionParams,
                                            bool* _aidl_return) override;
    ScopedAStatus processCaptureRequest(const std::vector<CaptureRequest>& in_requests,
                                        const std::vector<BufferCache>& in_cachesToRemove,
                                        int32_t* _aidl_return) override;
    ScopedAStatus signalStreamFlush(const std::vector<int32_t>& in_streamIds,
                                    int32_t in_streamConfigCounter) override;
    ScopedAStatus switchToOffline(const std::vector<int32_t>& in_streamsToKeep,
                                  CameraOfflineSessionInfo* out_offlineSessionInfo,
                                  std::shared_ptr<ICameraOfflineSession>* _aidl_return) override;
    ScopedAStatus repeatingRequestEnd(int32_t in_frameNumber,
                                      const std::vector<int32_t>& in_streamIds) override;

    Status importBuffer(int32_t streamId, uint64_t bufId, buffer_handle_t buf,
                        buffer_handle_t** outBufPtr) override;

    void notifyError(int32_t frameNumber, int32_t streamId, ErrorCode ec) override;

    Status processCaptureRequestError(const std::shared_ptr<HalRequest>& ptr,
                                      std::vector<NotifyMsg>* msgs,
                                      std::vector<CaptureResult>* results) override;

    Status processCaptureResult(std::shared_ptr<HalRequest>& ptr) override;
    ssize_t getJpegBufferSize(int32_t width, int32_t height) const override;

    // Called by CameraDevice to dump active device states
    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;

    static Status isStreamCombinationSupported(
            const StreamConfiguration& config,
            const std::vector<SupportedV4L2Format>& supportedFormats,
            const ExternalCameraConfig& devCfg);

    static const int kMaxProcessedStream = 2;
    static const int kMaxStallStream = 1;
    static const uint32_t kMaxBytesPerPixel = 3;

    class BufferRequestThread : public SimpleThread {
      public:
        BufferRequestThread(std::weak_ptr<OutputThreadInterface> parent,
                            std::shared_ptr<ICameraDeviceCallback> callbacks);

        int requestBufferStart(const std::vector<HalStreamBuffer>&);
        int waitForBufferRequestDone(
                /*out*/ std::vector<HalStreamBuffer>*);

        bool threadLoop() override;

      private:
        void waitForNextRequest();

        const std::weak_ptr<OutputThreadInterface> mParent;
        const std::shared_ptr<ICameraDeviceCallback> mCallbacks;

        std::mutex mLock;
        bool mRequestingBuffer = false;

        std::vector<HalStreamBuffer> mBufferReqs;
        std::vector<HalStreamBuffer> mPendingReturnBufferReqs;
        // mHalBufferReqs is not under mLock protection during the HIDL transaction
        std::vector<BufferRequest> mHalBufferReqs;

        // request buffers takes much less time in steady state, but can take much longer
        // when requesting 1st buffer from a stream.
        // TODO: consider a separate timeout for new vs. steady state?
        // TODO: or make sure framework is warming up the pipeline during configure new stream?
        static const int kReqProcTimeoutMs = 66;

        static const int kReqWaitTimeoutMs = 33;
        static const int kReqWaitTimesWarn = 90;   // 33ms * 90 ~= 3 sec
        std::condition_variable mRequestCond;      // signaled when a new buffer request incoming
        std::condition_variable mRequestDoneCond;  // signaled when a request is done
    };

    class OutputThread : public SimpleThread {
      public:
        OutputThread(std::weak_ptr<OutputThreadInterface> parent, CroppingType,
                     const common::V1_0::helper::CameraMetadata&,
                     std::shared_ptr<BufferRequestThread> bufReqThread);
        ~OutputThread();

        Status allocateIntermediateBuffers(const Size& v4lSize, const Size& thumbSize,
                                           const std::vector<Stream>& streams,
                                           uint32_t blobBufferSize);
        Status submitRequest(const std::shared_ptr<HalRequest>&);
        void flush();
        void dump(int fd);
        bool threadLoop() override;

        void setExifMakeModel(const std::string& make, const std::string& model);

        // The remaining request list is returned for offline processing
        std::list<std::shared_ptr<HalRequest>> switchToOffline();

      protected:
        static const int kFlushWaitTimeoutSec = 3;  // 3 sec
        static const int kReqWaitTimeoutMs = 33;    // 33ms
        static const int kReqWaitTimesMax = 90;     // 33ms * 90 ~= 3 sec

        // Methods to request output buffer in parallel
        int requestBufferStart(const std::vector<HalStreamBuffer>&);
        int waitForBufferRequestDone(
                /*out*/ std::vector<HalStreamBuffer>*);

        void waitForNextRequest(std::shared_ptr<HalRequest>* out);
        void signalRequestDone();

        int cropAndScaleLocked(std::shared_ptr<AllocatedFrame>& in, const Size& outSize,
                               YCbCrLayout* out);

        int cropAndScaleThumbLocked(std::shared_ptr<AllocatedFrame>& in, const Size& outSize,
                                    YCbCrLayout* out);

        int createJpegLocked(HalStreamBuffer& halBuf,
                             const common::V1_0::helper::CameraMetadata& settings);

        void clearIntermediateBuffers();

        const std::weak_ptr<OutputThreadInterface> mParent;
        const CroppingType mCroppingType;
        const common::V1_0::helper::CameraMetadata mCameraCharacteristics;

        mutable std::mutex mRequestListLock;       // Protect access to mRequestList,
                                                   // mProcessingRequest and mProcessingFrameNumber
        std::condition_variable mRequestCond;      // signaled when a new request is submitted
        std::condition_variable mRequestDoneCond;  // signaled when a request is done processing
        std::list<std::shared_ptr<HalRequest>> mRequestList;
        bool mProcessingRequest = false;
        uint32_t mProcessingFrameNumber = 0;

        // V4L2 frameIn
        // (MJPG decode)-> mYu12Frame
        // (Scale)-> mScaledYu12Frames
        // (Format convert) -> output gralloc frames
        mutable std::mutex mBufferLock;  // Protect access to intermediate buffers
        std::shared_ptr<AllocatedFrame> mYu12Frame;
        std::shared_ptr<AllocatedFrame> mYu12ThumbFrame;
        std::unordered_map<Size, std::shared_ptr<AllocatedFrame>, SizeHasher> mIntermediateBuffers;
        std::unordered_map<Size, std::shared_ptr<AllocatedFrame>, SizeHasher> mScaledYu12Frames;
        YCbCrLayout mYu12FrameLayout;
        YCbCrLayout mYu12ThumbFrameLayout;
        std::vector<uint8_t> mMuteTestPatternFrame;
        uint32_t mTestPatternData[4] = {0, 0, 0, 0};
        bool mCameraMuted = false;
        uint32_t mBlobBufferSize = 0;  // 0 -> HAL derive buffer size, else: use given size

        std::string mExifMake;
        std::string mExifModel;

        const std::shared_ptr<BufferRequestThread> mBufferRequestThread;
    };

  private:
    bool initialize();
    // To init/close different version of output thread
    void initOutputThread();
    void closeOutputThread();
    void closeBufferRequestThread();

    void closeImpl();
    Status initStatus() const;
    status_t initDefaultRequests();

    status_t fillCaptureResult(common::V1_0::helper::CameraMetadata& md, nsecs_t timestamp);
    int configureV4l2StreamLocked(const SupportedV4L2Format& fmt, double fps = 0.0);
    int v4l2StreamOffLocked();

    int setV4l2FpsLocked(double fps);

    std::unique_ptr<V4L2Frame> dequeueV4l2FrameLocked(
            /*out*/ nsecs_t* shutterTs);  // Called with mLock held

    void enqueueV4l2Frame(const std::shared_ptr<V4L2Frame>&);

    // Check if input Stream is one of supported stream setting on this device
    static bool isSupported(const Stream& stream,
                            const std::vector<SupportedV4L2Format>& supportedFormats,
                            const ExternalCameraConfig& cfg);

    Status importBufferLocked(int32_t streamId, uint64_t bufId, buffer_handle_t buf,
                              /*out*/ buffer_handle_t** outBufPtr);
    static void cleanupInflightFences(std::vector<int>& allFences, size_t numFences);
    void cleanupBuffersLocked(int id);

    void updateBufferCaches(const std::vector<BufferCache>& cachesToRemove);

    Status processOneCaptureRequest(const CaptureRequest& request);
    void notifyShutter(int32_t frameNumber, nsecs_t shutterTs);

    void invokeProcessCaptureResultCallback(std::vector<CaptureResult>& results, bool tryWriteFmq);
    Size getMaxJpegResolution() const;

    Size getMaxThumbResolution() const;

    int waitForV4L2BufferReturnLocked(std::unique_lock<std::mutex>& lk);

    // Main body of switchToOffline. This method does not invoke any callbacks
    // but instead returns the necessary callbacks in output arguments so callers
    // can callback later without holding any locks
    Status switchToOffline(const std::vector<int32_t>& offlineStreams,
                           /*out*/ std::vector<NotifyMsg>* msgs,
                           /*out*/ std::vector<CaptureResult>* results,
                           /*out*/ CameraOfflineSessionInfo* info,
                           /*out*/ std::shared_ptr<ICameraOfflineSession>* session);

    bool supportOfflineLocked(int32_t streamId);

    // Whether a request can be completely dropped when switching to offline
    bool canDropRequest(const std::vector<int32_t>& offlineStreams,
                        std::shared_ptr<HalRequest> halReq);

    void fillOfflineSessionInfo(const std::vector<int32_t>& offlineStreams,
                                std::deque<std::shared_ptr<HalRequest>>& offlineReqs,
                                const std::map<int, CirculatingBuffers>& circulatingBuffers,
                                /*out*/ CameraOfflineSessionInfo* info);

    // Protect (most of) HIDL interface methods from synchronized-entering
    mutable Mutex mInterfaceLock;

    mutable Mutex mLock;  // Protect all private members except otherwise noted
    const std::shared_ptr<ICameraDeviceCallback> mCallback;
    const ExternalCameraConfig& mCfg;
    const common::V1_0::helper::CameraMetadata mCameraCharacteristics;
    const std::vector<SupportedV4L2Format> mSupportedFormats;
    const CroppingType mCroppingType;
    const std::string mCameraId;

    // Not protected by mLock, this is almost a const.
    // Setup in constructor, reset in close() after OutputThread is joined
    unique_fd mV4l2Fd;

    // device is closed either
    //    - closed by user
    //    - init failed
    //    - camera disconnected
    bool mClosed = false;
    bool mInitialized = false;
    bool mInitFail = false;
    bool mFirstRequest = false;
    common::V1_0::helper::CameraMetadata mLatestReqSetting;

    bool mV4l2Streaming = false;
    SupportedV4L2Format mV4l2StreamingFmt;
    double mV4l2StreamingFps = 0.0;
    size_t mV4L2BufferCount = 0;

    static const int kBufferWaitTimeoutSec = 3;  // TODO: handle long exposure (or not allowing)
    std::mutex mV4l2BufferLock;                  // protect the buffer count and condition below
    std::condition_variable mV4L2BufferReturned;
    size_t mNumDequeuedV4l2Buffers = 0;
    uint32_t mMaxV4L2BufferSize = 0;

    // Not protected by mLock (but might be used when mLock is locked)
    std::shared_ptr<OutputThread> mOutputThread;

    // Stream ID -> Stream cache
    std::unordered_map<int, Stream> mStreamMap;

    std::mutex mInflightFramesLock;  // protect mInflightFrames
    std::unordered_set<uint32_t> mInflightFrames;

    // Stream ID -> circulating buffers map
    std::map<int, CirculatingBuffers> mCirculatingBuffers;
    // Protect mCirculatingBuffers, must not lock mLock after acquiring this lock
    mutable Mutex mCbsLock;

    std::mutex mAfTriggerLock;  // protect mAfTrigger
    bool mAfTrigger = false;

    uint32_t mBlobBufferSize = 0;

    static HandleImporter sHandleImporter;

    std::shared_ptr<BufferRequestThread> mBufferRequestThread;

    /* Beginning of members not changed after initialize() */
    using RequestMetadataQueue = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
    std::unique_ptr<RequestMetadataQueue> mRequestMetadataQueue;
    using ResultMetadataQueue = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
    std::shared_ptr<ResultMetadataQueue> mResultMetadataQueue;

    // Protect against invokeProcessCaptureResultCallback()
    Mutex mProcessCaptureResultLock;

    // tracks last seen stream config counter
    int32_t mLastStreamConfigCounter = -1;

    std::unordered_map<RequestTemplate, CameraMetadata> mDefaultRequests;

    const Size mMaxThumbResolution;
    const Size mMaxJpegResolution;

    std::string mExifMake;
    std::string mExifModel;
    /* End of members not changed after initialize() */

    // The max tolerant lag between the dequeued v4l2 buffer and current capture request.
    uint64_t mMaxLagNs;
};

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

#endif  // HARDWARE_INTERFACES_CAMERA_DEVICE_DEFAULT_EXTERNALCAMERADEVICESESSION_H_
