/*
 * Copyright (C) 2020 Uniontech Technology Co., Ltd.
 *
 * Author:     xinbo wang <wangxinbo@uniontech.com>
 *
 * Maintainer: xinbo wang <wangxinbo@uniontech.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "dnd_security.h"
#include "x11_dnd.h"
#include "wayland_dnd.h"
#include "log.h"
#include <string.h>
#include <dbus/dbus.h>

#define DBUS_USEC_SERVICE           "org.deepin.usec1"
#define DBUS_USEC_OBJ               "/org/deepin/usec1/AccessControl"
#define DBUS_USEC_INTF              "org.deepin.usec1.AccessControl"

DndSecurityPtr pDndSec;

bool isWaylandSession()
{
    if (!pDndSec)
        return pDndSec->isWayland;

    return false;
}

static int handle_security_verify(void *data, int type, int client, int target)
{
    // do somthting  client target
    // success return Permission::PermissionAllow
    // failed return Permission::PermissionDeny

    if (client == 13740 && target == 13786) {
        return PermissionDeny;
    }
    return PermissionAllow;
}

bool isAllowAce()
{
    DBusError err;
    dbus_error_init(&err);
    DBusConnection *conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
    if (dbus_error_is_set(&err)) {
        dbus_error_free(&err);
        return true;
    }
    DBusMessageIter args;
    int n = getpid();
    DBusMessage *message = dbus_message_new_method_call(DBUS_USEC_SERVICE, DBUS_USEC_OBJ, DBUS_USEC_INTF, "CanAuthorize");
    if (message == NULL)
        return true;
    char str[] = "clipboard";
    char *buff = str;
    dbus_message_iter_init_append(message, &args);
    dbus_message_iter_append_basic(&args, DBUS_TYPE_INT32, &n);
    dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &buff);

    dbus_error_init(&err);
    DBusMessage *reply = dbus_connection_send_with_reply_and_block(conn, message, -1, &err);
    if (dbus_error_is_set(&err)) {
        dbus_error_free(&err);
        return true;
    }

    DBusMessageIter array;
    DBusMessageIter replyIter;
    dbus_message_iter_init(reply, &replyIter);
    char *value;
    bool isAllow = false;
    dbus_message_iter_recurse(&replyIter, &array);
    while (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) {
        dbus_message_iter_get_basic(&array, &value);
        dbus_message_iter_next(&array);
        if (strcmp(value, "allow") == 0) {
            isAllow = true;
            break;
        }
    }

    dbus_message_unref(message);
    dbus_message_unref(reply);
    dbus_connection_unref(conn);

    return isAllow;
}

int InitDtkDisplay()
{
    if (!isAllowAce())
        return -1;

    pDndSec = (DndSecurityPtr)malloc(sizeof(DndSecurity));
    memset(pDndSec, 0, sizeof(DndSecurity));

    if (!pDndSec) {
        log_error("malloc dnd security failed \n");
        return -1;
    }

    if (!pDndSec->xdgSessionType) {
        pDndSec->xdgSessionType = getenv("XDG_SESSION_TYPE");
        pDndSec->isWayland = (pDndSec->xdgSessionType && strcmp(pDndSec->xdgSessionType, "wayland") == 0 ? true : false);
    }

    if(pDndSec->isWayland) {
        log_debug("current enviroment is wayland");
        pDndSec->InitBackend = initWaylandDnd;
        pDndSec->DestroyBackend = destoryWaylandDnd;
        pDndSec->GetSecuritySession = wGetSecuritySession;
        pDndSec->DestroySecuritySession = wDestroySecuritySession;
        pDndSec->ReportSecurityVerified = wReportSecurityVerified;
        pDndSec->GetSecurityClients = wGetSecurityClients;
    } else {
        log_debug("current enviroment is x11");
        pDndSec->InitBackend = initX11Dnd;
        pDndSec->DestroyBackend = destoryX11Dnd;
        pDndSec->GetSecuritySession = xGetSecuritySession;
        pDndSec->DestroySecuritySession = xDestroySecuritySession;
        pDndSec->ReportSecurityVerified = xReportSecurityVerified;
        pDndSec->GetSecurityClients = xGetSecurityClients;
    }
    DoSecurityVerifyCallback(NULL, handle_security_verify);
    log_debug("now init backend");
    return pDndSec->InitBackend();
}

void DestoryDtkDisplay()
{
    if (!pDndSec) {
        log_error("dnd security has been destroyed \n");
        return;
    }

    pDndSec->DestroyBackend();
    free(pDndSec);
}

int GetSecuritySession(SessionType types)
{
    return pDndSec->GetSecuritySession(types);
}

int DestroySecuritySession(int session)
{
    return pDndSec->DestroySecuritySession(session);
}

void DoSecurityVerifyCallback(void* data, DoSecurityVerifyPtr func)
{
    if (!func) {
        log_error("security verify callback is NULL \n");
        return;
    }

    pDndSec->userData = data;
    pDndSec->DoSecurityVerify = func;
}

void ReportSecurityVerified(int session, Permission result)
{
    pDndSec->ReportSecurityVerified(session, result);
}

struct dtk_array* GetSecurityClients()
{
    pDndSec->GetSecurityClients();
}
