/*
 * Copyright (C) 2023 Uniontech Technology Co., Ltd.
 *
 * Author:     zhijie geng <gengzhijie@uniontech.com>
 *
 * Maintainer: zhijie geng <gengzhiejie@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 "client.h"
#include "session.h"

#include <X11/SM/SMlib.h>
#include <xcb/xcb.h>

#include <sys/types.h>
#include <sys/time.h>
#include <sys/prctl.h>
#include <unistd.h>
#include <pwd.h>

UsmData *data = new UsmData();

static void save_yourself(SmcConn smc_conn, SmPointer client_data,int save_type,
                          Bool shutdown, int interact_style, Bool fast)
{
    Q_UNUSED(client_data);
    Q_UNUSED(save_type);
    Q_UNUSED(shutdown);
    Q_UNUSED(interact_style);
    Q_UNUSED(fast);

    SmProp props[4];
    SmPropValue *propvalue_0 = new SmPropValue();
    SmPropValue *propvalue_1 = new SmPropValue();
    SmPropValue *propvalue_2 = new SmPropValue[3]();
    SmPropValue *propvalue_3 = new SmPropValue();

    // props[0]
    prctl(PR_GET_NAME, data->processName, 0, 0, 0);
    propvalue_0->length = strlen(data->processName);
    propvalue_0->value = reinterpret_cast<SmPointer>(data->processName);
    props[0].name = const_cast<char *>(SmProgram);
    props[0].type = const_cast<char *>(SmARRAY8);
    props[0].num_vals = 1;
    props[0].vals = propvalue_0;

    // props[1]
    propvalue_1->length = data->entry != nullptr ? static_cast<int>(strlen(data->entry->pw_name)) : 0;
    propvalue_1->value = reinterpret_cast<SmPointer>(const_cast<char *>(data->entry != nullptr ? data->entry->pw_name : ""));
    props[1].name = const_cast<char *>(SmUserID);
    props[1].type = const_cast<char *>(SmARRAY8);
    props[1].num_vals = 1;
    props[1].vals = propvalue_1;

    // props[2]
    propvalue_2[0].length = data->program.length();
    propvalue_2[0].value = reinterpret_cast<SmPointer>(const_cast<char *>(data->program.c_str()));
    propvalue_2[1].length = strlen("--sessionfile");
    propvalue_2[1].value = reinterpret_cast<SmPointer>(const_cast<char *>("--sessionfile"));
    std::string session = std::string(data->clientId) + "_" + std::to_string(data->tv.tv_sec) + "_" + std::to_string(data->tv.tv_usec);
    propvalue_2[2].length = session.length();
    propvalue_2[2].value = reinterpret_cast<SmPointer>(const_cast<char *>(((session.c_str()))));
    props[2].name = const_cast<char *>(SmRestartCommand);
    props[2].type = const_cast<char *>(SmLISTofARRAY8);
    props[2].num_vals = 3;
    props[2].vals = propvalue_2;

    // props[3]
    propvalue_3->length = data->program.length();
    propvalue_3->value = reinterpret_cast<SmPointer>(const_cast<char *>(data->program.c_str()));
    props[3].name = const_cast<char *>(SmCloneCommand);
    props[3].type = const_cast<char *>(SmLISTofARRAY8);
    props[3].num_vals = 1;
    props[3].vals = propvalue_3;

    SmProp *p[4] = { &props[0], &props[1], &props[2], &props[3] };
    SmcSetProperties(smc_conn, 4, p);

    delete propvalue_0;
    delete propvalue_1;
    delete[] propvalue_2;
    delete propvalue_3;
}

static void die(SmcConn smc_conn, SmPointer client_data)
{
    Q_UNUSED(smc_conn);
    Q_UNUSED(client_data);

    char *prop_names[4] = { const_cast<char *>(SmProgram),
                            const_cast<char *>(SmUserID),
                            const_cast<char *>(SmRestartCommand),
                            const_cast<char *>(SmCloneCommand) };
    SmcDeleteProperties(smc_conn, 4, prop_names);
    SmcCloseConnection(smc_conn, 0, nullptr);
    exit(0);
}

static void save_complete(SmcConn smc_conn, SmPointer client_data)
{
    Q_UNUSED(smc_conn);
    Q_UNUSED(client_data);

    SmcSaveYourselfDone(smc_conn, true);
}

static void shutdown_cancelled(SmcConn smc_conn, SmPointer client_data)
{
    Q_UNUSED(smc_conn);
    Q_UNUSED(client_data);
}

int connectSM(unsigned long long wid)
{
    data->callbacks.save_yourself.callback = &save_yourself;
    data->callbacks.die.callback = &die;
    data->callbacks.save_complete.callback = &save_complete;
    data->callbacks.shutdown_cancelled.callback = &shutdown_cancelled;
    if (data->program == "") {
        char exe[4096] = { 0 };
        if (readlink("/proc/self/exe", exe, 4096) == -1) {
            return ERR_SYSTEM_CALL;
        }
        data->program = exe;
    }
    if (data->previousId == nullptr) {
        gettimeofday(&data->tv, nullptr);
    }
    data->client = new USMClient(wid);

    return NO_ERROR;
}

void disconnectSM()
{
    delete data->client;
}

int parseArguments(int argc, char *argv[])
{
    if (argc < 2) {
        return ERR_LESS_PARAMSTER;
    }
    if (strcmp(argv[1], "--sessionfile")) {
        return ERR_PARAMSTER_NOT_MATCH;
    }
    QString t = argv[2];
    QStringList strs = t.split("_");
    if (strs.count() < 2) {
        return ERR_LESS_PARAMSTER;
    }
    data->previousId = static_cast<char *>(malloc(strs[0].length() + 1));
    strcpy(data->previousId, strs[0].toLatin1().data());
    data->tv.tv_sec = strs[1].toLong();
    data->tv.tv_usec = strs[2].toLong();
    return NO_ERROR;
}

void requestSaveYourself()
{
    SmcRequestSaveYourself(data->client->connection(),
                           SmSaveLocal,
                           false,
                           SmInteractStyleNone,
                           true,
                           false);
}

int setWindowProperty(unsigned long long wid)
{
    xcb_connection_t *connection = xcb_connect(nullptr, nullptr);
    xcb_void_cookie_t cookie;
    xcb_generic_error_t *err;
    xcb_atom_t atom;
    xcb_intern_atom_cookie_t atom_cookie;

    if (data->clientId == nullptr) {
        return ERR_NULL_POINTER;
    }
    // set SM_CLIENT_ID
    atom_cookie = xcb_intern_atom(connection,
                                  1,
                                  strlen("SM_CLIENT_ID"),
                                  "SM_CLIENT_ID");
    xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply(connection,
                                                                atom_cookie,
                                                                nullptr);
    if (!atom_reply) {
        return ERR_NO_ATOM_REPLY;
    }
    atom = atom_reply->atom;
    cookie = xcb_change_property(connection,
                                 XCB_PROP_MODE_REPLACE,
                                 static_cast<xcb_window_t>(wid),
                                 atom,
                                 XCB_ATOM_STRING,
                                 8,
                                 static_cast<uint32_t>(strlen(data->clientId)),
                                 data->clientId);
    err = xcb_request_check(connection, cookie);
    if (err != nullptr) {
        return ERR_NORMAL;
    }

    return NO_ERROR;
}

const char* filename(unsigned long long wid)
{
    return data->fullFilenames[wid].c_str();
}