/*
 * 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 "dde_security_interface.h"
#include "log.h"

#include <assert.h>

static WaylandDndBackend *waylandDndBackend(DndSecurity *dnd_sec)
{
    if (!dnd_sec) {
        log_error("need init dtkdisplay content");
        return NULL;
    }

    return pDndSec->backend;
}

DdeSecurity *_ddeSecurity(DndSecurity* dnd_sec)
{
    WaylandDndBackendPtr backend = waylandDndBackend(dnd_sec);
    if (!backend) {
        log_error("dde security extra backend is null");
        return NULL;
    }

    return backend->dde_security;
}

DdeSecurity *ddeSecurity()
{
    return _ddeSecurity(pDndSec);
}

static DndSession *session_id2dnd(DdeSecurity *security, uint32_t session)
{
    if (!security) {
        log_error("wayland dnd security backend is NULL \n");
        return NULL;
    }

    DndSession *e;
    wl_list_for_each(e, &security->sessions, link) {
        if (e && e->seesionId == session) {
           log_debug("remove session id: %d \n", session);
           return e;
        }
    }

    return NULL;
}

static DndSession *session_ss2dnd(DdeSecurity *security, struct security_session *ss)
{
    if (!security) {
        log_error("wayland dnd security backend is NULL \n");
        return NULL;
    }

    DndSession *e;
    wl_list_for_each(e, &security->sessions, link) {
        if (e && e->ddeSession == ss) {
           log_debug("session id: %d \n", e->seesionId);
           return e;
        }
    }

    return NULL;
}

void handle_ace_clients_callback(void *data,
            struct dde_security *dde_security,
            uint32_t count,
            struct wl_array *clients)
{
    DndSecurity *dnd = (DndSecurity*)data;
    if (!dnd) {
        log_error("need init dtkdisplay content");
        return;
    }

    WaylandDndBackend *backend = dnd->backend;
    if (!backend) {
        log_error("dde security extra backend is null");
        return;
    }

    DdeSecurity *security = backend->dde_security;
    if (!security) {
        log_error("client management get pointer failed");
        return;
    }

    if (clients->size > 0 && ((clients->size % sizeof(uint32_t)) == 0)) {
        if (security->ace_clients == NULL) {
            security->ace_clients = malloc(sizeof(struct dtk_array));
        } else {
            dtk_array_release(security->ace_clients);
            free(security->ace_clients);

            security->ace_clients = malloc(sizeof(struct dtk_array));
        }

        dtk_array_init(security->ace_clients);
        dtk_array_copy(security->ace_clients, (uint32_t*)clients);
    } else {
        log_error("receive ace clients event error");
    }
}

void handle_verify_security(void *data,
            struct security_session *security_session,
            uint32_t types,
            uint32_t client,
            uint32_t target,
            uint32_t serial)
{
    DndSecurityPtr p_dnd_sec = (DndSecurityPtr)data;
    if (!p_dnd_sec) {
        log_error("dnd security pointer is NULL");
        return;
    }

    if (!p_dnd_sec->DoSecurityVerify) {
        log_error("dnd security verify callback function is NULL \n");
        return;
    }

    WaylandDndBackendPtr pWayland = p_dnd_sec->backend;
    if (!pWayland) {
        log_error("dnd security wayland backend is nullptr \n");
        return;
    }

    DndSession *e = session_ss2dnd(pWayland->dde_security, security_session);
    if (!e) {
        log_error("verify security get dnd session failed \n");
        return;
    }

    e->types = types;
    e->client = client;
    e->target = target;
    e->serial = serial;
    e->result = p_dnd_sec->DoSecurityVerify(p_dnd_sec->userData, types, client, target);

    if (e->result != PermissionDelay) {
        security_session_report_security(e->ddeSession, types, e->result, serial);
    }

    log_debug("security verify==client: %d, target: %d, result: %d== \n", client, target, e->result);
}

struct dde_security_listener security_listener =  {
    .ace_clients = handle_ace_clients_callback,
};

struct security_session_listener session_listener =  {
    .verify_security = handle_verify_security,
};

DdeSecurity *init_dde_security()
{
    DdeSecurity *dde_security = (DdeSecurity*)malloc(sizeof(DdeSecurity));
    memset(dde_security, 0, sizeof(DdeSecurity));

    if (dde_security) {
        wl_list_init(&dde_security->sessions);
    }

    dde_security->ace_clients = NULL;

    return dde_security;
}

void destory_dde_security(DdeSecurity *security)
{
    if (!security) {
        log_error("wayland backend has been destroyed \n");
        return;
    }

    if (!security->ddeSecurity) {
        log_error("dde Security protocol is null \n");
        return;
    }

    if (security->ace_clients) {
        dtk_array_release(security->ace_clients);
        free(security->ace_clients);
        security->ace_clients = NULL;
    }

    // security destory
    dde_security_destroy(security->ddeSecurity);

    // session destroy
    DndSession *e, *tmp;
    wl_list_for_each_safe(e, tmp, &security->sessions, link) {
       security_session_destroy(e->ddeSession);
       wl_list_remove(e);
       free(e);
    }

    free(security);
    memset(security, 0, sizeof(DdeSecurity));
}

void handle_dde_security(void *data,
                         void *wl_registry,
                         uint32_t name,
                         const char *interface,
                         uint32_t version)
{
    if (strcmp(interface, dde_security_interface.name) == 0) {
        WaylandDndBackend *backend= waylandDndBackend(data);
        if (backend && backend->dde_security == NULL)
            backend->dde_security = init_dde_security();

        if (backend->dde_security) {
            DdeSecurity *security = (DdeSecurity*)backend->dde_security;

            if (security && security->ddeSecurity == NULL) {
                security->ddeSecurity = wl_registry_bind(wl_registry, name, &dde_security_interface, 1);
                dde_security_add_listener(security->ddeSecurity, &security_listener, pDndSec);
                dde_security_get_ace_clients(security->ddeSecurity, getpid());
                wl_display_roundtrip(backend->display);
            }
        }
    }
}

int get_security_session(SessionType types)
{
    DdeSecurity *security = ddeSecurity();
    if (!security) {
        log_error("security protocol is NULL \n");
        return -1;
    }

    if (!security->ddeSecurity) {
        log_error("no dnd security protocol available \n");
        return -1;
    }

    struct security_session *session =  dde_security_get_session(security->ddeSecurity, types);
    wl_display_flush(((WaylandDndBackendPtr)pDndSec->backend)->display);
    security_session_add_listener(session, &session_listener, pDndSec);

    DndSession *s = malloc(sizeof (DndSession));
    uint32_t id = wl_list_length(&security->sessions) + 1;
    s->seesionId = id;
    s->ddeSession = session;
    wl_list_insert(&security->sessions, &s->link);

    return id;
}

int destroy_security_session(int session)
{
    DdeSecurity *security = ddeSecurity();
    if (!security) {
        log_error("destory security session wayland backend is NULL \n");
        return -1;
    }

    if(wl_list_length(&security->sessions) == 0) {
        log_warn("session is empty \n");
        return -1;
    }

    DndSession *e = session_id2dnd(security, session);
    if (e) {
        security_session_destroy(e->ddeSession);
        wl_list_remove(&e->link);
        free(e);
    }

    return 0;
}

void report_security_verified(int session, Permission result)
{
    DdeSecurity *security = ddeSecurity();
    if (!security) {
        log_error("report security verified wayland backend is NULL \n");
        return;
    }

    DndSession *e = session_id2dnd(security, session);
    if (e) {
        if (e->result != PermissionDelay) {
            log_warn("already reported security verified\n");
            return;
        }
        security_session_report_security(e->ddeSession, e->types, result, e->serial);
    }
}

struct dtk_array* get_security_clients()
{
    DdeSecurity *security = ddeSecurity();
    if (!security) {
        log_error("report security verified wayland backend is NULL \n");
        return NULL;
    }

    return security->ace_clients;
}
