// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

#define DVX_WIDGET_IMPL
// widgetProgressBar.c  -- ProgressBar widget
//
// A non-interactive fill-bar widget for displaying bounded progress.
// Supports both horizontal and vertical orientations via separate
// constructors rather than a runtime flag, because orientation is
// typically fixed at UI construction time and the two constructors
// read more clearly at the call site.
//
// Rendering: sunken 2px bevel border (inverted highlight/shadow to
// look recessed) with a solid fill rect proportional to value/maxValue.
// Uses integer division for fill size -- no floating point, which would
// be prohibitively expensive on a 486 without an FPU.
//
// The widget has no mouse or keyboard handlers -- it is purely display.
// Value is controlled externally through the set/get API.

#include "dvxWgtP.h"

// 2px sunken bevel on every side -- the content area is inset by this
// much on each axis, so 2*PROGRESSBAR_BEVEL_W total.
#define PROGRESSBAR_BEVEL_W  2

// Main-axis minimum length expressed in character widths, so the bar is wide
// enough to show meaningful progress granularity.
#define PROGRESSBAR_MIN_CHARS  12

static int32_t sTypeId = -1;

typedef struct {
    int32_t value;
    int32_t maxValue;
    bool    vertical;
} ProgressBarDataT;


// ============================================================
// Prototypes
// ============================================================

WidgetT *wgtProgressBar(WidgetT *parent);
int32_t  wgtProgressBarGetValue(const WidgetT *w);
void     wgtProgressBarSetValue(WidgetT *w, int32_t value);
WidgetT *wgtProgressBarV(WidgetT *parent);
void     wgtRegister(void);
void     widgetProgressBarCalcMinSize(WidgetT *w, const BitmapFontT *font);
void     widgetProgressBarPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);


WidgetT *wgtProgressBar(WidgetT *parent) {
    WidgetT *w = widgetAlloc(parent, sTypeId);

    if (!w) {
        return NULL;
    }

    ProgressBarDataT *d = calloc(1, sizeof(ProgressBarDataT));

    if (!d) {
        widgetAllocRollback(w);
        return NULL;
    }

    // calloc already zeroed value (0) and vertical (false).
    d->maxValue = 100;
    w->data     = d;
    return w;
}


int32_t wgtProgressBarGetValue(const WidgetT *w) {
    VALIDATE_WIDGET(w, sTypeId, 0);
    ProgressBarDataT *d = (ProgressBarDataT *)w->data;

    return d->value;
}


void wgtProgressBarSetValue(WidgetT *w, int32_t value) {
    VALIDATE_WIDGET_VOID(w, sTypeId);
    ProgressBarDataT *d = (ProgressBarDataT *)w->data;

    if (value < 0) {
        value = 0;
    }

    if (value > d->maxValue) {
        value = d->maxValue;
    }

    d->value = value;
    wgtInvalidatePaint(w);
}


WidgetT *wgtProgressBarV(WidgetT *parent) {
    WidgetT *w = widgetAlloc(parent, sTypeId);

    if (!w) {
        return NULL;
    }

    ProgressBarDataT *d = calloc(1, sizeof(ProgressBarDataT));

    if (!d) {
        widgetAllocRollback(w);
        return NULL;
    }

    // calloc already zeroed value (0).
    d->maxValue = 100;
    d->vertical = true;
    w->data     = d;
    return w;
}


// ============================================================
// DXE registration
// ============================================================

static const WidgetClassT sClassProgressBar = {
    .version = WGT_CLASS_VERSION,
    .flags   = 0,
    .handlers = {
        [WGT_METHOD_PAINT]         = (void *)widgetProgressBarPaint,
        [WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetProgressBarCalcMinSize,
    }
};


static const struct {
    WidgetT *(*create)(WidgetT *parent);
    WidgetT *(*createV)(WidgetT *parent);
    void     (*setValue)(WidgetT *w, int32_t value);
    int32_t  (*getValue)(const WidgetT *w);
} sApi = {
    .create   = wgtProgressBar,
    .createV  = wgtProgressBarV,
    .setValue  = wgtProgressBarSetValue,
    .getValue = wgtProgressBarGetValue
};

static const WgtPropDescT sProps[] = {
    { "Value", WGT_IFACE_INT, (void *)wgtProgressBarGetValue, (void *)wgtProgressBarSetValue, NULL }
};

static const WgtIfaceT sIface = {
    .basName      = "ProgressBar",
    .props        = sProps,
    .propCount    = 1,
    .methods      = NULL,
    .methodCount  = 0,
    .events       = NULL,
    .eventCount   = 0,
    .createSig    = WGT_CREATE_PARENT
};

void wgtRegister(void) {
    sTypeId = wgtRegisterClass(&sClassProgressBar);
    wgtRegisterApi("progressbar", &sApi);
    wgtRegisterIface("progressbar", &sIface);
}


// Min size is based on font metrics to keep the bar proportional to
// surrounding text. The cross-axis uses charHeight+4 (room for the
// bar plus 2px bevel on each side). The main axis uses 12 char widths
// so the bar is wide enough to show meaningful progress granularity.
void widgetProgressBarCalcMinSize(WidgetT *w, const BitmapFontT *font) {
    ProgressBarDataT *d = (ProgressBarDataT *)w->data;

    if (d->vertical) {
        w->calcMinW = font->charHeight + 2 * PROGRESSBAR_BEVEL_W;
        w->calcMinH = font->charWidth * PROGRESSBAR_MIN_CHARS;
    } else {
        w->calcMinW = font->charWidth * PROGRESSBAR_MIN_CHARS;
        w->calcMinH = font->charHeight + 2 * PROGRESSBAR_BEVEL_W;
    }
}


// Paint uses a two-pass approach: first the sunken bevel border, then
// the fill rect. The fill color defaults to activeTitleBg (the window
// title bar color) to provide strong visual contrast inside the
// recessed trough -- matching classic Win3.1/Motif progress bar style.
// When disabled, the fill becomes windowShadow to look grayed out.
//
// Vertical bars fill from bottom-up (natural "filling" metaphor),
// which requires computing the Y offset as (innerH - fillH).
void widgetProgressBarPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
    (void)font;
    ProgressBarDataT *d = (ProgressBarDataT *)w->data;
    uint32_t fg = w->enabled ? (w->fgColor ? w->fgColor : colors->activeTitleBg) : colors->windowShadow;
    uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;

    // Sunken border -- highlight/shadow are swapped vs raised bevel
    // to create the "pressed in" trough appearance
    BevelStyleT bevel;
    bevel.highlight = colors->windowShadow;
    bevel.shadow    = colors->windowHighlight;
    bevel.face      = bg;
    bevel.width     = PROGRESSBAR_BEVEL_W;
    drawBevel(disp, ops, w->x, w->y, w->w, w->h, &bevel);

    // Fill bar
    int32_t maxVal = d->maxValue;

    if (maxVal <= 0) {
        maxVal = 100;
    }

    int32_t innerW = w->w - 2 * PROGRESSBAR_BEVEL_W;
    int32_t innerH = w->h - 2 * PROGRESSBAR_BEVEL_W;

    if (d->vertical) {
        int32_t fillH = (innerH * d->value) / maxVal;

        if (fillH > innerH) {
            fillH = innerH;
        }

        if (fillH > 0) {
            rectFill(disp, ops, w->x + PROGRESSBAR_BEVEL_W, w->y + PROGRESSBAR_BEVEL_W + innerH - fillH, innerW, fillH, fg);
        }
    } else {
        int32_t fillW = (innerW * d->value) / maxVal;

        if (fillW > innerW) {
            fillW = innerW;
        }

        if (fillW > 0) {
            rectFill(disp, ops, w->x + PROGRESSBAR_BEVEL_W, w->y + PROGRESSBAR_BEVEL_W, fillW, innerH, fg);
        }
    }
}
