#include <syslog.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <systemd/sd-bus.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib-lowlevel.h> /* for glib main loop */
void add_element(xmlNode *node, const char *name, const char *content)
{
    xmlNode *new_node = xmlNewTextChild(node, NULL, BAD_CAST name, BAD_CAST content);
    if (new_node == NULL)
    {
        printf("Failed to add element %s with content %s", name, content);
    }
}

void delete_element(xmlNode *node, const char *name)
{
    xmlNode *cur_node = NULL;
    for (cur_node = node; cur_node; cur_node = cur_node->next)
    {
        if (cur_node->type == XML_ELEMENT_NODE && strcmp((char *)cur_node->name, name) == 0)
        {
            xmlUnlinkNode(cur_node);
            xmlFreeNode(cur_node);
            break;
        }
    }
}

void update_element(xmlNode *root, const char *name, const char *new_content)
{
    xmlNode *cur_node = NULL;
    for (cur_node = root->children; cur_node; cur_node = cur_node->next)
    {
        if (cur_node->type == XML_ELEMENT_NODE && strcmp((char *)cur_node->name, name) == 0)
        {
            xmlNodeSetContent(cur_node, BAD_CAST new_content);
            break;
        }
    }
}
bool search_element(xmlNode *root, const char *name)
{
    xmlNode *cur_node = NULL;
    for (cur_node = root->children; cur_node; cur_node = cur_node->next)
    {
        if (cur_node->type == XML_ELEMENT_NODE && strcmp((char *)cur_node->name, name) == 0)
        {
            syslog(LOG_INFO, "Element %s found with content: %s", name, (char *)xmlNodeGetContent(cur_node));
            return true;
        }
        else
            return false;
    }
}
int update_xml(char *result_etc, char *result_var)
{
    //int update_xml(char*path) {
    char *filename = "/etc/ostree/ost-ex.d/ostree_keepdata.xml";
    xmlDoc *doc = NULL;
    xmlNode *root_element = NULL;
    xmlNode *restore_sys_etc = NULL;
    xmlNode *restore_install_etc = NULL;
    xmlNode *restore_sys_var = NULL;
    xmlNode *restore_install_var = NULL;
    LIBXML_TEST_VERSION
    if (access(filename, F_OK) != -1)
    {
        syslog(LOG_INFO, "文件 %s 存在\n", filename);
        doc = xmlReadFile(filename, NULL, 0);

        if (doc == NULL)
        {
            // printf("Error: could not parse file %s", argv[1]);
            return 1;
        }
        root_element = xmlDocGetRootElement(doc);
    }
    else
    {

        // 创建一个新的XML文档
        doc = xmlNewDoc(BAD_CAST "1.0");
        root_element = xmlNewNode(NULL, BAD_CAST "root");
        xmlDocSetRootElement(doc, root_element);
    }

    //strcpy(str, path);
    char *token_etc = NULL;
    char *token_var = NULL;
    char delim[] = ";";
    char *saveptr;
    if (result_etc != NULL)
    {
        // 使用strtok_r函数分割字符串
        // token = strtok_r(str, delim, &saveptr);
        token_etc = strtok(result_etc, delim);

        while (token_etc != NULL)
        {
            syslog(LOG_INFO, "处理路径： %s", token_etc);
            // 在这里添加对路径的处理代码
            if (!search_element(root_element, token_etc))
            {
                syslog(LOG_INFO, "增加文档");
                add_element(root_element, "path", token_etc);
            }
            // 获取下一个子串
            // token = strtok_r(NULL, delim, &saveptr);
            token_etc = strtok(NULL, delim);
        }
    }
    if (result_var != NULL)
    {
        // 使用strtok_r函数分割字符串
        // token = strtok_r(str, delim, &saveptr);
        token_var = strtok(result_var, delim);

        while (token_var != NULL)
        {
            syslog(LOG_INFO, "处理路径： %s", token_var);
            // 在这里添加对路径的处理代码
            if (!search_element(root_element, token_var))
            {
                syslog(LOG_INFO, "增加文档");
                add_element(root_element, "path", token_var);
            }
            // 获取下一个子串
            // token = strtok_r(NULL, delim, &saveptr);
            token_var = strtok(NULL, delim);
        }
    }

    // 添加元素
    // add_element(root_element, "element1", "content1");
    // add_element(root_element, "element2", "content2");

    // // 删除元素
    // delete_element(root_element, "element1");

    // // 更新元素内容
    // update_element(root_element, "element2", "updated_content2");
    // 保存XML文档到文件
    // xmlSaveFormatFileEnc("output.xml", doc, "UTF-8", 1);
    xmlSaveFormatFileEnc(filename, doc, "UTF-8", 1);
    // 释放内存
    xmlFreeDoc(doc);
    xmlCleanupParser();

    return 0;
}
void check_directories(const char *base_path, const xmlNode *node, char **result)
{
    DIR *dir;
    struct dirent *entry;
    char path[1024];
    char path1[1024];
    int found = 0;

    if ((dir = opendir(base_path)) == NULL)
    {
        perror("opendir");
        return;
    }

    while ((entry = readdir(dir)) != NULL)
    {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
        {
            continue;
        }
        printf("entry->d_name=%s", entry->d_name);
        xmlNode *child = node->children;
        while (child != NULL)
        {
            if (child->type == XML_ELEMENT_NODE && strcmp((const char *)child->name, "path") == 0)
            {
                xmlChar *content = xmlNodeGetContent(child);
                // Remove the "/etc" or "/var" part from the content for comparison
                const char *relative_path = NULL;
                if (strstr((const char *)content, "/etc/") != NULL)
                {
                    relative_path = strstr((const char *)content, "/etc/") + 5; // Move past "/etc/"
                }
                else if (strstr((const char *)content, "/var/") != NULL)
                {
                    relative_path = strstr((const char *)content, "/var/") + 5; // Move past "/var/"
                }
                else
                {
                    relative_path = (const char *)content;
                }

                syslog(LOG_INFO, "----relative_path=%s\n", relative_path);
                if (strcmp(relative_path, entry->d_name) == 0)
                {
                    if (strlen(*result) > 0)
                    {
                        strcat(*result, ";");
                    }
                    strcat(*result, (const char *)content);
                }
                if (entry->d_type == DT_DIR)
                {
                    snprintf(path, sizeof(path), "%s/%s", base_path, entry->d_name);
                    DIR *subdir;
                    struct dirent *subentry;
                    if (!(subdir = opendir(path)))
                    {
                        perror("opendir");
                        continue;
                    }
                    while ((subentry = readdir(subdir)) != NULL)
                    {
                        if (strcmp(subentry->d_name, ".") == 0 || strcmp(subentry->d_name, "..") == 0)
                            continue;
                        snprintf(path1, sizeof(path), "%s/%s", entry->d_name, subentry->d_name);
                        if (strcmp(path1, relative_path) == 0)
                        {

                            if (strlen(*result) > 0)
                            {
                                strcat(*result, ";");
                            }
                            strcat(*result, (const char *)content);
                        }
                    }
                    closedir(subdir);
                }
                xmlFree(content);
            }
            child = child->next;
        }
    }
    closedir(dir);
}

void parse_and_check(const char *filename, const char *etc_tmpovl, const char *var_tmpovl)
{
    xmlDoc *doc = NULL;
    xmlNode *root_element = NULL;
    char *result_etc = malloc(1024 * sizeof(char));
    char *result_var = malloc(1024 * sizeof(char));
    int sys_etc_checked = 0;
    int sys_var_checked = 0;
    int install_etc_checked = 0;
    int install_var_checked = 0;

    LIBXML_TEST_VERSION

    doc = xmlReadFile(filename, NULL, 0);
    if (doc == NULL)
    {
        printf("Could not parse the XML file\n");
        return;
    }

    root_element = xmlDocGetRootElement(doc);
    for (xmlNode *node = root_element->children; node != NULL; node = node->next)
    {
        if (node->type == XML_ELEMENT_NODE)
        {
            if (strcmp((const char *)node->name, "restore_sys") == 0)
            {
                for (xmlNode *child = node->children; child != NULL; child = child->next)
                {
                    if (child->type == XML_ELEMENT_NODE)
                    {
                        if (strcmp((const char *)child->name, "etc") == 0 && !sys_etc_checked)
                        {
                            syslog(LOG_INFO, "-------------etc-------------\n");
                            check_directories(etc_tmpovl, child, &result_etc);
                            sys_etc_checked = 1;
                        }
                        else if (strcmp((const char *)child->name, "var") == 0 && !sys_var_checked)
                        {
                            syslog(LOG_INFO, "-------------var-------------\n");
                            check_directories(var_tmpovl, child, &result_var);
                            sys_var_checked = 1;
                        }
                    }
                }
            }
            else if (strcmp((const char *)node->name, "restore_install") == 0)
            {
                for (xmlNode *child = node->children; child != NULL; child = child->next)
                {
                    if (child->type == XML_ELEMENT_NODE)
                    {
                        if (strcmp((const char *)child->name, "etc") == 0 && !install_etc_checked)
                        {
                            syslog(LOG_INFO, "-------------etc1-------------\n");
                            check_directories(etc_tmpovl, child, &result_etc);
                            install_etc_checked = 1;
                        }
                        else if (strcmp((const char *)child->name, "var") == 0 && !install_var_checked)
                        {
                            syslog(LOG_INFO, "-------------var1-------------\n");
                            check_directories(var_tmpovl, child, &result_var);
                            install_var_checked = 1;
                        }
                    }
                }
            }
        }
    }

    xmlFreeDoc(doc);
    xmlCleanupParser();

    syslog(LOG_INFO, "Result for etc: %s\n", result_etc);
    syslog(LOG_INFO, "Result for var: %s\n", result_var);
    if (result_etc != NULL || result_var != NULL)
        update_xml(result_etc, result_var);
    return 0;
}

void read_coredata_xml()
{
    const char *filename = "/etc/ostree/ost-ex.d/ostree_coredata.xml";
    char *etc_tmpovl = NULL;
    char *var_tmpovl = NULL;
    FILE *file = fopen("/etc/ostree/ost-ex.d/ostree-maintian-keep-etc", "r");
    if (file == NULL)
    {
        perror("Error opening file");
        return 0;
    }

    char line[1024];
    int line_number = 0;

    while (fgets(line, sizeof(line), file))
    {
        // Remove newline character if present
        size_t len = strlen(line);
        if (len > 0 && line[len - 1] == '\n')
        {
            line[len - 1] = '\0';
        }

        if (line_number == 0)
        {
            etc_tmpovl = strdup(line);
        }
        else if (line_number == 1)
        {
            var_tmpovl = strdup(line);
            break; // We only need the first two lines
        }
        line_number++;
    }

    fclose(file);
    parse_and_check(filename, etc_tmpovl, var_tmpovl);

    return 0;
}
gboolean keep_maintain_conent()
{
    char command[255] = {};
    snprintf(command, sizeof(command), "%s", "/usr/sbin/keep_maintain_conent.sh");
    if (system(command) == 0)
    {
        syslog(LOG_INFO, "keep_maintain_conent");
        return TRUE;
    }
    return FALSE;
}

void send_a_method_call(DBusConnection *connection, char *param)
{
    DBusError err;
    DBusMessage *msg;
    DBusMessageIter arg;
    DBusPendingCall *pending;
    const char *value;
    dbus_error_init(&err);

    // 针对目的地地址，请参考图，创建一个method call消息。 Constructs a new message to invoke a method on a remote object.
    msg = dbus_message_new_method_call("com.kylin.MaintainMode",           // target for the method call
                                       "/com/kylin/MaintainMode/Object",   // object to call on
                                       "com.kylin.MaintainMode.interface", // interface to call on
                                       "GetFile");                         // method name
    if (msg == NULL)
    {
        g_printerr("Message NULL");
        return;
    }
    syslog(LOG_ERR, "WD--file=[%s]--fun=[%s]--line=[%d]", __FILE__, __FUNCTION__, __LINE__);
    //为消息添加参数。Append arguments
    dbus_message_iter_init_append(msg, &arg);
    if (!dbus_message_iter_append_basic(&arg, DBUS_TYPE_STRING, &param))
    {
        g_printerr("Out of Memory!");
        exit(1);
    }

    // 发送消息并获得reply的handle 。Queues a message to send, as with dbus_connection_send() , but also returns a DBusPendingCall used to receive a reply to the message.
    if (!dbus_connection_send_with_reply(connection, msg, &pending, -1))
    {
        g_printerr("Out of Memory!");
        exit(1);
    }

    if (pending == NULL)
    {
        g_printerr("Pending Call NULL: connection is disconnected ");
        dbus_message_unref(msg);
        return;
    }

    dbus_connection_flush(connection);
    dbus_message_unref(msg);

    // waiting a reply，在发送的时候，已经获取了method reply的handle，类型为DBusPendingCall。
    //  block until we recieve a reply， Block until the pending call is completed.
    dbus_pending_call_block(pending);
    // get the reply message，Gets the reply, or returns NULL if none has been received yet.
    msg = dbus_pending_call_steal_reply(pending);
    if (msg == NULL)
    {
        fprintf(stderr, "Reply Null/n");
        exit(1);
    }
    // free the pending message handle
    dbus_pending_call_unref(pending);
    // read the parameters
    if (!dbus_message_iter_init(msg, &arg))
        fprintf(stderr, "Message has no arguments!/n");

    if (dbus_message_iter_get_arg_type(&arg) == DBUS_TYPE_STRING)
    {

        dbus_message_iter_get_basic(&arg, &value);
        printf("Method call response returned string \"%s\".\n", value);
        //获取文件在哪里并更新到配置文件中ostree_keep
        // update_xml(value);
    }
    else
    {
        // 返回类型不正确
        g_error("Error: unexpected return type from method call.");
    }
    dbus_message_unref(msg);
    return;
}
int dbus_monitor(void)
{

    DBusConnection *connection;
    DBusError error;
    dbus_error_init(&error);
    // Connect to session bus
    connection = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
    if (dbus_error_is_set(&error))
    {
        syslog(LOG_ERR, "Connection Error (%s)\n", error.message);
        dbus_error_free(&error);
        return 1;
    }

    // Add a rule for incoming signals
    dbus_bus_add_match(connection, "type='signal',interface='com.kylin.MaintainMode.interface'", &error);
    dbus_connection_flush(connection);

    if (dbus_error_is_set(&error))
    {
        syslog(LOG_ERR, "Error adding D-Bus match rule (%s)\n", error.message);
        dbus_error_free(&error);
        return 1;
    }

    // Main event loop
    while (1)
    {
        DBusMessage *message;
        DBusMessageIter args;
        guint16 sigvalue;
        DBusDispatchStatus status = dbus_connection_read_write(connection, 1000); // 设置阻塞时间
        // Blocking call to receive a message
        message = dbus_connection_pop_message(connection);
        if (message != NULL)
        {
            if (dbus_message_is_signal(message, "com.kylin.MaintainMode.interface", "BootModeChanged"))
            {
                // 读取参数
                if (!dbus_message_iter_init(message, &args))
                    syslog(LOG_ERR, "消息没有参数!");
                else if (DBUS_TYPE_UINT32 != dbus_message_iter_get_arg_type(&args))
                    syslog(LOG_ERR, "参数格式错误！");
                else
                {
                    dbus_message_iter_get_basic(&args, &sigvalue);
                    char command[255] = {};
                    if (sigvalue == 1) // 是进入运维模式
                    {
                        //获取到信号，do what you do.....
                        syslog(LOG_INFO, "--go to maintain mode--");
                        snprintf(command, sizeof(command), "%s %s", "/usr/sbin/coreboot_copy.sh", "in");
                        if (system(command) == 0)
                        {
                            syslog(LOG_INFO, "copy success");
                        }
                        syslog(LOG_INFO, "--boot copy end--");
                    }
                    else if (sigvalue == 3)
                    {
                        syslog(LOG_INFO, "--exit and keep maintain--");
                        //需要获取即将保留下的文件
                        if (keep_maintain_conent())
                        {
                            read_coredata_xml();
                        }
                    }
                    else if (sigvalue == 5)
                    {
                        syslog(LOG_INFO, "------exit and don't keep maintain-----");
                        snprintf(command, sizeof(command), "%s %s", "/usr/sbin/coreboot_copy.sh", "out");
                        if (system(command) == 0)
                        {
                            syslog(LOG_INFO, "copy success");
                        }
                        syslog(LOG_INFO, "--restore boot end--");
                    }
                }
            }
            // Free the received message
            dbus_message_unref(message);
        }
    }
    // Close the connection
    dbus_connection_unref(connection);
    return 0;
}
int main(int argc, char **argv)
{
    dbus_monitor();
}
