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

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define LOG_TAG "drmhwc"

#include "DrmFbImporter.h"

#include <hardware/gralloc.h>
#include <utils/Trace.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

#include <cinttypes>
#include <system_error>

#include "utils/log.h"
#include "utils/properties.h"

namespace android {

auto DrmFbIdHandle::CreateInstance(BufferInfo *bo, GemHandle first_gem_handle,
                                   DrmDevice &drm)
    -> std::shared_ptr<DrmFbIdHandle> {
  // NOLINTNEXTLINE(misc-const-correctness)
  ATRACE_NAME("Import dmabufs and register FB");

  // NOLINTNEXTLINE(cppcoreguidelines-owning-memory): priv. constructor usage
  std::shared_ptr<DrmFbIdHandle> local(new DrmFbIdHandle(drm));

  local->gem_handles_[0] = first_gem_handle;
  int32_t err = 0;

  /* Framebuffer object creation require gem handle for every used plane */
  for (size_t i = 1; i < local->gem_handles_.size(); i++) {
    if (bo->prime_fds[i] > 0) {
      if (bo->prime_fds[i] != bo->prime_fds[0]) {
        err = drmPrimeFDToHandle(*drm.GetFd(), bo->prime_fds[i],
                                 &local->gem_handles_.at(i));
        if (err != 0) {
          ALOGE("failed to import prime fd %d errno=%d", bo->prime_fds[i],
                errno);
        }
      } else {
        local->gem_handles_.at(i) = local->gem_handles_[0];
      }
    }
  }

  auto has_modifiers = bo->modifiers[0] != DRM_FORMAT_MOD_NONE &&
                       bo->modifiers[0] != DRM_FORMAT_MOD_INVALID;

  if (!drm.HasAddFb2ModifiersSupport() && has_modifiers) {
    ALOGE("No ADDFB2 with modifier support. Can't import modifier %" PRIu64,
          bo->modifiers[0]);
    local.reset();
    return local;
  }

  /* Create framebuffer object */
  if (!has_modifiers) {
    err = drmModeAddFB2(*drm.GetFd(), bo->width, bo->height, bo->format,
                        local->gem_handles_.data(), &bo->pitches[0],
                        &bo->offsets[0], &local->fb_id_, 0);
  } else {
    err = drmModeAddFB2WithModifiers(*drm.GetFd(), bo->width, bo->height,
                                     bo->format, local->gem_handles_.data(),
                                     &bo->pitches[0], &bo->offsets[0],
                                     &bo->modifiers[0], &local->fb_id_,
                                     DRM_MODE_FB_MODIFIERS);
  }
  if (err != 0) {
    ALOGE("could not create drm fb %d", err);
    local.reset();
  }

  return local;
}

DrmFbIdHandle::~DrmFbIdHandle() {
  // NOLINTNEXTLINE(misc-const-correctness)
  ATRACE_NAME("Close FB and dmabufs");

  /* Destroy framebuffer object */
  if (drmModeRmFB(*drm_fd_, fb_id_) != 0) {
    ALOGE("Failed to rm fb");
  }

  /* Close GEM handles.
   *
   * WARNING: TODO(nobody):
   * From Linux side libweston relies on libgbm to get KMS handle and never
   * closes it (handle is closed by libgbm on buffer destruction)
   * Probably we should offer similar approach to users (at least on user
   * request via system properties)
   */
  struct drm_gem_close gem_close {};
  for (size_t i = 0; i < gem_handles_.size(); i++) {
    /* Don't close invalid handle. Close handle only once in cases
     * where several YUV planes located in the single buffer. */
    if (gem_handles_[i] == 0 ||
        (i != 0 && gem_handles_[i] == gem_handles_[0])) {
      continue;
    }
    gem_close.handle = gem_handles_[i];
    auto err = drmIoctl(*drm_fd_, DRM_IOCTL_GEM_CLOSE, &gem_close);
    if (err != 0) {
      ALOGE("Failed to close gem handle %d, errno: %d", gem_handles_[i], errno);
    }
  }
}

auto DrmFbImporter::GetOrCreateFbId(BufferInfo *bo)
    -> std::shared_ptr<DrmFbIdHandle> {
  /* TODO: Clean up DrmDevices and DrmFbImporter inter-dependency.
   *
   * DrmFbImporter can outlive DrmDevice which will cause issues when resources
   * are released. Addressing this would require a restructure on how
   * ResourceManager stores DrmDevices and DrmFbImporter to make sure they
   * depend on each other. For now, just acquire the DRM fd from the DrmDevice
   * to make sure it is not closed.
   */
  if (drm_fd_ == nullptr) {
    drm_fd_ = drm_->GetFd();
  }

  /* Lookup DrmFbIdHandle in cache first. First handle serves as a cache key. */
  GemHandle first_handle = 0;
  auto err = drmPrimeFDToHandle(*drm_fd_, bo->prime_fds[0], &first_handle);

  if (err != 0) {
    ALOGE("Failed to import prime fd %d ret=%d", bo->prime_fds[0], err);
    return {};
  }

  auto drm_fb_id_cached = drm_fb_id_handle_cache_.find(first_handle);

  if (drm_fb_id_cached != drm_fb_id_handle_cache_.end()) {
    if (auto drm_fb_id_handle_shared = drm_fb_id_cached->second.lock()) {
      return drm_fb_id_handle_shared;
    }
    drm_fb_id_handle_cache_.erase(drm_fb_id_cached);
  }

  /* Cleanup cached empty weak pointers */
  const int minimal_cleanup_size = 128;
  if (drm_fb_id_handle_cache_.size() > minimal_cleanup_size) {
    CleanupEmptyCacheElements();
  }

  /* No DrmFbIdHandle found in cache, create framebuffer object */
  auto fb_id_handle = DrmFbIdHandle::CreateInstance(bo, first_handle, *drm_);
  if (fb_id_handle) {
    drm_fb_id_handle_cache_[first_handle] = fb_id_handle;
  }

  return fb_id_handle;
}

}  // namespace android
