/*
 *  Copyright (C) 2005-2018 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 "DBusUtil.h"

#include "utils/log.h"

CVariant CDBusUtil::GetVariant(const char *destination, const char *object, const char *interface, const char *property)
{
//dbus-send --system --print-reply --dest=destination object org.freedesktop.DBus.Properties.Get string:interface string:property
  CDBusMessage message(destination, object, "org.freedesktop.DBus.Properties", "Get");
  CVariant result;

  message.AppendArgument(interface);
  message.AppendArgument(property);
  DBusMessage *reply = message.SendSystem();

  if (reply)
  {
    DBusMessageIter iter;

    if (dbus_message_iter_init(reply, &iter))
    {
      if (!dbus_message_has_signature(reply, "v"))
        CLog::Log(LOGERROR, "DBus: wrong signature on Get - should be \"v\" but was {}",
                  dbus_message_iter_get_signature(&iter));
      else
        result = ParseVariant(&iter);
    }
  }

  return result;
}

CVariant CDBusUtil::GetAll(const char *destination, const char *object, const char *interface)
{
  CDBusMessage message(destination, object, "org.freedesktop.DBus.Properties", "GetAll");
  CVariant properties;
  message.AppendArgument(interface);
  DBusMessage *reply = message.SendSystem();
  if (reply)
  {
    DBusMessageIter iter;
    if (dbus_message_iter_init(reply, &iter))
    {
      if (!dbus_message_has_signature(reply, "a{sv}"))
        CLog::Log(LOGERROR, "DBus: wrong signature on GetAll - should be \"a{{sv}}\" but was {}",
                  dbus_message_iter_get_signature(&iter));
      else
      {
        do
        {
          DBusMessageIter sub;
          dbus_message_iter_recurse(&iter, &sub);
          do
          {
            DBusMessageIter dict;
            dbus_message_iter_recurse(&sub, &dict);
            do
            {
              const char * key = NULL;

              dbus_message_iter_get_basic(&dict, &key);
              if (!dbus_message_iter_next(&dict))
                break;

              CVariant value = ParseVariant(&dict);

              if (!value.isNull())
                properties[key] = value;

            } while (dbus_message_iter_next(&dict));

          } while (dbus_message_iter_next(&sub));

        } while (dbus_message_iter_next(&iter));
      }
    }
  }

  return properties;
}

CVariant CDBusUtil::ParseVariant(DBusMessageIter *itr)
{
  DBusMessageIter variant;
  dbus_message_iter_recurse(itr, &variant);

  return ParseType(&variant);
}

CVariant CDBusUtil::ParseType(DBusMessageIter *itr)
{
  CVariant value;
  const char *    string  = NULL;
  dbus_int32_t    int32   = 0;
  dbus_uint32_t   uint32  = 0;
  dbus_int64_t    int64   = 0;
  dbus_uint64_t   uint64  = 0;
  dbus_bool_t     boolean = false;
  double          doublev = 0;

  int type = dbus_message_iter_get_arg_type(itr);
  switch (type)
  {
  case DBUS_TYPE_OBJECT_PATH:
  case DBUS_TYPE_STRING:
    dbus_message_iter_get_basic(itr, &string);
    value = string;
    break;
  case DBUS_TYPE_UINT32:
    dbus_message_iter_get_basic(itr, &uint32);
    value = (uint64_t)uint32;
    break;
  case DBUS_TYPE_BYTE:
  case DBUS_TYPE_INT32:
    dbus_message_iter_get_basic(itr, &int32);
    value = (int64_t)int32;
    break;
  case DBUS_TYPE_UINT64:
    dbus_message_iter_get_basic(itr, &uint64);
    value = (uint64_t)uint64;
    break;
  case DBUS_TYPE_INT64:
    dbus_message_iter_get_basic(itr, &int64);
    value = (int64_t)int64;
    break;
  case DBUS_TYPE_BOOLEAN:
    dbus_message_iter_get_basic(itr, &boolean);
    value = (bool)boolean;
    break;
  case DBUS_TYPE_DOUBLE:
    dbus_message_iter_get_basic(itr, &doublev);
    value = doublev;
    break;
  case DBUS_TYPE_ARRAY:
    DBusMessageIter array;
    dbus_message_iter_recurse(itr, &array);

    value = CVariant::VariantTypeArray;

    do
    {
      CVariant item = ParseType(&array);
      if (!item.isNull())
        value.push_back(item);
    } while (dbus_message_iter_next(&array));
    break;
  }

  return value;
}

bool CDBusUtil::TryMethodCall(DBusBusType bus, const char* destination, const char* object, const char* interface, const char* method)
{
  CDBusMessage message(destination, object, interface, method);
  CDBusError error;
  message.Send(bus, error);
  if (error)
  {
    error.Log(LOGDEBUG, std::string("DBus method call to ") + interface + "." + method + " at " + object + " of " + destination + " failed");
  }
  return !error;
}

bool CDBusUtil::TryMethodCall(DBusBusType bus, std::string const& destination, std::string const& object, std::string const& interface, std::string const& method)
{
  return TryMethodCall(bus, destination.c_str(), object.c_str(), interface.c_str(), method.c_str());
}

CDBusConnection::CDBusConnection() = default;

bool CDBusConnection::Connect(DBusBusType bus, bool openPrivate)
{
  CDBusError error;
  Connect(bus, error, openPrivate);
  if (error)
  {
    error.Log(LOGWARNING, "DBus connection failed");
    return false;
  }

  return true;
}

bool CDBusConnection::Connect(DBusBusType bus, CDBusError& error, bool openPrivate)
{
  if (m_connection)
  {
    throw std::logic_error("Cannot reopen connected DBus connection");
  }

  m_connection.get_deleter().closeBeforeUnref = openPrivate;

  if (openPrivate)
  {
    m_connection.reset(dbus_bus_get_private(bus, error));
  }
  else
  {
    m_connection.reset(dbus_bus_get(bus, error));
  }

  return !!m_connection;
}

CDBusConnection::operator DBusConnection*()
{
  return m_connection.get();
}

void CDBusConnection::DBusConnectionDeleter::operator()(DBusConnection* connection) const
{
  if (closeBeforeUnref)
  {
    dbus_connection_close(connection);
  }
  dbus_connection_unref(connection);
}

void CDBusConnection::Destroy()
{
  m_connection.reset();
}


CDBusError::CDBusError()
{
  dbus_error_init(&m_error);
}

CDBusError::~CDBusError()
{
  Reset();
}

void CDBusError::Reset()
{
 dbus_error_free(&m_error);
}

CDBusError::operator DBusError*()
{
  return &m_error;
}

bool CDBusError::IsSet() const
{
  return dbus_error_is_set(&m_error);
}

CDBusError::operator bool()
{
  return IsSet();
}

CDBusError::operator bool() const
{
  return IsSet();
}

std::string CDBusError::Name() const
{
  if (!IsSet())
  {
    throw std::logic_error("Cannot retrieve name of unset DBus error");
  }
  return m_error.name;
}

std::string CDBusError::Message() const
{
  if (!IsSet())
  {
    throw std::logic_error("Cannot retrieve message of unset DBus error");
  }
  return m_error.message;
}

void CDBusError::Log(std::string const& message) const
{
  Log(LOGERROR, message);
}

void CDBusError::Log(int level, const std::string& message) const
{
  if (!IsSet())
  {
    throw std::logic_error("Cannot log unset DBus error");
  }
  CLog::Log(level, "{}: {} - {}", message, m_error.name, m_error.message);
}
