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

#include "decorations.h"
#include "client.h"
#include "config.h"
#include "util.h"
#include "workspace.h"

#include <string.h>
#include <stdbool.h>
#include <X11/Xft/Xft.h>

#define BUTTON_SIZE 16
#define BUTTON_PADDING 4

#define RESIZE_EDGE 8

#define DEC_XFT_COLOR_CACHE_SIZE 8

typedef struct {
    unsigned long pixel;
    XftColor xft_color;
    bool valid;
    int lru_counter;
} DecCachedXftColor;

static DecCachedXftColor dec_xft_color_cache[DEC_XFT_COLOR_CACHE_SIZE];
static Visual *dec_cached_visual = NULL;
static int dec_xft_color_lru_counter = 0;

static XftDraw *dec_cached_xft_draw = NULL;
static Window dec_cached_window = None;

static XftDraw *dec_get_xft_draw(Window w)
{
    if (dec_cached_window == w && dec_cached_xft_draw != NULL) {
        return dec_cached_xft_draw;
    }
    if (dec_cached_xft_draw != NULL) {
        XftDrawDestroy(dec_cached_xft_draw);
    }
    if (dec_cached_visual == NULL) {
        dec_cached_visual = DefaultVisual(dwn->display, dwn->screen);
    }
    dec_cached_xft_draw = XftDrawCreate(dwn->display, w, dec_cached_visual, dwn->colormap);
    dec_cached_window = w;
    return dec_cached_xft_draw;
}

static XftColor *dec_get_cached_xft_color(unsigned long color)
{
    if (dec_cached_visual == NULL) {
        dec_cached_visual = DefaultVisual(dwn->display, dwn->screen);
    }

    for (int i = 0; i < DEC_XFT_COLOR_CACHE_SIZE; i++) {
        if (dec_xft_color_cache[i].valid && dec_xft_color_cache[i].pixel == color) {
            dec_xft_color_cache[i].lru_counter = ++dec_xft_color_lru_counter;
            return &dec_xft_color_cache[i].xft_color;
        }
    }

    int oldest_idx = 0;
    int oldest_lru = dec_xft_color_cache[0].lru_counter;
    for (int i = 1; i < DEC_XFT_COLOR_CACHE_SIZE; i++) {
        if (!dec_xft_color_cache[i].valid) {
            oldest_idx = i;
            break;
        }
        if (dec_xft_color_cache[i].lru_counter < oldest_lru) {
            oldest_lru = dec_xft_color_cache[i].lru_counter;
            oldest_idx = i;
        }
    }

    if (dec_xft_color_cache[oldest_idx].valid) {
        XftColorFree(dwn->display, dec_cached_visual, dwn->colormap,
                     &dec_xft_color_cache[oldest_idx].xft_color);
    }

    XRenderColor render_color;
    render_color.red = ((color >> 16) & 0xFF) * 257;
    render_color.green = ((color >> 8) & 0xFF) * 257;
    render_color.blue = (color & 0xFF) * 257;
    render_color.alpha = 0xFFFF;

    XftColorAllocValue(dwn->display, dec_cached_visual, dwn->colormap,
                       &render_color, &dec_xft_color_cache[oldest_idx].xft_color);
    dec_xft_color_cache[oldest_idx].pixel = color;
    dec_xft_color_cache[oldest_idx].valid = true;
    dec_xft_color_cache[oldest_idx].lru_counter = ++dec_xft_color_lru_counter;

    return &dec_xft_color_cache[oldest_idx].xft_color;
}

void decorations_init(void)
{
    LOG_DEBUG("Decorations system initialized");
}

void decorations_cleanup(void)
{
}


void decorations_render(Client *client, bool focused)
{
    if (client == NULL || client->frame == None) {
        return;
    }

    if (dwn == NULL || dwn->display == NULL || dwn->config == NULL) {
        return;
    }

    decorations_render_title_bar(client, focused);
    decorations_render_buttons(client, focused);
    decorations_render_border(client, focused);
}

void decorations_render_title_bar(Client *client, bool focused)
{
    (void)focused;

    if (client == NULL || client->frame == None) {
        return;
    }

    if (dwn == NULL || dwn->display == NULL || dwn->gc == None || dwn->config == NULL) {
        return;
    }

    Display *dpy = dwn->display;
    int title_height = config_get_title_height();
    int border = client->border_width;
    const ColorScheme *colors = config_get_colors();

    Workspace *ws = workspace_get(client->workspace);
    bool is_focused = (ws != NULL && ws->focused == client);
    unsigned long base_bg = is_focused ? colors->title_focused_bg : colors->title_unfocused_bg;
    unsigned long bg_color = ambient_glow_bg(base_bg, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
    unsigned long fg_color = client->taskbar_color;
    fg_color = adjust_color_for_background(fg_color, bg_color);

    XSetForeground(dpy, dwn->gc, bg_color);
    XFillRectangle(dpy, client->frame, dwn->gc,
                   border, border,
                   client->width, title_height);

    bool is_alt_tab_selection = (dwn->is_alt_tabbing && dwn->alt_tab_client == client);
    XftFont *title_font = (is_alt_tab_selection && dwn->xft_font_bold != NULL) ?
                          dwn->xft_font_bold : dwn->xft_font;

    if (client->title[0] != '\0' && title_font != NULL) {
        int text_y = border + (title_height + title_font->ascent) / 2;
        int text_x = border + BUTTON_PADDING;

        int max_width = client->width - 3 * (BUTTON_SIZE + BUTTON_PADDING) - 2 * BUTTON_PADDING;
        if (max_width < BUTTON_SIZE) {
            return;
        }
        char display_title[256];
        size_t disp_len = strlen(client->title);
        if (disp_len >= sizeof(display_title) - 3) disp_len = sizeof(display_title) - 4;
        memcpy(display_title, client->title, disp_len);
        display_title[disp_len] = '\0';

        XGlyphInfo extents;
        XftTextExtentsUtf8(dpy, title_font,
                           (const FcChar8 *)display_title, strlen(display_title), &extents);
        int text_width = extents.xOff;

        bool title_truncated = false;
        while (text_width > max_width && strlen(display_title) > 3) {
            size_t len = strlen(display_title);
            size_t cut = len - 1;
            while (cut > 0 && (display_title[cut] & 0xC0) == 0x80) {
                cut--;
            }
            if (cut > 0) cut--;
            while (cut > 0 && (display_title[cut] & 0xC0) == 0x80) {
                cut--;
            }
            display_title[cut] = '\0';
            title_truncated = true;

            XftTextExtentsUtf8(dpy, title_font,
                               (const FcChar8 *)display_title, strlen(display_title), &extents);
            text_width = extents.xOff;
        }
        if (title_truncated) {
            strncat(display_title, "...", sizeof(display_title) - strlen(display_title) - 1);
        }

        XftDraw *xft_draw = dec_get_xft_draw(client->frame);
        if (xft_draw != NULL) {
            XftColor *xft_color = dec_get_cached_xft_color(fg_color);
            XftDrawStringUtf8(xft_draw, xft_color, title_font,
                              text_x, text_y,
                              (const FcChar8 *)display_title, strlen(display_title));
        }
    }
}

void decorations_render_buttons(Client *client, bool focused)
{
    if (client == NULL || client->frame == None) {
        return;
    }

    if (dwn == NULL || dwn->display == NULL || dwn->gc == None || dwn->config == NULL) {
        return;
    }

    int min_buttons_width = 3 * (BUTTON_SIZE + BUTTON_PADDING) + BUTTON_PADDING;
    if (client->width < min_buttons_width) {
        return;
    }

    Display *dpy = dwn->display;
    (void)focused;
    int title_height = config_get_title_height();
    int border = client->border_width;

    int button_y = border + (title_height - BUTTON_SIZE) / 2;
    int close_x = border + client->width - BUTTON_SIZE - BUTTON_PADDING;
    int max_x = close_x - BUTTON_SIZE - BUTTON_PADDING;
    int min_x = max_x - BUTTON_SIZE - BUTTON_PADDING;

    if (close_x < border || max_x < border || min_x < border) {
        return;
    }

    const ColorScheme *colors = config_get_colors();
    Workspace *ws = workspace_get(client->workspace);
    bool is_focused = (ws != NULL && ws->focused == client);
    unsigned long base_bg = is_focused ? colors->title_focused_bg : colors->title_unfocused_bg;
    unsigned long bg_color = ambient_glow_bg(base_bg, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);

    unsigned long close_color = ambient_glow_accent(0xff0055, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
    unsigned long max_color = ambient_glow_accent(0x00ff00, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
    unsigned long min_color = ambient_glow_accent(0xffff00, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);

    XSetForeground(dpy, dwn->gc, bg_color);
    XFillRectangle(dpy, client->frame, dwn->gc, close_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
    XSetForeground(dpy, dwn->gc, close_color);
    XDrawLine(dpy, client->frame, dwn->gc,
              close_x + 3, button_y + 3,
              close_x + BUTTON_SIZE - 4, button_y + BUTTON_SIZE - 4);
    XDrawLine(dpy, client->frame, dwn->gc,
              close_x + BUTTON_SIZE - 4, button_y + 3,
              close_x + 3, button_y + BUTTON_SIZE - 4);

    XSetForeground(dpy, dwn->gc, bg_color);
    XFillRectangle(dpy, client->frame, dwn->gc, max_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
    XSetForeground(dpy, dwn->gc, max_color);
    XDrawRectangle(dpy, client->frame, dwn->gc,
                   max_x + 3, button_y + 3,
                   BUTTON_SIZE - 7, BUTTON_SIZE - 7);

    XSetForeground(dpy, dwn->gc, bg_color);
    XFillRectangle(dpy, client->frame, dwn->gc, min_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
    XSetForeground(dpy, dwn->gc, min_color);
    XDrawLine(dpy, client->frame, dwn->gc,
              min_x + 3, button_y + BUTTON_SIZE - 5,
              min_x + BUTTON_SIZE - 4, button_y + BUTTON_SIZE - 5);
}

void decorations_render_border(Client *client, bool focused)
{
    /* Borders are disabled for a smooth look */
    (void)client;
    (void)focused;
}


ButtonType decorations_hit_test_button(Client *client, int x, int y)
{
    if (client == NULL) {
        return BUTTON_COUNT;
    }

    int title_height = config_get_title_height();
    int border = client->border_width;

    if (y < border || y > border + title_height) {
        return BUTTON_COUNT;
    }

    int button_y = border + (title_height - BUTTON_SIZE) / 2;
    int close_x = border + client->width - BUTTON_SIZE - BUTTON_PADDING;
    int max_x = close_x - BUTTON_SIZE - BUTTON_PADDING;
    int min_x = max_x - BUTTON_SIZE - BUTTON_PADDING;

    if (x >= close_x && x < close_x + BUTTON_SIZE &&
        y >= button_y && y < button_y + BUTTON_SIZE) {
        return BUTTON_CLOSE;
    }

    if (x >= max_x && x < max_x + BUTTON_SIZE &&
        y >= button_y && y < button_y + BUTTON_SIZE) {
        return BUTTON_MAXIMIZE;
    }

    if (x >= min_x && x < min_x + BUTTON_SIZE &&
        y >= button_y && y < button_y + BUTTON_SIZE) {
        return BUTTON_MINIMIZE;
    }

    return BUTTON_COUNT;
}

bool decorations_hit_test_title_bar(Client *client, int x, int y)
{
    if (client == NULL) {
        return false;
    }

    int title_height = config_get_title_height();
    int border = client->border_width;

    if (y >= border && y < border + title_height) {
        ButtonType btn = decorations_hit_test_button(client, x, y);
        return btn == BUTTON_COUNT;
    }

    return false;
}

bool decorations_hit_test_resize_area(Client *client, int x, int y, int *direction)
{
    if (client == NULL || direction == NULL) {
        return false;
    }

    int title_height = config_get_title_height();
    int border = client->border_width;
    int frame_width = client->width + 2 * border;
    int frame_height = client->height + title_height + 2 * border;

    *direction = 0;

    bool left = (x < RESIZE_EDGE);
    bool right = (x > frame_width - RESIZE_EDGE);
    bool top = (y < RESIZE_EDGE);
    bool bottom = (y > frame_height - RESIZE_EDGE);

    if (!left && !right && !top && !bottom) {
        return false;
    }

    if (left)   *direction |= 1;
    if (right)  *direction |= 2;
    if (top)    *direction |= 4;
    if (bottom) *direction |= 8;

    return true;
}


void decorations_button_press(Client *client, ButtonType button)
{
    if (client == NULL) {
        return;
    }

    switch (button) {
        case BUTTON_CLOSE:
            LOG_DEBUG("Close button pressed for %s", client->title);
            client_close(client);
            break;

        case BUTTON_MAXIMIZE:
            LOG_DEBUG("Maximize button pressed for %s", client->title);
            client_toggle_maximize(client);
            break;

        case BUTTON_MINIMIZE:
            LOG_DEBUG("Minimize button pressed for %s", client->title);
            client_minimize(client);
            break;

        default:
            break;
    }
}


void decorations_draw_text(Window window, GC gc, int x, int y,
                           const char *text, unsigned long color)
{
    if (text == NULL || dwn == NULL || dwn->display == NULL) {
        return;
    }

    if (dwn->xft_font != NULL) {
        XftDraw *xft_draw = dec_get_xft_draw(window);
        if (xft_draw != NULL) {
            XftColor *xft_color = dec_get_cached_xft_color(color);
            XftDrawStringUtf8(xft_draw, xft_color, dwn->xft_font,
                              x, y, (const FcChar8 *)text, strlen(text));
            return;
        }
    }

    XSetForeground(dwn->display, gc, color);
    XDrawString(dwn->display, window, gc, x, y, text, strlen(text));
}
