/*
 * DWN - Desktop Window Manager
 * retoor <retoor@molodetz.nl>
 * Layout algorithms implementation
 */

#include "layout.h"
#include "client.h"
#include "workspace.h"
#include "config.h"
#include "util.h"
#include "panel.h"
#include "notifications.h"

static const char *layout_names[] = {
    "Tiling",
    "Floating",
    "Monocle",
    "Centered",
    "Columns",
    "Fibonacci"
};

static const char *layout_symbols[] = {
    "[]=",
    "><>",
    "[M]",
    "|M|",
    "|||",
    "[@]"
};


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

    switch (ws->layout) {
        case LAYOUT_TILING:
            layout_arrange_tiling(workspace);
            break;
        case LAYOUT_FLOATING:
            layout_arrange_floating(workspace);
            break;
        case LAYOUT_MONOCLE:
            layout_arrange_monocle(workspace);
            break;
        case LAYOUT_CENTERED_MASTER:
            layout_arrange_centered_master(workspace);
            break;
        case LAYOUT_COLUMNS:
            layout_arrange_columns(workspace);
            break;
        case LAYOUT_FIBONACCI:
            layout_arrange_fibonacci(workspace);
            break;
        default:
            layout_arrange_tiling(workspace);
            break;
    }

    panel_raise_all();
    notifications_raise_all();
}


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

    int area_x, area_y, area_width, area_height;
    layout_get_usable_area(&area_x, &area_y, &area_width, &area_height);

    int gap = config_get_gap();
    int title_height = config_get_title_height();
    int border = config_get_border_width();

    int n = layout_count_tiled_clients(workspace);
    if (n == 0) {
        return;
    }

    int master_count = ws->master_count;
    if (master_count > n) {
        master_count = n;
    }

    int master_width;
    if (n <= master_count) {
        master_width = area_width - 2 * gap;
    } else {
        master_width = (int)((area_width - 3 * gap) * ws->master_ratio);
    }

    int stack_width = area_width - master_width - 3 * gap;
    if (stack_width < 50) stack_width = 50;

    int i = 0;
    int master_y = area_y + gap;
    int stack_y = area_y + gap;

    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) {
            c = next;
            continue;
        }
        
        if (client_is_floating(c) || client_is_fullscreen(c) || client_is_minimized(c)) {
            c = next;
            continue;
        }

        int x, y, w, h;

        if (i < master_count) {
            int master_h = (area_height - 2 * gap - (master_count - 1) * gap) / master_count;

            x = area_x + gap;
            y = master_y;
            w = master_width;
            h = master_h;

            master_y += h + gap;
        } else {
            int stack_count = n - master_count;
            int stack_h = (area_height - 2 * gap - (stack_count - 1) * gap) / stack_count;

            x = area_x + gap + master_width + gap;
            y = stack_y;
            w = stack_width;
            h = stack_h;

            stack_y += h + gap;
        }

        int max_y = area_y + area_height - gap;
        int max_x = area_x + area_width - gap;

        if (y + h > max_y) {
            h = max_y - y;
        }
        if (x + w > max_x) {
            w = max_x - x;
        }

        int actual_h = h - title_height - 2 * border;
        int actual_w = w - 2 * border;

        if (actual_h < 50) actual_h = 50;
        if (actual_w < 50) actual_w = 50;

        client_move_resize(c, x + border, y + title_height + border,
                           actual_w, actual_h);

        i++;
        
        c = next;
    }
}


void layout_arrange_floating(int workspace)
{
    int area_x, area_y, area_width, area_height;
    layout_get_usable_area(&area_x, &area_y, &area_width, &area_height);

    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) {
            c = next;
            continue;
        }
        
        if (client_is_minimized(c)) {
            c = next;
            continue;
        }

        int title_h = (dwn->config && dwn->config->show_decorations) ?
                      config_get_title_height() : 0;
        int border = c->border_width;

        /* Constrain top */
        if (c->y - title_h - border < area_y) {
            c->y = area_y + title_h + border;
        }

        /* Constrain left */
        if (c->x - border < area_x) {
            c->x = area_x + border;
        }

        /* Constrain bottom */
        if (c->y + c->height + border > area_y + area_height) {
            c->y = area_y + area_height - c->height - border;
            /* If it still violates top after moving up, we must resize */
            if (c->y - title_h - border < area_y) {
                c->y = area_y + title_h + border;
                c->height = area_y + area_height - c->y - border;
            }
        }

        /* Constrain right */
        if (c->x + c->width + border > area_x + area_width) {
            c->x = area_x + area_width - c->width - border;
            /* If it still violates left after moving left, we must resize */
            if (c->x - border < area_x) {
                c->x = area_x + border;
                c->width = area_x + area_width - c->x - border;
            }
        }

        if (c->width < 50) c->width = 50;
        if (c->height < 50) c->height = 50;

        client_configure(c);
        
        c = next;
    }
}


void layout_arrange_monocle(int workspace)
{
    int area_x, area_y, area_width, area_height;
    layout_get_usable_area(&area_x, &area_y, &area_width, &area_height);

    int gap = config_get_gap();
    int title_height = config_get_title_height();
    int border = config_get_border_width();

    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) {
            c = next;
            continue;
        }
        
        if (client_is_floating(c) || client_is_fullscreen(c) || client_is_minimized(c)) {
            c = next;
            continue;
        }

        int x = area_x + gap;
        int y = area_y + gap;
        int w = area_width - 2 * gap - 2 * border;
        int h = area_height - 2 * gap - title_height - 2 * border;

        if (h < 50) h = 50;
        if (w < 50) w = 50;

        client_move_resize(c, x + border, y + title_height + border, w, h);
        
        c = next;
    }
}


int layout_get_usable_area(int *x, int *y, int *width, int *height)
{
    if (dwn == NULL) {
        return -1;
    }

    int panel_height = config_get_panel_height();
    bool top_panel = (dwn->config != NULL) ? dwn->config->top_panel_enabled : true;
    bool bottom_panel = (dwn->config != NULL) ? dwn->config->bottom_panel_enabled : true;

    *x = 0;
    *y = top_panel ? panel_height : 0;
    *width = dwn->screen_width;
    *height = dwn->screen_height;

    if (top_panel) {
        *height -= panel_height;
    }
    if (bottom_panel) {
        *height -= panel_height;
    }

    return 0;
}

int layout_count_tiled_clients(int workspace)
{
    int count = 0;

    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) {
            c = next;
            continue;
        }
        
        if (client_is_floating(c) || client_is_fullscreen(c) || client_is_minimized(c)) {
            c = next;
            continue;
        }
        
        count++;
        
        c = next;
    }

    return count;
}

void layout_apply_snap_constraint(Client *c, int area_x, int area_y,
                                  int area_w, int area_h, int gap)
{
    if (c == NULL) {
        return;
    }

    int title_h = (dwn->config && dwn->config->show_decorations) ?
                  config_get_title_height() : 0;
    int border = c->border_width;

    int half_w = area_w / 2;
    int half_h = area_h / 2;

    switch (c->snap.horizontal) {
    case SNAP_H_LEFT:
        c->x = area_x + gap + border;
        c->width = half_w - gap * 2 - 2 * border;
        break;
    case SNAP_H_RIGHT:
        c->x = area_x + half_w + gap + border;
        c->width = half_w - gap * 2 - 2 * border;
        break;
    case SNAP_H_FULL:
        c->x = area_x + gap + border;
        c->width = area_w - gap * 2 - 2 * border;
        break;
    case SNAP_H_NONE:
        break;
    }

    switch (c->snap.vertical) {
    case SNAP_V_TOP:
        c->y = area_y + gap + title_h + border;
        c->height = half_h - gap * 2 - title_h - 2 * border;
        break;
    case SNAP_V_BOTTOM:
        c->y = area_y + half_h + gap + title_h + border;
        c->height = half_h - gap * 2 - title_h - 2 * border;
        break;
    case SNAP_V_FULL:
        c->y = area_y + gap + title_h + border;
        c->height = area_h - gap * 2 - title_h - 2 * border;
        break;
    case SNAP_V_NONE:
        break;
    }
}

const char *layout_get_name(LayoutType layout)
{
    if (layout >= 0 && layout < LAYOUT_COUNT) {
        return layout_names[layout];
    }
    return "Unknown";
}

const char *layout_get_symbol(LayoutType layout)
{
    if (layout >= 0 && layout < LAYOUT_COUNT) {
        return layout_symbols[layout];
    }
    return "???";
}


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

    int area_x, area_y, area_width, area_height;
    layout_get_usable_area(&area_x, &area_y, &area_width, &area_height);

    int gap = config_get_gap();
    int title_height = config_get_title_height();
    int border = config_get_border_width();

    int n = layout_count_tiled_clients(workspace);
    if (n == 0) {
        return;
    }

    int master_count = ws->master_count;
    if (master_count > n) {
        master_count = n;
    }

    if (n == 1) {
        for (Client *c = dwn->client_list; c != NULL; c = c->next) {
            if (c->flags & CLIENT_UNMANAGING) continue;
            if (c->workspace != (unsigned int)workspace) continue;
            if (client_is_floating(c) || client_is_fullscreen(c) || client_is_minimized(c)) continue;

            int x = area_x + gap + border;
            int y = area_y + gap + title_height + border;
            int w = area_width - 2 * gap - 2 * border;
            int h = area_height - 2 * gap - title_height - 2 * border;

            client_move_resize(c, x, y, w, h);
            break;
        }
        return;
    }

    int stack_count = n - master_count;
    int left_stack = stack_count / 2;
    int right_stack = stack_count - left_stack;

    int master_width = (int)((area_width - 4 * gap) * ws->master_ratio);
    int side_width = (area_width - master_width - 4 * gap) / 2;

    int master_x = area_x + gap + side_width + gap;
    int left_x = area_x + gap;
    int right_x = master_x + master_width + gap;

    int i = 0;
    int master_y = area_y + gap;
    int left_y = area_y + gap;
    int right_y = area_y + gap;
    int left_i = 0;
    int right_i = 0;

    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) {
            c = next;
            continue;
        }

        if (client_is_floating(c) || client_is_fullscreen(c) || client_is_minimized(c)) {
            c = next;
            continue;
        }

        int x, y, w, h;

        if (i < master_count) {
            int master_h = (area_height - 2 * gap - (master_count - 1) * gap) / master_count;

            x = master_x;
            y = master_y;
            w = master_width;
            h = master_h;

            master_y += h + gap;
        } else if (left_i < left_stack) {
            int left_h = left_stack > 0 ?
                         (area_height - 2 * gap - (left_stack - 1) * gap) / left_stack : 0;

            x = left_x;
            y = left_y;
            w = side_width;
            h = left_h;

            left_y += h + gap;
            left_i++;
        } else {
            int right_h = right_stack > 0 ?
                          (area_height - 2 * gap - (right_stack - 1) * gap) / right_stack : 0;

            x = right_x;
            y = right_y;
            w = side_width;
            h = right_h;

            right_y += h + gap;
            right_i++;
        }

        int actual_h = h - title_height - 2 * border;
        int actual_w = w - 2 * border;

        if (actual_h < 50) actual_h = 50;
        if (actual_w < 50) actual_w = 50;

        client_move_resize(c, x + border, y + title_height + border,
                           actual_w, actual_h);

        i++;
        c = next;
    }
}


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

    int area_x, area_y, area_width, area_height;
    layout_get_usable_area(&area_x, &area_y, &area_width, &area_height);

    int gap = config_get_gap();
    int title_height = config_get_title_height();
    int border = config_get_border_width();

    int n = layout_count_tiled_clients(workspace);
    if (n == 0) {
        return;
    }

    int col_width = (area_width - (n + 1) * gap) / n;
    if (col_width < 50) col_width = 50;

    int i = 0;
    int col_x = area_x + gap;

    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) {
            c = next;
            continue;
        }

        if (client_is_floating(c) || client_is_fullscreen(c) || client_is_minimized(c)) {
            c = next;
            continue;
        }

        int x = col_x;
        int y = area_y + gap;
        int w = col_width - 2 * border;
        int h = area_height - 2 * gap - title_height - 2 * border;

        if (w < 50) w = 50;
        if (h < 50) h = 50;

        client_move_resize(c, x + border, y + title_height + border, w, h);

        col_x += col_width + gap;
        i++;
        c = next;
    }
}


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

    int area_x, area_y, area_width, area_height;
    layout_get_usable_area(&area_x, &area_y, &area_width, &area_height);

    int gap = config_get_gap();
    int title_height = config_get_title_height();
    int border = config_get_border_width();

    int n = layout_count_tiled_clients(workspace);
    if (n == 0) {
        return;
    }

    int fib_x = area_x + gap;
    int fib_y = area_y + gap;
    int fib_w = area_width - 2 * gap;
    int fib_h = area_height - 2 * gap;

    int i = 0;

    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) {
            c = next;
            continue;
        }

        if (client_is_floating(c) || client_is_fullscreen(c) || client_is_minimized(c)) {
            c = next;
            continue;
        }

        int x = fib_x;
        int y = fib_y;
        int w = fib_w;
        int h = fib_h;

        if (i < n - 1) {
            switch (i % 4) {
            case 0:
                w = (int)(fib_w * ws->master_ratio);
                fib_x += w + gap;
                fib_w -= w + gap;
                break;
            case 1:
                h = (int)(fib_h * ws->master_ratio);
                fib_y += h + gap;
                fib_h -= h + gap;
                break;
            case 2:
                w = (int)(fib_w * ws->master_ratio);
                fib_w -= w + gap;
                x = fib_x + fib_w + gap;
                break;
            case 3:
                h = (int)(fib_h * ws->master_ratio);
                fib_h -= h + gap;
                y = fib_y + fib_h + gap;
                break;
            }
        }

        int actual_w = w - 2 * border;
        int actual_h = h - title_height - 2 * border;

        if (actual_w < 50) actual_w = 50;
        if (actual_h < 50) actual_h = 50;

        client_move_resize(c, x + border, y + title_height + border,
                           actual_w, actual_h);

        i++;
        c = next;
    }
}
