
#include "ui.h"

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

/* XP-ish flat palette, same family as Phase 1's placeholder panel. */
#define RGB_FACE    0xD4D0C8u
#define RGB_LIGHT   0xFFFFFFu
#define RGB_SHADOW  0x808080u
#define RGB_TITLE   0x0A246Au
#define RGB_TRACKBG 0xC7D8EAu
#define RGB_HIGHLIGHT 0x3070D0u /* keyboard nav cursor, distinct from selection's black and the red region outline */
/* Region-selection highlight tint. Core X11 drawing has no real alpha
 * blending, so "translucent yellow" is approximated with one flat,
 * pale color instead of actually compositing -- light enough to read
 * as a wash sitting under the waveform rather than paint over it. */
#define RGB_SELECTION_TINT 0xFFF2B0u
/* Icon glyph color for the Record/Stop buttons -- the universal
 * "recording" red, allocated once up front like the rest of the
 * palette rather than re-allocated on every redraw. */
#define RGB_ICON_RECORD 0xE00000u

/* Track color cycle, borrowed from snippysnappy's drawing-tool palette
 * (red, green, blue, black, gold) -- same idea Audacity uses to give
 * each track a visually distinct waveform color. */
static const unsigned long PALETTE_RGB[UI_PALETTE_SIZE] = {
    0xFF0000u, 0x00A000u, 0x0000FFu, 0x000000u, 0xFFD400u
};

static unsigned long g_face, g_light, g_shadow, g_title, g_trackbg, g_black, g_highlight;
static unsigned long g_selection_tint;
static unsigned long g_icon_record;
static unsigned long g_palette[UI_PALETTE_SIZE];

/* Pastel badge colors -- see UiBadgeColor in ui.h. Light enough that
 * plain black text (ui_draw_button's usual look) stays readable. */
static const unsigned long BADGE_RGB[UI_BADGE_COUNT] = {
    0xD4D0C8u, /* UI_BADGE_NEUTRAL -- same as RGB_FACE, on purpose */
    0xB6E6B0u, /* UI_BADGE_GREEN */
    0xE6B8E6u, /* UI_BADGE_PURPLE */
    0xF5C089u, /* UI_BADGE_ORANGE */
    0xB0D4F0u, /* UI_BADGE_BLUE */
    0xF0B0C8u, /* UI_BADGE_PINK */
};
static unsigned long g_badge[UI_BADGE_COUNT];

static unsigned long alloc_color(Display *dpy, Colormap cmap, unsigned long rgb) {
    XColor c;
    c.red   = ((rgb >> 16) & 0xFF) * 257;
    c.green = ((rgb >> 8)  & 0xFF) * 257;
    c.blue  = (rgb & 0xFF) * 257;
    c.flags = DoRed | DoGreen | DoBlue;
    XAllocColor(dpy, cmap, &c);
    return c.pixel;
}

void ui_init_colors(Display *dpy, Colormap cmap) {
    g_face    = alloc_color(dpy, cmap, RGB_FACE);
    g_light   = alloc_color(dpy, cmap, RGB_LIGHT);
    g_shadow  = alloc_color(dpy, cmap, RGB_SHADOW);
    g_title   = alloc_color(dpy, cmap, RGB_TITLE);
    g_trackbg = alloc_color(dpy, cmap, RGB_TRACKBG);
    g_black   = alloc_color(dpy, cmap, 0x000000u);
    g_highlight = alloc_color(dpy, cmap, RGB_HIGHLIGHT);
    g_selection_tint = alloc_color(dpy, cmap, RGB_SELECTION_TINT);
    g_icon_record = alloc_color(dpy, cmap, RGB_ICON_RECORD);
    for (int i = 0; i < UI_PALETTE_SIZE; i++)
        g_palette[i] = alloc_color(dpy, cmap, PALETTE_RGB[i]);
    for (int i = 0; i < UI_BADGE_COUNT; i++)
        g_badge[i] = alloc_color(dpy, cmap, BADGE_RGB[i]);
}

void ui_draw_bevel(Display *dpy, Drawable d, GC gc, int x, int y, int w, int h, int pressed) {
    unsigned long face = pressed ? g_shadow : g_face;
    unsigned long topleft = pressed ? g_shadow : g_light;
    unsigned long botright = pressed ? g_light : g_shadow;

    XSetForeground(dpy, gc, face);
    XFillRectangle(dpy, d, gc, x, y, w, h);

    XSetForeground(dpy, gc, topleft);
    XDrawLine(dpy, d, gc, x, y, x + w, y);
    XDrawLine(dpy, d, gc, x, y, x, y + h);

    XSetForeground(dpy, gc, botright);
    XDrawLine(dpy, d, gc, x, y + h, x + w, y + h);
    XDrawLine(dpy, d, gc, x + w, y, x + w, y + h);
}

void ui_draw_button(Display *dpy, Drawable d, GC gc, int x, int y, int w, int h,
                     const char *label, int pressed) {
    ui_draw_bevel(dpy, d, gc, x, y, w, h, pressed);

    XSetForeground(dpy, gc, g_black);
    int len = (int)strlen(label);
    int approx_char_w = 6; /* rough estimate for the default X11 font */
    int tw = len * approx_char_w;
    int tx = x + (w - tw) / 2;
    int ty = y + h / 2 + 4;
    if (tx < x) tx = x + 2;
    XDrawString(dpy, d, gc, tx, ty, label, len);
}

/* Same shape as ui_draw_button, but flat-filled with a badge color
 * instead of raised XP-gray -- these are indicators, not clickable
 * buttons, so they deliberately don't look pressable. */
void ui_draw_badge(Display *dpy, Drawable d, GC gc, int x, int y, int w, int h,
                    const char *label, UiBadgeColor color) {
    if (color < 0 || color >= UI_BADGE_COUNT)
        color = UI_BADGE_NEUTRAL;

    XSetForeground(dpy, gc, g_badge[color]);
    XFillRectangle(dpy, d, gc, x, y, w, h);
    XSetForeground(dpy, gc, g_black);
    XDrawRectangle(dpy, d, gc, x, y, w, h);

    int len = (int)strlen(label);
    int approx_char_w = 6;
    int tw = len * approx_char_w;
    int tx = x + (w - tw) / 2;
    int ty = y + h / 2 + 4;
    if (tx < x) tx = x + 2;
    XDrawString(dpy, d, gc, tx, ty, label, len);
}

void ui_draw_icon_button(Display *dpy, Drawable d, GC gc, int x, int y, int w, int h,
                          UiIcon icon, int pressed) {
    ui_draw_bevel(dpy, d, gc, x, y, w, h, pressed);

    int cx = x + w / 2;
    int cy = y + h / 2;
    /* Glyphs are sized off the smaller of w/h so they never run into
     * the bevel edge, and shrink a little further so there's always
     * a few pixels of breathing room inside the button. */
    int r = (w < h ? w : h) / 2 - 4;
    if (r < 3) r = 3;

    switch (icon) {
    case UI_ICON_RECORD:
        XSetForeground(dpy, gc, g_black);
        XDrawArc(dpy, d, gc, cx - r - 1, cy - r - 1, 2 * r + 2, 2 * r + 2, 0, 360 * 64);
        XSetForeground(dpy, gc, g_icon_record);
        XFillArc(dpy, d, gc, cx - r, cy - r, 2 * r, 2 * r, 0, 360 * 64);
        break;

    case UI_ICON_STOP: {
        int s = r;
        XSetForeground(dpy, gc, g_black);
        XDrawRectangle(dpy, d, gc, cx - s - 1, cy - s - 1, 2 * s + 2, 2 * s + 2);
        XSetForeground(dpy, gc, g_icon_record);
        XFillRectangle(dpy, d, gc, cx - s, cy - s, 2 * s, 2 * s);
        break;
    }

    case UI_ICON_PLAY: {
        XPoint pts[3] = {
            { (short)(cx - r + 1), (short)(cy - r) },
            { (short)(cx - r + 1), (short)(cy + r) },
            { (short)(cx + r),     (short)cy },
        };
        XSetForeground(dpy, gc, g_black);
        XFillPolygon(dpy, d, gc, pts, 3, Convex, CoordModeOrigin);
        break;
    }

    case UI_ICON_PAUSE: {
        int bar_w = r / 2 > 0 ? r / 2 : 1;
        int gap = bar_w;
        XSetForeground(dpy, gc, g_black);
        XFillRectangle(dpy, d, gc, cx - gap - bar_w, cy - r, bar_w, 2 * r);
        XFillRectangle(dpy, d, gc, cx + gap, cy - r, bar_w, 2 * r);
        break;
    }

    case UI_ICON_LOOP: {
        XSetForeground(dpy, gc, g_black);
        XSetLineAttributes(dpy, gc, 2, LineSolid, CapButt, JoinMiter);
        /* Most of a circle, left open at the bottom-right so an
         * arrowhead can cap it there -- reads as "going around". */
        XDrawArc(dpy, d, gc, cx - r, cy - r, 2 * r, 2 * r, -40 * 64, 300 * 64);
        double ang = -0.698132; /* -40 degrees, in radians (avoids relying on M_PI's visibility) */
        int tipx = cx + (int)(r * cos(ang));
        int tipy = cy - (int)(r * sin(ang));
        XPoint head[3] = {
            { (short)tipx,     (short)(tipy - 4) },
            { (short)(tipx + 5), (short)tipy },
            { (short)(tipx - 1), (short)(tipy + 4) },
        };
        XSetLineAttributes(dpy, gc, 1, LineSolid, CapButt, JoinMiter);
        XFillPolygon(dpy, d, gc, head, 3, Convex, CoordModeOrigin);
        break;
    }

    case UI_ICON_RESTART: {
        /* "|<" -- a vertical bar (the stop/start edge) followed by
         * ONE large left-pointing triangle (rewind-to-start). Replaces
         * the old stacked-double-triangle "|<<" glyph, whose two
         * halves read as fiddly/offset at this icon size. */
        XSetForeground(dpy, gc, g_black);
        int bar_x = cx - r;
        XFillRectangle(dpy, d, gc, bar_x, cy - r, 2, 2 * r);
        int tri_x0 = bar_x + 3 + r; /* base (right edge) of the triangle */
        XPoint tri[3] = {
            { (short)tri_x0,             (short)(cy - r) },
            { (short)tri_x0,             (short)(cy + r) },
            { (short)(tri_x0 - r - 2),   (short)cy },
        };
        XFillPolygon(dpy, d, gc, tri, 3, Convex, CoordModeOrigin);
        break;
    }

    case UI_ICON_DELETE: {
        XSetForeground(dpy, gc, g_black);
        XSetLineAttributes(dpy, gc, 2, LineSolid, CapButt, JoinMiter);
        XDrawLine(dpy, d, gc, cx - r, cy - r, cx + r, cy + r);
        XDrawLine(dpy, d, gc, cx - r, cy + r, cx + r, cy - r);
        XSetLineAttributes(dpy, gc, 1, LineSolid, CapButt, JoinMiter);
        break;
    }

    case UI_ICON_UP: {
        XPoint pts[3] = {
            { (short)cx,         (short)(cy - r) },
            { (short)(cx - r),   (short)(cy + r - 1) },
            { (short)(cx + r),   (short)(cy + r - 1) },
        };
        XSetForeground(dpy, gc, g_black);
        XFillPolygon(dpy, d, gc, pts, 3, Convex, CoordModeOrigin);
        break;
    }

    case UI_ICON_DOWN: {
        XPoint pts[3] = {
            { (short)cx,         (short)(cy + r) },
            { (short)(cx - r),   (short)(cy - r + 1) },
            { (short)(cx + r),   (short)(cy - r + 1) },
        };
        XSetForeground(dpy, gc, g_black);
        XFillPolygon(dpy, d, gc, pts, 3, Convex, CoordModeOrigin);
        break;
    }

    case UI_ICON_PENCIL: {
        /* A diagonal body (thick line) from the bottom-left grip to the
         * top-right tip, plus a small filled triangle nib at that tip --
         * reads as "pencil" at icon size without needing real
         * perspective. r is small here (the rename button is tiny), so
         * this stays deliberately simple. */
        XSetForeground(dpy, gc, g_black);
        XSetLineAttributes(dpy, gc, 2, LineSolid, CapButt, JoinMiter);
        XDrawLine(dpy, d, gc, cx - r, cy + r, cx + r - 1, cy - r + 1);
        XSetLineAttributes(dpy, gc, 1, LineSolid, CapButt, JoinMiter);
        XPoint nib[3] = {
            { (short)(cx + r - 2), (short)(cy - r) },
            { (short)(cx + r),     (short)(cy - r + 2) },
            { (short)(cx + r - 2), (short)(cy - r + 2) },
        };
        XFillPolygon(dpy, d, gc, nib, 3, Convex, CoordModeOrigin);
        break;
    }

    case UI_ICON_SPEAKER:
    case UI_ICON_SPEAKER_MUTED: {
        /* Classic speaker silhouette: a small square "back" plus a
         * trapezoid "horn" opening to the right. Unmuted gets two
         * small arcs standing in for sound waves; muted swaps those
         * for an X drawn over the whole glyph -- the universal
         * "crossed out" convention, same idea as UI_ICON_DELETE. */
        XSetForeground(dpy, gc, g_black);
        int box_w = r / 2 > 1 ? r / 2 : 1;
        int box_x0 = cx - r;
        XFillRectangle(dpy, d, gc, box_x0, cy - r / 2, box_w, r);
        XPoint horn[4] = {
            { (short)(box_x0 + box_w), (short)(cy - r / 2) },
            { (short)(box_x0 + box_w), (short)(cy + r / 2) },
            { (short)(cx + r / 2),     (short)(cy + r) },
            { (short)(cx + r / 2),     (short)(cy - r) },
        };
        XFillPolygon(dpy, d, gc, horn, 4, Convex, CoordModeOrigin);
        if (icon == UI_ICON_SPEAKER) {
            XDrawArc(dpy, d, gc, cx + r / 2, cy - r, r, 2 * r, -55 * 64, 110 * 64);
            XDrawArc(dpy, d, gc, cx + r, cy - r, r, 2 * r, -45 * 64, 90 * 64);
        } else {
            XSetLineAttributes(dpy, gc, 2, LineSolid, CapButt, JoinMiter);
            XDrawLine(dpy, d, gc, cx - r, cy - r, cx + r + 2, cy + r);
            XDrawLine(dpy, d, gc, cx - r, cy + r, cx + r + 2, cy - r);
            XSetLineAttributes(dpy, gc, 1, LineSolid, CapButt, JoinMiter);
        }
        break;
    }

    case UI_ICON_NONE:
    default:
        break;
    }
}

void ui_draw_waveform(Display *dpy, Drawable d, GC gc, int x, int y, int w, int h,
                       const int16_t *samples, size_t num_samples,
                       size_t view_start_frame, double samples_per_pixel, unsigned long color) {
    if (!samples || num_samples == 0 || w <= 0 || h <= 0 || samples_per_pixel <= 0.0)
        return;

    int mid = y + h / 2;
    XSetForeground(dpy, gc, color);

    for (int col = 0; col < w; col++) {
        double start_d = (double)view_start_frame + (double)col * samples_per_pixel;
        double end_d = (double)view_start_frame + (double)(col + 1) * samples_per_pixel;
        if (start_d >= (double)num_samples)
            break; /* past this track's actual audio -- and everything further right is too */
        if (end_d < 0)
            continue; /* entirely before this track's start (scrolled left past frame 0) */

        size_t start = start_d < 0 ? 0 : (size_t)start_d;
        size_t end = end_d < 0 ? 0 : (size_t)end_d;
        if (end <= start) end = start + 1;
        if (end > num_samples) end = num_samples;
        if (start >= num_samples) break;

        int16_t mn = samples[start], mx = samples[start];
        for (size_t i = start + 1; i < end; i++) {
            int16_t s = samples[i];
            if (s < mn) mn = s;
            if (s > mx) mx = s;
        }

        int y1 = mid - (int)((double)mx / 32768.0 * (h / 2));
        int y2 = mid - (int)((double)mn / 32768.0 * (h / 2));
        if (y1 == y2) y2 = y1 + 1; /* ensure at least a 1px dot for near-silent columns */
        XDrawLine(dpy, d, gc, x + col, y1, x + col, y2);
    }
}

void ui_draw_track_row(Display *dpy, Drawable d, GC gc, int x, int y, int w, int h,
                        const Track *t, int index, size_t view_start_frame,
                        double samples_per_pixel, int selected, int highlighted,
                        int hl_x0, int hl_x1) {
    XSetForeground(dpy, gc, g_trackbg);
    XFillRectangle(dpy, d, gc, x, y, w, h);

    /* Region-selection tint -- under the border/divider/label/buttons
     * and especially the waveform peaks drawn below, so it reads as a
     * background wash rather than something painted over the content.
     * Clipped to the waveform side of the row; the label strip never
     * gets tinted. */
    if (hl_x1 > hl_x0) {
        int wf_left = x + UI_TRACK_LABEL_W;
        int wf_right = x + w;
        int tx0 = hl_x0 < wf_left ? wf_left : hl_x0;
        int tx1 = hl_x1 > wf_right ? wf_right : hl_x1;
        if (tx1 > tx0) {
            XSetForeground(dpy, gc, g_selection_tint);
            XFillRectangle(dpy, d, gc, tx0, y, tx1 - tx0, h);
        }
    }

    /* Border: black + thick when selected (locked in), blue + thick when
     * only highlighted (keyboard cursor, not yet confirmed), thin gray
     * otherwise. Selected wins if both are true. */
    int emphasize = selected || highlighted;
    XSetForeground(dpy, gc, selected ? g_black : (highlighted ? g_highlight : g_shadow));
    if (emphasize) {
        XSetLineAttributes(dpy, gc, 2, LineSolid, CapButt, JoinMiter);
        XDrawRectangle(dpy, d, gc, x + 1, y + 1, w - 3, h - 3);
        XSetLineAttributes(dpy, gc, 1, LineSolid, CapButt, JoinMiter);
    } else {
        XDrawRectangle(dpy, d, gc, x, y, w - 1, h - 1);
    }

    /* Divider between the label strip and the waveform area. */
    XSetForeground(dpy, gc, g_shadow);
    XDrawLine(dpy, d, gc, x + UI_TRACK_LABEL_W, y, x + UI_TRACK_LABEL_W, y + h);

    XSetForeground(dpy, gc, g_black);
    XDrawString(dpy, d, gc, x + UI_TRACK_MUTE_BTN_W + 4, y + 14, t->name, (int)strlen(t->name));

    /* Mute, unlike Del/Rec/Up/Dn/rename below, is always visible (not
     * gated on selected/highlighted) -- it's state you want to see at
     * a glance across the whole list, not just the active row. */
    ui_draw_icon_button(dpy, d, gc, x + UI_TRACK_MUTE_BTN_X, y + UI_TRACK_MUTE_BTN_Y,
                   UI_TRACK_MUTE_BTN_W, UI_TRACK_MUTE_BTN_H,
                   atomic_load(&t->muted) ? UI_ICON_SPEAKER_MUTED : UI_ICON_SPEAKER, 0);

    /* Buttons show on EITHER selected or highlighted now, not just
     * selected -- a plain click (see main.c's handle_click()) only
     * moves the highlight cursor here and reveals these, without
     * committing the track as the copy/cut/paste target the way it
     * used to. That still takes an explicit confirm (Enter), exactly
     * like keyboard Up/Down navigation already required. */
    if (selected || highlighted) {
        ui_draw_icon_button(dpy, d, gc, x + UI_TRACK_DEL_BTN_X, y + UI_TRACK_BTN_Y_OFF,
                       UI_TRACK_DEL_BTN_W, UI_TRACK_BTN_H, UI_ICON_DELETE, 0);
        ui_draw_icon_button(dpy, d, gc, x + UI_TRACK_REC_BTN_X, y + UI_TRACK_BTN_Y_OFF,
                       UI_TRACK_REC_BTN_W, UI_TRACK_BTN_H, UI_ICON_RECORD, 0);
        ui_draw_icon_button(dpy, d, gc, x + UI_TRACK_UP_BTN_X, y + UI_TRACK_BTN2_Y_OFF,
                       UI_TRACK_UP_BTN_W, UI_TRACK_BTN_H, UI_ICON_UP, 0);
        ui_draw_icon_button(dpy, d, gc, x + UI_TRACK_DN_BTN_X, y + UI_TRACK_BTN2_Y_OFF,
                       UI_TRACK_DN_BTN_W, UI_TRACK_BTN_H, UI_ICON_DOWN, 0);
        ui_draw_icon_button(dpy, d, gc, x + UI_TRACK_RENAME_BTN_X, y + UI_TRACK_RENAME_BTN_Y,
                       UI_TRACK_RENAME_BTN_W, UI_TRACK_RENAME_BTN_H, UI_ICON_PENCIL, 0);
    }

    int wf_x = x + UI_TRACK_LABEL_W + 2;
    int wf_y = y + 4;
    int wf_w = w - UI_TRACK_LABEL_W - 6;
    int wf_h = h - 8;
    if (wf_w > 0 && wf_h > 0) {
        unsigned long color = g_palette[((index % UI_PALETTE_SIZE) + UI_PALETTE_SIZE) % UI_PALETTE_SIZE];
        ui_draw_waveform(dpy, d, gc, wf_x, wf_y, wf_w, wf_h,
                          t->clip.samples, t->clip.num_samples, view_start_frame, samples_per_pixel, color);
    }
}

/* "Nice" tick intervals to choose from, in seconds. We pick the
 * smallest one that still keeps ticks at least ~50px apart at the
 * current zoom level, so the ruler stays readable whether you're
 * looking at the whole project or a tenth of a second of it. */
static const double NICE_INTERVALS_SEC[] = {
    0.1, 0.25, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 1800, 3600
};
#define NUM_NICE_INTERVALS (int)(sizeof(NICE_INTERVALS_SEC) / sizeof(NICE_INTERVALS_SEC[0]))

void ui_draw_time_ruler(Display *dpy, Drawable d, GC gc, int x, int y, int w, int h,
                         size_t view_start_frame, double samples_per_pixel, uint32_t sample_rate) {
    if (w <= 0 || h <= 0 || samples_per_pixel <= 0.0 || sample_rate == 0)
        return;

    XSetForeground(dpy, gc, g_face);
    XFillRectangle(dpy, d, gc, x, y, w, h);
    XSetForeground(dpy, gc, g_shadow);
    XDrawLine(dpy, d, gc, x, y + h - 1, x + w, y + h - 1);

    double pixels_per_sec = (double)sample_rate / samples_per_pixel;
    double interval_sec = NICE_INTERVALS_SEC[NUM_NICE_INTERVALS - 1];
    for (int i = 0; i < NUM_NICE_INTERVALS; i++) {
        if (NICE_INTERVALS_SEC[i] * pixels_per_sec >= 50.0) {
            interval_sec = NICE_INTERVALS_SEC[i];
            break;
        }
    }

    double view_start_sec = (double)view_start_frame / sample_rate;
    double view_end_sec = view_start_sec + (double)w / pixels_per_sec;
    double first_tick = floor(view_start_sec / interval_sec) * interval_sec;

    XSetForeground(dpy, gc, g_black);
    for (double t = first_tick; t <= view_end_sec; t += interval_sec) {
        if (t < 0) continue;
        int px = x + (int)((t - view_start_sec) * pixels_per_sec);
        if (px < x || px > x + w) continue;
        XDrawLine(dpy, d, gc, px, y + h - 6, px, y + h - 1);

        int mins = (int)(t / 60);
        double secs = t - mins * 60;
        char label[32];
        if (interval_sec < 1.0)
            snprintf(label, sizeof(label), "%d:%04.1f", mins, secs);
        else
            snprintf(label, sizeof(label), "%d:%02d", mins, (int)secs);
        XDrawString(dpy, d, gc, px + 2, y + h - 8, label, (int)strlen(label));
    }
}

