/*
 *  Copyright (C) 2005-2020 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#include "DRMObject.h"

#include "utils/log.h"

#include <algorithm>
#include <array>

using namespace KODI::WINDOWING::GBM;

namespace
{

constexpr std::array<std::pair<uint32_t, const char*>, 8> DrmModeObjectTypes = {
    {{DRM_MODE_OBJECT_CRTC, "crtc"},
     {DRM_MODE_OBJECT_CONNECTOR, "connector"},
     {DRM_MODE_OBJECT_ENCODER, "encoder"},
     {DRM_MODE_OBJECT_MODE, "mode"},
     {DRM_MODE_OBJECT_PROPERTY, "property"},
     {DRM_MODE_OBJECT_FB, "framebuffer"},
     {DRM_MODE_OBJECT_BLOB, "blob"},
     {DRM_MODE_OBJECT_PLANE, "plane"}}};
}

CDRMObject::CDRMObject(int fd) : m_fd(fd)
{
}

std::string CDRMObject::GetTypeName() const
{
  auto name = std::find_if(DrmModeObjectTypes.begin(), DrmModeObjectTypes.end(),
                           [this](const auto& p) { return p.first == m_type; });
  if (name != DrmModeObjectTypes.end())
    return name->second;

  return "invalid type";
}

std::string CDRMObject::GetPropertyName(uint32_t propertyId) const
{
  auto prop = std::find_if(m_propsInfo.begin(), m_propsInfo.end(),
                           [&propertyId](const auto& p) { return p->prop_id == propertyId; });
  if (prop != m_propsInfo.end())
    return prop->get()->name;

  return "invalid property";
}

uint32_t CDRMObject::GetPropertyId(std::string_view name) const
{
  auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(),
                               [&name](const auto& prop) { return prop->name == name; });

  if (property != m_propsInfo.end())
    return property->get()->prop_id;

  return 0;
}

bool CDRMObject::GetProperties(uint32_t id, uint32_t type)
{
  m_props.reset(drmModeObjectGetProperties(m_fd, id, type));
  if (!m_props)
    return false;

  m_id = id;
  m_type = type;

  for (uint32_t i = 0; i < m_props->count_props; i++)
    m_propsInfo.emplace_back(std::unique_ptr<drmModePropertyRes, DrmModePropertyResDeleter>(
        drmModeGetProperty(m_fd, m_props->props[i])));

  return true;
}

std::optional<uint64_t> CDRMObject::GetPropertyEnumValue(std::string_view name,
                                                         std::string_view valueName) const
{
  auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(),
                               [&name](const auto& prop) { return prop->name == name; });

  if (property == m_propsInfo.end())
    return {};

  auto prop = property->get();

  if (!static_cast<bool>(drm_property_type_is(prop, DRM_MODE_PROP_ENUM)))
    return {};

  for (int j = 0; j < prop->count_enums; j++)
  {
    if (prop->enums[j].name != valueName)
      continue;

    return std::make_optional<uint64_t>(prop->enums[j].value);
  }

  return {};
}

std::optional<uint64_t> CDRMObject::GetPropertyValue(std::string_view name) const
{
  auto property =
      std::ranges::find_if(m_propsInfo, [&name](const auto& prop) { return prop->name == name; });

  if (property == m_propsInfo.end())
    return {};

  return m_props->prop_values[std::ranges::distance(m_propsInfo.begin(), property)];
}

bool CDRMObject::CachePropertyValue(uint32_t propertyId, uint64_t value)
{
  for (size_t i = 0; i < m_propsInfo.size(); i++)
  {
    if (m_propsInfo[i]->prop_id == propertyId)
    {
      m_props->prop_values[i] = value;
      return true;
    }
  }
  return false;
}

std::optional<std::pair<uint64_t, uint64_t>> CDRMObject::GetRangePropertyLimits(
    std::string_view name) const
{
  auto property =
      std::ranges::find_if(m_propsInfo, [&name](const auto& prop) { return prop->name == name; });

  if (property == m_propsInfo.end())
    return {};

  auto prop = property->get();

  if (!static_cast<bool>(drm_property_type_is(prop, DRM_MODE_PROP_RANGE)))
    return {};

  if (prop->count_values != 2)
    return {};

  return std::make_pair(prop->values[0], prop->values[1]);
}

bool CDRMObject::SetProperty(std::string_view name, uint64_t value)
{
  auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(),
                               [&name](const auto& prop) { return prop->name == name; });

  if (property != m_propsInfo.end())
  {
    if (drmModeObjectSetProperty(m_fd, m_id, m_type, property->get()->prop_id, value))
      return false;
    return CachePropertyValue(property->get()->prop_id, value);
  }

  return false;
}

std::optional<bool> CDRMObject::IsPropertyImmutable(std::string_view name) const
{
  auto property =
      std::ranges::find_if(m_propsInfo, [&name](const auto& prop) { return prop->name == name; });

  if (property == m_propsInfo.end())
    return {};

  return static_cast<bool>(drm_property_type_is(property->get(), DRM_MODE_PROP_IMMUTABLE));
}

bool CDRMObject::SupportsProperty(std::string_view name)
{
  auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(),
                               [&name](const auto& prop) { return prop->name == name; });

  if (property != m_propsInfo.end())
    return true;

  return false;
}
