/*
 * This file is part of the Polkit-tqt project
 * Copyright (C) 2009 Daniel Nicoletti <dantti85-pk@yahoo.com.br>
 * Copyright (C) 2009 Dario Freddi <drf@kde.org>
 * Copyright (C) 2009 Jaroslav Reznik <jreznik@redhat.com>
 * Copyright (C) 2009 Radek Novacek <rnovacek@redhat.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB. If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <polkit/polkit.h>

#include "polkit-tqt-authority.h"


namespace PolkitTQt
{

//--------------------------------------
// General functions
//--------------------------------------

Authority *Authority::m_theAuthority = nullptr;

Authority* Authority::instance(PolkitAuthority *authority)
{
  if (!m_theAuthority)
  {
    m_theAuthority = new Authority();
  }
  return m_theAuthority;
}

Authority::Result polkitResultToResult(PolkitAuthorizationResult *result)
{
  if (polkit_authorization_result_get_is_challenge(result))
  {
    return Authority::Challenge;
  }
  else if (polkit_authorization_result_get_is_authorized(result))
  {
    return Authority::Yes;
  }
  else
  {
    return Authority::No;
  }
}

ActionDescription::List actionsToListAndFree(GList *glist)
{
  ActionDescription::List result;
  for (GList *glist2 = glist; glist2; glist2 = g_list_next(glist2))
  {
    gpointer i = glist2->data;
    result.append(ActionDescription(static_cast<PolkitActionDescription*>(i)));
    if (i)
    {
      g_object_unref(i);
    }
  }
  g_list_free(glist);
  return result;
}

//--------------------------------------
// Authority::Private
//--------------------------------------

class Authority::Private
{
  public:
    Private(Authority *qq) : q(qq), pkAuthority(nullptr), m_hasError(false)
    {
    }

    ~Private();

    void init();

    /** Use this method to set the error message to \p message. Set recover to \c true
     * to try to reinitialize this object with init() method
     */
    void setError(ErrorCode code, const TQString &details = TQString::null, bool recover = false);

    Authority *q;
    PolkitAuthority *pkAuthority;
    bool m_hasError;
    ErrorCode m_lastError;
    TQString m_errorDetails;

    GCancellable *m_checkAuthorizationCancellable;
    GCancellable *m_enumerateActionsCancellable;
    GCancellable *m_registerAuthenticationAgentCancellable;
    GCancellable *m_unregisterAuthenticationAgentCancellable;
    GCancellable *m_authenticationAgentResponseCancellable;
    GCancellable *m_enumerateTemporaryAuthorizationsCancellable;
    GCancellable *m_revokeTemporaryAuthorizationsCancellable;
    GCancellable *m_revokeTemporaryAuthorizationCancellable;

    static void pk_config_changed();
    static void checkAuthorizationCallback(GObject *object, GAsyncResult *result, gpointer user_data);
    static void enumerateActionsCallback(GObject *object, GAsyncResult *result, gpointer user_data);
    static void registerAuthenticationAgentCallback(GObject *object, GAsyncResult *result, gpointer user_data);
    static void unregisterAuthenticationAgentCallback(GObject *object, GAsyncResult *result, gpointer user_data);
    static void authenticationAgentResponseCallback(GObject *object, GAsyncResult *result, gpointer user_data);
    static void enumerateTemporaryAuthorizationsCallback(GObject *object, GAsyncResult *result, gpointer user_data);
    static void revokeTemporaryAuthorizationsCallback(GObject *object, GAsyncResult *result, gpointer user_data);
    static void revokeTemporaryAuthorizationCallback(GObject *object, GAsyncResult *result, gpointer user_data);
};

Authority::Private::~Private()
{
  if (m_checkAuthorizationCancellable)
  {
    g_object_unref(m_checkAuthorizationCancellable);
  }
  if (m_enumerateActionsCancellable)
  {
    g_object_unref(m_enumerateActionsCancellable);
  }
  if (m_registerAuthenticationAgentCancellable)
  {
    g_object_unref(m_registerAuthenticationAgentCancellable);
  }
  if (m_unregisterAuthenticationAgentCancellable)
  {
    g_object_unref(m_unregisterAuthenticationAgentCancellable);
  }
  if (m_authenticationAgentResponseCancellable)
  {
    g_object_unref(m_authenticationAgentResponseCancellable);
  }
  if (m_enumerateTemporaryAuthorizationsCancellable)
  {
    g_object_unref(m_enumerateTemporaryAuthorizationsCancellable);
  }
  if (m_revokeTemporaryAuthorizationsCancellable)
  {
    g_object_unref(m_revokeTemporaryAuthorizationsCancellable);
  }
  if (m_revokeTemporaryAuthorizationCancellable)
  {
    g_object_unref(m_revokeTemporaryAuthorizationCancellable);
  }
}

void Authority::Private::init()
{
  m_checkAuthorizationCancellable = g_cancellable_new();
  m_enumerateActionsCancellable = g_cancellable_new();
  m_registerAuthenticationAgentCancellable = g_cancellable_new();
  m_unregisterAuthenticationAgentCancellable = g_cancellable_new();
  m_authenticationAgentResponseCancellable = g_cancellable_new();
  m_enumerateTemporaryAuthorizationsCancellable = g_cancellable_new();
  m_revokeTemporaryAuthorizationsCancellable = g_cancellable_new();
  m_revokeTemporaryAuthorizationCancellable = g_cancellable_new();

  GError *gerror = nullptr;
  if (pkAuthority == nullptr)
  {
    pkAuthority = polkit_authority_get_sync(nullptr, &gerror);
    if (gerror != nullptr)
    {
      setError(E_GetAuthority, gerror->message);
      g_error_free(gerror);
      return;
    }
  }

  if (pkAuthority == nullptr)
  {
    return;
  }

  // connect changed signal
  g_signal_connect(G_OBJECT(pkAuthority), "changed", G_CALLBACK(pk_config_changed), nullptr);
}

void Authority::Private::setError(Authority::ErrorCode code, const TQString &details, bool recover)
{
  if (recover)
  {
    init();
  }
  m_lastError = code;
  m_errorDetails = details;
  m_hasError = true;
}

//--------------------------------------
// Authority
//--------------------------------------

Authority::Authority(TQObject *parent) : TQObject(parent), d(new Private(this))
{
  d->init();
}

Authority::~Authority()
{
  if (d->pkAuthority)
  {
    g_object_unref(d->pkAuthority);
  }
  delete d;
}

bool Authority::hasError() const
{
  return d->m_hasError;
}

Authority::ErrorCode Authority::lastError() const
{
  return d->m_lastError;
}

const TQString Authority::errorDetails() const
{
  if (d->m_lastError == E_None)
  {
    return TQString::null;
  }
  else
  {
    return d->m_errorDetails;
  }
}

void Authority::clearError()
{
  d->m_hasError = false;
  d->m_lastError = E_None;
}

void Authority::Private::pk_config_changed()
{
  emit Authority::instance()->configChanged();
}

PolkitAuthority* Authority::polkitAuthority() const
{
  return d->pkAuthority;
}

Authority::Result Authority::checkAuthorizationSync(const TQString &actionId,
        const Subject &subject, AuthorizationFlags flags)
{
  if (Authority::instance()->hasError())
  {
    return Unknown;
  }

  if (!subject.isValid())
  {
    d->setError(E_WrongSubject);
    return Unknown;
  }

  GError *error = nullptr;
  PolkitAuthorizationResult *pk_result = polkit_authority_check_authorization_sync(d->pkAuthority,
          subject.subject(), actionId.ascii(), nullptr, (PolkitCheckAuthorizationFlags)(int)flags,
          nullptr, &error);
  if (error != nullptr)
  {
    d->setError(E_CheckFailed, error->message);
    g_error_free(error);
    return Unknown;
  }

  if (!pk_result)
  {
    d->setError(E_UnknownResult);
    return Unknown;
  }
  else
  {
    Authority::Result res = polkitResultToResult(pk_result);
    g_object_unref(pk_result);
    return res;
  }
}

void Authority::checkAuthorization(const TQString &actionId, const Subject &subject, AuthorizationFlags flags)
{
  if (Authority::instance()->hasError())
  {
    return;
  }

  if (!subject.isValid())
  {
    d->setError(E_WrongSubject);
    return;
  }

  polkit_authority_check_authorization(d->pkAuthority, subject.subject(), actionId.ascii(), nullptr,
          (PolkitCheckAuthorizationFlags)(int)flags, d->m_checkAuthorizationCancellable,
          d->checkAuthorizationCallback, this);
}

void Authority::Private::checkAuthorizationCallback(GObject *object, GAsyncResult *result, gpointer user_data)
{
  Authority *authority = (Authority*)user_data;
  if (!authority)
  {
    return;
  }

  GError *error = nullptr;
  PolkitAuthorizationResult *pkResult = polkit_authority_check_authorization_finish(
          (PolkitAuthority*)object, result, &error);
  if (error != nullptr)
  {
    // We don't want to set error if this is cancellation of some action
    if (error->code != 1)
    {
      authority->d->setError(E_CheckFailed, error->message);
    }
    g_error_free(error);
    return;
  }
  if (pkResult != nullptr)
  {
    emit authority->checkAuthorizationFinished(polkitResultToResult(pkResult));
    g_object_unref(pkResult);
  }
  else
  {
    authority->d->setError(E_UnknownResult);
  }
}

void Authority::checkAuthorizationCancel()
{
  if (!g_cancellable_is_cancelled(d->m_checkAuthorizationCancellable))
  {
    g_cancellable_cancel(d->m_checkAuthorizationCancellable);
  }
}

ActionDescription::List Authority::enumerateActionsSync()
{
  if (Authority::instance()->hasError())
  {
    return ActionDescription::List();
  }

  GError *error = nullptr;
  GList *glist = polkit_authority_enumerate_actions_sync(d->pkAuthority, nullptr, &error);
  if (error != nullptr)
  {
    d->setError(E_EnumFailed, error->message);
    g_error_free(error);
    return ActionDescription::List();
  }

  return actionsToListAndFree(glist);
}

void Authority::enumerateActions()
{
  if (Authority::instance()->hasError())
  {
    return;
  }

  polkit_authority_enumerate_actions(d->pkAuthority, d->m_enumerateActionsCancellable,
          d->enumerateActionsCallback, Authority::instance());
}

void Authority::Private::enumerateActionsCallback(GObject *object, GAsyncResult *result, gpointer user_data)
{
  Authority *authority = (Authority*)user_data;
  if (!authority)
  {
    return;
  }
  GError *error = nullptr;
  GList *list = polkit_authority_enumerate_actions_finish((PolkitAuthority*)object, result, &error);
  if (error != nullptr)
  {
    // We don't want to set error if this is cancellation of some action
    if (error->code != 1)
    {
      authority->d->setError(E_EnumFailed, error->message);
    }
    g_error_free(error);
    return;
  }

  emit authority->enumerateActionsFinished(actionsToListAndFree(list));
}

void Authority::enumerateActionsCancel()
{
  if (!g_cancellable_is_cancelled(d->m_enumerateActionsCancellable))
  {
    g_cancellable_cancel(d->m_enumerateActionsCancellable);
  }
}

bool Authority::registerAuthenticationAgentSync(const Subject &subject, const TQString &locale,
        const TQString &objectPath)
{
  if (Authority::instance()->hasError())
  {
    return false;
  }

  if (!subject.isValid())
  {
    d->setError(E_WrongSubject);
    return false;
  }

  GError *error = nullptr;
  gboolean result = polkit_authority_register_authentication_agent_sync(d->pkAuthority,
          subject.subject(), locale.ascii(), objectPath.ascii(), nullptr, &error);
  if (error)
  {
    d->setError(E_RegisterFailed, error->message);
    g_error_free(error);
    return false;
  }

  return result;
}

void Authority::registerAuthenticationAgent(const Subject &subject, const TQString &locale,
        const TQString &objectPath)
{
  if (Authority::instance()->hasError())
  {
    return;
  }

  if (!subject.isValid())
  {
    d->setError(E_WrongSubject);
    return;
  }

  polkit_authority_register_authentication_agent(d->pkAuthority, subject.subject(), locale.ascii(),
          objectPath.ascii(), d->m_registerAuthenticationAgentCancellable,
          d->registerAuthenticationAgentCallback, this);
}

void Authority::Private::registerAuthenticationAgentCallback(GObject *object, GAsyncResult *result,
        gpointer user_data)
{
  Authority *authority = (Authority*)user_data;
  if (!authority)
  {
    return;
  }
  GError *error = nullptr;
  bool res = polkit_authority_register_authentication_agent_finish((PolkitAuthority*)object, result, &error);
  if (error != nullptr)
  {
    // We don't want to set error if this is cancellation of some action
    if (error->code != 1)
    {
      authority->d->setError(E_EnumFailed , error->message);
    }
    g_error_free(error);
    return;
  }

  emit authority->registerAuthenticationAgentFinished(res);
}

void Authority::registerAuthenticationAgentCancel()
{
  if (!g_cancellable_is_cancelled(d->m_registerAuthenticationAgentCancellable))
  {
    g_cancellable_cancel(d->m_registerAuthenticationAgentCancellable);
  }
}

bool Authority::unregisterAuthenticationAgentSync(const Subject &subject, const TQString &objectPath)
{
  if (d->pkAuthority)
  {
    return false;
  }

  if (!subject.isValid())
  {
    d->setError(E_WrongSubject);
    return false;
  }

  GError *error = nullptr;
  bool result = polkit_authority_unregister_authentication_agent_sync(d->pkAuthority,
          subject.subject(), objectPath.utf8().data(), nullptr, &error);
  if (error != nullptr)
  {
    d->setError(E_UnregisterFailed, error->message);
    g_error_free(error);
    return false;
  }

  return result;
}

void Authority::unregisterAuthenticationAgent(const Subject &subject, const TQString &objectPath)
{
  if (Authority::instance()->hasError())
  {
    return;
  }

  if (!subject.isValid())
  {
    d->setError(E_WrongSubject);
    return;
  }

  polkit_authority_unregister_authentication_agent(d->pkAuthority, subject.subject(),
          objectPath.utf8().data(), d->m_unregisterAuthenticationAgentCancellable,
          d->unregisterAuthenticationAgentCallback, this);
}

void Authority::Private::unregisterAuthenticationAgentCallback(GObject *object,
        GAsyncResult *result, gpointer user_data)
{
  Authority *authority = (Authority*)user_data;
  if (!authority)
  {
    return;
  }
  GError *error = nullptr;
  bool res = polkit_authority_unregister_authentication_agent_finish((PolkitAuthority*)object,
          result, &error);
  if (error != nullptr)
  {
    // We don't want to set error if this is cancellation of some action
    if (error->code != 1)
    {
      authority->d->setError(E_UnregisterFailed, error->message);
    }
    g_error_free(error);
    return;
  }

  emit authority->unregisterAuthenticationAgentFinished(res);
}

void Authority::unregisterAuthenticationAgentCancel()
{
  if (!g_cancellable_is_cancelled(d->m_unregisterAuthenticationAgentCancellable))
  {
    g_cancellable_cancel(d->m_unregisterAuthenticationAgentCancellable);
  }
}

bool Authority::authenticationAgentResponseSync(const TQString &cookie, const Identity &identity)
{
  if (Authority::instance()->hasError())
  {
    return false;
  }

  if (cookie.isEmpty() || !identity.isValid())
  {
    d->setError(E_CookieOrIdentityEmpty);
    return false;
  }

  GError *error = nullptr;
  bool result = polkit_authority_authentication_agent_response_sync(d->pkAuthority,
          cookie.utf8().data(), identity.identity(), nullptr, &error);
  if (error != nullptr)
  {
    d->setError(E_AgentResponseFailed, error->message);
    g_error_free(error);
    return false;
  }

  return result;
}

void Authority::authenticationAgentResponse(const TQString &cookie, const Identity &identity)
{
  if (Authority::instance()->hasError())
  {
    return;
  }

  if (cookie.isEmpty() || !identity.isValid())
  {
    d->setError(E_CookieOrIdentityEmpty);
    return;
  }

  polkit_authority_authentication_agent_response(d->pkAuthority, cookie.utf8().data(),
          identity.identity(), d->m_authenticationAgentResponseCancellable,
          d->authenticationAgentResponseCallback, this);
}

void Authority::Private::authenticationAgentResponseCallback(GObject *object, GAsyncResult *result,
        gpointer user_data)
{
  Authority *authority = (Authority*)user_data;
  if (!authority)
  {
    return;
  }
  GError *error = nullptr;
  bool res = polkit_authority_authentication_agent_response_finish((PolkitAuthority*)object, result,
          &error);
  if (error != nullptr)
  {
    // We don't want to set error if this is cancellation of some action
    if (error->code != 1)
    {
      authority->d->setError(E_AgentResponseFailed, error->message);
    }
    g_error_free(error);
    return;
  }

  emit authority->authenticationAgentResponseFinished(res);
}

void Authority::authenticationAgentResponseCancel()
{
  if (!g_cancellable_is_cancelled(d->m_authenticationAgentResponseCancellable))
  {
    g_cancellable_cancel(d->m_authenticationAgentResponseCancellable);
  }
}

TemporaryAuthorization::List Authority::enumerateTemporaryAuthorizationsSync(const Subject &subject)
{
  TemporaryAuthorization::List result;

  GError *error = nullptr;
  GList *glist = polkit_authority_enumerate_temporary_authorizations_sync(d->pkAuthority,
          subject.subject(), nullptr, &error);
  if (error != nullptr)
  {
    d->setError(E_EnumFailed, error->message);
    g_error_free(error);
    return result;
  }

  GList *glist2;
  for (glist2 = glist; glist2 != nullptr; glist2 = g_list_next(glist2))
  {
    result.append(TemporaryAuthorization((PolkitTemporaryAuthorization*)glist2->data));
    if (glist2->data)
    {
      g_object_unref(glist2->data);
    }
  }
  g_list_free(glist);
  return result;
}

void Authority::Private::enumerateTemporaryAuthorizationsCallback(GObject *object,
        GAsyncResult *result, gpointer user_data)
{
  Authority *authority = (Authority*)user_data;
  if (!authority)
  {
    return;
  }

  GError *error = nullptr;
  GList *glist = polkit_authority_enumerate_temporary_authorizations_finish((PolkitAuthority*)object,
          result, &error);
  if (error != nullptr)
  {
    // We don't want to set error if this is cancellation of some action
    if (error->code != 1)
    {
      authority->d->setError(E_EnumFailed, error->message);
    }
    g_error_free(error);
    return;
  }
  TemporaryAuthorization::List res;
  GList *glist2;
  for (glist2 = glist; glist2 != nullptr; glist2 = g_list_next(glist2))
  {
    res.append(TemporaryAuthorization((PolkitTemporaryAuthorization*)glist2->data));
    if (glist2->data)
    {
      g_object_unref(glist2->data);
    }
  }
  g_list_free(glist);
  emit authority->enumerateTemporaryAuthorizationsFinished(res);
}

void Authority::enumerateTemporaryAuthorizationsCancel()
{
  if (!g_cancellable_is_cancelled(d->m_enumerateTemporaryAuthorizationsCancellable))
  {
    g_cancellable_cancel(d->m_enumerateTemporaryAuthorizationsCancellable);
  }
}

bool Authority::revokeTemporaryAuthorizationsSync(const Subject &subject)
{
  if (Authority::instance()->hasError())
  {
    return false;
  }

  GError *error = nullptr;
  bool result = polkit_authority_revoke_temporary_authorizations_sync(d->pkAuthority,
          subject.subject(), nullptr, &error);
  if (error != nullptr)
  {
    d->setError(E_RevokeFailed, error->message);
    g_error_free(error);
    return false;
  }
  return result;
}

void Authority::revokeTemporaryAuthorizations(const Subject &subject)
{
  if (Authority::instance()->hasError())
  {
    return;
  }

  polkit_authority_revoke_temporary_authorizations(d->pkAuthority, subject.subject(),
          d->m_revokeTemporaryAuthorizationsCancellable,
          d->revokeTemporaryAuthorizationsCallback, this);
}

void Authority::Private::revokeTemporaryAuthorizationsCallback(GObject *object,
        GAsyncResult *result, gpointer user_data)
{
  Authority *authority = (Authority*)user_data;
  if (!authority)
  {
    return;
  }

  GError *error = nullptr;
  bool res = polkit_authority_revoke_temporary_authorizations_finish((PolkitAuthority*)object, result, &error);
  if (error != nullptr)
  {
    // We don't want to set error if this is cancellation of some action
    if (error->code != 1)
    {
      authority->d->setError(E_RevokeFailed, error->message);
    }
    g_error_free(error);
    return;
  }

  emit authority->revokeTemporaryAuthorizationsFinished(res);
}

void Authority::revokeTemporaryAuthorizationsCancel()
{
  if (!g_cancellable_is_cancelled(d->m_revokeTemporaryAuthorizationsCancellable))
  {
    g_cancellable_cancel(d->m_revokeTemporaryAuthorizationsCancellable);
  }
}

bool Authority::revokeTemporaryAuthorizationSync(const TQString &id)
{
  if (Authority::instance()->hasError())
  {
    return false;
  }

  GError *error = nullptr;
  bool result =  polkit_authority_revoke_temporary_authorization_by_id_sync(d->pkAuthority,
          id.utf8().data(), nullptr, &error);
  if (error != nullptr)
  {
    d->setError(E_RevokeFailed, error->message);
    g_error_free(error);
    return false;
  }
  return result;
}

void Authority::revokeTemporaryAuthorization(const TQString &id)
{
  if (Authority::instance()->hasError())
  {
    return;
  }

  polkit_authority_revoke_temporary_authorization_by_id(d->pkAuthority, id.utf8().data(),
      d->m_revokeTemporaryAuthorizationCancellable,
      d->revokeTemporaryAuthorizationCallback, this);
}

void Authority::Private::revokeTemporaryAuthorizationCallback(GObject *object,
        GAsyncResult *result, gpointer user_data)
{
  Authority *authority = (Authority*)user_data;
  if (!authority)
  {
    return;
  }

  GError *error = nullptr;
  bool res = polkit_authority_revoke_temporary_authorization_by_id_finish((PolkitAuthority*)object,
          result, &error);
  if (error != nullptr)
  {
    // We don't want to set error if this is cancellation of some action
    if (error->code != 1)
    {
      authority->d->setError(E_RevokeFailed, error->message);
    }
    g_error_free(error);
    return;
  }

  emit authority->revokeTemporaryAuthorizationFinished(res);
}

void Authority::revokeTemporaryAuthorizationCancel()
{
  if (!g_cancellable_is_cancelled(d->m_revokeTemporaryAuthorizationCancellable))
  {
    g_cancellable_cancel(d->m_revokeTemporaryAuthorizationCancellable);
  }
}

}

#include "polkit-tqt-authority.moc"
