/*
 * Copyright (C) 2015-2021 Intel Corporation.
 *
 * 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.
 */

#pragma once

#include <linux/videodev2.h>

#include <memory>
#include <queue>
#include <vector>

#ifdef CAL_BUILD
#include <cros-camera/v4l2_device.h>
#else
#include <v4l2_device.h>
#endif

#include "api/Parameters.h"
#include "iutils/Utils.h"

namespace icamera {

typedef struct v4l2_buffer v4l2_buffer_t;

/* CameraBuffer is the core buffers for HAL. The buffer usage is described by the
 * BufferUsage. CameraBuffer are constructed based on usage */
enum BufferUsage {
    BUFFER_USAGE_GENERAL = 0,
    BUFFER_USAGE_PSYS_STATS,
    BUFFER_USAGE_PSYS_INPUT,
    BUFFER_USAGE_MIPI_CAPTURE,
    BUFFER_USAGE_METADATA,
    BUFFER_USAGE_PSYS_INTERNAL,
};

class CameraBuffer {
 public:
    // assist function to create frame buffers
    static std::shared_ptr<CameraBuffer> create(int cameraId, int usage, int memory,
                                                unsigned int size, int index, int srcFmt = -1,
                                                int srcWidth = -1, int srcHeight = -1);

 public:
    CameraBuffer(int cameraId, int usage, int memory, uint32_t size, int index, int format = -1);
    virtual ~CameraBuffer();

 public:
    // user buffer information
    int getWidth() const { return mU->s.width; }
    int getHeight() const { return mU->s.height; }
    int getStride() const { return mU->s.stride; }
    int getFormat() const { return mU->s.format; }
    int getStreamType() const { return mU->s.streamType; }
    int getStreamUsage() const { return mU->s.usage; }
    int getStreamId() const { return mU->s.id; }
    int getFlags() const { return mU->flags; }

    // v4l2 buffer information
    uint32_t getIndex(void) const { return mV.Index(); }

    uint32_t getSequence(void) const { return mV.Sequence(); }
    void setSequence(uint32_t sequence) { mV.SetSequence(sequence); }

    uint32_t getField() const { return mV.Field(); }
    void setField(uint32_t field) { mV.SetField(field); }

    struct timeval getTimestamp(void) const {
        return mV.Timestamp();
    }
    void setTimestamp(struct timeval timestamp) { mV.SetTimestamp(timestamp); }

    int getFd(int planeIndex = 0);

    uint32_t getMemory(void) const { return mV.Memory(); }

    int numPlanes() { return mNumPlanes; }

    // For debug only v4l2 buffer information
    int getCsi2Port(void) const { return (mV.RequestFd() >> 4) & 0xf; }
    int getVirtualChannel(void) const { return mV.RequestFd() & 0xf; }

    /* u buffer is used to attach user private structure pointer
     * in CameraBuffer.
     *
     * Now, one of this usage is linking camera_buffer_t to CameraBuffer
     * together, so that we can get each pointer by other.
     * Notes: Please don't abuse this. It is only used in CameraDevice for user buffer
     */
    camera_buffer_t* getUserBuffer() { return mU; }
    // update the user  buffer with latest v4l2 buffer info from driver
    void updateUserBuffer(void);
    // Update the v4l2 flags according to user buffer flag
    void UpdateFlags(void);
    void updateFlags(void);

    // Check if the specific flag in "mU->flags" is set or not
    bool isFlagsSet(int flag);
    // The ubuffer is from application
    void setUserBufferInfo(camera_buffer_t* ubuffer);
    void setUserBufferInfo(int format, int width, int height);
    void setUserBufferInfo(int format, int width, int height, void* usrPtr);

    uint32_t getBufferSize(int planeIndex = 0) { return mV.Length(planeIndex); }
    void setBufferSize(unsigned int size, int planeIndex = 0) { mV.SetLength(size, planeIndex); }

    unsigned int getBytesused(int planeIndex = 0) { return mV.BytesUsed(planeIndex); }
    void setBytesused(unsigned int bytes, int planeIndex = 0) {
        mV.SetBytesUsed(bytes, planeIndex);
    }

    void* getBufferAddr(int planeIndex = 0) { return getAddr(planeIndex); }
    void setBufferAddr(void* addr, int planeIndex = 0) { return setAddr(addr, planeIndex); }

    void updateV4l2Buffer(const v4l2_buffer_t& v4l2buf);

    V4L2Buffer& getV4L2Buffer() { return mV; }

    int getUsage() const { return mBufferUsage; }

    void setSettingSequence(int64_t sequence) { mSettingSequence = sequence; }
    int64_t getSettingSequence() const { return mSettingSequence; }

    // Buffers are allocated the buffers by Camera
    int allocateMemory(V4L2VideoNode* vDevice = nullptr);

 public:
    static void* mapDmaBufferAddr(int fd, unsigned int bufferSize);
    static void unmapDmaBufferAddr(void* addr, unsigned int bufferSize);

 private:
    CameraBuffer(const CameraBuffer&);
    CameraBuffer& operator=(const CameraBuffer&);

    void freeMemory();
    int exportMmapDmabuf(V4L2VideoNode* vDevice);

    int allocateMmap(V4L2VideoNode* dev);
    int allocateUserPtr();
    void freeUserPtr();
    void freeMmap();
    void* getAddr(int plane = 0);
    void setAddr(void* userAddr, int plane = 0);
    void initBuffer(int memType, v4l2_buf_type bufType, uint32_t size, int idx, int num_plane);

    void setFd(int val, int plane);

 protected:
    V4L2Buffer mV;
    int mNumPlanes;

 private:
    // To tag whether the memory is allocated by CameraBuffer class. We need to free them
    bool mAllocatedMemory;

    int mBufferflag;
    camera_buffer_t* mU;
    int mBufferUsage;
    int64_t mSettingSequence;

    void* mMmapAddrs[VIDEO_MAX_PLANES];
    int mDmaFd[VIDEO_MAX_PLANES];
};

typedef std::vector<std::shared_ptr<CameraBuffer> > CameraBufVector;
typedef std::queue<std::shared_ptr<CameraBuffer> > CameraBufQ;

}  // namespace icamera
