/*
 * DWN - Desktop Window Manager
 * Layout Plugin Manager Implementation
 */

#include "plugins/layout_plugin.h"
#include "core/wm_list.h"
#include "core/wm_hashmap.h"
#include "core/wm_string.h"
#include "core/wm_client.h"
#include "dwn.h"
#include "util.h"
#include "config.h"

#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>

/*==============================================================================
 * Helper for casting dlsym result to function pointer (avoids ISO C warning)
 *============================================================================*/

union dlsym_cast {
    void *obj_ptr;
    LayoutPluginEntryFunc func_ptr;
};

/*==============================================================================
 * Internal Structures
 *============================================================================*/

struct LayoutManager {
    WmList *plugins;           /* List of LayoutPlugin* */
    WmHashMap *by_name;        /* Map: name -> LayoutPlugin* */
    LayoutPlugin *default_layout;
    bool initialized;
};

struct WmLayoutState {
    void *plugin_data;
    WmWorkspaceId workspace;
};

/*==============================================================================
 * Global State
 *============================================================================*/

static LayoutManager *g_layout_manager = NULL;

/*==============================================================================
 * Layout Manager Lifecycle
 *============================================================================*/

LayoutManager* wm_layout_manager_get(void) {
    return g_layout_manager;
}

bool wm_layout_manager_init(void) {
    if (g_layout_manager) return true;
    
    g_layout_manager = calloc(1, sizeof(LayoutManager));
    if (!g_layout_manager) return false;
    
    g_layout_manager->plugins = wm_list_new();
    g_layout_manager->by_name = wm_hashmap_new_string_key();
    
    if (!g_layout_manager->plugins || !g_layout_manager->by_name) {
        wm_layout_manager_shutdown();
        return false;
    }
    
    /* Register built-in layouts */
    /* These will be implemented as static plugins */
    
    g_layout_manager->initialized = true;
    LOG_INFO("Layout manager initialized");
    return true;
}

void wm_layout_manager_shutdown(void) {
    if (!g_layout_manager) return;
    
    /* Unload all plugins */
    AbstractClient *client = NULL;  /* Dummy for iteration */
    (void)client;
    
    /* Destroy all loaded plugins */
    while (!wm_list_is_empty(g_layout_manager->plugins)) {
        LayoutPlugin *plugin = wm_list_first(g_layout_manager->plugins);
        wm_layout_manager_unload(g_layout_manager, plugin);
    }
    
    wm_list_destroy(g_layout_manager->plugins);
    wm_hashmap_destroy(g_layout_manager->by_name);
    
    free(g_layout_manager);
    g_layout_manager = NULL;
    
    LOG_INFO("Layout manager shutdown");
}

/*==============================================================================
 * Layout Plugin Management
 *============================================================================*/

bool wm_layout_manager_register(LayoutManager *mgr, LayoutPlugin *plugin) {
    if (!mgr || !plugin || !plugin->interface) return false;
    
    /* Check API version */
    if (plugin->interface->api_version != WM_LAYOUT_PLUGIN_API_VERSION) {
        LOG_ERROR("Layout plugin '%s' has incompatible API version %d (expected %d)",
                  plugin->interface->name, plugin->interface->api_version,
                  WM_LAYOUT_PLUGIN_API_VERSION);
        return false;
    }
    
    /* Check if already registered */
    if (wm_hashmap_contains_string(mgr->by_name, plugin->interface->name)) {
        LOG_WARN("Layout plugin '%s' is already registered", plugin->interface->name);
        return false;
    }
    
    /* Initialize plugin */
    if (plugin->interface->init) {
        if (!plugin->interface->init(plugin)) {
            LOG_ERROR("Failed to initialize layout plugin '%s'", plugin->interface->name);
            return false;
        }
    }
    
    /* Add to lists */
    wm_list_append(mgr->plugins, plugin);
    wm_hashmap_insert_string(mgr->by_name, plugin->interface->name, plugin);
    
    /* Set as default if first layout */
    if (!mgr->default_layout) {
        mgr->default_layout = plugin;
    }
    
    LOG_INFO("Registered layout plugin: %s (%s)", 
             plugin->interface->name, plugin->interface->description);
    return true;
}

void wm_layout_manager_unregister(LayoutManager *mgr, LayoutPlugin *plugin) {
    if (!mgr || !plugin) return;
    
    /* Shutdown plugin */
    if (plugin->interface && plugin->interface->shutdown) {
        plugin->interface->shutdown(plugin);
    }
    
    /* Remove from lists */
    wm_list_remove(mgr->plugins, plugin);
    wm_hashmap_remove_string(mgr->by_name, plugin->interface->name);
    
    /* Clear default if this was it */
    if (mgr->default_layout == plugin) {
        mgr->default_layout = wm_list_first(mgr->plugins);
    }
    
    LOG_INFO("Unregistered layout plugin: %s", plugin->interface->name);
}

LayoutPlugin* wm_layout_manager_load(LayoutManager *mgr, const char *path) {
    if (!mgr || !path) return NULL;
    
    void *handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
    if (!handle) {
        LOG_ERROR("Failed to load layout plugin '%s': %s", path, dlerror());
        return NULL;
    }
    
    /* Get entry point */
    union dlsym_cast cast;
    cast.obj_ptr = dlsym(handle, WM_LAYOUT_PLUGIN_ENTRY);
    LayoutPluginEntryFunc entry = cast.func_ptr;
    if (!entry) {
        LOG_ERROR("Layout plugin '%s' missing entry point '%s'", 
                  path, WM_LAYOUT_PLUGIN_ENTRY);
        dlclose(handle);
        return NULL;
    }
    
    /* Get interface */
    const LayoutPluginInterface *iface = entry();
    if (!iface) {
        LOG_ERROR("Layout plugin '%s' returned NULL interface", path);
        dlclose(handle);
        return NULL;
    }
    
    /* Create plugin instance */
    LayoutPlugin *plugin = calloc(1, sizeof(LayoutPlugin));
    if (!plugin) {
        dlclose(handle);
        return NULL;
    }
    
    plugin->interface = iface;
    plugin->handle = handle;
    plugin->path = strdup(path);
    plugin->enabled = true;
    
    /* Register */
    if (!wm_layout_manager_register(mgr, plugin)) {
        free(plugin->path);
        free(plugin);
        dlclose(handle);
        return NULL;
    }
    
    return plugin;
}

void wm_layout_manager_unload(LayoutManager *mgr, LayoutPlugin *plugin) {
    if (!mgr || !plugin) return;
    
    void *handle = plugin->handle;
    
    wm_layout_manager_unregister(mgr, plugin);
    
    free(plugin->path);
    
    /* Free workspace states */
    if (plugin->workspace_states) {
        for (int i = 0; i < plugin->num_workspaces; i++) {
            if (plugin->workspace_states[i] && plugin->interface->state_destroy) {
                plugin->interface->state_destroy(plugin, plugin->workspace_states[i]);
            }
        }
        free(plugin->workspace_states);
    }
    
    free(plugin);
    
    if (handle) {
        dlclose(handle);
    }
}

LayoutPlugin* wm_layout_manager_get_layout(LayoutManager *mgr, const char *name) {
    if (!mgr || !name) return NULL;
    return wm_hashmap_get_string(mgr->by_name, name);
}

LayoutPlugin* wm_layout_manager_get_default(LayoutManager *mgr) {
    return mgr ? mgr->default_layout : NULL;
}

void wm_layout_manager_set_default(LayoutManager *mgr, LayoutPlugin *plugin) {
    if (mgr) mgr->default_layout = plugin;
}

const char** wm_layout_manager_list(LayoutManager *mgr, int *count) {
    if (!mgr || !count) return NULL;
    
    *count = wm_list_size(mgr->plugins);
    if (*count == 0) return NULL;
    
    const char **names = malloc(*count * sizeof(const char*));
    if (!names) return NULL;
    
    int i = 0;
    size_t idx = 0;
    while (idx < wm_list_size(mgr->plugins)) {
        LayoutPlugin *plugin = wm_list_get(mgr->plugins, idx++);
        if (plugin) names[i++] = plugin->interface->name;
    }
    
    return names;
}

/*==============================================================================
 * Layout Context
 *============================================================================*/

static bool get_workspace_info(WmWorkspaceId workspace, WmLayoutWorkArea *work_area,
                                int *client_count, int *master_count, int *stack_count,
                                float *master_ratio, int *master_count_target) {
    if (!dwn || !work_area || workspace < 0 || workspace >= MAX_WORKSPACES) {
        return false;
    }
    
    Workspace *ws = &dwn->workspaces[workspace];
    
    /* Calculate work area */
    work_area->x = 0;
    work_area->y = 0;
    work_area->width = dwn->screen_width;
    work_area->height = dwn->screen_height;
    work_area->padding_top = 0;
    work_area->padding_bottom = 0;
    work_area->padding_left = 0;
    work_area->padding_right = 0;
    work_area->gap = 0;
    
    if (dwn->config) {
        work_area->gap = config_get_gap();
        if (dwn->config->top_panel_enabled) {
            work_area->padding_top = config_get_panel_height();
        }
        if (dwn->config->bottom_panel_enabled) {
            work_area->padding_bottom = config_get_panel_height();
        }
    }
    
    work_area->y += work_area->padding_top;
    work_area->height -= work_area->padding_top + work_area->padding_bottom;
    
    /* Count clients */
    int count = 0;
    int masters = 0;
    int stacks = 0;
    
    for (Client *c = ws->clients; c; c = c->next) {
        if (!(c->flags & (CLIENT_MINIMIZED | CLIENT_UNMANAGING | CLIENT_FLOATING))) {
            count++;
            if (masters < ws->master_count) {
                masters++;
            } else {
                stacks++;
            }
        }
    }
    
    *client_count = count;
    *master_count = masters;
    *stack_count = stacks;
    *master_ratio = ws->master_ratio;
    *master_count_target = ws->master_count;
    
    return true;
}

bool wm_layout_context_init(LayoutContext *ctx, WmWorkspaceId workspace) {
    if (!ctx || !g_layout_manager) return false;
    
    memset(ctx, 0, sizeof(*ctx));
    
    ctx->workspace = workspace;
    
    if (!get_workspace_info(workspace, &ctx->work_area, &ctx->client_count,
                            &ctx->master_count, &ctx->stack_count,
                            &ctx->master_ratio, &ctx->master_count_target)) {
        return false;
    }
    
    ctx->gap = ctx->work_area.gap;
    
    return true;
}

void wm_layout_context_cleanup(LayoutContext *ctx) {
    if (!ctx) return;
    /* Nothing to cleanup currently */
}

/*==============================================================================
 * Layout Arrangement
 *============================================================================*/

typedef struct {
    AbstractClient **clients;
    WmLayoutGeometry *geometries;
    int count;
    int capacity;
} ArrangeData;

static bool collect_client(AbstractClient *client, void *data) {
    ArrangeData *ad = data;
    
    if (ad->count >= ad->capacity) return false;
    
    /* Skip floating, minimized, and unmanaged clients */
    if (wm_client_is_floating(client) || 
        wm_client_is_minimized(client) ||
        wm_client_has_state(client, WM_CLIENT_STATE_DESTROYING)) {
        return true;
    }
    
    ad->clients[ad->count] = client;
    ad->geometries[ad->count] = (WmLayoutGeometry){0};
    ad->count++;
    
    return true;
}

bool wm_layout_arrange(LayoutPlugin *plugin, WmWorkspaceId workspace) {
    if (!plugin || !plugin->interface || !plugin->interface->arrange) {
        return false;
    }
    
    /* Initialize context */
    LayoutContext ctx;
    if (!wm_layout_context_init(&ctx, workspace)) {
        return false;
    }
    
    ctx.config = &plugin->config;
    
    /* Get or create workspace state */
    if (workspace >= plugin->num_workspaces) {
        /* Expand workspace states array */
        int new_size = workspace + 1;
        WmLayoutState **new_states = realloc(plugin->workspace_states, 
                                              new_size * sizeof(WmLayoutState*));
        if (!new_states) {
            wm_layout_context_cleanup(&ctx);
            return false;
        }
        
        /* Initialize new entries */
        for (int i = plugin->num_workspaces; i < new_size; i++) {
            new_states[i] = NULL;
        }
        
        plugin->workspace_states = new_states;
        plugin->num_workspaces = new_size;
    }
    
    if (!plugin->workspace_states[workspace] && plugin->interface->state_create) {
        plugin->workspace_states[workspace] = 
            plugin->interface->state_create(plugin, workspace);
    }
    
    ctx.state = plugin->workspace_states[workspace];
    
    /* Collect clients */
    int max_clients = wm_client_count_on_workspace(workspace);
    if (max_clients == 0) {
        wm_layout_context_cleanup(&ctx);
        return true;
    }
    
    AbstractClient **clients = malloc(max_clients * sizeof(AbstractClient*));
    WmLayoutGeometry *geometries = malloc(max_clients * sizeof(WmLayoutGeometry));
    
    if (!clients || !geometries) {
        free(clients);
        free(geometries);
        wm_layout_context_cleanup(&ctx);
        return false;
    }
    
    ArrangeData data = { clients, geometries, 0, max_clients };
    wm_client_foreach_on_workspace(workspace, collect_client, &data);
    
    if (data.count == 0) {
        free(clients);
        free(geometries);
        wm_layout_context_cleanup(&ctx);
        return true;
    }
    
    /* Calculate layout */
    bool success = plugin->interface->arrange(plugin, &ctx, clients, data.count, geometries);
    
    /* Apply geometries */
    if (success) {
        for (int i = 0; i < data.count; i++) {
            AbstractClient *client = clients[i];
            WmLayoutGeometry *geom = &geometries[i];
            
            if (geom->hidden) {
                wm_client_hide(client);
            } else {
                wm_client_show(client);
                
                if (geom->fullscreen) {
                    wm_client_set_state(client, WM_CLIENT_STATE_FULLSCREEN);
                } else {
                    wm_client_move_resize(client, geom->x, geom->y, 
                                          geom->width, geom->height);
                }
            }
        }
    }
    
    free(clients);
    free(geometries);
    wm_layout_context_cleanup(&ctx);
    
    return success;
}

bool wm_layout_arrange_clients(LayoutPlugin *plugin, LayoutContext *ctx,
                                AbstractClient **clients, int num_clients) {
    if (!plugin || !plugin->interface || !plugin->interface->arrange) {
        return false;
    }
    
    WmLayoutGeometry *geometries = calloc(num_clients, sizeof(WmLayoutGeometry));
    if (!geometries) return false;
    
    bool success = plugin->interface->arrange(plugin, ctx, clients, num_clients, geometries);
    
    if (success) {
        for (int i = 0; i < num_clients; i++) {
            WmLayoutGeometry *geom = &geometries[i];
            if (!geom->hidden && !geom->fullscreen) {
                wm_client_move_resize(clients[i], geom->x, geom->y,
                                      geom->width, geom->height);
            }
        }
    }
    
    free(geometries);
    return success;
}
