#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <sys/types.h>
#include <errno.h>
#include <inttypes.h>
#include "log.h"
#include "sd-bus.h"
#include "main-func.h"
#include "fd-util.h"
#include "macro.h"
#include <fcntl.h>
#include <sys/inotify.h>
#include <pthread.h>
#include <sys/utsname.h>
#include "string-util.h"

#include "conf-parser.h"
#include "parse-util.h"
#include "strv.h"
#include "constants.h"

#define EVENT_NUM  12
#define STATIC_MACHINE_DROP_CONF "/lib/systemd/machine-eq.conf"

struct Manager {
        sd_bus* bus;
        sd_bus_slot* slot;
        int fd;
        int dmi_filter;
        int loongarch_check;
        int JJW_graphics_card;
        // Add some configurations here.
};

typedef struct Manager Manager;

typedef struct MachineConfig {
    bool allow_drop;         /* Allow Machine drop chache */
    bool allow_drop_all_loongarch;
    char **machine_list;       /* Machine list */
} MachineConfig;

MachineConfig* free_machine_config(MachineConfig *mc);
DEFINE_TRIVIAL_CLEANUP_FUNC(MachineConfig*, free_machine_config);

static int parse_machine_config(MachineConfig **ret_machine_config) {
    _cleanup_(free_machine_configp) MachineConfig *mc;
    int allow_drop = -1;
    int allow_drop_all_loongarch = -1;
    mc = new0(MachineConfig, 1);
    if (!mc)
        return log_oom();

    const ConfigTableItem items[] = {
        { "MachineDrop", "AllowMachineDrop",          config_parse_tristate, 0, &allow_drop },
        { "MachineDrop", "AllowMachineDropALLLoongarch",          config_parse_tristate, 0, &allow_drop_all_loongarch },
        { "MachineDrop", "MachineList",               config_parse_strv, 0, &mc->machine_list  },
        {}
    };
    const char* dropin_dirname;
    dropin_dirname = strjoina(STATIC_MACHINE_DROP_CONF, ".d");

    (void) config_parse_many(STRV_MAKE_CONST(STATIC_MACHINE_DROP_CONF),
            (const char* const*) CONF_PATHS_STRV("systemd/machine-eq.conf.d"), dropin_dirname, NULL,
            "MachineDrop\0", config_item_table_lookup, items,
            CONFIG_PARSE_RELAXED, NULL, NULL, NULL);

    mc->allow_drop = allow_drop != 0;
    mc->allow_drop_all_loongarch = allow_drop_all_loongarch != 0;

    *ret_machine_config = TAKE_PTR(mc);

    return 0;
}

MachineConfig* free_machine_config(MachineConfig *mc)
{
    if (!mc)
        return NULL;

    strv_free(mc->machine_list);

    return mfree(mc);
}

const char* event_str[EVENT_NUM] =
{
        "IN_ACCESS",
        "IN_MODIFY",
        "IN_ATTRIB",
        "IN_CLOSE_WRITE",
        "IN_CLOSE_NOWRITE",
        "IN_OPEN",
        "IN_MOVED_FROM",
        "IN_MOVED_TO",
        "IN_CREATE",
        "IN_DELETE",
        "IN_DELETE_SELF",
        "IN_MOVE_SELF"
};

static Manager* manager_new(void)
{
        Manager* manager;

        manager = new0(Manager, 1);
        if (!manager)
                return NULL;

        return manager;
}

static void manager_free(Manager* manager)
{
        sd_bus_slot_unref(manager->slot);
        sd_bus_unref(manager->bus);

        free(manager);
}

static int check_free(long free_size)
{
        char meminfo_line[128];
        char* token;
        FILE *fp, *fp1;

        fp1 = fopen("/proc/sys/vm/min_free_kbytes", "r");
        if (fp1 == NULL)
                return 0;

        if (fgets(meminfo_line, 128, fp1) != NULL) {
              free_size += atol(meminfo_line);
        }
        fclose(fp1);

        fp = fopen("/proc/meminfo", "r");
        if (fp == NULL)
                return 0;

        while (fgets(meminfo_line, 128, fp) != NULL)
        {
                token = strtok(meminfo_line, " ");
                printf("%s=", token);
                if (strcmp("MemFree:", token) == 0)
                {
                        token = strtok(NULL, " ");
                        if (atol(token) < free_size)
                        {
                                fclose(fp);
                                return 1;
                        }
                        else
                        {
                                break;
                        }
                }
        }
        fclose(fp);
        return 0;
}

static int check_vendor_name(const char* input_name)
{
        char devname[256];
        const char* dirname = "/sys/bus/pci/devices";
        char* filename;
        DIR* dir;
        struct dirent* de;
        char name[256];
        int bufsize;
        int ret = 0;

        dir = opendir(dirname);
        if (dir == NULL)
                return 0;

        bufsize = sizeof(devname);

        strncpy(devname, dirname, bufsize);
        devname[bufsize - 1] = '\0';
        filename = devname + strlen(devname);
        bufsize -= strlen(devname);

        *filename++ = '/';
        bufsize--;
        if (bufsize <= 0)
                return 0;

        while ((de = readdir(dir))) {
                if (de->d_name[0] == '.' &&
                        (de->d_name[1] == '\0' ||
                                (de->d_name[1] == '.' && de->d_name[2] == '\0')))
                        continue;
                strncpy(filename, de->d_name, bufsize);
                strcat(filename, "/vendor");
                filename[bufsize - 1] = '\0';

                FILE* fp = fopen(devname, "r");
                if (fp == NULL)
                        continue;

                fgets(name, 256, fp);
                if (input_name && (strcmp(name, input_name) == 0)) {
                        ret = 1;
                        fclose(fp);
                        fp = NULL;
                        break;
                }
                fclose(fp);
                fp = NULL;
        }
        closedir(dir);

        return ret;
}

static int check_dmi(const char* s,const char* p) {
        int j = 0;
        int s_len = strlen(s);
        int p_len = strlen(p);
        for (int i = 0, last_p = 0, s_start = 0;i < s_len;)
        {
                if (j < p_len && (s[i] == p[j]))
                {
                        i++;
                        j++;
                }
                else if (j < p_len && p[j] == '*')
                {
                        s_start = i;
                        last_p = ++j;
                }
                else if (last_p != 0)
                {
                        i = s_start++;
                        j = last_p;
                }
                else
                        return 0;
        }
        for (;j < p_len && p[j] == '*';++j);
        return j == p_len;
}

static int inhibit(sd_bus* bus, const char* what) {
        _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL;
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
        const char* who = "systemd-drop-cache", * reason = "drop-cache for S3", * mode = "delay";
        int fd;
        int r;

        r = sd_bus_call_method(bus,
                "org.freedesktop.login1",
                "/org/freedesktop/login1",
                "org.freedesktop.login1.Manager",
                "Inhibit",
                &error,
                &reply,
                "ssss", what, who, reason, mode);
        if(r < 0){
                log_info("Error, in inhibit, sd_bus_call_method ret r < 0!\n");
                return -1;
        }

        r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_UNIX_FD, &fd);
        if(r < 0){
                log_info("Error, in inhibit, sd_bus_message_read_basic ret r < 0!\n");
                return -1;
        }

        return fcntl(fd, F_DUPFD_CLOEXEC, 3);
}

static int inotifyTask(char* path)
{
        int fd = -1;

        fd = inotify_init();
        if (fd < 0)
        {
                log_info("Error no.%d: %s\n", errno, strerror(errno));
                return -1;
        }

        int wd1 = -1;

        struct inotify_event* event;

        int length;
        int nread;

        char buf[BUFSIZ];

        int i = 0;

        buf[sizeof(buf) - 1] = 0;

        wd1 = inotify_add_watch(fd, path, IN_MODIFY);
        if (wd1 < 0)
        {
                log_info("Error no.%d: %s\n", errno, strerror(errno));
                return -1;
        }

        while (1)
        {
                length = read(fd, buf, sizeof(buf) - 1);

                nread = 0;

                // inotify 事件发生时
                while (length > 0)
                {
                        printf("\n");

                        event = (struct inotify_event*)&buf[nread];

                        // 遍历所有事件
                        for (i = 0; i < EVENT_NUM; i++)
                        {
                                // 判断事件是否发生
                                if ((event->mask >> i) & 1)
                                {
                                        // 该监控项为目录或目录下的文件时
                                        if (event->len > 0)
                                        {
                                                log_debug("%s --- %s", event->name, event_str[i]);
                                        }
                                        // 该监控项为文件时
                                        else if (event->len == 0)
                                        {
                                                if (event->wd == wd1)
                                                {
                                                        if (check_free(2000000))
                                                        {
                                                                log_debug("%s --- %s", path, event_str[i]);
                                                                FILE* fp = NULL;
                                                                if ((fp = fopen("/proc/sys/vm/drop_caches", "w")) == NULL)
                                                                {
                                                                        log_info("Cannot open /proc/sys/vm/drop_caches\n");
                                                                        exit(1);
                                                                }
                                                                fputc('3', fp);
                                                                fclose(fp);
                                                        }
                                                }
                                        }
                                }
                        }

                        nread = nread + sizeof(struct inotify_event) + event->len;

                        length = length - sizeof(struct inotify_event) - event->len;
                }

        }

        close(fd);

        return 0;
}

static int match_prepare_for_sleep(sd_bus_message* message,
        void* userdata, sd_bus_error* error)
{
        bool sleep;
        int r;
        Manager* manager = (Manager*)userdata;
        int drop = 0;

        r = sd_bus_message_read(message, "b", &sleep);
        if (r < 0) {
                log_info("Failed to parse signal: \n");
                return r;
        }

        if (sleep == true) {
                if (manager->loongarch_check) {
                        if (check_free(2000000)) {
                                log_debug("loongarch 2.0G mem need free");
                                drop = 1;
                        }
                }
                if (manager->JJW_graphics_card) {
                        if (check_free(500000)) {
                                log_debug("JJW_graphics_card 500M mem need free");
                                drop = 1;
                        }
                }

                if (drop)
                {
                        FILE* fp = NULL;
                        if ((fp = fopen("/proc/sys/vm/drop_caches", "w")) == NULL)
                        {
                                log_info("Cannot open /proc/sys/vm/drop_caches\n");
                                return 1;
                        } else {
                                fputc('3', fp);
                                fclose(fp);
                                log_info("drop cache");
                        }

                        safe_close(manager->fd);
                }
                else if(manager->JJW_graphics_card || manager->dmi_filter)
                {
                        safe_close(manager->fd);
                }
        }

        if (sleep == false) {
                _cleanup_(sd_bus_unrefp) sd_bus* bus = NULL;

                r = sd_bus_open_system(&bus);
                if(r < 0) {
                     log_info("Error, in match_prepare_for_sleep, sd_bus_open_system ret r < 0!\n");   
                }

                if (manager->JJW_graphics_card || manager->dmi_filter) {
                        manager->fd = inhibit(bus, "sleep");
                        if(manager->fd <= 0){
                                log_info("Error, in match_prepare_for_sleep, sd_bus_open_system ret manager->fd <= 0!\n");
                        }
                }
        }
        return r;
}

static int manager_connect_bus(Manager* manager) {
        int r;

        /* Connect to the user bus this time */
        r = sd_bus_open_system(&manager->bus);
        if (r < 0) {
                log_info("Failed to connect to system bus: \n");
                return r;
        }

        r = sd_bus_add_match(
                manager->bus,
                NULL,
                "type='signal',"
                "sender='org.freedesktop.login1',"
                "interface='org.freedesktop.login1.Manager',"
                "member='PrepareForSleep',"
                "path='/org/freedesktop/login1'",
                match_prepare_for_sleep,
                manager);

        if (r < 0) {
                log_info("Failed to add match\n");
                return r;
        }

        return 0;
}

static int manager_startup(Manager* manager)
{
        int r;
        /* Connect to the bus */
        r = manager_connect_bus(manager);
        if (r < 0)
                return r;

        return 0;
}

static int manager_run(Manager* manager)
{
        int r;

        for (;;) {
                /* Process requests */
                r = sd_bus_process(manager->bus, NULL);
                if (r < 0) {
                        log_info("Failed to process bus");
                        return r;
                }
                if (r > 0) /* we processed a request, try to process another one, right-away */
                        continue;

                /* Wait for the next request to process */
                r = sd_bus_wait(manager->bus, (uint64_t)-1);
                if (r < 0) {
                        log_info("Failed to wait on bus");
                        return r;
                }
        }
}

static void* ThreadWork(void* arg)
{
        char* p = (char*)arg;
        inotifyTask(p);
        pthread_exit(NULL);
}

static int run(int argc, char* argv[])
{
        _cleanup_(free_machine_configp) MachineConfig *machine_config = NULL;
        int r, dmi_filter = 0;
        char dmibuf[1024] = { 0 };
        int loongarch_check = 0;
        struct utsname info;

        if (uname(&info) >= 0) {
                if (!strcmp(info.machine, "loongarch64")) {
                      loongarch_check = 1;
                }
        }

        parse_machine_config(&machine_config);

        if (!machine_config->allow_drop || !loongarch_check) {
                log_info("machine no need drop cache");
        } else {
                if (machine_config->allow_drop_all_loongarch) {
                        dmi_filter = 1;
                        log_debug("drop cache all loongarch");
                } else {
                    FILE* modalias = fopen("/sys/class/dmi/id/modalias", "r");
                    if (fgets(dmibuf, 1024, modalias) != NULL) {
                        dmibuf[strlen(dmibuf) - 1] = 0;
                    }
                    if (!strv_isempty(machine_config->machine_list)) {
                        STRV_FOREACH(list, machine_config->machine_list) {
                                log_debug("list:%s", *list);
                                if (check_dmi(dmibuf, *list)) {
                                        dmi_filter = 1;
                                        log_debug("dmi_filter = 1");
                                        break;
                                }
                        }
                    }
                    fclose(modalias);
                }
        }

        if (dmi_filter > 0) {
                const char* power_state = "/sys/power/state";
                pthread_t tid;
                r = pthread_create(&tid, NULL, ThreadWork, (void*)power_state);
                if (r != 0)
                {
                        log_info("pthread_create");
                        return -1;
                }
                pthread_detach(tid);
        }

        Manager* manager = NULL;
        manager = manager_new();

        if (!manager) {
                log_info("Failed to create manager.");
                goto finish;
        }
        manager->dmi_filter = dmi_filter;
        manager->loongarch_check = loongarch_check;
        manager->JJW_graphics_card = check_vendor_name("0x0731\n");

        _cleanup_(sd_bus_unrefp) sd_bus* bus = NULL;

        r = sd_bus_open_system(&bus);
        if(r < 0){
                log_info("Error,sd_bus_open_system ret r < 0.\n");
                goto finish;
        }

        if (manager->JJW_graphics_card || manager->dmi_filter) {
                log_debug("register inhibit");
                manager->fd = inhibit(bus, "sleep");
                if(manager->fd <= 0){
                        log_info("Failed to get manager->fd.\n");
                        goto finish;
                }
        }

        r = manager_startup(manager);
        if (r < 0) {
                log_info("Failed to fully start up daemon");
                goto finish;
        }

        r = manager_run(manager);

finish:
        manager_free(manager);
        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

DEFINE_MAIN_FUNCTION(run);
