/*
 * DWN - Desktop Window Manager
 * retoor <retoor@molodetz.nl>
 * Application launcher with .desktop file support
 */

#include "applauncher.h"
#include "config.h"
#include "util.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#include <pwd.h>
#include <limits.h>

static AppLauncherState launcher_state = {0};

static char recent_file_path[PATH_MAX] = {0};

static void scan_desktop_dir(const char *dir);
static int parse_desktop_file(const char *path, AppEntry *entry);
static void load_recent_apps(void);
static void save_recent_apps(void);
static void add_to_recent(const char *desktop_id);
static char *strip_field_codes(const char *exec);
static int compare_apps(const void *a, const void *b);


void applauncher_init(void)
{
    memset(&launcher_state, 0, sizeof(launcher_state));

    const char *home = getenv("HOME");
    if (home == NULL) {
        struct passwd *pw = getpwuid(getuid());
        home = pw ? pw->pw_dir : "/tmp";
    }
    snprintf(recent_file_path, sizeof(recent_file_path),
             "%s/.local/share/dwn/recent_apps", home);

    char dir_path[PATH_MAX];
    snprintf(dir_path, sizeof(dir_path), "%s/.local/share/dwn", home);
    mkdir(dir_path, 0755);

    load_recent_apps();

    applauncher_refresh();

    LOG_INFO("App launcher initialized with %d applications", launcher_state.app_count);
}

void applauncher_cleanup(void)
{
    save_recent_apps();
}

void applauncher_refresh(void)
{
    launcher_state.app_count = 0;

    scan_desktop_dir("/usr/share/applications");

    const char *home = getenv("HOME");
    if (home) {
        char user_apps[PATH_MAX];
        snprintf(user_apps, sizeof(user_apps), "%s/.local/share/applications", home);
        scan_desktop_dir(user_apps);
    }

    qsort(launcher_state.apps, launcher_state.app_count,
          sizeof(AppEntry), compare_apps);

    LOG_DEBUG("Refreshed app list: %d applications found", launcher_state.app_count);
}


static void scan_desktop_dir(const char *dir)
{
    DIR *d = opendir(dir);
    if (d == NULL) {
        return;
    }

    struct dirent *entry;
    while ((entry = readdir(d)) != NULL) {
        if (launcher_state.app_count >= MAX_APPS) {
            break;
        }

        size_t len = strlen(entry->d_name);
        if (len < 9 || strcmp(entry->d_name + len - 8, ".desktop") != 0) {
            continue;
        }

        char path[1024];
        snprintf(path, sizeof(path), "%s/%s", dir, entry->d_name);

        bool exists = false;
        for (int i = 0; i < launcher_state.app_count; i++) {
            if (strcmp(launcher_state.apps[i].desktop_id, entry->d_name) == 0) {
                exists = true;
                break;
            }
        }
        if (exists) {
            continue;
        }

        AppEntry app = {0};
        if (parse_desktop_file(path, &app) == 0) {
            snprintf(app.desktop_id, sizeof(app.desktop_id), "%s", entry->d_name);
            launcher_state.apps[launcher_state.app_count++] = app;
        }
    }

    closedir(d);
}


static int parse_desktop_file(const char *path, AppEntry *entry)
{
    FILE *f = fopen(path, "r");
    if (f == NULL) {
        return -1;
    }

    char line[1024];
    bool in_desktop_entry = false;
    bool has_name = false;
    bool has_exec = false;

    entry->hidden = false;
    entry->terminal = false;

    while (fgets(line, sizeof(line), f) != NULL) {
        size_t len = strlen(line);
        while (len > 0 && (line[len-1] == '\n' || line[len-1] == '\r' ||
                          line[len-1] == ' ' || line[len-1] == '\t')) {
            line[--len] = '\0';
        }

        if (line[0] == '[') {
            in_desktop_entry = (strcmp(line, "[Desktop Entry]") == 0);
            continue;
        }

        if (!in_desktop_entry) {
            continue;
        }

        char *eq = strchr(line, '=');
        if (eq == NULL) {
            continue;
        }

        *eq = '\0';
        char *key = line;
        char *value = eq + 1;

        if (strchr(key, '[') != NULL) {
            continue;
        }

        if (strcmp(key, "Name") == 0 && !has_name) {
            strncpy(entry->name, value, sizeof(entry->name) - 1);
            has_name = true;
        } else if (strcmp(key, "Exec") == 0 && !has_exec) {
            strncpy(entry->exec, value, sizeof(entry->exec) - 1);
            has_exec = true;
        } else if (strcmp(key, "Icon") == 0) {
            strncpy(entry->icon, value, sizeof(entry->icon) - 1);
        } else if (strcmp(key, "Terminal") == 0) {
            entry->terminal = (strcasecmp(value, "true") == 0);
        } else if (strcmp(key, "Type") == 0) {
            if (strcmp(value, "Application") != 0) {
                fclose(f);
                return -1;
            }
        } else if (strcmp(key, "Hidden") == 0 || strcmp(key, "NoDisplay") == 0) {
            if (strcasecmp(value, "true") == 0) {
                entry->hidden = true;
            }
        }
    }

    fclose(f);

    if (!has_name || !has_exec || entry->hidden) {
        return -1;
    }

    return 0;
}


static void load_recent_apps(void)
{
    launcher_state.recent_count = 0;

    FILE *f = fopen(recent_file_path, "r");
    if (f == NULL) {
        return;
    }

    char line[256];
    while (fgets(line, sizeof(line), f) != NULL &&
           launcher_state.recent_count < MAX_RECENT_APPS) {
        size_t len = strlen(line);
        if (len > 0 && line[len-1] == '\n') {
            line[len-1] = '\0';
        }
        if (line[0] != '\0') {
            snprintf(launcher_state.recent[launcher_state.recent_count],
                     sizeof(launcher_state.recent[0]), "%s", line);
            launcher_state.recent_count++;
        }
    }

    fclose(f);
    LOG_DEBUG("Loaded %d recent apps", launcher_state.recent_count);
}

static void save_recent_apps(void)
{
    FILE *f = fopen(recent_file_path, "w");
    if (f == NULL) {
        LOG_WARN("Could not save recent apps to %s", recent_file_path);
        return;
    }

    for (int i = 0; i < launcher_state.recent_count; i++) {
        fprintf(f, "%s\n", launcher_state.recent[i]);
    }

    fclose(f);
}

static void add_to_recent(const char *desktop_id)
{
    for (int i = 0; i < launcher_state.recent_count; i++) {
        if (strcmp(launcher_state.recent[i], desktop_id) == 0) {
            for (int j = i; j > 0; j--) {
                memcpy(launcher_state.recent[j], launcher_state.recent[j-1],
                       sizeof(launcher_state.recent[0]));
            }
            snprintf(launcher_state.recent[0], sizeof(launcher_state.recent[0]),
                     "%s", desktop_id);
            save_recent_apps();
            return;
        }
    }

    if (launcher_state.recent_count < MAX_RECENT_APPS) {
        launcher_state.recent_count++;
    }

    for (int i = launcher_state.recent_count - 1; i > 0; i--) {
        memcpy(launcher_state.recent[i], launcher_state.recent[i-1],
               sizeof(launcher_state.recent[0]));
    }
    snprintf(launcher_state.recent[0], sizeof(launcher_state.recent[0]),
             "%s", desktop_id);

    save_recent_apps();
}


static char *strip_field_codes(const char *exec)
{
    static char result[PATH_MAX];
    size_t j = 0;

    for (size_t i = 0; exec[i] && j < sizeof(result) - 1; i++) {
        if (exec[i] == '%' && exec[i+1]) {
            char code = exec[i+1];
            if (code == 'f' || code == 'F' || code == 'u' || code == 'U' ||
                code == 'd' || code == 'D' || code == 'n' || code == 'N' ||
                code == 'i' || code == 'c' || code == 'k') {
                i++;
                if (exec[i+1] == ' ') {
                    i++;
                }
                continue;
            }
        }
        result[j++] = exec[i];
    }
    result[j] = '\0';

    while (j > 0 && (result[j-1] == ' ' || result[j-1] == '\t')) {
        result[--j] = '\0';
    }

    return result;
}

static int compare_apps(const void *a, const void *b)
{
    const AppEntry *app_a = (const AppEntry *)a;
    const AppEntry *app_b = (const AppEntry *)b;
    return strcasecmp(app_a->name, app_b->name);
}

static AppEntry *find_app_by_id(const char *desktop_id)
{
    for (int i = 0; i < launcher_state.app_count; i++) {
        if (strcmp(launcher_state.apps[i].desktop_id, desktop_id) == 0) {
            return &launcher_state.apps[i];
        }
    }
    return NULL;
}

static AppEntry *find_app_by_name(const char *name)
{
    for (int i = 0; i < launcher_state.app_count; i++) {
        if (strcmp(launcher_state.apps[i].name, name) == 0) {
            return &launcher_state.apps[i];
        }
    }
    return NULL;
}


void applauncher_show(void)
{
    if (launcher_state.app_count == 0) {
        LOG_WARN("No applications found");
        return;
    }

    size_t buf_size = launcher_state.app_count * 140;
    char *choices = dwn_malloc(buf_size);
    if (choices == NULL) {
        LOG_ERROR("Failed to allocate memory for app list");
        return;
    }
    choices[0] = '\0';

    bool *added = dwn_calloc(launcher_state.app_count, sizeof(bool));
    if (added == NULL) {
        dwn_free(choices);
        return;
    }

    size_t pos = 0;

    for (int i = 0; i < launcher_state.recent_count; i++) {
        AppEntry *app = find_app_by_id(launcher_state.recent[i]);
        if (app != NULL) {
            for (int j = 0; j < launcher_state.app_count; j++) {
                if (&launcher_state.apps[j] == app) {
                    added[j] = true;
                    break;
                }
            }
            size_t name_len = strlen(app->name);
            if (pos + name_len + 2 < buf_size) {
                memcpy(choices + pos, app->name, name_len);
                pos += name_len;
                choices[pos++] = '\n';
            }
        }
    }

    for (int i = 0; i < launcher_state.app_count; i++) {
        if (added[i]) {
            continue;
        }
        size_t name_len = strlen(launcher_state.apps[i].name);
        if (pos + name_len + 2 < buf_size) {
            memcpy(choices + pos, launcher_state.apps[i].name, name_len);
            pos += name_len;
            choices[pos++] = '\n';
        }
    }

    if (pos > 0) {
        choices[pos-1] = '\0';
    }

    dwn_free(added);

    char tmp_path[] = "/tmp/dwn_apps_XXXXXX";
    int tmp_fd = mkstemp(tmp_path);
    if (tmp_fd < 0) {
        dwn_free(choices);
        LOG_ERROR("Failed to create temp file for app list");
        return;
    }

    ssize_t written = write(tmp_fd, choices, strlen(choices));
    close(tmp_fd);
    dwn_free(choices);

    if (written < 0) {
        unlink(tmp_path);
        LOG_ERROR("Failed to write app list to temp file");
        return;
    }

    char cmd[1024];
    snprintf(cmd, sizeof(cmd),
             "cat '%s' | dmenu -i -l 10 -p 'Run:'; rm -f '%s'",
             tmp_path, tmp_path);

    FILE *p = popen(cmd, "r");

    if (p == NULL) {
        LOG_ERROR("Failed to run dmenu");
        return;
    }

    char selected[256] = {0};
    if (fgets(selected, sizeof(selected), p) != NULL) {
        size_t len = strlen(selected);
        if (len > 0 && selected[len-1] == '\n') {
            selected[len-1] = '\0';
        }
    }
    pclose(p);

    if (selected[0] == '\0') {
        return;
    }

    AppEntry *app = find_app_by_name(selected);
    if (app != NULL) {
        applauncher_launch(app->desktop_id);
    } else {
        spawn_async(selected);
    }
}

void applauncher_launch(const char *desktop_id)
{
    AppEntry *app = find_app_by_id(desktop_id);
    if (app == NULL) {
        LOG_WARN("App not found: %s", desktop_id);
        return;
    }

    char *exec_cmd = strip_field_codes(app->exec);

    char cmd[1024];
    if (app->terminal) {
        const char *terminal = config_get_terminal();
        snprintf(cmd, sizeof(cmd), "%s -e %s", terminal, exec_cmd);
    } else {
        snprintf(cmd, sizeof(cmd), "%s", exec_cmd);
    }

    LOG_INFO("Launching: %s (%s)", app->name, cmd);
    spawn_async(cmd);

    add_to_recent(desktop_id);
}
