/*
 * DWN - Desktop Window Manager
 * retoor <retoor@molodetz.nl>
 * Workspace management implementation
 */

#include "workspace.h"
#include "client.h"
#include "layout.h"
#include "atoms.h"
#include "util.h"
#include "config.h"
#include "panel.h"
#include "api.h"
#include "decorations.h"

#include <string.h>
#include <stdio.h>


void workspace_init(void)
{
    if (dwn == NULL) {
        return;
    }

    LOG_DEBUG("Initializing %d workspaces", MAX_WORKSPACES);

    for (int i = 0; i < MAX_WORKSPACES; i++) {
        Workspace *ws = &dwn->workspaces[i];

        ws->clients = NULL;
        ws->focused = NULL;
        ws->mru_head = NULL;
        ws->mru_tail = NULL;
        ws->layout = (dwn->config != NULL) ?
                     dwn->config->default_layout : LAYOUT_TILING;
        ws->master_ratio = (dwn->config != NULL) ?
                           dwn->config->default_master_ratio : 0.55f;
        ws->master_count = (dwn->config != NULL) ?
                           dwn->config->default_master_count : 1;

        snprintf(ws->name, sizeof(ws->name), "%d", i + 1);
    }

    dwn->current_workspace = 0;
}

void workspace_cleanup(void)
{
}


Workspace *workspace_get(int index)
{
    if (dwn == NULL || index < 0 || index >= MAX_WORKSPACES) {
        return NULL;
    }
    return &dwn->workspaces[index];
}

Workspace *workspace_get_current(void)
{
    return workspace_get(dwn->current_workspace);
}

int workspace_get_current_index(void)
{
    return dwn != NULL ? dwn->current_workspace : 0;
}


void workspace_switch(int index)
{
    if (dwn == NULL || index < 0 || index >= MAX_WORKSPACES) {
        return;
    }

    if (index == dwn->current_workspace) {
        return;
    }

    LOG_DEBUG("Switching from workspace %d to %d",
              dwn->current_workspace + 1, index + 1);

    XGrabServer(dwn->display);

    workspace_hide(dwn->current_workspace);

    int old_workspace = dwn->current_workspace;
    dwn->current_workspace = index;

    workspace_show(index);

    workspace_arrange(index);

    Workspace *ws = workspace_get(index);
    if (ws != NULL) {
        if (ws->focused != NULL) {
            client_focus(ws->focused, true);
        } else {
            Client *first = workspace_get_first_client(index);
            if (first != NULL) {
                client_focus(first, true);
            } else {
                XSetInputFocus(dwn->display, dwn->root, RevertToPointerRoot, CurrentTime);
                atoms_set_active_window(None);
            }
        }
    }

    atoms_set_current_desktop(index);

    panel_raise_all();

    XUngrabServer(dwn->display);
    XSync(dwn->display, False);

    api_emit_workspace_switched(old_workspace, index);
}

void workspace_switch_next(void)
{
    int next = (dwn->current_workspace + 1) % MAX_WORKSPACES;
    workspace_switch(next);
}

void workspace_switch_prev(void)
{
    int prev = (dwn->current_workspace - 1 + MAX_WORKSPACES) % MAX_WORKSPACES;
    workspace_switch(prev);
}


void workspace_add_client(int workspace, Client *client)
{
    if (client == NULL || workspace < 0 || workspace >= MAX_WORKSPACES) {
        return;
    }

    Workspace *ws = workspace_get(workspace);
    if (ws == NULL) {
        return;
    }

    client->workspace = workspace;

    api_emit_workspace_window_added(workspace, client->window);
}

void workspace_remove_client(int workspace, Client *client)
{
    if (client == NULL || workspace < 0 || workspace >= MAX_WORKSPACES) {
        return;
    }

    Workspace *ws = workspace_get(workspace);
    if (ws == NULL) {
        return;
    }

    if (ws->focused == client) {
        ws->focused = NULL;
    }

    api_emit_workspace_window_removed(workspace, client->window);
}

void workspace_move_client(Client *client, int new_workspace)
{
    if (client == NULL || new_workspace < 0 || new_workspace >= MAX_WORKSPACES) {
        return;
    }

    if (client->flags & CLIENT_UNMANAGING) {
        LOG_DEBUG("workspace_move_client: client %lu being unmanged, skipping", client->window);
        return;
    }

    if (client->workspace == (unsigned int)new_workspace) {
        return;
    }

    int old_workspace = client->workspace;

    LOG_DEBUG("Moving window '%s' from workspace %d to %d",
              client->title, old_workspace + 1, new_workspace + 1);

    workspace_remove_client(old_workspace, client);
    workspace_add_client(new_workspace, client);
    atoms_set_window_desktop(client->window, new_workspace);

    Workspace *target_ws = workspace_get(new_workspace);
    if (target_ws != NULL) {
        target_ws->focused = client;
    }

    workspace_arrange(old_workspace);

    workspace_switch(new_workspace);
}

Client *workspace_get_first_client(int workspace)
{
    if (workspace < 0 || workspace >= MAX_WORKSPACES) {
        return NULL;
    }

    for (Client *c = dwn->client_list; c != NULL; c = c->next) {
        if (c->workspace == (unsigned int)workspace && !client_is_minimized(c)) {
            return c;
        }
    }

    return NULL;
}

Client *workspace_get_focused_client(int workspace)
{
    Workspace *ws = workspace_get(workspace);
    return ws != NULL ? ws->focused : NULL;
}


void workspace_set_layout(int workspace, LayoutType layout)
{
    Workspace *ws = workspace_get(workspace);
    if (ws == NULL) {
        return;
    }

    int old_layout = ws->layout;
    ws->layout = layout;

    workspace_arrange(workspace);

    api_emit_workspace_layout_changed(workspace, old_layout, layout);

    LOG_DEBUG("Workspace %d layout set to %d", workspace + 1, layout);
}

LayoutType workspace_get_layout(int workspace)
{
    Workspace *ws = workspace_get(workspace);
    return ws != NULL ? ws->layout : LAYOUT_TILING;
}

void workspace_cycle_layout(int workspace)
{
    Workspace *ws = workspace_get(workspace);
    if (ws == NULL) {
        return;
    }

    int old_layout = ws->layout;
    ws->layout = (ws->layout + 1) % LAYOUT_COUNT;

    workspace_arrange(workspace);

    api_emit_workspace_layout_changed(workspace, old_layout, ws->layout);

    LOG_INFO("Workspace %d: %s layout", workspace + 1, layout_get_name(ws->layout));
}

void workspace_set_master_ratio(int workspace, float ratio)
{
    Workspace *ws = workspace_get(workspace);
    if (ws == NULL) {
        return;
    }

    float old_ratio = ws->master_ratio;

    if (ratio < 0.1f) ratio = 0.1f;
    if (ratio > 0.9f) ratio = 0.9f;

    ws->master_ratio = ratio;

    workspace_arrange(workspace);

    if (old_ratio != ws->master_ratio) {
        api_emit_workspace_master_ratio_changed(workspace, old_ratio, ws->master_ratio);
    }
}

void workspace_adjust_master_ratio(int workspace, float delta)
{
    Workspace *ws = workspace_get(workspace);
    if (ws != NULL) {
        workspace_set_master_ratio(workspace, ws->master_ratio + delta);
    }
}

void workspace_set_master_count(int workspace, int count)
{
    Workspace *ws = workspace_get(workspace);
    if (ws == NULL) {
        return;
    }

    int old_count = ws->master_count;

    if (count < 1) count = 1;
    ws->master_count = count;

    workspace_arrange(workspace);

    if (old_count != ws->master_count) {
        api_emit_workspace_master_count_changed(workspace, old_count, ws->master_count);
    }
}

void workspace_adjust_master_count(int workspace, int delta)
{
    Workspace *ws = workspace_get(workspace);
    if (ws != NULL) {
        workspace_set_master_count(workspace, ws->master_count + delta);
    }
}


void workspace_arrange(int workspace)
{
    Workspace *ws = workspace_get(workspace);
    if (ws == NULL) {
        return;
    }

    if (workspace != dwn->current_workspace) {
        return;
    }

    layout_arrange(workspace);

    api_emit_workspace_arranged(workspace, ws->layout);
}

void workspace_arrange_current(void)
{
    workspace_arrange(dwn->current_workspace);
    panel_raise_all();
}


void workspace_show(int workspace)
{
    if (workspace < 0 || workspace >= MAX_WORKSPACES) {
        return;
    }

    for (Client *c = dwn->client_list; c != NULL; ) {
        Client *next = c->next;
        
        if (c->flags & CLIENT_UNMANAGING) {
            c = next;
            continue;
        }
        
        if (c->workspace == (unsigned int)workspace && !client_is_minimized(c)) {
            client_show(c);
        }
        
        c = next;
    }
}

void workspace_hide(int workspace)
{
    if (workspace < 0 || workspace >= MAX_WORKSPACES) {
        return;
    }

    for (Client *c = dwn->client_list; c != NULL; ) {
        Client *next = c->next;
        
        if (c->flags & CLIENT_UNMANAGING) {
            c = next;
            continue;
        }
        
        if (c->workspace == (unsigned int)workspace) {
            client_hide(c);
        }
        
        c = next;
    }
}


void workspace_set_name(int workspace, const char *name)
{
    Workspace *ws = workspace_get(workspace);
    if (ws == NULL || name == NULL) {
        return;
    }

    strncpy(ws->name, name, sizeof(ws->name) - 1);
    ws->name[sizeof(ws->name) - 1] = '\0';

    atoms_update_desktop_names();
}

const char *workspace_get_name(int workspace)
{
    Workspace *ws = workspace_get(workspace);
    return ws != NULL ? ws->name : "";
}

int workspace_client_count(int workspace)
{
    return client_count_on_workspace(workspace);
}

bool workspace_is_empty(int workspace)
{
    return workspace_client_count(workspace) == 0;
}


void workspace_focus_next(void)
{
    Workspace *ws = workspace_get_current();
    if (ws == NULL || ws->focused == NULL) {
        return;
    }

    Client *next = ws->focused->next;
    while (next != NULL && next->workspace != (unsigned int)dwn->current_workspace) {
        next = next->next;
    }

    if (next == NULL) {
        next = workspace_get_first_client(dwn->current_workspace);
    }

    if (next != NULL && next != ws->focused) {
        client_focus(next, true);
    }
}

void workspace_focus_prev(void)
{
    Workspace *ws = workspace_get_current();
    if (ws == NULL || ws->focused == NULL) {
        return;
    }

    Client *prev = ws->focused->prev;
    while (prev != NULL && prev->workspace != (unsigned int)dwn->current_workspace) {
        prev = prev->prev;
    }

    if (prev == NULL) {
        for (Client *c = client_get_last(); c != NULL; c = c->prev) {
            if (c->workspace == (unsigned int)dwn->current_workspace) {
                prev = c;
                break;
            }
        }
    }

    if (prev != NULL && prev != ws->focused) {
        client_focus(prev, true);
    }
}

void workspace_focus_master(void)
{
    Client *master = workspace_get_first_client(dwn->current_workspace);
    if (master != NULL) {
        client_focus(master, true);
    }
}


void workspace_mru_push(int workspace, Client *client)
{
    if (client == NULL || workspace < 0 || workspace >= MAX_WORKSPACES) {
        return;
    }

    Workspace *ws = workspace_get(workspace);
    if (ws == NULL) {
        return;
    }

    workspace_mru_remove(workspace, client);

    client->mru_next = ws->mru_head;
    client->mru_prev = NULL;

    if (ws->mru_head != NULL) {
        ws->mru_head->mru_prev = client;
    }
    ws->mru_head = client;

    if (ws->mru_tail == NULL) {
        ws->mru_tail = client;
    }
}

void workspace_mru_remove(int workspace, Client *client)
{
    if (client == NULL || workspace < 0 || workspace >= MAX_WORKSPACES) {
        return;
    }

    Workspace *ws = workspace_get(workspace);
    if (ws == NULL) {
        return;
    }

    if (client->mru_prev != NULL) {
        client->mru_prev->mru_next = client->mru_next;
    } else if (ws->mru_head == client) {
        ws->mru_head = client->mru_next;
    }

    if (client->mru_next != NULL) {
        client->mru_next->mru_prev = client->mru_prev;
    } else if (ws->mru_tail == client) {
        ws->mru_tail = client->mru_prev;
    }

    client->mru_next = NULL;
    client->mru_prev = NULL;
}

Client *workspace_mru_get_previous(int workspace, Client *current)
{
    Workspace *ws = workspace_get(workspace);
    if (ws == NULL) {
        return NULL;
    }

    Client *candidate = ws->mru_head;
    while (candidate != NULL) {
        if (candidate != current && !client_is_minimized(candidate)) {
            return candidate;
        }
        candidate = candidate->mru_next;
    }

    return NULL;
}

void workspace_alt_tab_next(void)
{
    if (dwn == NULL) return;

    Workspace *ws = workspace_get_current();
    if (ws == NULL || ws->mru_head == NULL) return;

    if (!dwn->is_alt_tabbing) {
        dwn->is_alt_tabbing = true;
        dwn->alt_tab_client = ws->focused;
        XGrabKeyboard(dwn->display, dwn->root, True,
                      GrabModeAsync, GrabModeAsync, CurrentTime);
    }

    Client *previous_selection = dwn->alt_tab_client;
    Client *current = dwn->alt_tab_client;
    if (current == NULL) {
        current = ws->mru_head;
    }

    if (current != NULL && current->mru_next != NULL) {
        dwn->alt_tab_client = current->mru_next;
    } else {
        dwn->alt_tab_client = ws->mru_head;
    }

    if (previous_selection != NULL && previous_selection->frame != None) {
        decorations_render(previous_selection, false);
    }

    if (dwn->alt_tab_client != NULL) {
        client_focus(dwn->alt_tab_client, false);
        if (dwn->alt_tab_client->frame != None) {
            decorations_render(dwn->alt_tab_client, true);
        }
    }
}

void workspace_alt_tab_prev(void)
{
    if (dwn == NULL) return;

    Workspace *ws = workspace_get_current();
    if (ws == NULL || ws->mru_head == NULL) return;

    if (!dwn->is_alt_tabbing) {
        dwn->is_alt_tabbing = true;
        dwn->alt_tab_client = ws->focused;
        XGrabKeyboard(dwn->display, dwn->root, True,
                      GrabModeAsync, GrabModeAsync, CurrentTime);
    }

    Client *previous_selection = dwn->alt_tab_client;
    Client *current = dwn->alt_tab_client;

    if (current != NULL && current->mru_prev != NULL) {
        dwn->alt_tab_client = current->mru_prev;
    } else {
        dwn->alt_tab_client = ws->mru_tail;
    }

    if (previous_selection != NULL && previous_selection->frame != None) {
        decorations_render(previous_selection, false);
    }

    if (dwn->alt_tab_client != NULL) {
        client_focus(dwn->alt_tab_client, false);
        if (dwn->alt_tab_client->frame != None) {
            decorations_render(dwn->alt_tab_client, true);
        }
    }
}

void workspace_end_alt_tab(void)
{
    if (dwn == NULL || !dwn->is_alt_tabbing) return;

    dwn->is_alt_tabbing = false;

        if (dwn->alt_tab_client != NULL) {

            workspace_mru_push(dwn->current_workspace, dwn->alt_tab_client);

            /* Redraw panel to show new MRU order */

            panel_render_all();

        }

        

        dwn->alt_tab_client = NULL;

        XUngrabKeyboard(dwn->display, CurrentTime);

    }

    