// Copyright 2020 The MediaPipe Authors.
//
// 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 MEDIAPIPE_FRAMEWORK_FORMATS_IMAGE_H_
#define MEDIAPIPE_FRAMEWORK_FORMATS_IMAGE_H_

#include <utility>

#include "absl/synchronization/mutex.h"
#include "mediapipe/framework/formats/image_format.pb.h"
#include "mediapipe/framework/formats/image_frame.h"
#include "mediapipe/framework/port/logging.h"
#include "mediapipe/gpu/gpu_buffer.h"
#include "mediapipe/gpu/gpu_buffer_format.h"
#include "mediapipe/gpu/gpu_buffer_storage_image_frame.h"
#include "mediapipe/gpu/image_frame_view.h"

#if !MEDIAPIPE_DISABLE_GPU

#if defined(__APPLE__)
#include <CoreVideo/CoreVideo.h>

#include "mediapipe/objc/CFHolder.h"
#include "mediapipe/objc/util.h"
#endif  // defined(__APPLE__)

#if !MEDIAPIPE_GPU_BUFFER_USE_CV_PIXEL_BUFFER  // OSX, use GL textures.
#include "mediapipe/gpu/gl_texture_buffer.h"
#endif  // MEDIAPIPE_GPU_BUFFER_USE_CV_PIXEL_BUFFER

#endif  // !MEDIAPIPE_DISABLE_GPU

namespace mediapipe {

using ImageFrameSharedPtr = std::shared_ptr<ImageFrame>;

// This class wraps ImageFrame(CPU) & GpuBuffer(GPU) data.
// An instance of Image acts as an opaque reference to the underlying
// data objects. Image also maintains backwards compatability with GpuBuffer.
//
// Accessing GPU storage requires a valid OpenGL context active beforehand.
// i.e.: GetGlTextureBufferSharedPtr() & ConvertToGpu() & GetGpuBuffer()
//       should be called inside an active GL context.
//
// Note: 'use_gpu_' flag is used to keep track of where data is (dirty bit).
//
// TODO Refactor Image to use 'Impl' class delegation system.
//
class Image {
 public:
  // Default constructor creates invalid object.
  Image() = default;

  // Copy and move constructors and assignment operators are supported.
  Image(const Image& other) = default;
  Image(Image&& other) = default;
  Image& operator=(const Image& other) = default;
  Image& operator=(Image&& other) = default;

  // Creates an Image representing the same image content as the ImageFrame
  // the input shared pointer points to, and retaining shared ownership.
  explicit Image(ImageFrameSharedPtr image_frame)
      : gpu_buffer_(std::make_shared<GpuBufferStorageImageFrame>(
            std::move(image_frame))) {
    use_gpu_ = false;
  }

  // CPU getters.
  ImageFrameSharedPtr GetImageFrameSharedPtr() const {
    // Write view currently because the return type does not point to const IF.
    return gpu_buffer_.GetWriteView<ImageFrame>();
  }

  // Creates an Image representing the same image content as the input GPU
  // buffer in platform-specific representations.
#if !MEDIAPIPE_DISABLE_GPU
#if MEDIAPIPE_GPU_BUFFER_USE_CV_PIXEL_BUFFER
  explicit Image(CFHolder<CVPixelBufferRef> pixel_buffer)
      : Image(mediapipe::GpuBuffer(std::move(pixel_buffer))) {}
  explicit Image(CVPixelBufferRef pixel_buffer)
      : Image(mediapipe::GpuBuffer(pixel_buffer)) {}
#else
  explicit Image(mediapipe::GlTextureBufferSharedPtr texture_buffer)
      : Image(mediapipe::GpuBuffer(std::move(texture_buffer))) {}
#endif  // MEDIAPIPE_GPU_BUFFER_USE_CV_PIXEL_BUFFER
  explicit Image(mediapipe::GpuBuffer gpu_buffer) {
    use_gpu_ = true;
    gpu_buffer_ = gpu_buffer;
  }

  // GPU getters.
#if MEDIAPIPE_GPU_BUFFER_USE_CV_PIXEL_BUFFER
  CVPixelBufferRef GetCVPixelBufferRef() const {
    if (use_gpu_ == false) ConvertToGpu();
    return mediapipe::GetCVPixelBufferRef(gpu_buffer_);
  }
#else
  mediapipe::GlTextureBufferSharedPtr GetGlTextureBufferSharedPtr() const {
    if (use_gpu_ == false) ConvertToGpu();
    return gpu_buffer_.internal_storage<mediapipe::GlTextureBuffer>();
  }
#endif  // MEDIAPIPE_GPU_BUFFER_USE_CV_PIXEL_BUFFER
  // Get a GPU view. Automatically uploads from CPU if needed.
  const mediapipe::GpuBuffer GetGpuBuffer() const {
    if (use_gpu_ == false) ConvertToGpu();
    return gpu_buffer_;
  }
#endif  // !MEDIAPIPE_DISABLE_GPU

  // Returns image properties.
  int width() const;
  int height() const;
  int channels() const;
  int step() const;  // Row size in bytes.
  bool UsesGpu() const { return use_gpu_; }
  ImageFormat::Format image_format() const;
  mediapipe::GpuBufferFormat format() const;

  // Converts to true iff valid.
  explicit operator bool() const { return operator!=(nullptr); }

  bool operator==(const Image& other) const;
  bool operator!=(const Image& other) const { return !operator==(other); }

  // Allow comparison with nullptr.
  bool operator==(std::nullptr_t other) const;
  bool operator!=(std::nullptr_t other) const { return !operator==(other); }

  // Allow assignment from nullptr.
  Image& operator=(std::nullptr_t other);

  // Lock/Unlock pixel data.
  // Should be used exclusively by the PixelLock helper class.
  void LockPixels() const ABSL_EXCLUSIVE_LOCK_FUNCTION();
  void UnlockPixels() const ABSL_UNLOCK_FUNCTION();

  // Helper utility for GPU->CPU data transfer.
  bool ConvertToCpu() const;
  // Helper utility for CPU->GPU data transfer.
  // *Requires a valid OpenGL context to be active before calling!*
  bool ConvertToGpu() const;

 private:
  mutable mediapipe::GpuBuffer gpu_buffer_;
  mutable bool use_gpu_ = false;
};

inline int Image::width() const { return gpu_buffer_.width(); }

inline int Image::height() const { return gpu_buffer_.height(); }

inline ImageFormat::Format Image::image_format() const {
  return mediapipe::ImageFormatForGpuBufferFormat(gpu_buffer_.format());
}

inline mediapipe::GpuBufferFormat Image::format() const {
  return gpu_buffer_.format();
}

inline bool Image::operator==(std::nullptr_t other) const {
  return gpu_buffer_ == other;
}

inline bool Image::operator==(const Image& other) const {
  return gpu_buffer_ == other.gpu_buffer_;
}

inline Image& Image::operator=(std::nullptr_t other) {
  gpu_buffer_ = other;
  return *this;
}

inline int Image::channels() const {
  return ImageFrame::NumberOfChannelsForFormat(image_format());
}

inline int Image::step() const {
  return gpu_buffer_.GetReadView<ImageFrame>()->WidthStep();
}

inline void Image::LockPixels() const {
  ConvertToCpu();  // Download data if necessary.
}

inline void Image::UnlockPixels() const {}

// Helper class for getting access to Image CPU data,
// and handles automatically locking/unlocking CPU data access.
//
// Returns pointer to first pixel, or nullptr if invaild Image is provided
//
// Example use:
//   Image buf = ...
//   {
//     PixelLock lock(&buf);
//     uint8* buf_ptr = lock.Pixels();
//     ... use buf_ptr to access pixel data ...
//     ... lock released automatically at end of scope ...
//   }
//
// Note: should be used in separate minimal scope where possible; see example^.
//
class PixelReadLock {
 public:
  explicit PixelReadLock(const Image& image) {
    buffer_ = &image;
    if (buffer_) {
      buffer_->LockPixels();
      frame_ = buffer_->GetImageFrameSharedPtr();
    }
  }
  ~PixelReadLock() {
    if (buffer_) buffer_->UnlockPixels();
  }
  PixelReadLock(const PixelReadLock&) = delete;

  const uint8* Pixels() const {
    if (frame_) return frame_->PixelData();
    return nullptr;
  }

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

 private:
  const Image* buffer_ = nullptr;
  std::shared_ptr<ImageFrame> frame_;
};

class PixelWriteLock {
 public:
  explicit PixelWriteLock(Image* image) {
    buffer_ = image;
    if (buffer_) {
      buffer_->LockPixels();
      frame_ = buffer_->GetImageFrameSharedPtr();
    }
  }
  ~PixelWriteLock() {
    if (buffer_) buffer_->UnlockPixels();
  }
  PixelWriteLock(const PixelWriteLock&) = delete;

  uint8* Pixels() {
    if (frame_) return frame_->MutablePixelData();
    return nullptr;
  }

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

 private:
  const Image* buffer_ = nullptr;
  std::shared_ptr<ImageFrame> frame_;
};

}  // namespace mediapipe

#endif  // MEDIAPIPE_FRAMEWORK_FORMATS_IMAGE_H_
