/* ========================================================================= */
/**
 * @file resizebar_area.c
 *
 * @copyright
 * 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_area.h"

#include <cairo.h>
#include <inttypes.h>
#include <libbase/libbase.h>
#include <linux/input-event-codes.h>
#include <stdlib.h>
#include <wayland-server-core.h>
#define WLR_USE_UNSTABLE
#include <wlr/interfaces/wlr_buffer.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/util/edges.h>
#undef WLR_USE_UNSTABLE

#include "buffer.h"
#include "gfxbuf.h"  // IWYU pragma: keep
#include "input.h"
#include "primitives.h"
#include "test.h"  // IWYU pragma: keep
#include "tile.h"
#include "util.h"
#include "workspace.h"

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

/** State of an element of the resize bar. */
struct _wlmtk_resizebar_area_t {
    /** Superclass: Buffer. */
    wlmtk_buffer_t            super_buffer;
    /** Original virtual method table of the superclass element. */
    wlmtk_element_vmt_t       orig_super_element_vmt;

    /** WLR buffer holding the buffer in released state. */
    struct wlr_buffer         *released_wlr_buffer_ptr;
    /** WLR buffer holding the buffer in pressed state. */
    struct wlr_buffer         *pressed_wlr_buffer_ptr;

    /** Whether the area is currently pressed or not. */
    bool                      pressed;

    /** Window to which the resize bar area belongs. To initiate resizing. */
    wlmtk_window_t           *window_ptr;
    /** Edges that the resizebar area controls. */
    uint32_t                  edges;
};

static void _wlmtk_resizebar_area_element_destroy(
    wlmtk_element_t *element_ptr);
static bool _wlmtk_resizebar_area_element_pointer_button(
    wlmtk_element_t *element_ptr,
    const wlmtk_button_event_t *button_event_ptr);

static void draw_state(wlmtk_resizebar_area_t *resizebar_area_ptr);
static struct wlr_buffer *create_buffer(
    bs_gfxbuf_t *gfxbuf_ptr,
    unsigned position,
    unsigned width,
    const wlmtk_resizebar_style_t *style_ptr,
    bool pressed);

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

/** Buffer implementation for title of the title bar. */
static const wlmtk_element_vmt_t resizebar_area_element_vmt = {
    .destroy = _wlmtk_resizebar_area_element_destroy,
    .pointer_button = _wlmtk_resizebar_area_element_pointer_button,
};

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

/* ------------------------------------------------------------------------- */
wlmtk_resizebar_area_t *wlmtk_resizebar2_area_create(
    wlmtk_window_t *window_ptr,
    uint32_t edges)
{
    wlmtk_resizebar_area_t *resizebar_area_ptr = logged_calloc(
        1, sizeof(wlmtk_resizebar_area_t));
    if (NULL == resizebar_area_ptr) return NULL;
    BS_ASSERT(NULL != window_ptr);
    resizebar_area_ptr->window_ptr = window_ptr;
    resizebar_area_ptr->edges = edges;

    wlmtk_pointer_cursor_t cursor = WLMTK_POINTER_CURSOR_DEFAULT;
    switch (resizebar_area_ptr->edges) {
    case WLR_EDGE_BOTTOM:
        cursor = WLMTK_POINTER_CURSOR_RESIZE_S;
        break;
    case WLR_EDGE_BOTTOM | WLR_EDGE_LEFT:
        cursor = WLMTK_POINTER_CURSOR_RESIZE_SW;
        break;
    case WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT:
        cursor = WLMTK_POINTER_CURSOR_RESIZE_SE;
        break;
    default:
        bs_log(BS_ERROR, "Unsupported edge %"PRIx32, edges);
    }

    if (!wlmtk_buffer_init(&resizebar_area_ptr->super_buffer)) {
        wlmtk_resizebar_area_destroy(resizebar_area_ptr);
        return NULL;
    }
    resizebar_area_ptr->orig_super_element_vmt = wlmtk_element_extend(
        &resizebar_area_ptr->super_buffer.super_element,
        &resizebar_area_element_vmt);
    resizebar_area_ptr->super_buffer.pointer_cursor = cursor;

    draw_state(resizebar_area_ptr);
    return resizebar_area_ptr;
}

/* ------------------------------------------------------------------------- */
void wlmtk_resizebar_area_destroy(
    wlmtk_resizebar_area_t *resizebar_area_ptr)
{
    wlr_buffer_drop_nullify(
        &resizebar_area_ptr->released_wlr_buffer_ptr);
    wlr_buffer_drop_nullify(
        &resizebar_area_ptr->pressed_wlr_buffer_ptr);

    wlmtk_buffer_fini(&resizebar_area_ptr->super_buffer);
    free(resizebar_area_ptr);
}

/* ------------------------------------------------------------------------- */
bool wlmtk_resizebar_area_redraw(
    wlmtk_resizebar_area_t *resizebar_area_ptr,
    bs_gfxbuf_t *gfxbuf_ptr,
    unsigned position,
    unsigned width,
    const wlmtk_resizebar_style_t *style_ptr)
{
    struct wlr_buffer *released_wlr_buffer_ptr = create_buffer(
        gfxbuf_ptr, position, width, style_ptr, false);
    struct wlr_buffer *pressed_wlr_buffer_ptr = create_buffer(
        gfxbuf_ptr, position, width, style_ptr, true);

    if (NULL == released_wlr_buffer_ptr ||
        NULL == pressed_wlr_buffer_ptr) {
        wlr_buffer_drop_nullify(&released_wlr_buffer_ptr);
        wlr_buffer_drop_nullify(&pressed_wlr_buffer_ptr);
        return false;
    }

    wlr_buffer_drop_nullify(
        &resizebar_area_ptr->released_wlr_buffer_ptr);
    resizebar_area_ptr->released_wlr_buffer_ptr = released_wlr_buffer_ptr;
    wlr_buffer_drop_nullify(
        &resizebar_area_ptr->pressed_wlr_buffer_ptr);
    resizebar_area_ptr->pressed_wlr_buffer_ptr = pressed_wlr_buffer_ptr;

    draw_state(resizebar_area_ptr);
    return true;
}

/* ------------------------------------------------------------------------- */
wlmtk_element_t *wlmtk_resizebar_area_element(
    wlmtk_resizebar_area_t *resizebar_area_ptr)
{
    return &resizebar_area_ptr->super_buffer.super_element;
}

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

/* ------------------------------------------------------------------------- */
/** Dtor. */
void _wlmtk_resizebar_area_element_destroy(wlmtk_element_t *element_ptr)
{
    wlmtk_resizebar_area_t *resizebar_area_ptr = BS_CONTAINER_OF(
        element_ptr, wlmtk_resizebar_area_t, super_buffer.super_element);
    wlmtk_resizebar_area_destroy(resizebar_area_ptr);
}

/* ------------------------------------------------------------------------- */
/** See @ref wlmtk_element_vmt_t::pointer_button. */
bool _wlmtk_resizebar_area_element_pointer_button(
    wlmtk_element_t *element_ptr,
    const wlmtk_button_event_t *button_event_ptr)
{
    wlmtk_resizebar_area_t *resizebar_area_ptr = BS_CONTAINER_OF(
        element_ptr, wlmtk_resizebar_area_t, super_buffer.super_element);

    if (button_event_ptr->button != BTN_LEFT) return true;

    switch (button_event_ptr->type) {
    case WLMTK_BUTTON_DOWN:
        resizebar_area_ptr->pressed = true;

        wlmtk_workspace_begin_window_resize(
            wlmtk_window_get_workspace(resizebar_area_ptr->window_ptr),
            resizebar_area_ptr->window_ptr,
            resizebar_area_ptr->edges);
        draw_state(resizebar_area_ptr);
        break;

    case WLMTK_BUTTON_UP:
        resizebar_area_ptr->pressed = false;
        draw_state(resizebar_area_ptr);
        break;

    default:
        break;
    }

    return true;
}

/* ------------------------------------------------------------------------- */
/**
 * Draws the buffer in current state (released or pressed).
 *
 * @param resizebar_area_ptr
 */
void draw_state(wlmtk_resizebar_area_t *resizebar_area_ptr)
{
    if (!resizebar_area_ptr->pressed) {
        wlmtk_buffer_set(
            &resizebar_area_ptr->super_buffer,
            resizebar_area_ptr->released_wlr_buffer_ptr);
    } else {
        wlmtk_buffer_set(
            &resizebar_area_ptr->super_buffer,
            resizebar_area_ptr->pressed_wlr_buffer_ptr);
    }
}

/* ------------------------------------------------------------------------- */
/**
 * Creates a resizebar area texture.
 *
 * @param gfxbuf_ptr
 * @param position
 * @param width
 * @param style_ptr
 * @param pressed
 *
 * @return A pointer to a newly allocated `struct wlr_buffer`.
 */
struct wlr_buffer *create_buffer(
    bs_gfxbuf_t *gfxbuf_ptr,
    unsigned position,
    unsigned width,
    const wlmtk_resizebar_style_t *style_ptr,
    bool pressed)
{
    struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer(
        width, style_ptr->height);
    if (NULL == wlr_buffer_ptr) return NULL;

    bs_gfxbuf_copy_area(
        bs_gfxbuf_from_wlr_buffer(wlr_buffer_ptr), 0, 0,
        gfxbuf_ptr, position, 0, width, style_ptr->height);

    cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr);
    if (NULL == cairo_ptr) {
        wlr_buffer_drop(wlr_buffer_ptr);
        return false;
    }
    wlmaker_primitives_draw_bezel_at(
        cairo_ptr, 0, 0, width,
        style_ptr->height, style_ptr->bezel_width, !pressed);
    cairo_destroy(cairo_ptr);

    return wlr_buffer_ptr;
}

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

static void test_area(bs_test_t *test_ptr);

const bs_test_case_t wlmtk_resizebar_area_test_cases[] = {
    { 1, "area", test_area },
    { 0, NULL, NULL }
};

/* ------------------------------------------------------------------------- */
/** Tests the area behaviour: Must initiate resize, set the edges. */
void test_area(bs_test_t *test_ptr)
{
    struct wl_display *display_ptr = wl_display_create();
    BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr);
    struct wlr_output_layout *wlr_output_layout_ptr =
        wlr_output_layout_create(display_ptr);
    BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr);
    struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 };
    wlmtk_test_wlr_output_init(&output);
    wlr_output_layout_add(wlr_output_layout_ptr, &output, 0, 0);

    wlmtk_tile_style_t ts = {};
    wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create(
        wlr_output_layout_ptr, "t", &ts);
    BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr);
    wlmtk_workspace_enable(ws_ptr, true);

    wlmtk_window_t *w = wlmtk_test_window_create(NULL);
    BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w);
    wlmtk_util_test_wlr_box_listener_t l = {};
    wlmtk_util_connect_test_wlr_box_listener(
        &wlmtk_window_events(w)->request_size, &l);
    wlmtk_workspace_map_window(ws_ptr, w);

    wlmtk_resizebar_area_t *area_ptr = wlmtk_resizebar2_area_create(
        w, WLR_EDGE_BOTTOM);
    BS_TEST_VERIFY_NEQ(test_ptr, NULL, area_ptr);
    wlmtk_element_t *element_ptr = wlmtk_resizebar_area_element(area_ptr);
    wlmtk_element_set_visible(element_ptr, true);

    // Draw and verify release state.
    wlmtk_resizebar_style_t style = { .height = 7, .bezel_width = 1.0 };
    bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(30, 7);
    bs_gfxbuf_clear(gfxbuf_ptr, 0xff604020);
    BS_TEST_VERIFY_TRUE(
        test_ptr,
        wlmtk_resizebar_area_redraw(area_ptr, gfxbuf_ptr, 10, 12, &style));
    bs_gfxbuf_destroy(gfxbuf_ptr);
    BS_TEST_VERIFY_GFXBUF_EQUALS_PNG(
        test_ptr,
        bs_gfxbuf_from_wlr_buffer(area_ptr->super_buffer.wlr_buffer_ptr),
        "toolkit/resizebar_area_released.png");
    BS_TEST_VERIFY_EQ(test_ptr, 0, l.calls);

    // Pointer must be inside the button for accepting DOWN.
    wlmtk_pointer_motion_event_t mev = { .x = 1, .y = 1 };
    BS_TEST_VERIFY_TRUE(
        test_ptr,
        wlmtk_element_pointer_motion(element_ptr, &mev));
    // Button down: pressed.
    wlmtk_button_event_t button = {
        .button = BTN_LEFT, .type = WLMTK_BUTTON_DOWN
    };
    BS_TEST_VERIFY_TRUE(
        test_ptr,
        wlmtk_element_pointer_button(element_ptr,  &button));
    BS_TEST_VERIFY_GFXBUF_EQUALS_PNG(
        test_ptr,
        bs_gfxbuf_from_wlr_buffer(area_ptr->super_buffer.wlr_buffer_ptr),
        "toolkit/resizebar_area_pressed.png");

    BS_TEST_VERIFY_EQ(
        test_ptr,
        WLR_EDGE_BOTTOM,
        wlmtk_window_get_resize_edges(w));

    wlmtk_element_destroy(element_ptr);

    wlmtk_workspace_unmap_window(ws_ptr, w);
    wlmtk_window_destroy(w);
    wlmtk_workspace_destroy(ws_ptr);
    wl_display_destroy(display_ptr);
}

/* == End of resizebar_area.c ============================================== */
