/*
 * vim:ts=4:sw=4:expandtab
 *
 * i3 - an improved dynamic tiling window manager
 * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
 *
 * gaps.c: gaps logic: whether to display gaps at all, and how big
 *         they should be.
 *
 */
#include "all.h"

/**
 * Calculates the effective gap sizes for a container.
 */
gaps_t calculate_effective_gaps(Con *con) {
    Con *workspace = con_get_workspace(con);
    if (workspace == NULL)
        return (gaps_t){0, 0, 0, 0, 0};

    bool one_child = con_num_visible_children(workspace) <= 1 ||
                     (con_num_children(workspace) == 1 &&
                      (TAILQ_FIRST(&(workspace->nodes_head))->layout == L_TABBED ||
                       TAILQ_FIRST(&(workspace->nodes_head))->layout == L_STACKED));

    if (config.smart_gaps == SMART_GAPS_ON && one_child)
        return (gaps_t){0, 0, 0, 0, 0};

    gaps_t gaps = {
        .inner = (workspace->gaps.inner + config.gaps.inner),
        .top = 0,
        .right = 0,
        .bottom = 0,
        .left = 0};

    if (config.smart_gaps != SMART_GAPS_INVERSE_OUTER || one_child) {
        gaps.top = workspace->gaps.top + config.gaps.top;
        gaps.right = workspace->gaps.right + config.gaps.right;
        gaps.bottom = workspace->gaps.bottom + config.gaps.bottom;
        gaps.left = workspace->gaps.left + config.gaps.left;
    }

    return gaps;
}

/*
 * Decides whether the container should be inset.
 */
bool gaps_should_inset_con(Con *con, int children) {
    /* No parent? None of the conditionals below can be true. */
    if (con->parent == NULL) {
        return false;
    }

    /* Floating split containers should never have gaps inside them. */
    if (con_inside_floating(con)) {
        return false;
    }

    const bool leaf_or_stacked_tabbed =
        con_is_leaf(con) ||
        (con->layout == L_STACKED || con->layout == L_TABBED);

    /* Inset direct children of the workspace that are leaf containers or
       stacked/tabbed containers. */
    if (leaf_or_stacked_tabbed &&
        con->parent->type == CT_WORKSPACE) {
        return true;
    }

    /* Inset direct children of vertical or horizontal split containers at any
       depth in the tree. Do not inset as soon as any parent is a stacked or
       tabbed container, to avoid double insets. */
    if (leaf_or_stacked_tabbed &&
        !con_inside_stacked_or_tabbed(con) &&
        con->parent->type == CT_CON &&
        (con->parent->layout == L_SPLITH ||
         con->parent->layout == L_SPLITV)) {
        return true;
    }

    return false;
}

/*
 * Returns whether the given container has an adjacent container in the
 * specified direction. In other words, this returns true if and only if
 * the container is not touching the edge of the screen in that direction.
 */
bool gaps_has_adjacent_container(Con *con, direction_t direction) {
    Con *workspace = con_get_workspace(con);
    Con *fullscreen = con_get_fullscreen_con(workspace, CF_GLOBAL);
    if (fullscreen == NULL)
        fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);

    /* If this container is fullscreen by itself, there's no adjacent container. */
    if (con == fullscreen)
        return false;

    Con *first = con;
    Con *second = NULL;
    bool found_neighbor = resize_find_tiling_participants(&first, &second, direction, false);
    if (!found_neighbor)
        return false;

    /* If we have an adjacent container and nothing is fullscreen, we consider it. */
    if (fullscreen == NULL)
        return true;

    /* For fullscreen containers, only consider the adjacent container if it is also fullscreen. */
    return con_has_parent(con, fullscreen) && con_has_parent(second, fullscreen);
}

/*
 * Returns the configured gaps for this workspace based on the workspace name,
 * number, and configured workspace gap assignments.
 */
gaps_t gaps_for_workspace(Con *ws) {
    gaps_t gaps = (gaps_t){0, 0, 0, 0, 0};
    gaps_mask_t mask = 0;
    struct Workspace_Assignment *assignment;
    TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
        if (strcmp(assignment->name, ws->name) == 0) {
            gaps = assignment->gaps;
            mask = assignment->gaps_mask;
            break;
        } else if (ws->num != -1 && name_is_digits(assignment->name) && ws_name_to_number(assignment->name) == ws->num) {
            gaps = assignment->gaps;
            mask = assignment->gaps_mask;
        }
    }
    if (mask == 0) {
        return gaps;
    }

    if (mask & GAPS_INNER) {
        gaps.inner -= config.gaps.inner;
    }
    if (mask & GAPS_TOP) {
        gaps.top -= config.gaps.top;
    }
    if (mask & GAPS_RIGHT) {
        gaps.right -= config.gaps.right;
    }
    if (mask & GAPS_BOTTOM) {
        gaps.bottom -= config.gaps.bottom;
    }
    if (mask & GAPS_LEFT) {
        gaps.left -= config.gaps.left;
    }

    return gaps;
}

/*
 * Re-applies all workspace gap assignments to existing workspaces after
 * reloading the configuration file.
 *
 */
void gaps_reapply_workspace_assignments(void) {
    Con *output, *workspace = NULL;
    TAILQ_FOREACH (output, &(croot->nodes_head), nodes) {
        Con *content = output_get_content(output);
        TAILQ_FOREACH (workspace, &(content->nodes_head), nodes) {
            DLOG("updating gap assignments for workspace %s\n", workspace->name);
            workspace->gaps = gaps_for_workspace(workspace);
        }
    }
}
