#include "include/private/dvr/display_client.h"

#include <cutils/native_handle.h>
#include <log/log.h>
#include <pdx/default_transport/client_channel.h>
#include <pdx/default_transport/client_channel_factory.h>
#include <pdx/status.h>

#include <mutex>

#include <private/dvr/display_protocol.h>
#include <private/dvr/native_buffer.h>

using android::pdx::ErrorStatus;
using android::pdx::LocalHandle;
using android::pdx::LocalChannelHandle;
using android::pdx::Status;
using android::pdx::Transaction;
using android::pdx::rpc::IfAnyOf;

namespace android {
namespace dvr {
namespace display {

Surface::Surface(LocalChannelHandle channel_handle, int* error)
    : BASE{pdx::default_transport::ClientChannel::Create(
          std::move(channel_handle))} {
  auto status = InvokeRemoteMethod<DisplayProtocol::GetSurfaceInfo>();
  if (!status) {
    ALOGE("Surface::Surface: Failed to get surface info: %s",
          status.GetErrorMessage().c_str());
    Close(status.error());
    if (error)
      *error = status.error();
  }

  surface_id_ = status.get().surface_id;
  z_order_ = status.get().z_order;
  visible_ = status.get().visible;
}

Surface::Surface(const SurfaceAttributes& attributes, int* error)
    : BASE{pdx::default_transport::ClientChannelFactory::Create(
               DisplayProtocol::kClientPath),
           kInfiniteTimeout} {
  auto status = InvokeRemoteMethod<DisplayProtocol::CreateSurface>(attributes);
  if (!status) {
    ALOGE("Surface::Surface: Failed to create display surface: %s",
          status.GetErrorMessage().c_str());
    Close(status.error());
    if (error)
      *error = status.error();
  }

  surface_id_ = status.get().surface_id;
  z_order_ = status.get().z_order;
  visible_ = status.get().visible;
}

Status<void> Surface::SetVisible(bool visible) {
  return SetAttributes(
      {{SurfaceAttribute::Visible, SurfaceAttributeValue{visible}}});
}

Status<void> Surface::SetZOrder(int z_order) {
  return SetAttributes(
      {{SurfaceAttribute::ZOrder, SurfaceAttributeValue{z_order}}});
}

Status<void> Surface::SetAttributes(const SurfaceAttributes& attributes) {
  auto status = InvokeRemoteMethod<DisplayProtocol::SetAttributes>(attributes);
  if (!status) {
    ALOGE(
        "Surface::SetAttributes: Failed to set display surface "
        "attributes: %s",
        status.GetErrorMessage().c_str());
    return status.error_status();
  }

  // Set the local cached copies of the attributes we care about from the full
  // set of attributes sent to the display service.
  for (const auto& attribute : attributes) {
    const auto& key = attribute.first;
    const auto* variant = &attribute.second;
    bool invalid_value = false;
    switch (key) {
      case SurfaceAttribute::Visible:
        invalid_value =
            !IfAnyOf<int32_t, int64_t, bool>::Get(variant, &visible_);
        break;
      case SurfaceAttribute::ZOrder:
        invalid_value = !IfAnyOf<int32_t>::Get(variant, &z_order_);
        break;
    }

    if (invalid_value) {
      ALOGW(
          "Surface::SetAttributes: Failed to set display surface "
          "attribute %d because of incompatible type: %d",
          key, variant->index());
    }
  }

  return {};
}

Status<std::unique_ptr<ProducerQueue>> Surface::CreateQueue(
    uint32_t width, uint32_t height, uint32_t format, size_t metadata_size) {
  ALOGD_IF(TRACE, "Surface::CreateQueue: Creating empty queue.");
  auto status = InvokeRemoteMethod<DisplayProtocol::CreateQueue>(
      ProducerQueueConfigBuilder()
          .SetDefaultWidth(width)
          .SetDefaultHeight(height)
          .SetDefaultFormat(format)
          .SetMetadataSize(metadata_size)
          .Build());
  if (!status) {
    ALOGE("Surface::CreateQueue: Failed to create queue: %s",
          status.GetErrorMessage().c_str());
    return status.error_status();
  }

  auto producer_queue = ProducerQueue::Import(status.take());
  if (!producer_queue) {
    ALOGE("Surface::CreateQueue: Failed to import producer queue!");
    return ErrorStatus(ENOMEM);
  }

  return {std::move(producer_queue)};
}

Status<std::unique_ptr<ProducerQueue>> Surface::CreateQueue(
    uint32_t width, uint32_t height, uint32_t layer_count, uint32_t format,
    uint64_t usage, size_t capacity, size_t metadata_size) {
  ALOGD_IF(TRACE,
           "Surface::CreateQueue: width=%u height=%u layer_count=%u format=%u "
           "usage=%" PRIx64 " capacity=%zu",
           width, height, layer_count, format, usage, capacity);
  auto status = CreateQueue(width, height, format, metadata_size);
  if (!status)
    return status.error_status();

  auto producer_queue = status.take();

  ALOGD_IF(TRACE, "Surface::CreateQueue: Allocating %zu buffers...", capacity);
  auto allocate_status = producer_queue->AllocateBuffers(
      width, height, layer_count, format, usage, capacity);
  if (!allocate_status) {
    ALOGE("Surface::CreateQueue: Failed to allocate buffer on queue_id=%d: %s",
          producer_queue->id(), allocate_status.GetErrorMessage().c_str());
    return allocate_status.error_status();
  }

  return {std::move(producer_queue)};
}

DisplayClient::DisplayClient(int* error)
    : BASE(pdx::default_transport::ClientChannelFactory::Create(
               DisplayProtocol::kClientPath),
           kInfiniteTimeout) {
  if (error)
    *error = Client::error();
}

Status<Metrics> DisplayClient::GetDisplayMetrics() {
  return InvokeRemoteMethod<DisplayProtocol::GetMetrics>();
}

Status<std::string> DisplayClient::GetConfigurationData(
    ConfigFileType config_type) {
  auto status =
      InvokeRemoteMethod<DisplayProtocol::GetConfigurationData>(config_type);
  if (!status && status.error() != ENOENT) {
    ALOGE(
        "DisplayClient::GetConfigurationData: Unable to get"
        "configuration data. Error: %s",
        status.GetErrorMessage().c_str());
  }
  return status;
}

Status<std::unique_ptr<Surface>> DisplayClient::CreateSurface(
    const SurfaceAttributes& attributes) {
  int error;
  if (auto client = Surface::Create(attributes, &error))
    return {std::move(client)};
  else
    return ErrorStatus(error);
}

pdx::Status<std::unique_ptr<IonBuffer>> DisplayClient::SetupGlobalBuffer(
    DvrGlobalBufferKey key, size_t size, uint64_t usage) {
  auto status =
      InvokeRemoteMethod<DisplayProtocol::SetupGlobalBuffer>(key, size, usage);
  if (!status) {
    ALOGE(
        "DisplayClient::SetupGlobalBuffer: Failed to create the global buffer "
        "%s",
        status.GetErrorMessage().c_str());
    return status.error_status();
  }

  auto ion_buffer = std::make_unique<IonBuffer>();
  auto native_buffer_handle = status.take();
  const int ret = native_buffer_handle.Import(ion_buffer.get());
  if (ret < 0) {
    ALOGE(
        "DisplayClient::GetGlobalBuffer: Failed to import global buffer: "
        "key=%d; error=%s",
        key, strerror(-ret));
    return ErrorStatus(-ret);
  }

  return {std::move(ion_buffer)};
}

pdx::Status<void> DisplayClient::DeleteGlobalBuffer(DvrGlobalBufferKey key) {
  auto status = InvokeRemoteMethod<DisplayProtocol::DeleteGlobalBuffer>(key);
  if (!status) {
    ALOGE("DisplayClient::DeleteGlobalBuffer Failed: %s",
          status.GetErrorMessage().c_str());
  }

  return status;
}

Status<std::unique_ptr<IonBuffer>> DisplayClient::GetGlobalBuffer(
    DvrGlobalBufferKey key) {
  auto status = InvokeRemoteMethod<DisplayProtocol::GetGlobalBuffer>(key);
  if (!status) {
    ALOGE(
        "DisplayClient::GetGlobalBuffer: Failed to get named buffer: key=%d; "
        "error=%s",
        key, status.GetErrorMessage().c_str());
    return status.error_status();
  }

  auto ion_buffer = std::make_unique<IonBuffer>();
  auto native_buffer_handle = status.take();
  const int ret = native_buffer_handle.Import(ion_buffer.get());
  if (ret < 0) {
    ALOGE(
        "DisplayClient::GetGlobalBuffer: Failed to import global buffer: "
        "key=%d; error=%s",
        key, strerror(-ret));
    return ErrorStatus(-ret);
  }

  return {std::move(ion_buffer)};
}

Status<bool> DisplayClient::IsVrAppRunning() {
  return InvokeRemoteMethod<DisplayProtocol::IsVrAppRunning>();
}

}  // namespace display
}  // namespace dvr
}  // namespace android
