/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *     * Neither the name of The Linux Foundation nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
#ifndef CAMERA_UTIL_STEREO_H
#define CAMERA_UTIL_STEREO_H

#include <memory>
#include <mutex>
#include <vector>
#include "camera.h"
#include "camera_parameters.h"

namespace camera {



/**
 * Interface for multi camera listener object. Client needs to
 * implement this interface to get access to camera data and
 * control events. The methods in listener can be invoked from
 * multiple different threads. Client needs to make sure that
 * implementation is thread-safe.
 * 
 * Array listener will receive a vector of ICameraFrames, with
 * each ICameraFrame being part of the same logical image (one from
 * each physical sensor that is part of the logical setup)
 */
class ICameraDeviceArrayListener
{
public:

    virtual ~ICameraDeviceArrayListener() {}

    /**
     * This function is called when an error is generated by camera
     * driver
     */
    virtual void onError() {}

    /**
     * This function is called when a control event needs to be
     * delivered to a client.
     *
     * @param control [in]: control event object
     */
    virtual void onControl(const ControlEvent& control) {}

    /**
     * This function is called when a preview frame is matched up
     * by the camera array
     *
     * @param frame [in]: pointer to an existing ICameraFrame
     *              generated by camera
     */
    virtual void onPreviewFrame(std::vector<ICameraFrame *> frames) {}

    /**
     * This function is called when a preview frame is generated by
     * camera.
     *
     * @param frame [in]: pointer to an existing ICameraFrame
     *              generated by camera
     */
    virtual void onVideoFrame(std::vector<ICameraFrame *> frames) {}

    /**
     * This function is called when a snapshot frame is generated by
     * camera.
     *
     * @param frame [in]: pointer to an existing ICameraFrame
     *              generated by camera
     */
    virtual void onPictureFrame(std::vector<ICameraFrame *> frames) {}

    /**
     * This function is called when a metadata frame is generated by
     * camera.
     *
     * @param frame [in]: pointer to an existing ICameraFrame
     *              generated by camera
     */
    virtual void onMetaDataFrame(std::vector<ICameraFrame *> frames) {}
};


class ICameraDeviceArray
{
public:
  virtual ~ICameraDeviceArray() {}

  /**
   * Get number of cameras in this array config
   */
  virtual int getNumberOfCameras() const = 0;

  /**
   *  Set the parameters for all the cameras in the array config
   */
  virtual int setParameters(const ICameraParameters& params) = 0;

  /**
   *  Set the parameters for all the cameras in the array config
   */
  virtual int setParameters(const CameraParams& params) = 0;

  /**
   * Add a listener to receive frame callbacks
   * @note ICameraArray does not take ownership of the ICameraArrayListener
   *
   */
  virtual int addListener(ICameraDeviceArrayListener *listener) = 0;

  /**
   * Remove frame callback listener
   */
  virtual int removeListener(ICameraDeviceArrayListener *listener) = 0;

  /**
   * Starts preview stream on all cameras in the array config
   *
   * @return int : 0 on success
   */
  virtual int startPreview() = 0;

  /**
   * Stop preview stream on all cameras in the array config
   */
  virtual void stopPreview() = 0;

  /**
   * Start video record stream on all cameras in the array config
   *
   * @return int : 0 on success
   */
  virtual int startRecording() = 0;

  /**
   * Stop video record stream on all cameras in the array config
   */
  virtual void stopRecording() = 0;

  /**
   * @brief Take a picture on all cameras in the array config
   *
   * Preview needs to be running when this method is called. Video
   * recording may or may not be running. If successful,
   * onPictureFrame() callback is called with image data for
   * picture for each sensor. Client needs to wait for this callback before taking
   * any more pictures.
   *
   * @return int
   */
   virtual int takePicture() = 0;


};


/**
 * First use ICameraDevice::CreateInstance to create the instances for the individual
 * cameras from the HAL etc. Then pass these created instances to 
 * the CameraArray constructor.
 * The CameraArray will take over ownership of these ICameraDevices
 * and will free them when it is destroyed.
 *
 * CameraArray will handle the the correlation of individual camera frames from all 
 * sensors in the array config, and provide a single callback (one of the callbacks in 
 * ICameraDeviceArrayListener) that has the ICameraFrames for all sensors that are part of the
 * single logical multi-camera frame
 */
class CameraArray : public ICameraDeviceArray
{
public:

    CameraArray(std::vector<ICameraDevice *> cameradevices, int fps);

    virtual ~CameraArray();

    /**
     * Get number of cameras in this camera array configuration
     */
    virtual int getNumberOfCameras() const override;

    /**
     *  Set the parameters for all the cameras in the array config
     */
    virtual int setParameters(const ICameraParameters& params) override;

    /**
     *  Set the parameters for all the cameras in the array config
     */
    virtual int setParameters(const CameraParams& params) override;

    /**
     * Add a listener to receive frame callbacks
     * @note
     * When CameraUtilStereo is deinitialized, any listeners that have not been removed will be not
     * be deleted by the destructor i.e. CameraUtilStereo does not deal with ownership of the
     * ICameraStereoListeners
     */
    virtual int addListener(ICameraDeviceArrayListener *listener) override;

    /**
     * Remove frame callback listener
     */
    virtual int removeListener(ICameraDeviceArrayListener *listener) override;

    /**
     * Starts preview stream on all cameras in the stereo config
     *
     * @return int : 0 on success
     */
    virtual int startPreview() override;

    /**
     * Stop preview stream on all cameras in the stereo config
     */
    virtual void stopPreview() override;

    /**
     * Start video record stream on all cameras in the stereo config
     *
     * @return int : 0 on success
     */
    virtual int startRecording() override;

    /**
     * Stop video record stream on all cameras in the stereo config
     */
    virtual void stopRecording() override;

    /**
     * @brief Take a picture on all cameras in the stereo stream
     *
     * Preview needs to be running when this method is called. Video
     * recording may or may not be running. If successful,
     * onPictureFrame() callback is called with image data for
     * picture for each sensor. Client needs to wait for this callback before taking
     * any more pictures.
     *
     * @return int
     */
     virtual int takePicture() override;

private:

  struct CameraDeviceDeleter {
    void operator()(ICameraDevice *device) const {
        ICameraDevice::deleteInstance(&device);
    }
  };

  class CameraArrayListener : public ICameraListener
  {
  public:
    CameraArrayListener(int camId, CameraArray* util,
        std::shared_ptr<ICameraDevice> device);
    virtual ~CameraArrayListener();

    virtual void onError();
    virtual void onControl(const ControlEvent& control);
    virtual void onPreviewFrame(ICameraFrame *frame);
    virtual void onVideoFrame(ICameraFrame *frame);
    virtual void onPictureFrame(ICameraFrame *frame);
    virtual void onMetadataFrame(ICameraFrame *frame);

  private:
    int camId_;
    CameraArray *util_;
    std::shared_ptr<ICameraDevice> device_;
  };

  struct CameraFramesCache {
    // frameCache[0] is the frame with oldest timestamp
    // frameCache[frameCache.size() - 1] is the newest frame
    std::vector<ICameraFrame *> frameCache;
  };

  std::vector<std::shared_ptr<ICameraDevice> > cameraDevices_;
  // Listeners to the ICameraDevice instances of each camera
  // We are owners and lifetime determiners of these listeners, 
  // since we actually remove the listeners in CameraArrayListener
  // destructor, hence the unique_ptr.
  std::vector<std::unique_ptr<CameraArrayListener> > listeners_;
  // Listeners to the CamaraArray itself - we don't deal with ownership
  // of these - that would be upto our client.
  std::vector<ICameraDeviceArrayListener *> stereo_listeners_;

  // Frames caches for all the ICameraDevices we are controlling
  std::vector<CameraFramesCache> cameraFramesCaches_;
  std::mutex framesCacheMutex_;
  int fps_;
  uint64_t maxTimeStampDiff_;

  CameraArray& operator=(const CameraArray&) = delete;
  CameraArray(const CameraArray&) = delete;

  void onError(int camId);
  void onControl(int camId, const ControlEvent& control);
  void onPreviewFrame(int camId, ICameraFrame *frame);
  void onVideoFrame(int camId, ICameraFrame *frame);
  void onPictureFrame(int camId, ICameraFrame *frame);
  void onMetadataFrame(int camId, ICameraFrame *frame);
  void processPreviewFrame(int camId, ICameraFrame *frame,
    std::vector<ICameraFrame *>& stereoCandidates);
  // Call with framesCacheMutex_ held only
  void CheckForStereoMatchLocked(int camId, ICameraFrame *frame,
    std::vector<ICameraFrame *>& stereoCandidates);
  bool isAcceptableTimeStampDiff(uint64_t timeStamp1, uint64_t timeStamp2);
};

/**
  * This class is a factory for creating an instance of CameraArray for the special case of two cameras
  * connected in a stereo config
  */
class StereoCameraUtilFactory {
public:
  static ICameraDeviceArray *createStereoDevice(CameraParams *params);
};

}



#endif
