/*
 * Copyright (C) 2013 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_PRO_CAMERA_H
#define ANDROID_HARDWARE_PRO_CAMERA_H

#include <utils/Timers.h>
#include <utils/KeyedVector.h>
#include <gui/IGraphicBufferProducer.h>
#include <system/camera.h>
#include <camera/IProCameraCallbacks.h>
#include <camera/IProCameraUser.h>
#include <camera/Camera.h>
#include <camera/CameraMetadata.h>
#include <gui/CpuConsumer.h>

#include <gui/Surface.h>

#include <utils/Condition.h>
#include <utils/Mutex.h>

#include <camera/CameraBase.h>

struct camera_metadata;

namespace android {

// All callbacks on this class are concurrent
// (they come from separate threads)
class ProCameraListener : virtual public RefBase
{
public:
    virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2) = 0;

    // Lock has been acquired. Write operations now available.
    virtual void onLockAcquired() = 0;
    // Lock has been released with exclusiveUnlock.
    virtual void onLockReleased() = 0;
    // Lock has been stolen by another client.
    virtual void onLockStolen() = 0;

    // Lock free.
    virtual void onTriggerNotify(int32_t msgType, int32_t ext1, int32_t ext2)
                                                                            = 0;
    // onFrameAvailable and OnResultReceived can come in with any order,
    // use android.sensor.timestamp and LockedBuffer.timestamp to correlate them

    /**
      * A new metadata buffer has been received.
      * -- Ownership of request passes on to the callee, free with
      *    free_camera_metadata.
      */
    virtual void onResultReceived(int32_t frameId, camera_metadata* result) = 0;

    // TODO: make onFrameAvailable pure virtual

    // A new frame buffer has been received for this stream.
    // -- This callback only fires for createStreamCpu streams
    // -- A buffer may be obtained by calling cpuConsumer->lockNextBuffer
    // -- Use buf.timestamp to correlate with result's android.sensor.timestamp
    // -- The buffer should be accessed with CpuConsumer::lockNextBuffer
    //      and CpuConsumer::unlockBuffer
    virtual void onFrameAvailable(int /*streamId*/,
                                  const sp<CpuConsumer>& /*cpuConsumer*/) {
    }

};

class ProCamera;

template <>
struct CameraTraits<ProCamera>
{
    typedef ProCameraListener     TCamListener;
    typedef IProCameraUser        TCamUser;
    typedef IProCameraCallbacks   TCamCallbacks;
};

class ProCamera :
    public CameraBase<ProCamera>,
    public BnProCameraCallbacks
{
public:
    /**
     * Connect a shared camera. By default access is restricted to read only
     * (Lock free) operations. To be able to submit custom requests a lock needs
     * to be acquired with exclusive[Try]Lock.
     */
    static sp<ProCamera> connect(int cameraId);
    virtual ~ProCamera();

    /**
     * Exclusive Locks:
     * - We may request exclusive access to a camera if no other
     *   clients are using the camera. This works as a traditional
     *   client, writing/reading any camera state.
     * - An application opening the camera (a regular 'Camera') will
     *   always steal away the exclusive lock from a ProCamera,
     *   this will call onLockReleased.
     * - onLockAcquired will be called again once it is possible
     *   to again exclusively lock the camera.
     *
     */

    /**
     * All exclusiveLock/unlock functions are asynchronous. The remote endpoint
     * shall not block while waiting to acquire the lock. Instead the lock
     * notifications will come in asynchronously on the listener.
     */

    /**
      * Attempt to acquire the lock instantly (non-blocking)
      * - If this succeeds, you do not need to wait for onLockAcquired
      *   but the event will still be fired
      *
      * Returns -EBUSY if already locked. 0 on success.
      */
    status_t exclusiveTryLock();
    // always returns 0. wait for onLockAcquired before lock is acquired.
    status_t exclusiveLock();
    // release a lock if we have one, or cancel the lock request.
    status_t exclusiveUnlock();

    // exclusive lock = do whatever we want. no lock = read only.
    bool hasExclusiveLock();

    /**
     * < 0 error, >= 0 the request ID. streaming to have the request repeat
     *    until cancelled.
     * The request queue is flushed when a lock is released or stolen
     *    if not locked will return PERMISSION_DENIED
     */
    int submitRequest(const struct camera_metadata* metadata,
                                                        bool streaming = false);
    // if not locked will return PERMISSION_DENIED, BAD_VALUE if requestId bad
    status_t cancelRequest(int requestId);

    /**
     * Ask for a stream to be enabled.
     * Lock free. Service maintains counter of streams.
     */
    status_t requestStream(int streamId);
// TODO: remove requestStream, its useless.

    /**
      * Delete a stream.
      * Lock free.
      *
      * NOTE: As a side effect this cancels ALL streaming requests.
      *
      * Errors: BAD_VALUE if unknown stream ID.
      *         PERMISSION_DENIED if the stream wasn't yours
      */
    status_t deleteStream(int streamId);

    /**
      * Create a new HW stream, whose sink will be the window.
      * Lock free. Service maintains counter of streams.
      * Errors: -EBUSY if too many streams created
      */
    status_t createStream(int width, int height, int format,
                          const sp<Surface>& surface,
                          /*out*/
                          int* streamId);

    /**
      * Create a new HW stream, whose sink will be the SurfaceTexture.
      * Lock free. Service maintains counter of streams.
      * Errors: -EBUSY if too many streams created
      */
    status_t createStream(int width, int height, int format,
                          const sp<IGraphicBufferProducer>& bufferProducer,
                          /*out*/
                          int* streamId);
    status_t createStreamCpu(int width, int height, int format,
                          int heapCount,
                          /*out*/
                          sp<CpuConsumer>* cpuConsumer,
                          int* streamId);
    status_t createStreamCpu(int width, int height, int format,
                          int heapCount,
                          bool synchronousMode,
                          /*out*/
                          sp<CpuConsumer>* cpuConsumer,
                          int* streamId);

    // Create a request object from a template.
    status_t createDefaultRequest(int templateId,
                                 /*out*/
                                  camera_metadata** request) const;

    // Get static camera metadata
    camera_metadata* getCameraInfo(int cameraId);

    // Blocks until a frame is available (CPU streams only)
    // - Obtain the frame data by calling CpuConsumer::lockNextBuffer
    // - Release the frame data after use with CpuConsumer::unlockBuffer
    // Return value:
    // - >0 - number of frames available to be locked
    // - <0 - error (refer to error codes)
    // Error codes:
    // -ETIMEDOUT if it took too long to get a frame
    int waitForFrameBuffer(int streamId);

    // Blocks until a metadata result is available
    // - Obtain the metadata by calling consumeFrameMetadata()
    // Error codes:
    // -ETIMEDOUT if it took too long to get a frame
    status_t waitForFrameMetadata();

    // Get the latest metadata. This is destructive.
    // - Calling this repeatedly will produce empty metadata objects.
    // - Use waitForFrameMetadata to sync until new data is available.
    CameraMetadata consumeFrameMetadata();

    // Convenience method to drop frame buffers (CPU streams only)
    // Return values:
    //  >=0 - number of frames dropped (up to count)
    //  <0  - error code
    // Error codes:
    //   BAD_VALUE - invalid streamId or count passed
    int dropFrameBuffer(int streamId, int count);

protected:
    ////////////////////////////////////////////////////////
    // IProCameraCallbacks implementation
    ////////////////////////////////////////////////////////
    virtual void        notifyCallback(int32_t msgType,
                                       int32_t ext,
                                       int32_t ext2);

    virtual void        onLockStatusChanged(
                                IProCameraCallbacks::LockStatus newLockStatus);

    virtual void        onResultReceived(int32_t frameId,
                                         camera_metadata* result);
private:
    ProCamera(int cameraId);

    class ProFrameListener : public CpuConsumer::FrameAvailableListener {
    public:
        ProFrameListener(wp<ProCamera> camera, int streamID) {
            mCamera = camera;
            mStreamId = streamID;
        }

    protected:
        virtual void onFrameAvailable() {
            sp<ProCamera> c = mCamera.promote();
            if (c.get() != NULL) {
                c->onFrameAvailable(mStreamId);
            }
        }

    private:
        wp<ProCamera> mCamera;
        int mStreamId;
    };
    friend class ProFrameListener;

    struct StreamInfo
    {
        StreamInfo(int streamId) {
            this->streamID = streamId;
            cpuStream = false;
            frameReady = 0;
        }

        StreamInfo() {
            streamID = -1;
            cpuStream = false;
        }

        int  streamID;
        bool cpuStream;
        sp<CpuConsumer> cpuConsumer;
        bool synchronousMode;
        sp<ProFrameListener> frameAvailableListener;
        sp<Surface> stc;
        int frameReady;
    };

    Condition mWaitCondition;
    Mutex     mWaitMutex;
    static const nsecs_t mWaitTimeout = 1000000000; // 1sec
    KeyedVector<int, StreamInfo> mStreams;
    bool mMetadataReady;
    CameraMetadata mLatestMetadata;

    void onFrameAvailable(int streamId);

    StreamInfo& getStreamInfo(int streamId);

    friend class CameraBase;
};

}; // namespace android

#endif
