/* ========================================================================= */
/**
 * @file resizebar.c
 *
 * @copyright
 * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch)
 * Copyright 2023 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "resizebar.h"

#include <cairo.h>
#include <libbase/libbase.h>
#include <libbase/plist.h>
#include <stdlib.h>
#include <toolkit/box.h>
#include <toolkit/primitives.h>
#include <toolkit/resizebar_area.h>
#define WLR_USE_UNSTABLE
#include <wlr/interfaces/wlr_buffer.h>
#include <wlr/util/edges.h>
#undef WLR_USE_UNSTABLE

#include "test.h"  // IWYU pragma: keep

/* == Declarations ========================================================= */

/** State of the title bar. */
struct _wlmtk_resizebar_t {
    /** Superclass: Box. */
    wlmtk_box_t               super_box;

    /** Current width of the resize bar. */
    unsigned                  width;
    /** Style of the resize bar. */
    const struct wlmtk_resizebar_style *style_ptr;

    /** Background. */
    bs_gfxbuf_t               *gfxbuf_ptr;

    /** Left element of the resizebar. */
    wlmtk_resizebar_area_t    *left_area_ptr;
    /** Center element of the resizebar. */
    wlmtk_resizebar_area_t    *center_area_ptr;
    /** Right element of the resizebar. */
    wlmtk_resizebar_area_t    *right_area_ptr;
};

static void _wlmtk_resizebar_element_destroy(wlmtk_element_t *element_ptr);
static bool _wlmtk_resizebar_redraw(
    wlmtk_resizebar_t *resizebar_ptr,
    const struct wlmtk_resizebar_style *style_ptr,
    unsigned width);
static bool redraw_buffers(
    wlmtk_resizebar_t *resizebar_ptr,
    const struct wlmtk_resizebar_style *style_ptr,
    unsigned width);

/* == Data ================================================================= */

/** Virtual method table extension for the resizebar's element superclass. */
static const wlmtk_element_vmt_t resizebar_element_vmt = {
    .destroy = _wlmtk_resizebar_element_destroy,
};

/** Descriptor for decoding the "ResizeBar" dict below "Window". */
const bspl_desc_t wlmtk_resizebar_style_desc[] = {
    BSPL_DESC_CUSTOM(
        "Fill", true, struct wlmtk_resizebar_style, fill, fill,
        wlmtk_style_decode_fill, NULL, NULL, NULL),
    BSPL_DESC_UINT64(
        "Height", true, struct wlmtk_resizebar_style, height, height, 7),
    BSPL_DESC_UINT64(
        "BezelWidth", true, struct wlmtk_resizebar_style, bezel_width, bezel_width,
        1),
    BSPL_DESC_UINT64(
        "CornerWidth", true, struct wlmtk_resizebar_style, corner_width,
        corner_width, 1),
    BSPL_DESC_SENTINEL()
};

/* == Exported methods ===================================================== */

/* ------------------------------------------------------------------------- */
wlmtk_resizebar_t *wlmtk_resizebar_create(
    wlmtk_window_t *window_ptr,
    const struct wlmtk_resizebar_style *style_ptr)
{
    static const struct wlmtk_margin_style empty_margin_style = {};
    wlmtk_resizebar_t *resizebar_ptr = logged_calloc(
        1, sizeof(wlmtk_resizebar_t));
    if (NULL == resizebar_ptr) return NULL;
    resizebar_ptr->style_ptr = style_ptr;

    if (!wlmtk_box_init(&resizebar_ptr->super_box,
                        WLMTK_BOX_HORIZONTAL,
                        &empty_margin_style)) {
        wlmtk_resizebar_destroy(resizebar_ptr);
        return NULL;
    }
    wlmtk_element_extend(
        &resizebar_ptr->super_box.super_container.super_element,
        &resizebar_element_vmt);

    resizebar_ptr->left_area_ptr = wlmtk_resizebar_area_create(
        window_ptr, WLR_EDGE_LEFT | WLR_EDGE_BOTTOM);
    if (NULL == resizebar_ptr->left_area_ptr) {
        wlmtk_resizebar_destroy(resizebar_ptr);
        return NULL;
    }
    wlmtk_box_add_element_front(
        &resizebar_ptr->super_box,
        wlmtk_resizebar_area_element(resizebar_ptr->left_area_ptr));

    resizebar_ptr->center_area_ptr = wlmtk_resizebar_area_create(
        window_ptr, WLR_EDGE_BOTTOM);
    if (NULL == resizebar_ptr->center_area_ptr) {
        wlmtk_resizebar_destroy(resizebar_ptr);
        return NULL;
    }
    wlmtk_box_add_element_back(
        &resizebar_ptr->super_box,
        wlmtk_resizebar_area_element(resizebar_ptr->center_area_ptr));

    resizebar_ptr->right_area_ptr = wlmtk_resizebar_area_create(
        window_ptr, WLR_EDGE_RIGHT | WLR_EDGE_BOTTOM);
    if (NULL == resizebar_ptr->right_area_ptr) {
        wlmtk_resizebar_destroy(resizebar_ptr);
        return NULL;
    }
    wlmtk_box_add_element_back(
        &resizebar_ptr->super_box,
        wlmtk_resizebar_area_element(resizebar_ptr->right_area_ptr));

    return resizebar_ptr;
}

/* ------------------------------------------------------------------------- */
void wlmtk_resizebar_destroy(wlmtk_resizebar_t *resizebar_ptr)
{
    if (NULL != resizebar_ptr->right_area_ptr) {
        wlmtk_box_remove_element(
            &resizebar_ptr->super_box,
            wlmtk_resizebar_area_element(resizebar_ptr->right_area_ptr));
        wlmtk_resizebar_area_destroy(resizebar_ptr->right_area_ptr);
        resizebar_ptr->right_area_ptr = NULL;
    }
    if (NULL != resizebar_ptr->center_area_ptr) {
        wlmtk_box_remove_element(
            &resizebar_ptr->super_box,
            wlmtk_resizebar_area_element(resizebar_ptr->center_area_ptr));
        wlmtk_resizebar_area_destroy(resizebar_ptr->center_area_ptr);
        resizebar_ptr->center_area_ptr = NULL;
    }
    if (NULL != resizebar_ptr->left_area_ptr) {
        wlmtk_box_remove_element(
            &resizebar_ptr->super_box,
            wlmtk_resizebar_area_element(resizebar_ptr->left_area_ptr));

        wlmtk_resizebar_area_destroy(resizebar_ptr->left_area_ptr);
        resizebar_ptr->left_area_ptr = NULL;
    }

    if (NULL != resizebar_ptr->gfxbuf_ptr) {
        bs_gfxbuf_destroy(resizebar_ptr->gfxbuf_ptr);
        resizebar_ptr->gfxbuf_ptr = NULL;
    }

    wlmtk_box_fini(&resizebar_ptr->super_box);
    free(resizebar_ptr);
}

/* ------------------------------------------------------------------------- */
bool wlmtk_resizebar_set_width(
    wlmtk_resizebar_t *resizebar_ptr,
    unsigned width)
{
    if (resizebar_ptr->width == width) return true;
    return _wlmtk_resizebar_redraw(
        resizebar_ptr, resizebar_ptr->style_ptr, width);
}

/* ------------------------------------------------------------------------- */
bool wlmtk_resizebar_set_style(
    wlmtk_resizebar_t *resizebar_ptr,
    const struct wlmtk_resizebar_style *style_ptr)
{
    if (!_wlmtk_resizebar_redraw(
            resizebar_ptr, style_ptr, resizebar_ptr->width)) return false;
    resizebar_ptr->style_ptr = style_ptr;
    return true;
}

/* ------------------------------------------------------------------------- */
wlmtk_element_t *wlmtk_resizebar_element(wlmtk_resizebar_t *resizebar_ptr)
{
    return &resizebar_ptr->super_box.super_container.super_element;
}

/* == Local (static) methods =============================================== */

/* ------------------------------------------------------------------------- */
/** Virtual destructor: Wraps to our dtor. */
void _wlmtk_resizebar_element_destroy(wlmtk_element_t *element_ptr)
{
    wlmtk_resizebar_t *resizebar_ptr = BS_CONTAINER_OF(
        element_ptr, wlmtk_resizebar_t,
        super_box.super_container.super_element);
    wlmtk_resizebar_destroy(resizebar_ptr);
}

/* ------------------------------------------------------------------------- */
/** Redraws the resizebar. */
bool _wlmtk_resizebar_redraw(
    wlmtk_resizebar_t *resizebar_ptr,
    const struct wlmtk_resizebar_style *style_ptr,
    unsigned width)
{
    if (!redraw_buffers(resizebar_ptr, style_ptr, width)) return false;
    BS_ASSERT(width == resizebar_ptr->width);
    BS_ASSERT(width == resizebar_ptr->gfxbuf_ptr->width);

    int right_corner_width = BS_MIN((int)width, (int)style_ptr->corner_width);
    int left_corner_width = BS_MAX(0, BS_MIN((int)width - right_corner_width,
                                             (int)style_ptr->corner_width));
    int center_width = BS_MAX(
        0, (int)width - right_corner_width - left_corner_width);

    if (!wlmtk_resizebar_area_redraw(
            resizebar_ptr->left_area_ptr,
            resizebar_ptr->gfxbuf_ptr,
            0, left_corner_width,
            style_ptr)) {
        return false;
    }
    if (!wlmtk_resizebar_area_redraw(
            resizebar_ptr->center_area_ptr,
            resizebar_ptr->gfxbuf_ptr,
            left_corner_width, center_width,
            style_ptr)) {
        return false;
    }
    if (!wlmtk_resizebar_area_redraw(
            resizebar_ptr->right_area_ptr,
            resizebar_ptr->gfxbuf_ptr,
            left_corner_width + center_width, right_corner_width,
            style_ptr)) {
        return false;
    }

    wlmtk_element_set_visible(
        wlmtk_resizebar_area_element(resizebar_ptr->left_area_ptr),
        0 < left_corner_width);
    wlmtk_element_set_visible(
        wlmtk_resizebar_area_element(resizebar_ptr->center_area_ptr),
        0 < center_width);
    wlmtk_element_set_visible(
        wlmtk_resizebar_area_element(resizebar_ptr->right_area_ptr),
        0 < right_corner_width);

    wlmtk_element_layout(wlmtk_box_element(&resizebar_ptr->super_box));
    wlmtk_element_invalidate_parent_layout(
        wlmtk_box_element(&resizebar_ptr->super_box));
    return true;
}

/* ------------------------------------------------------------------------- */
/** Redraws the resizebar's background in appropriate size. */
bool redraw_buffers(wlmtk_resizebar_t *resizebar_ptr,
                    const struct wlmtk_resizebar_style *style_ptr,
                    unsigned width)
{
    cairo_t *cairo_ptr;

    bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(width, style_ptr->height);
    if (NULL == gfxbuf_ptr) return false;
    cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr);
    if (NULL == cairo_ptr) {
        bs_gfxbuf_destroy(gfxbuf_ptr);
        return false;
    }
    wlmaker_primitives_cairo_fill(cairo_ptr, &style_ptr->fill);
    cairo_destroy(cairo_ptr);

    if (NULL != resizebar_ptr->gfxbuf_ptr) {
        bs_gfxbuf_destroy(resizebar_ptr->gfxbuf_ptr);
    }
    resizebar_ptr->gfxbuf_ptr = gfxbuf_ptr;
    resizebar_ptr->width = width;
    return true;
}

/* == Unit tests =========================================================== */

static void test_variable_width(bs_test_t *test_ptr);

/** Test cases */
static const bs_test_case_t _wlmtk_resizebar_test_cases[] = {
    { 1, "variable_width", test_variable_width },
    BS_TEST_CASE_SENTINEL()
};

const bs_test_set_t wlmtk_resizebar_test_set = BS_TEST_SET(
    true, "resizebar", _wlmtk_resizebar_test_cases);

/* ------------------------------------------------------------------------- */
/** Performs resizing and verifies the elements are shown as expected. */
void test_variable_width(bs_test_t *test_ptr)
{
    wlmtk_window_t *w = wlmtk_test_window_create(NULL);
    struct wlmtk_resizebar_style style = { .height = 10, .corner_width = 17 };
    wlmtk_resizebar_t *resizebar_ptr = wlmtk_resizebar_create(w, &style);
    BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, resizebar_ptr);

    wlmtk_element_t *left_elem_ptr = wlmtk_resizebar_area_element(
        resizebar_ptr->left_area_ptr);
    wlmtk_element_t *center_elem_ptr = wlmtk_resizebar_area_element(
        resizebar_ptr->center_area_ptr);
    wlmtk_element_t *right_elem_ptr = wlmtk_resizebar_area_element(
        resizebar_ptr->right_area_ptr);

    // Zero width. Zero visibility.
    BS_TEST_VERIFY_FALSE(test_ptr, left_elem_ptr->visible);
    BS_TEST_VERIFY_FALSE(test_ptr, center_elem_ptr->visible);
    BS_TEST_VERIFY_FALSE(test_ptr, right_elem_ptr->visible);

    // Not enough space for the center element with all margins.
    BS_TEST_VERIFY_TRUE(
        test_ptr, wlmtk_resizebar_set_width(resizebar_ptr, 33));
    BS_TEST_VERIFY_EQ(test_ptr, 10, resizebar_ptr->gfxbuf_ptr->height);
    BS_TEST_VERIFY_TRUE(test_ptr, left_elem_ptr->visible);
    BS_TEST_VERIFY_FALSE(test_ptr, center_elem_ptr->visible);
    BS_TEST_VERIFY_TRUE(test_ptr, right_elem_ptr->visible);
    BS_TEST_VERIFY_EQ(test_ptr, 16, right_elem_ptr->x);

    // Sufficient space for all the elements.
    style = (struct wlmtk_resizebar_style){ .height = 7, .corner_width = 16 };
    BS_TEST_VERIFY_TRUE(
        test_ptr, wlmtk_resizebar_set_style(resizebar_ptr, &style));
    BS_TEST_VERIFY_EQ(test_ptr, 7, resizebar_ptr->gfxbuf_ptr->height);
    BS_TEST_VERIFY_TRUE(
        test_ptr, wlmtk_resizebar_set_width(resizebar_ptr, 33));
    BS_TEST_VERIFY_TRUE(test_ptr, left_elem_ptr->visible);
    BS_TEST_VERIFY_TRUE(test_ptr, center_elem_ptr->visible);
    BS_TEST_VERIFY_TRUE(test_ptr, right_elem_ptr->visible);
    BS_TEST_VERIFY_EQ(test_ptr, 16, center_elem_ptr->x);
    BS_TEST_VERIFY_EQ(test_ptr, 17, right_elem_ptr->x);

    // Not enough space for the center element with all margins.
    BS_TEST_VERIFY_TRUE(
        test_ptr, wlmtk_resizebar_set_width(resizebar_ptr, 32));
    BS_TEST_VERIFY_TRUE(test_ptr, left_elem_ptr->visible);
    BS_TEST_VERIFY_FALSE(test_ptr, center_elem_ptr->visible);
    BS_TEST_VERIFY_TRUE(test_ptr, right_elem_ptr->visible);
    BS_TEST_VERIFY_EQ(test_ptr, 16, right_elem_ptr->x);

    // Not enough space for center and left element.
    BS_TEST_VERIFY_TRUE(
        test_ptr, wlmtk_resizebar_set_width(resizebar_ptr, 16));
    BS_TEST_VERIFY_FALSE(test_ptr, left_elem_ptr->visible);
    BS_TEST_VERIFY_FALSE(test_ptr, center_elem_ptr->visible);
    BS_TEST_VERIFY_TRUE(test_ptr, right_elem_ptr->visible);
    BS_TEST_VERIFY_EQ(test_ptr, 0, right_elem_ptr->x);

    wlmtk_element_destroy(wlmtk_resizebar_element(resizebar_ptr));
    wlmtk_window_destroy(w);
}

/* == End of resizebar.c =================================================== */
