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

#define LOG_TAG "hwc-drm-display-pipeline"

#include "DrmDisplayPipeline.h"

#include "DrmAtomicStateManager.h"
#include "DrmConnector.h"
#include "DrmCrtc.h"
#include "DrmDevice.h"
#include "DrmEncoder.h"
#include "DrmPlane.h"
#include "utils/log.h"
#include "utils/properties.h"

namespace android {

template <class O>
auto PipelineBindable<O>::BindPipeline(DrmDisplayPipeline *pipeline,
                                       bool return_object_if_bound)
    -> std::shared_ptr<BindingOwner<O>> {
  auto owner_object = owner_object_.lock();
  if (owner_object) {
    if (bound_pipeline_ == pipeline && return_object_if_bound) {
      return owner_object;
    }

    return {};
  }
  owner_object = std::make_shared<BindingOwner<O>>(static_cast<O *>(this));

  owner_object_ = owner_object;
  bound_pipeline_ = pipeline;
  return owner_object;
}

static auto TryCreatePipeline(DrmDevice &dev, DrmConnector &connector,
                              DrmEncoder &enc, DrmCrtc &crtc)
    -> std::unique_ptr<DrmDisplayPipeline> {
  /* Check if resources are available */

  auto pipe = std::make_unique<DrmDisplayPipeline>();
  pipe->device = &dev;

  pipe->connector = connector.BindPipeline(pipe.get());
  pipe->encoder = enc.BindPipeline(pipe.get());
  pipe->crtc = crtc.BindPipeline(pipe.get());

  if (!pipe->connector || !pipe->encoder || !pipe->crtc) {
    return {};
  }

  std::vector<DrmPlane *> primary_planes;
  std::vector<DrmPlane *> overlay_planes;

  /* Attach necessary resources */
  auto display_planes = std::vector<DrmPlane *>();
  for (const auto &plane : dev.GetPlanes()) {
    if (plane->IsCrtcSupported(crtc)) {
      if (plane->GetType() == DRM_PLANE_TYPE_PRIMARY) {
        primary_planes.emplace_back(plane.get());
      } else if (plane->GetType() == DRM_PLANE_TYPE_OVERLAY) {
        overlay_planes.emplace_back(plane.get());
      } else {
        ALOGI("Ignoring cursor plane %d", plane->GetId());
      }
    }
  }

  if (primary_planes.empty()) {
    ALOGE("Primary plane for CRTC %d not found", crtc.GetId());
    return {};
  }

  if (primary_planes.size() > 1) {
    ALOGE("Found more than 1 primary plane for CRTC %d", crtc.GetId());
    return {};
  }

  pipe->primary_plane = primary_planes[0]->BindPipeline(pipe.get());
  if (!pipe->primary_plane) {
    ALOGE("Primary plane %d is already owned. Internal error.",
          primary_planes[0]->GetId());
    return {};
  }

  pipe->atomic_state_manager = std::make_unique<DrmAtomicStateManager>(
      pipe.get());

  return pipe;
}

static auto TryCreatePipelineUsingEncoder(DrmDevice &dev, DrmConnector &conn,
                                          DrmEncoder &enc)
    -> std::unique_ptr<DrmDisplayPipeline> {
  /* First try to use the currently-bound crtc */
  auto *crtc = dev.FindCrtcById(enc.GetCurrentCrtcId());
  if (crtc != nullptr) {
    auto pipeline = TryCreatePipeline(dev, conn, enc, *crtc);
    if (pipeline) {
      return pipeline;
    }
  }

  /* Try to find a possible crtc which will work */
  for (const auto &crtc : dev.GetCrtcs()) {
    if (enc.SupportsCrtc(*crtc)) {
      auto pipeline = TryCreatePipeline(dev, conn, enc, *crtc);
      if (pipeline) {
        return pipeline;
      }
    }
  }

  /* We can't use this encoder, but nothing went wrong, try another one */
  return {};
}

auto DrmDisplayPipeline::CreatePipeline(DrmConnector &connector)
    -> std::unique_ptr<DrmDisplayPipeline> {
  auto &dev = connector.GetDev();
  /* Try to use current setup first */
  auto *encoder = dev.FindEncoderById(connector.GetCurrentEncoderId());

  if (encoder != nullptr) {
    auto pipeline = TryCreatePipelineUsingEncoder(dev, connector, *encoder);
    if (pipeline) {
      return pipeline;
    }
  }

  for (const auto &enc : dev.GetEncoders()) {
    if (connector.SupportsEncoder(*enc)) {
      auto pipeline = TryCreatePipelineUsingEncoder(dev, connector, *enc);
      if (pipeline) {
        return pipeline;
      }
    }
  }

  ALOGE("Could not find a suitable encoder/crtc for connector %s",
        connector.GetName().c_str());

  return {};
}

static bool ReadUseOverlayProperty() {
  char use_overlay_planes_prop[PROPERTY_VALUE_MAX];
  property_get("vendor.hwc.drm.use_overlay_planes", use_overlay_planes_prop,
               "1");
  constexpr int kStrtolBase = 10;
  return strtol(use_overlay_planes_prop, nullptr, kStrtolBase) != 0;
}

auto DrmDisplayPipeline::GetUsablePlanes()
    -> std::vector<std::shared_ptr<BindingOwner<DrmPlane>>> {
  std::vector<std::shared_ptr<BindingOwner<DrmPlane>>> planes;
  planes.emplace_back(primary_plane);

  static bool use_overlay_planes = ReadUseOverlayProperty();

  if (use_overlay_planes) {
    for (const auto &plane : device->GetPlanes()) {
      if (plane->IsCrtcSupported(*crtc->Get())) {
        if (plane->GetType() == DRM_PLANE_TYPE_OVERLAY) {
          auto op = plane->BindPipeline(this, true);
          if (op) {
            planes.emplace_back(op);
          }
        }
      }
    }
  }

  return planes;
}

}  // namespace android