/*
 * DWN - Desktop Window Manager
 * retoor <retoor@molodetz.nl>
 * Generic slider widget implementation
 */

#include "slider.h"
#include "config.h"
#include "util.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xft/Xft.h>

/* XFT support for slider */
static XftDraw *slider_xft_draw = NULL;
static Drawable slider_xft_drawable = None;
static Visual *slider_cached_visual = NULL;

static XftDraw *slider_get_xft_draw(Drawable d)
{
    if (slider_xft_drawable == d && slider_xft_draw != NULL) {
        return slider_xft_draw;
    }
    if (slider_xft_draw != NULL) {
        XftDrawDestroy(slider_xft_draw);
    }
    if (slider_cached_visual == NULL) {
        slider_cached_visual = DefaultVisual(dwn->display, dwn->screen);
    }
    slider_xft_draw = XftDrawCreate(dwn->display, d, slider_cached_visual, dwn->colormap);
    slider_xft_drawable = d;
    return slider_xft_draw;
}

static int slider_get_text_width(const char *text)
{
    if (dwn == NULL || text == NULL) {
        return 0;
    }

    if (dwn->xft_font != NULL) {
        XGlyphInfo extents;
        XftTextExtentsUtf8(dwn->display, dwn->xft_font,
                           (const FcChar8 *)text, strlen(text), &extents);
        return extents.xOff;
    }

    if (dwn->font != NULL) {
        return XTextWidth(dwn->font, text, strlen(text));
    }

    return strlen(text) * 8;
}

static void slider_draw_text(Drawable d, int x, int y, const char *text, unsigned long color)
{
    if (dwn == NULL || dwn->display == NULL || text == NULL) {
        return;
    }

    if (dwn->xft_font != NULL) {
        XftDraw *xft_draw = slider_get_xft_draw(d);
        if (xft_draw != NULL) {
            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;
            
            XftColor xft_color;
            XftColorAllocValue(dwn->display, slider_cached_visual, dwn->colormap,
                               &render_color, &xft_color);
            XftDrawStringUtf8(xft_draw, &xft_color, dwn->xft_font,
                              x, y, (const FcChar8 *)text, strlen(text));
            XftColorFree(dwn->display, slider_cached_visual, dwn->colormap, &xft_color);
            return;
        }
    }

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

/*============================================================================*/

GenericSlider *slider_create(int x, int y, 
                              const char *label_prefix,
                              bool show_percentage,
                              SliderValueChangedCallback callback,
                              void *user_data)
{
    if (dwn == NULL || dwn->display == NULL) {
        return NULL;
    }

    GenericSlider *slider = dwn_calloc(1, sizeof(GenericSlider));
    if (slider == NULL) {
        return NULL;
    }

    slider->x = x;
    slider->y = y;
    slider->width = SLIDER_WIDTH;
    slider->height = SLIDER_HEIGHT;
    slider->value = 50;
    slider->label_prefix = label_prefix;
    slider->show_percentage = show_percentage;
    slider->on_value_changed = callback;
    slider->user_data = user_data;
    slider->visible = false;
    slider->dragging = false;

    /* Create window */
    XSetWindowAttributes attrs;
    attrs.override_redirect = True;
    attrs.background_pixel = config_get_colors()->panel_bg;
    attrs.border_pixel = config_get_colors()->border_focused;
    attrs.event_mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask;

    slider->window = XCreateWindow(
        dwn->display,
        dwn->root,
        x, y, slider->width, slider->height,
        1,
        CopyFromParent,
        CopyFromParent,
        CopyFromParent,
        CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWEventMask,
        &attrs
    );

    return slider;
}

void slider_destroy(GenericSlider *slider)
{
    if (slider == NULL) {
        return;
    }

    if (slider->window != None) {
        XDestroyWindow(dwn->display, slider->window);
    }

    dwn_free(slider);
}

void slider_show(GenericSlider *slider, int x, int y)
{
    if (slider == NULL || dwn == NULL || dwn->display == NULL) {
        return;
    }

    slider->x = x;
    slider->y = y;

    /* Reposition window */
    XMoveWindow(dwn->display, slider->window, x, y);

    /* Ensure it's above the panel */
    XWindowChanges changes;
    changes.stack_mode = Above;
    XConfigureWindow(dwn->display, slider->window, CWStackMode, &changes);

    slider->visible = true;
    XMapRaised(dwn->display, slider->window);
    slider_render(slider);
}

void slider_hide(GenericSlider *slider)
{
    if (slider == NULL || slider->window == None) {
        return;
    }

    slider->visible = false;
    slider->dragging = false;
    XUnmapWindow(dwn->display, slider->window);
}

bool slider_is_visible(const GenericSlider *slider)
{
    return slider != NULL && slider->visible;
}

void slider_render(GenericSlider *slider)
{
    if (slider == NULL || slider->window == None || !slider->visible) {
        return;
    }

    Display *dpy = dwn->display;
    const ColorScheme *colors = config_get_colors();

    /* Clear background */
    XSetForeground(dpy, dwn->gc, colors->panel_bg);
    XFillRectangle(dpy, slider->window, dwn->gc, 0, 0, slider->width, slider->height);

    /* Draw track */
    int track_x = (slider->width - 4) / 2;
    int track_y = SLIDER_PADDING + 20;  /* Leave room for label at top */
    int track_height = slider->height - track_y - SLIDER_PADDING - 15;  /* Room for value at bottom */

    /* Background track */
    XSetForeground(dpy, dwn->gc, colors->workspace_inactive);
    XFillRectangle(dpy, slider->window, dwn->gc, track_x, track_y, 4, track_height);

    /* Filled portion */
    int fill_height = (slider->value * track_height) / 100;
    int fill_y = track_y + track_height - fill_height;

    XSetForeground(dpy, dwn->gc, colors->workspace_active);
    XFillRectangle(dpy, slider->window, dwn->gc, track_x, fill_y, 4, fill_height);

    /* Draw knob */
    int knob_y = fill_y - (SLIDER_KNOB_HEIGHT / 2);
    if (knob_y < track_y - 4) knob_y = track_y - 4;
    if (knob_y > track_y + track_height - 4) knob_y = track_y + track_height - 4;

    XSetForeground(dpy, dwn->gc, colors->panel_fg);
    XFillRectangle(dpy, slider->window, dwn->gc,
                   track_x - 4, knob_y, 12, SLIDER_KNOB_HEIGHT);

    /* Draw label at top */
    char label_text[32];
    snprintf(label_text, sizeof(label_text), "%s", slider->label_prefix);
    int label_width = slider_get_text_width(label_text);
    int label_x = (slider->width - label_width) / 2;
    slider_draw_text(slider->window, label_x, 15, label_text, colors->panel_fg);

    /* Draw value at bottom */
    char value_text[16];
    if (slider->show_percentage) {
        snprintf(value_text, sizeof(value_text), "%d%%", slider->value);
    } else {
        snprintf(value_text, sizeof(value_text), "%d", slider->value);
    }
    int value_width = slider_get_text_width(value_text);
    int value_x = (slider->width - value_width) / 2;
    slider_draw_text(slider->window, value_x, slider->height - 4, value_text, colors->panel_fg);

    XFlush(dpy);
}

void slider_handle_click(GenericSlider *slider, int x, int y)
{
    (void)x;

    if (slider == NULL) {
        return;
    }

    slider->dragging = true;

    /* Calculate new value from y position */
    int track_y = SLIDER_PADDING + 20;
    int track_height = slider->height - track_y - SLIDER_PADDING - 15;

    int relative_y = y - track_y;
    if (relative_y < 0) relative_y = 0;
    if (relative_y > track_height) relative_y = track_height;

    int new_value = 100 - (relative_y * 100 / track_height);
    if (new_value < 0) new_value = 0;
    if (new_value > 100) new_value = 100;

    if (new_value != slider->value) {
        slider->value = new_value;
        slider_render(slider);
        
        if (slider->on_value_changed) {
            slider->on_value_changed(slider, slider->value);
        }
    }
}

void slider_handle_motion(GenericSlider *slider, int x, int y)
{
    if (slider == NULL || !slider->visible || !slider->dragging) {
        return;
    }

    slider_handle_click(slider, x, y);
}

void slider_handle_release(GenericSlider *slider)
{
    if (slider == NULL) {
        return;
    }

    slider->dragging = false;
}

void slider_set_value(GenericSlider *slider, int value)
{
    if (slider == NULL) {
        return;
    }

    if (value < 0) value = 0;
    if (value > 100) value = 100;

    slider->value = value;
    
    if (slider->visible) {
        slider_render(slider);
    }
}

int slider_get_value(const GenericSlider *slider)
{
    return slider != NULL ? slider->value : 0;
}

bool slider_hit_test(const GenericSlider *slider, int x, int y)
{
    if (slider == NULL || !slider->visible) {
        return false;
    }

    return (x >= slider->x && x < slider->x + slider->width &&
            y >= slider->y && y < slider->y + slider->height);
}
