/*
 * 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 <map>
#include <vector>

#include "CameraBuffer.h"
#include "CameraEvent.h"
#include "iutils/Errors.h"
#include "iutils/Thread.h"

/**
 * These are the abstract Classes for buffer communication between different class of HAL
 */
namespace icamera {

class BufferProducer;

/**
 * BufferConsumer listens on the onFrameAvailable event from the producer by
 * calling setBufferProducer
 */
class BufferConsumer {
 public:
    virtual ~BufferConsumer() {}
    virtual int onFrameAvailable(Port port, const std::shared_ptr<CameraBuffer>& camBuffer) = 0;
    virtual void setBufferProducer(BufferProducer* producer) = 0;
};

/**
 * BufferProcuder get the buffers from consumer by "qbuf".
 * Notfiy the consumer by calling the onFramAvaible interface of consumer.
 * The consumer must be registered by "addFrameAvailableListener" before getting
 * any buffer done notification.
 */
class BufferProducer : public EventSource {
 public:
    explicit BufferProducer(int memType = V4L2_MEMORY_USERPTR);
    virtual ~BufferProducer() {}
    virtual int qbuf(Port port, const std::shared_ptr<CameraBuffer>& camBuffer) = 0;
    virtual int allocateMemory(Port port, const std::shared_ptr<CameraBuffer>& camBuffer) = 0;
    virtual void addFrameAvailableListener(BufferConsumer* listener) = 0;
    virtual void removeFrameAvailableListener(BufferConsumer* listener) = 0;
    int getMemoryType(void) const { return mMemType; }

 private:
    int mMemType;
};

class BufferQueue : public BufferConsumer, public BufferProducer, public EventListener {
 public:
    BufferQueue();
    virtual ~BufferQueue();

    /**
     * \brief the notify when poll one frame buffer
     *
     * Push the CameraBuffer to InputQueue and send a signal if needed
     */
    virtual int onFrameAvailable(Port port, const std::shared_ptr<CameraBuffer>& camBuffer);

    /**
     * \brief Register the BufferProducer
     *
     * Register the BufferProducer: Psys, software, or captureUnit
     */
    virtual void setBufferProducer(BufferProducer* producer);

    /**
     * \brief Queue one buffer to producer
     *
     * Push this buffer to output queue
     */
    virtual int qbuf(Port port, const std::shared_ptr<CameraBuffer>& camBuffer);

    /**
     * \brief allocate memory
     *
     * Not support this function in Psys and SWProcessor
     */
    virtual int allocateMemory(Port port, const std::shared_ptr<CameraBuffer>& camBuffer) {
        return -1;
    }

    /**
     * \brief Add the get frame listener
     */
    virtual void addFrameAvailableListener(BufferConsumer* listener);

    /**
     * \brief Remove the get frame listener
     */
    virtual void removeFrameAvailableListener(BufferConsumer* listener);

    /**
     * \brief Set all frames configuration
     *
     * Must be called before configure which needs use frame configuration.
     */
    virtual void setFrameInfo(const std::map<Port, stream_t>& inputInfo,
                              const std::map<Port, stream_t>& outputInfo);

    /*
     * \brief Get all frames configuration
     */
    virtual void getFrameInfo(std::map<Port, stream_t>& inputInfo,
                              std::map<Port, stream_t>& outputInfo) const;

    /**
     * \brief Register user buffers to processor(PSys)
     */
    virtual int registerUserOutputBufs(Port port, const std::shared_ptr<CameraBuffer>& camBuffer) {
        return OK;
    }

    /**
     * \brief Common Interface
     */
    virtual int start() = 0;
    virtual void stop() = 0;
    virtual int setParameters(const Parameters& param) { return OK; }
    virtual int getParameters(Parameters& param) { return OK; }
    virtual int configure(const std::vector<ConfigMode>& configModes) { return OK; }

 protected:
    virtual int processNewFrame() = 0;

    /**
     * \brief Clear and initialize input and output buffer queues.
     */
    void clearBufferQueues();
    /**
     * \brief Wait for available input and output buffers.
     *
     * Only fetch buffer from the buffer queue, need pop buffer from
     * the queue after the buffer is used, and need to be protected by mBufferQueueLock.
     */
    int waitFreeBuffersInQueue(ConditionLock& lock,
                               std::map<Port, std::shared_ptr<CameraBuffer> >& cInBuffer,
                               std::map<Port, std::shared_ptr<CameraBuffer> >& cOutBuffer,
                               int64_t timeout = 0);
    /**
     * \brief Buffers allocation for producer
     */
    int allocProducerBuffers(int camId, int bufNum);

 protected:
    /**
     * \brief The process new frame buffer thread
     *
     * Use this thread listen to the input queue and output queue.
     * And do process if these two queues are not empty
     */
    class ProcessThread : public Thread {
        BufferQueue* mProcessor;

     public:
        explicit ProcessThread(BufferQueue* p) : mProcessor(p) {}

        virtual bool threadLoop() {
            int ret = mProcessor->processNewFrame();
            return (ret == 0);
        }
    };
    static const nsecs_t kWaitDuration = 10000000000;  // 10000ms

    BufferProducer* mBufferProducer;
    std::vector<BufferConsumer*> mBufferConsumerList;

    std::map<Port, stream_t> mInputFrameInfo;
    std::map<Port, stream_t> mOutputFrameInfo;

    std::map<Port, CameraBufQ> mInputQueue;
    std::map<Port, CameraBufQ> mOutputQueue;

    // For internal buffers allocation for producer
    std::map<Port, CameraBufVector> mInternalBuffers;

    // Guard for BufferQueue public API
    Mutex mBufferQueueLock;
    Condition mFrameAvailableSignal;
    Condition mOutputAvailableSignal;

    // for the thread loop
    ProcessThread* mProcessThread;
    bool mThreadRunning;  // state of the processor. true after start and false after stop

 private:
    int queueInputBuffer(Port port, const std::shared_ptr<CameraBuffer>& camBuffer);
};

}  // namespace icamera
