// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd.
//
// SPDX-License-Identifier: GPL-1.0-or-later

#include <stdlib.h>

#include "effect_p.h"
#include "output.h"
#include "theme.h"
#include "util/color.h"
#include "view/view.h"

struct wallpaper_output {
    struct wl_list link;
    struct wallpaper_effect *effect;

    struct output *output;
    struct wl_listener geometry;
    struct wl_listener disable;

    struct ky_scene_buffer *buffer;
    struct ky_scene_rect *rect;
};

struct wallpaper_effect {
    struct effect *effect;
    struct wl_listener enable;
    struct wl_listener disable;
    struct wl_listener destroy;

    struct wl_list outputs;
    struct wl_listener new_enabled_output;
    struct wl_listener background_update;
};

static void wallpaper_output_destroy(struct wallpaper_output *output)
{
    wl_list_remove(&output->link);
    wl_list_remove(&output->geometry.link);
    wl_list_remove(&output->disable.link);

    if (output->buffer) {
        ky_scene_node_destroy(&output->buffer->node);
    } else if (output->rect) {
        ky_scene_node_destroy(&output->rect->node);
    }

    free(output);
}

static void wallpaper_output_update_buffer(struct wallpaper_output *output)
{
    struct kywc_box *geo = &output->output->geometry;
    struct wlr_fbox dst = { geo->x, geo->y, geo->width, geo->height };
    struct wlr_fbox src = { 0 };

    int width, height;
    output_layout_get_size(&width, &height);
    bool repeated = theme_manager_get_background_box(&dst, &src, width, height);
    ky_scene_buffer_set_repeated(output->buffer, repeated);

    ky_scene_buffer_set_source_box(output->buffer, &src);
    ky_scene_node_set_position(&output->buffer->node, dst.x, dst.y);
    ky_scene_buffer_set_dest_size(output->buffer, dst.width, dst.height);

    pixman_region32_t region;
    pixman_region32_init_rect(&region, 0, 0, dst.width, dst.height);
    ky_scene_buffer_set_opaque_region(output->buffer, &region);
    pixman_region32_fini(&region);
}

static void wallpaper_output_update_rect(struct wallpaper_output *output)
{
    struct kywc_box *geo = &output->output->geometry;
    ky_scene_node_set_position(&output->rect->node, geo->x, geo->y);
    ky_scene_rect_set_size(output->rect, geo->width, geo->height);
}

static void output_handle_geometry(struct wl_listener *listener, void *data)
{
    struct wallpaper_output *output = wl_container_of(listener, output, geometry);

    if (output->buffer) {
        wallpaper_output_update_buffer(output);
    } else if (output->rect) {
        wallpaper_output_update_rect(output);
    }
}

static void output_handle_disable(struct wl_listener *listener, void *data)
{
    struct wallpaper_output *output = wl_container_of(listener, output, disable);
    struct wallpaper_effect *effect = output->effect;

    wallpaper_output_destroy(output);

    // update all outputs if BACKGROUND_OPTION_SPANNED
    wl_list_for_each(output, &effect->outputs, link) {
        if (output->buffer) {
            wallpaper_output_update_buffer(output);
        }
    }
}

static void wallpaper_output_update_node(struct wallpaper_output *output, struct wlr_buffer *buffer,
                                         int32_t color)
{
    if (buffer) {
        if (output->rect) {
            ky_scene_node_destroy(&output->rect->node);
            output->rect = NULL;
        }
        if (!output->buffer) {
            struct view_layer *layer = view_manager_get_layer(LAYER_DESKTOP, false);
            output->buffer = ky_scene_buffer_create(layer->tree, buffer);
            ky_scene_node_set_input_bypassed(&output->buffer->node, true);
            ky_scene_node_lower_to_bottom(&output->buffer->node);
        } else {
            ky_scene_buffer_set_buffer(output->buffer, buffer);
        }
    } else if (color >= 0) {
        if (output->buffer) {
            ky_scene_node_destroy(&output->buffer->node);
            output->buffer = NULL;
        }
        float solid[4];
        color_uint24_to_float(solid, color);
        if (!output->rect) {
            struct view_layer *layer = view_manager_get_layer(LAYER_DESKTOP, false);
            output->rect = ky_scene_rect_create(layer->tree, 1, 1, solid);
            ky_scene_node_set_input_bypassed(&output->rect->node, true);
            ky_scene_node_lower_to_bottom(&output->rect->node);
        } else {
            ky_scene_rect_set_color(output->rect, solid);
        }
    }
}

static void wallpaper_output_create(struct wallpaper_effect *effect,
                                    struct kywc_output *kywc_output)
{
    struct wallpaper_output *output = calloc(1, sizeof(*output));
    if (!output) {
        return;
    }

    output->effect = effect;
    wl_list_insert(&effect->outputs, &output->link);

    output->output = output_from_kywc_output(kywc_output);
    output->geometry.notify = output_handle_geometry;
    wl_signal_add(&output->output->events.geometry, &output->geometry);
    output->disable.notify = output_handle_disable;
    wl_signal_add(&output->output->events.disable, &output->disable);

    int32_t color = -1;
    struct wlr_buffer *buffer = theme_manager_get_background(&color);
    if (!buffer && color < 0) {
        return;
    }

    wallpaper_output_update_node(output, buffer, color);

    if (output->buffer) {
        // update all outputs if BACKGROUND_OPTION_SPANNED
        wl_list_for_each(output, &effect->outputs, link) {
            wallpaper_output_update_buffer(output);
        }
    } else if (output->rect) {
        wallpaper_output_update_rect(output);
    }
}

static void handle_new_enabled_output(struct wl_listener *listener, void *data)
{
    struct wallpaper_effect *effect = wl_container_of(listener, effect, new_enabled_output);
    struct kywc_output *output = data;
    wallpaper_output_create(effect, output);
}

static void handle_background_update(struct wl_listener *listener, void *data)
{
    int32_t color = -1;
    struct wlr_buffer *buffer = theme_manager_get_background(&color);
    if (!buffer && color < 0) {
        return;
    }

    struct wallpaper_effect *effect = wl_container_of(listener, effect, background_update);
    struct wallpaper_output *output;
    wl_list_for_each(output, &effect->outputs, link) {
        wallpaper_output_update_node(output, buffer, color);
        if (output->buffer) {
            wallpaper_output_update_buffer(output);
        } else if (output->rect) {
            wallpaper_output_update_rect(output);
        }
    }
}

static bool create_request(struct kywc_output *kywc_output, int index, void *data)
{
    struct wallpaper_effect *effect = data;
    wallpaper_output_create(effect, kywc_output);
    return false;
}

static void handle_effect_enable(struct wl_listener *listener, void *data)
{
    struct wallpaper_effect *effect = wl_container_of(listener, effect, enable);
    /* get current enabled output */
    output_manager_for_each_output(create_request, true, effect);

    output_manager_add_new_enabled_listener(&effect->new_enabled_output);
    theme_manager_add_background_update_listener(&effect->background_update);
}

static void handle_effect_disable(struct wl_listener *listener, void *data)
{
    struct wallpaper_effect *effect = wl_container_of(listener, effect, disable);
    wl_list_remove(&effect->new_enabled_output.link);
    wl_list_init(&effect->new_enabled_output.link);
    wl_list_remove(&effect->background_update.link);
    wl_list_init(&effect->background_update.link);

    struct wallpaper_output *output, *tmp;
    wl_list_for_each_safe(output, tmp, &effect->outputs, link) {
        wallpaper_output_destroy(output);
    }
}

static void handle_effect_destroy(struct wl_listener *listener, void *data)
{
    struct wallpaper_effect *effect = wl_container_of(listener, effect, destroy);
    wl_list_remove(&effect->new_enabled_output.link);
    wl_list_remove(&effect->background_update.link);
    wl_list_remove(&effect->destroy.link);
    wl_list_remove(&effect->enable.link);
    wl_list_remove(&effect->disable.link);
    free(effect);
}

static bool handle_effect_configure(struct effect *effect, const struct effect_option *option)
{
    if (effect_option_is_enabled_option(option)) {
        return true;
    }

    return false;
}

static const struct effect_interface wallpaper_effect_impl = {
    .configure = handle_effect_configure,
};

bool wallpaper_effect_create(struct effect_manager *manager)
{
    struct wallpaper_effect *effect = calloc(1, sizeof(*effect));
    if (!effect) {
        return false;
    }

    effect->effect = effect_create("wallpaper", 0, false, &wallpaper_effect_impl, effect);
    if (!effect->effect) {
        free(effect);
        return false;
    }

    effect->enable.notify = handle_effect_enable;
    wl_signal_add(&effect->effect->events.enable, &effect->enable);
    effect->disable.notify = handle_effect_disable;
    wl_signal_add(&effect->effect->events.disable, &effect->disable);
    effect->destroy.notify = handle_effect_destroy;
    wl_signal_add(&effect->effect->events.destroy, &effect->destroy);

    wl_list_init(&effect->outputs);
    effect->new_enabled_output.notify = handle_new_enabled_output;
    wl_list_init(&effect->new_enabled_output.link);

    effect->background_update.notify = handle_background_update;
    wl_list_init(&effect->background_update.link);

    if (effect->effect->enabled) {
        handle_effect_enable(&effect->enable, NULL);
    }

    return true;
}
