#include "pch.h"
#include "app_core/brush_ui.h"
#include "legacy_brush_ui_services.h"
#include "legacy_ui_overlay_services.h"
#include "log.h"
#include "node_panel_stroke.h"
#include "canvas.h"
#include "node_button.h"
#include "app.h"
#include "abr.h"

namespace {

void add_stroke_float(
    pp::app::BrushStrokePanelInput& input,
    pp::app::BrushStrokeFloatSetting setting,
    float value)
{
    input.float_values.push_back(pp::app::BrushStrokeFloatValue {
        .setting = setting,
        .value = value,
    });
}

void add_stroke_bool(
    pp::app::BrushStrokePanelInput& input,
    pp::app::BrushStrokeBoolSetting setting,
    bool value)
{
    input.bool_values.push_back(pp::app::BrushStrokeBoolValue {
        .setting = setting,
        .value = value,
    });
}

void add_stroke_blend(
    pp::app::BrushStrokePanelInput& input,
    pp::app::BrushStrokeBlendSetting setting,
    int blend_mode)
{
    input.blend_values.push_back(pp::app::BrushStrokeBlendValue {
        .setting = setting,
        .blend_mode = blend_mode,
    });
}

pp::app::BrushStrokePanelInput make_stroke_panel_input(const Brush& brush)
{
    pp::app::BrushStrokePanelInput input;
    input.float_values.reserve(29);
    input.bool_values.reserve(24);
    input.blend_values.reserve(3);

    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_size, brush.m_tip_size);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_spacing, brush.m_tip_spacing);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_flow, brush.m_tip_flow);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_opacity, brush.m_tip_opacity);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_angle, brush.m_tip_angle);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_angle_smooth, brush.m_tip_angle_smooth);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_mix, brush.m_tip_mix);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_wet, brush.m_tip_wet);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_noise, brush.m_tip_noise);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_scale, brush.m_jitter_scale);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_angle, brush.m_jitter_angle);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_scatter, brush.m_jitter_scatter);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_flow, brush.m_jitter_flow);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_opacity, brush.m_jitter_opacity);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_hue, brush.m_jitter_hue);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_saturation, brush.m_jitter_sat);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_value, brush.m_jitter_val);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_aspect, brush.m_jitter_aspect);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::dual_size, brush.m_dual_size);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::dual_spacing, brush.m_dual_spacing);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::dual_flow, brush.m_dual_flow);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::dual_scatter, brush.m_dual_scatter);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_aspect, brush.m_tip_aspect);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::dual_opacity, brush.m_dual_opacity);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::dual_rotate, brush.m_dual_rotate);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::pattern_scale, brush.m_pattern_scale);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::pattern_brightness, brush.m_pattern_brightness);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::pattern_contrast, brush.m_pattern_contrast);
    add_stroke_float(input, pp::app::BrushStrokeFloatSetting::pattern_depth, brush.m_pattern_depth);

    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::jitter_hsv_each_sample, brush.m_jitter_hsv_eachsample);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_angle_follow, brush.m_tip_angle_follow);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_angle_init, brush.m_tip_angle_init);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_flow_pressure, brush.m_tip_flow_pressure);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_opacity_pressure, brush.m_tip_opacity_pressure);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_size_pressure, brush.m_tip_size_pressure);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::jitter_aspect_both_axis, brush.m_jitter_aspect_bothaxis);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_invert, brush.m_tip_invert);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_flip_x, brush.m_tip_flipx);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_flip_y, brush.m_tip_flipy);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::pattern_enabled, brush.m_pattern_enabled);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::dual_enabled, brush.m_dual_enabled);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::dual_scatter_both_axis, brush.m_dual_scatter_bothaxis);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::dual_invert, brush.m_dual_invert);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::dual_flip_x, brush.m_dual_flipx);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::dual_flip_y, brush.m_dual_flipy);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::dual_random_flip, brush.m_dual_randflip);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_random_flip_x, brush.m_tip_randflipx);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_random_flip_y, brush.m_tip_randflipy);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::pattern_each_sample, brush.m_pattern_eachsample);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::pattern_invert, brush.m_pattern_invert);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::pattern_flip_x, brush.m_pattern_flipx);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::pattern_flip_y, brush.m_pattern_flipy);
    add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::pattern_random_offset, brush.m_pattern_rand_offset);

    add_stroke_blend(input, pp::app::BrushStrokeBlendSetting::tip, brush.m_blend_mode);
    add_stroke_blend(input, pp::app::BrushStrokeBlendSetting::dual, brush.m_dual_blend_mode);
    add_stroke_blend(input, pp::app::BrushStrokeBlendSetting::pattern, brush.m_pattern_blend_mode);

    input.tip_thumbnail_path = brush.m_brush_thumb_path;
    input.dual_thumbnail_path = brush.m_dual_thumb_path;
    input.pattern_thumbnail_path = brush.m_pattern_thumb_path;
    return input;
}

template <pp::panopainter::LegacyPopupOverlay PopupT>
bool open_stroke_popup_with_handles(
    NodePanelStroke& panel,
    const std::shared_ptr<PopupT>& popup,
    const glm::vec2& tick_pos,
    float button_height) noexcept
{
    if (!popup) {
        return false;
    }

    panel.close_popup_overlay_handles();

    auto tick = pp::panopainter::make_legacy_overlay_node_for_anchor<NodeImage>(panel);
    tick->SetPositioning(YGPositionTypeAbsolute);
    tick->SetSize(16, 32);
    tick->SetPosition(tick_pos.x, tick_pos.y + (button_height - 32) * 0.5f);
    tick->set_image("data/ui/popup-tick.png");
    tick->m_scale = { 1, 1 };

    const auto popup_overlay = pp::panopainter::open_legacy_overlay_node_with_handle(panel, popup);
    const auto tick_overlay = pp::panopainter::open_legacy_overlay_node_with_handle(panel, tick);
    if (!popup_overlay || !tick_overlay) {
        pp::panopainter::close_legacy_overlay_handles_if_open(panel, popup_overlay, tick_overlay);
        panel.m_popup_overlay_handle = {};
        panel.m_tick_overlay_handle = {};
        return false;
    }

    panel.m_popup_overlay_handle = popup_overlay.value();
    panel.m_tick_overlay_handle = tick_overlay.value();
    popup->on_popup_close = [&panel](Node*) {
        panel.close_popup_overlay_handles();
    };
    return true;
}

} // namespace

Node* NodePanelStroke::clone_instantiate() const
{
    return new NodePanelStroke();
}

void NodePanelStroke::clone_finalize(Node* dest) const
{
    NodePanelStroke* n = static_cast<NodePanelStroke*>(dest);
    n->init_controls();
}

void NodePanelStroke::init()
{
    init_template_file("data/dialogs/panel-stroke.xml", "tpl-panel-stroke");
    init_controls();
}

void NodePanelStroke::update_controls()
{
    if (!Canvas::I || !Canvas::I->m_current_brush) {
        return;
    }

    const auto view = pp::app::plan_brush_stroke_panel_view(
        make_stroke_panel_input(*Canvas::I->m_current_brush));
    if (!view) {
        LOG("Brush stroke panel view failed: %s", view.status().message);
        return;
    }

    const auto set_slider = [this](NodeSliderH* slider, float value) {
        const auto curve = m_curves.find(slider);
        slider->m_value = curve != m_curves.end() ? curve->second.to_slider(value) : value;
    };

    for (const auto& value : view.value().float_values) {
        switch (value.setting) {
        case pp::app::BrushStrokeFloatSetting::tip_size: set_slider(m_tip_size, value.value); break;
        case pp::app::BrushStrokeFloatSetting::tip_spacing: set_slider(m_tip_spacing, value.value); break;
        case pp::app::BrushStrokeFloatSetting::tip_flow: set_slider(m_tip_flow, value.value); break;
        case pp::app::BrushStrokeFloatSetting::tip_opacity: set_slider(m_tip_opacity, value.value); break;
        case pp::app::BrushStrokeFloatSetting::tip_angle: set_slider(m_tip_angle, value.value); break;
        case pp::app::BrushStrokeFloatSetting::tip_angle_smooth: set_slider(m_tip_angle_smooth, value.value); break;
        case pp::app::BrushStrokeFloatSetting::tip_mix: set_slider(m_tip_mix, value.value); break;
        case pp::app::BrushStrokeFloatSetting::tip_wet: set_slider(m_tip_wet, value.value); break;
        case pp::app::BrushStrokeFloatSetting::tip_noise: set_slider(m_tip_noise, value.value); break;
        case pp::app::BrushStrokeFloatSetting::jitter_scale: set_slider(m_jitter_scale, value.value); break;
        case pp::app::BrushStrokeFloatSetting::jitter_angle: set_slider(m_jitter_angle, value.value); break;
        case pp::app::BrushStrokeFloatSetting::jitter_scatter: set_slider(m_jitter_scatter, value.value); break;
        case pp::app::BrushStrokeFloatSetting::jitter_flow: set_slider(m_jitter_flow, value.value); break;
        case pp::app::BrushStrokeFloatSetting::jitter_opacity: set_slider(m_jitter_opacity, value.value); break;
        case pp::app::BrushStrokeFloatSetting::jitter_hue: set_slider(m_jitter_hue, value.value); break;
        case pp::app::BrushStrokeFloatSetting::jitter_saturation: set_slider(m_jitter_sat, value.value); break;
        case pp::app::BrushStrokeFloatSetting::jitter_value: set_slider(m_jitter_val, value.value); break;
        case pp::app::BrushStrokeFloatSetting::jitter_aspect: set_slider(m_jitter_aspect, value.value); break;
        case pp::app::BrushStrokeFloatSetting::dual_size: set_slider(m_dual_size, value.value); break;
        case pp::app::BrushStrokeFloatSetting::dual_spacing: set_slider(m_dual_spacing, value.value); break;
        case pp::app::BrushStrokeFloatSetting::dual_flow: set_slider(m_dual_flow, value.value); break;
        case pp::app::BrushStrokeFloatSetting::dual_scatter: set_slider(m_dual_scatter, value.value); break;
        case pp::app::BrushStrokeFloatSetting::tip_aspect: set_slider(m_tip_aspect, value.value); break;
        case pp::app::BrushStrokeFloatSetting::dual_opacity: set_slider(m_dual_opacity, value.value); break;
        case pp::app::BrushStrokeFloatSetting::dual_rotate: set_slider(m_dual_rotate, value.value); break;
        case pp::app::BrushStrokeFloatSetting::pattern_scale: set_slider(m_pattern_scale, value.value); break;
        case pp::app::BrushStrokeFloatSetting::pattern_brightness: set_slider(m_pattern_brightness, value.value); break;
        case pp::app::BrushStrokeFloatSetting::pattern_contrast: set_slider(m_pattern_contrast, value.value); break;
        case pp::app::BrushStrokeFloatSetting::pattern_depth: set_slider(m_pattern_depth, value.value); break;
        default: break;
        }
    }

    for (const auto& value : view.value().bool_values) {
        switch (value.setting) {
        case pp::app::BrushStrokeBoolSetting::jitter_hsv_each_sample: m_jitter_hsv_eachsample->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::tip_angle_follow: m_tip_angle_follow->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::tip_angle_init: m_tip_angle_init->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::tip_flow_pressure: m_tip_flow_pressure->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::tip_opacity_pressure: m_tip_opacity_pressure->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::tip_size_pressure: m_tip_size_pressure->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::jitter_aspect_both_axis: m_jitter_aspect_bothaxis->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::tip_invert: m_tip_invert->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::tip_flip_x: m_tip_flipx->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::tip_flip_y: m_tip_flipy->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::pattern_enabled: m_pattern_enabled->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::dual_enabled: m_dual_enabled->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::dual_scatter_both_axis: m_dual_scatter_bothaxis->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::dual_invert: m_dual_invert->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::dual_flip_x: m_dual_flipx->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::dual_flip_y: m_dual_flipy->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::dual_random_flip: m_dual_randflip->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::tip_random_flip_x: m_tip_randflipx->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::tip_random_flip_y: m_tip_randflipy->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::pattern_each_sample: m_pattern_eachsample->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::pattern_invert: m_pattern_invert->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::pattern_flip_x: m_pattern_flipx->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::pattern_flip_y: m_pattern_flipy->set_value(value.value); break;
        case pp::app::BrushStrokeBoolSetting::pattern_random_offset: m_pattern_rand_offset->set_value(value.value); break;
        default: break;
        }
    }

    for (const auto& value : view.value().blend_values) {
        switch (value.setting) {
        case pp::app::BrushStrokeBlendSetting::tip: m_blend_mode->set_index(value.blend_mode); break;
        case pp::app::BrushStrokeBlendSetting::dual: m_dual_blend_mode->set_index(value.blend_mode); break;
        case pp::app::BrushStrokeBlendSetting::pattern: m_pattern_blend_mode->set_index(value.blend_mode); break;
        }
    }

    if (view.value().updates_preview) {
        m_preview->m_brush = Canvas::I->m_current_brush;
        m_preview->draw_stroke();
    }

    if (m_brush_thumb->m_path != view.value().tip_thumbnail_path)
        m_brush_thumb->set_image(view.value().tip_thumbnail_path);
    if (m_dual_brush_thumb->m_path != view.value().dual_thumbnail_path)
        m_dual_brush_thumb->set_image(view.value().dual_thumbnail_path);
    if (m_pattern_thumb->m_path != view.value().pattern_thumbnail_path)
        m_pattern_thumb->set_image(view.value().pattern_thumbnail_path);
}

void NodePanelStroke::set_flow(float value, bool normalized, bool propagate)
{
    float v = normalized ? value : m_curves[m_tip_flow].to_slider(value);
    m_tip_flow->set_value(v, propagate);
}

void NodePanelStroke::set_size(float value, bool normalized, bool propagate)
{
    float v = normalized ? value : m_curves[m_tip_size].to_value(value);
    m_tip_size->set_value(v, propagate);
}

void NodePanelStroke::init_fold(const std::string& name)
{
    if (auto b = find<NodeButton>(("button-unfold-" + name).c_str())) {
        b->on_click = [this,name](Node*) {
            find(("fold-" + name).c_str())->ToggleVisibility();
        };
    }
}

void NodePanelStroke::init_controls()
{
    m_brush_popup = std::make_shared<NodePanelBrush>();
    m_brush_popup->set_manager(m_manager);
    m_brush_popup->m_dir_name = "brushes";
    m_brush_popup->init();
    m_brush_popup->create();
    m_brush_popup->loaded();
    m_brush_popup->SetPositioning(YGPositionTypeAbsolute);
    m_brush_popup->SetSize(300, 400);
    pp::panopainter::configure_legacy_popup_overlay(*m_brush_popup);

    m_pattern_popup = std::make_shared<NodePanelBrush>();
    m_pattern_popup->set_manager(m_manager);
    m_pattern_popup->m_dir_name = "patterns";
    m_pattern_popup->init();
    m_pattern_popup->create();
    m_pattern_popup->loaded();
    m_pattern_popup->SetPositioning(YGPositionTypeAbsolute);
    m_pattern_popup->SetSize(300, 400);
    pp::panopainter::configure_legacy_popup_overlay(*m_pattern_popup);

    //m_presets_popup = std::make_shared<NodePanelBrushPreset>();
    //m_presets_popup->m_manager = m_manager;
    //m_presets_popup->init();
    //m_presets_popup->create();
    //m_presets_popup->loaded();
    //m_presets_popup->SetPositioning(YGPositionTypeAbsolute);
    //m_presets_popup->SetSize(YGUndefined, 400);
    //m_presets_popup->m_mouse_ignore = false;
    //m_presets_popup->m_flood_events = true;
    //m_presets_popup->m_capture_children = false;

    m_default_brush_index = std::max(m_brush_popup->find_brush("Round-Hard"), 0);
    const int br_idx = m_default_brush_index;

    // init main brush
    auto default_brush = std::make_shared<Brush>();
    default_brush->load_tip(m_brush_popup->get_texture_path(br_idx), m_brush_popup->get_thumb_path(br_idx));
    //default_brush->load_dual(m_brush_popup->get_texture_path(br_idx), m_brush_popup->get_thumb_path(br_idx));
    //default_brush->load_pattern(m_pattern_popup->get_texture_path(0), m_pattern_popup->get_thumb_path(0));
    default_brush->m_tip_size = 30;
    default_brush->m_tip_flow = .9f;
    default_brush->m_tip_spacing = .1f;
    default_brush->m_tip_opacity = 1.f;
    Canvas::I->m_current_brush = default_brush;

    // BRUSH PRESETS

    m_preset_button = find<NodeButtonCustom>("preset-button");
    m_preset_button->on_click = [this](Node*) {
        auto screen = root()->m_size;
        glm::vec2 pos = m_preset_button->m_pos + glm::vec2(m_preset_button->m_size.x, 0);
        if (!open_stroke_popup_with_handles(*this, App::I->presets, pos, m_preset_button->m_size.y)) {
            return;
        }
        float hh = App::I->presets->m_container->m_children.size() > 10 ? App::I->height / App::I->zoom * .75f : 400.f;
        App::I->presets->SetHeight(glm::max(hh, 400.f));
        App::I->presets->SetWidth(300);
        root()->update();
        if ((pos.y + App::I->presets->m_size.y) > screen.y)
            pos.y = screen.y - App::I->presets->m_size.y;
        if (pos.y < 0)
            pos.y = 0;
        App::I->presets->SetPosition(pos.x + 16, pos.y);
        App::I->presets->SetPositioning(YGPositionTypeAbsolute);
        pp::panopainter::activate_legacy_popup_overlay(*App::I->presets);
        root()->update();
        App::I->presets->on_brush_changed = [this](Node* target, std::shared_ptr<Brush>& b) {
            // don't change some params
            //b->m_tip_size = Canvas::I->m_current_brush->m_tip_size;
            auto old_color = Canvas::I->m_current_brush->m_tip_color;
            *Canvas::I->m_current_brush = *b;
            Canvas::I->m_current_brush->m_tip_color = old_color;
            Canvas::I->m_current_brush->load();
            //m_preview->draw_stroke();
            m_brush_thumb->set_image(b->m_brush_thumb_path);
            m_dual_brush_thumb->set_image(b->m_dual_thumb_path);
            m_pattern_thumb->set_image(b->m_pattern_thumb_path);
            update_controls();
            if (on_stroke_change)
                on_stroke_change(this);
        };
    };

    // BRUSH TIP SHAPE

    m_brush_thumb = find<NodeImage>("tip-change-thumb");
    m_brush_thumb->set_image(m_brush_popup->get_thumb_path(br_idx));
    m_brush_button = find<NodeButtonCustom>("tip-change");
    m_brush_button->on_click = [this](Node*) {
        auto screen = root()->m_size;
        glm::vec2 pos = m_brush_button->m_pos + glm::vec2(m_brush_button->m_size.x, 0);
        if (!open_stroke_popup_with_handles(*this, m_brush_popup, pos, m_brush_button->m_size.y)) {
            return;
        }
        root()->update();
        if ((pos.y + m_brush_popup->m_size.y) > screen.y)
            pos.y = screen.y - m_brush_popup->m_size.y;
        if (pos.y < 0)
            pos.y = 0;
        m_brush_popup->SetPosition(pos.x + 16, pos.y);
        pp::panopainter::activate_legacy_popup_overlay(*m_brush_popup);
        root()->update();

        m_brush_popup->on_brush_changed = [this](Node*, int index) {
            if (on_brush_changed)
                on_brush_changed(this, m_brush_popup->get_texture_path(index), m_brush_popup->get_thumb_path(index));
            m_brush_thumb->set_image(m_brush_popup->get_thumb_path(index));
            //m_brush_popup->mouse_release();
            //m_brush_popup->m_parent->remove_child(m_brush_popup.get());
        };
    };

    // DUAL BRUSH TIP SHAPE

    m_dual_brush_thumb = find<NodeImage>("dual-change-thumb");
    m_dual_brush_thumb->set_image(m_brush_popup->get_thumb_path(br_idx));
    m_dual_brush_button = find<NodeButtonCustom>("dual-change");
    m_dual_brush_button->on_click = [this](Node*) {
        auto screen = root()->m_size;
        glm::vec2 pos = m_dual_brush_button->m_pos + glm::vec2(m_dual_brush_button->m_size.x, 0);
        if (!open_stroke_popup_with_handles(*this, m_brush_popup, pos, m_dual_brush_button->m_size.y)) {
            return;
        }
        root()->update();
        if ((pos.y + m_brush_popup->m_size.y) > screen.y)
            pos.y = screen.y - m_brush_popup->m_size.y;
        if (pos.y < 0)
            pos.y = 0;
        m_brush_popup->SetPosition(pos.x + 16, pos.y);
        pp::panopainter::activate_legacy_popup_overlay(*m_brush_popup);
        root()->update();

        m_brush_popup->on_brush_changed = [this](Node*, int index) {
            m_dual_enabled->set_value(true, true);
            if (on_dual_changed)
                on_dual_changed(this, m_brush_popup->get_texture_path(index), m_brush_popup->get_thumb_path(index));
            m_dual_brush_thumb->set_image(m_brush_popup->get_thumb_path(index));
        };
    };


    // PATTERN IMAGE

    m_pattern_thumb = find<NodeImage>("pattern-change-thumb");
    m_pattern_thumb->set_image(m_pattern_popup->get_thumb_path(0));
    m_pattern_button = find<NodeButtonCustom>("pattern-change");
    m_pattern_button->on_click = [this](Node*) {
        auto screen = root()->m_size;
        glm::vec2 pos = m_pattern_button->m_pos + glm::vec2(m_pattern_button->m_size.x, 0);
        if (!open_stroke_popup_with_handles(*this, m_pattern_popup, pos, m_pattern_button->m_size.y)) {
            return;
        }
        root()->update();
        if ((pos.y + m_pattern_popup->m_size.y) > screen.y)
            pos.y = screen.y - m_pattern_popup->m_size.y;
        if (pos.y < 0)
            pos.y = 0;
        m_pattern_popup->SetPosition(pos.x + 16, pos.y);
        pp::panopainter::activate_legacy_popup_overlay(*m_pattern_popup);
        root()->update();

        m_pattern_popup->on_brush_changed = [this](Node*, int index) {
            m_pattern_enabled->set_value(true, true);
            if (on_pattern_changed)
                on_pattern_changed(this, m_pattern_popup->get_texture_path(index), m_pattern_popup->get_thumb_path(index));
            m_pattern_thumb->set_image(m_pattern_popup->get_thumb_path(index));
        };
    };


    m_preview = find<NodeStrokePreview>("canvas");
    m_preview->m_draw_first = true;

    m_blend_mode = find<NodeComboBox>("blend-mode");
    m_blend_mode->on_select = [this](Node*, int index) {
        const auto plan = pp::app::plan_brush_stroke_blend_mode(pp::app::BrushStrokeBlendSetting::tip, index);
        if (plan) {
            execute_stroke_control_plan(plan.value());
        }
    };

    init_slider(m_tip_size, "tip-size", pp::app::BrushStrokeFloatSetting::tip_size, &Brush::m_tip_size);
    init_slider(m_tip_spacing, "tip-spacing", pp::app::BrushStrokeFloatSetting::tip_spacing, &Brush::m_tip_spacing);
    init_slider(m_tip_flow, "tip-flow", pp::app::BrushStrokeFloatSetting::tip_flow, &Brush::m_tip_flow);
    init_slider(m_tip_opacity, "tip-opacity", pp::app::BrushStrokeFloatSetting::tip_opacity, &Brush::m_tip_opacity);
    init_slider(m_tip_angle, "tip-angle", pp::app::BrushStrokeFloatSetting::tip_angle, &Brush::m_tip_angle);
    init_slider(m_tip_angle_smooth, "tip-angle-smooth", pp::app::BrushStrokeFloatSetting::tip_angle_smooth, &Brush::m_tip_angle_smooth);
    init_slider(m_tip_mix, "tip-mix", pp::app::BrushStrokeFloatSetting::tip_mix, &Brush::m_tip_mix);
    init_slider(m_tip_wet, "tip-wet", pp::app::BrushStrokeFloatSetting::tip_wet, &Brush::m_tip_wet);
    init_slider(m_tip_noise, "tip-noise", pp::app::BrushStrokeFloatSetting::tip_noise, &Brush::m_tip_noise);
    init_slider(m_tip_hue, "tip-hue", pp::app::BrushStrokeFloatSetting::tip_hue, &Brush::m_tip_hue);
    init_slider(m_tip_sat, "tip-sat", pp::app::BrushStrokeFloatSetting::tip_saturation, &Brush::m_tip_sat);
    init_slider(m_tip_val, "tip-val", pp::app::BrushStrokeFloatSetting::tip_value, &Brush::m_tip_val);
    init_slider(m_jitter_scale, "jitter-scale", pp::app::BrushStrokeFloatSetting::jitter_scale, &Brush::m_jitter_scale);
    init_slider(m_jitter_angle, "jitter-angle", pp::app::BrushStrokeFloatSetting::jitter_angle, &Brush::m_jitter_angle);
    init_slider(m_jitter_scatter, "jitter-scatter", pp::app::BrushStrokeFloatSetting::jitter_scatter, &Brush::m_jitter_scatter);
    init_slider(m_jitter_flow, "jitter-flow", pp::app::BrushStrokeFloatSetting::jitter_flow, &Brush::m_jitter_flow);
    init_slider(m_jitter_opacity, "jitter-opacity", pp::app::BrushStrokeFloatSetting::jitter_opacity, &Brush::m_jitter_opacity);
    init_slider(m_jitter_hue, "jitter-hue", pp::app::BrushStrokeFloatSetting::jitter_hue, &Brush::m_jitter_hue);
    init_slider(m_jitter_sat, "jitter-sat", pp::app::BrushStrokeFloatSetting::jitter_saturation, &Brush::m_jitter_sat);
    init_slider(m_jitter_val, "jitter-val", pp::app::BrushStrokeFloatSetting::jitter_value, &Brush::m_jitter_val);
    init_slider(m_jitter_aspect, "jitter-aspect", pp::app::BrushStrokeFloatSetting::jitter_aspect, &Brush::m_jitter_aspect);

    init_checkbox(m_tip_angle_init, "tip-angle-init", pp::app::BrushStrokeBoolSetting::tip_angle_init, &Brush::m_tip_angle_init);
    init_checkbox(m_tip_angle_follow, "tip-angle-follow", pp::app::BrushStrokeBoolSetting::tip_angle_follow, &Brush::m_tip_angle_follow);
    init_checkbox(m_tip_flow_pressure, "tip-flow-pressure", pp::app::BrushStrokeBoolSetting::tip_flow_pressure, &Brush::m_tip_flow_pressure);
    init_checkbox(m_tip_opacity_pressure, "tip-opacity-pressure", pp::app::BrushStrokeBoolSetting::tip_opacity_pressure, &Brush::m_tip_opacity_pressure);
    init_checkbox(m_tip_size_pressure, "tip-size-pressure", pp::app::BrushStrokeBoolSetting::tip_size_pressure, &Brush::m_tip_size_pressure);
    init_checkbox(m_jitter_scatter_bothaxis, "jitter-scatter-bothaxis", pp::app::BrushStrokeBoolSetting::jitter_scatter_both_axis, &Brush::m_jitter_scatter_bothaxis);
    init_checkbox(m_jitter_aspect_bothaxis, "jitter-aspect-bothaxis", pp::app::BrushStrokeBoolSetting::jitter_aspect_both_axis, &Brush::m_jitter_aspect_bothaxis);
    init_checkbox(m_jitter_hsv_eachsample, "jitter-hsv-eachsample", pp::app::BrushStrokeBoolSetting::jitter_hsv_each_sample, &Brush::m_jitter_hsv_eachsample);

    init_checkbox(m_tip_invert, "tip-invert", pp::app::BrushStrokeBoolSetting::tip_invert, &Brush::m_tip_invert);
    init_checkbox(m_tip_flipx, "tip-flipx", pp::app::BrushStrokeBoolSetting::tip_flip_x, &Brush::m_tip_flipx);
    init_checkbox(m_tip_flipy, "tip-flipy", pp::app::BrushStrokeBoolSetting::tip_flip_y, &Brush::m_tip_flipy);
    init_checkbox(m_pattern_enabled, "pattern-enabled", pp::app::BrushStrokeBoolSetting::pattern_enabled, &Brush::m_pattern_enabled);
    init_checkbox(m_dual_enabled, "dual-enabled", pp::app::BrushStrokeBoolSetting::dual_enabled, &Brush::m_dual_enabled);
    init_checkbox(m_dual_scatter_bothaxis, "dual-scatter-bothaxis", pp::app::BrushStrokeBoolSetting::dual_scatter_both_axis, &Brush::m_dual_scatter_bothaxis);
    init_checkbox(m_dual_invert, "dual-invert", pp::app::BrushStrokeBoolSetting::dual_invert, &Brush::m_dual_invert);
    init_checkbox(m_dual_flipx, "dual-flipx", pp::app::BrushStrokeBoolSetting::dual_flip_x, &Brush::m_dual_flipx);
    init_checkbox(m_dual_flipy, "dual-flipy", pp::app::BrushStrokeBoolSetting::dual_flip_y, &Brush::m_dual_flipy);
    init_checkbox(m_dual_randflip, "dual-randflip", pp::app::BrushStrokeBoolSetting::dual_random_flip, &Brush::m_dual_randflip);
    init_checkbox(m_tip_randflipx, "tip-randflipx", pp::app::BrushStrokeBoolSetting::tip_random_flip_x, &Brush::m_tip_randflipx);
    init_checkbox(m_tip_randflipy, "tip-randflipy", pp::app::BrushStrokeBoolSetting::tip_random_flip_y, &Brush::m_tip_randflipy);
    init_checkbox(m_pattern_eachsample, "pattern-eachsample", pp::app::BrushStrokeBoolSetting::pattern_each_sample, &Brush::m_pattern_eachsample);

    init_checkbox(m_pattern_invert, "pattern-invert", pp::app::BrushStrokeBoolSetting::pattern_invert, &Brush::m_pattern_invert);
    init_checkbox(m_pattern_flipx, "pattern-flipx", pp::app::BrushStrokeBoolSetting::pattern_flip_x, &Brush::m_pattern_flipx);
    init_checkbox(m_pattern_flipy, "pattern-flipy", pp::app::BrushStrokeBoolSetting::pattern_flip_y, &Brush::m_pattern_flipy);
    init_checkbox(m_pattern_rand_offset, "pattern-rand-offset", pp::app::BrushStrokeBoolSetting::pattern_random_offset, &Brush::m_pattern_rand_offset);

    init_slider(m_dual_size, "dual-size", pp::app::BrushStrokeFloatSetting::dual_size, &Brush::m_dual_size);
    init_slider(m_dual_spacing, "dual-spacing", pp::app::BrushStrokeFloatSetting::dual_spacing, &Brush::m_dual_spacing);
    init_slider(m_dual_scatter, "dual-scatter", pp::app::BrushStrokeFloatSetting::dual_scatter, &Brush::m_dual_scatter);
    init_slider(m_tip_aspect, "tip-aspect", pp::app::BrushStrokeFloatSetting::tip_aspect, &Brush::m_tip_aspect);
    init_slider(m_dual_opacity, "dual-opacity", pp::app::BrushStrokeFloatSetting::dual_opacity, &Brush::m_dual_opacity);
    init_slider(m_dual_flow, "dual-flow", pp::app::BrushStrokeFloatSetting::dual_flow, &Brush::m_dual_flow);
    init_slider(m_dual_rotate, "dual-rotate", pp::app::BrushStrokeFloatSetting::dual_rotate, &Brush::m_dual_rotate);
    init_slider(m_pattern_scale, "pattern-scale", pp::app::BrushStrokeFloatSetting::pattern_scale, &Brush::m_pattern_scale);
    init_slider(m_pattern_brightness, "pattern-brightness", pp::app::BrushStrokeFloatSetting::pattern_brightness, &Brush::m_pattern_brightness);
    init_slider(m_pattern_contrast, "pattern-contrast", pp::app::BrushStrokeFloatSetting::pattern_contrast, &Brush::m_pattern_contrast);
    init_slider(m_pattern_depth, "pattern-depth", pp::app::BrushStrokeFloatSetting::pattern_depth, &Brush::m_pattern_depth);

    SliderCurve curve_cubic {
        [](float v) { return glm::pow(v, 3.f); },
        [](float v) { return glm::pow(v, 1.f / 3.f); },
    };
    SliderCurve curve_quad {
        [](float v) { return glm::pow(v, 2.f); },
        [](float v) { return glm::pow(v, 1.f / 2.f); },
    };
    SliderCurve curve_mix {
        [](float v) { return glm::pow(v, 1.f / 10.f); },
        [](float v) { return glm::pow(v, 10.f); },
    };
    SliderCurve curve_linear1k_perc {
        [](float v) { return v * 10.f; },
        [](float v) { return v * 0.1f; },
    };
    SliderCurve curve_size1k_perc {
        [](float v) { 
            float ret = 0;
            if (v > 0.00f) ret += glm::pow(glm::min(1.f, (v - 0.00f) / 0.50f), 2.f) *  1.f;
            if (v > 0.50f) ret += glm::min(1.f, (v - 0.50f) / 0.25f) *  2.f;
            if (v > 0.75f) ret += glm::min(1.f, (v - 0.75f) / 0.10f) *  4.f;
            if (v > 0.85f) ret += glm::min(1.f, (v - 0.85f) / 0.05f) *  5.f;
            if (v > 0.90f) ret += glm::min(1.f, (v - 0.90f) / 0.10f) * 10.f;
            return glm::max(.01f, ret);
        },
        [](float v) { 
            float ret = 0;
            if (v >  0.f) ret += glm::pow(glm::min(1.f, (v - 0.f) /  1.f), 1.f / 2.f) * 0.50f;
            if (v >  1.f) ret += glm::min(1.f, (v - 1.f) /  2.f) * 0.25f;
            if (v >  2.f) ret += glm::min(1.f, (v - 2.f) /  4.f) * 0.10f;
            if (v >  4.f) ret += glm::min(1.f, (v - 4.f) /  5.f) * 0.05f;
            if (v >  5.f) ret += glm::min(1.f, (v - 5.f) / 10.f) * 0.10f;
            return ret;
        },
    };
    SliderCurve curve_size5k_pix {
        [](float v) {
            float ret = 0;
            if (v > 0.00f) ret += glm::pow(glm::min(1.f, (v - 0.00f) / 0.50f), 2.f) *  100.f;
            if (v > 0.50f) ret += glm::min(1.f, (v - 0.50f) / 0.25f) *  200.f;
            if (v > 0.75f) ret += glm::min(1.f, (v - 0.75f) / 0.10f) *  400.f;
            if (v > 0.85f) ret += glm::min(1.f, (v - 0.85f) / 0.05f) * 1000.f;
            if (v > 0.90f) ret += glm::min(1.f, (v - 0.90f) / 0.10f) * 5000.f;
            return glm::max(1.f, ret);
        },
        [](float v) {
            float ret = 0;
            if (v >    0.f) ret += glm::pow(glm::min(1.f, (v -    0.f) /  100.f), 1.f / 2.f) * 0.50f;
            if (v >  100.f) ret += glm::min(1.f, (v -  100.f) /  200.f) * 0.25f;
            if (v >  200.f) ret += glm::min(1.f, (v -  200.f) /  400.f) * 0.10f;
            if (v >  400.f) ret += glm::min(1.f, (v -  400.f) / 1000.f) * 0.05f;
            if (v > 1000.f) ret += glm::min(1.f, (v - 1000.f) / 5000.f) * 0.10f;
            return ret;
        },
    };

    m_curves[m_tip_size] = curve_size5k_pix;
    m_curves[m_tip_spacing] = curve_size1k_perc;
    m_curves[m_tip_flow] = curve_quad;
    m_curves[m_dual_size] = curve_size1k_perc;
    m_curves[m_dual_spacing] = curve_size1k_perc;
    m_curves[m_dual_scatter] = curve_linear1k_perc;
    m_curves[m_dual_flow] = curve_quad;
    m_curves[m_pattern_scale] = curve_size1k_perc;
    m_curves[m_jitter_scatter] = curve_linear1k_perc;
    m_curves[m_tip_mix] = curve_mix;

    m_tip_aspect_reset = find<NodeButtonCustom>("tip-aspect-reset");
    m_tip_aspect_reset->on_click = [this](Node*) {
        execute_stroke_control_plan(pp::app::plan_brush_tip_aspect_reset());
    };

    m_dual_blend_mode = find<NodeComboBox>("dual-blend-mode");
    m_dual_blend_mode->on_select = [this](Node*, int index) {
        const auto plan = pp::app::plan_brush_stroke_blend_mode(pp::app::BrushStrokeBlendSetting::dual, index);
        if (plan) {
            execute_stroke_control_plan(plan.value());
        }
    };

    m_pattern_blend_mode = find<NodeComboBox>("pattern-blend-mode");
    m_pattern_blend_mode->on_select = [this](Node*, int index) {
        const auto plan = pp::app::plan_brush_stroke_blend_mode(pp::app::BrushStrokeBlendSetting::pattern, index);
        if (plan) {
            execute_stroke_control_plan(plan.value());
        }
    };

    m_preview->m_brush = Canvas::I->m_current_brush;
    m_preview->draw_stroke();

    init_fold("color");
    init_fold("metrics");
    init_fold("pattern");
    init_fold("dualbrush");
    init_fold("medium");
    init_fold("colorvar");
    init_fold("jitter");

    if (auto b = find<NodeButton>("button-unfold-all")) {
        b->on_click = [this](Node*) {
            static bool visible = true;
            visible = !visible;
            find("fold-color")->SetVisibility(visible);
            find("fold-metrics")->SetVisibility(visible);
            find("fold-pattern")->SetVisibility(visible);
            find("fold-dualbrush")->SetVisibility(visible);
            find("fold-medium")->SetVisibility(visible);
            find("fold-colorvar")->SetVisibility(visible);
            find("fold-jitter")->SetVisibility(visible);
        };
    }

    m_brush_settings_reset = find<NodeButton>("brush-settings-reset");
    m_brush_settings_reset->on_click = [this](Node*) {
        execute_stroke_control_plan(pp::app::plan_brush_default_settings_reset());
    };

    update_controls();
}

void NodePanelStroke::init_slider(
    NodeSliderH*& target,
    const char* id,
    pp::app::BrushStrokeFloatSetting setting,
    float Brush::* prop)
{
    target = find<NodeSliderH>(id);
    target->on_value_changed = std::bind(&NodePanelStroke::handle_slide,
        this, setting, prop, std::placeholders::_1, std::placeholders::_2);
    //m_canvas->m_brush->*prop = target->m_values;
}

void NodePanelStroke::handle_slide(
    pp::app::BrushStrokeFloatSetting setting,
    float Brush::* prop,
    Node* target,
    float value)
{
    auto curve = m_curves.find((NodeSliderH*)target);
    const auto brush_value = curve != m_curves.end() ? curve->second.to_value(value) : value;
    const auto plan = pp::app::plan_brush_stroke_float_setting(setting, brush_value);
    if (plan) {
        execute_stroke_control_plan(plan.value());
    } else {
        Canvas::I->m_current_brush.get()->*prop = brush_value;
    }
}

void NodePanelStroke::init_checkbox(
    NodeCheckBox*& target,
    const char* id,
    pp::app::BrushStrokeBoolSetting setting,
    bool Brush::* prop)
{
    target = find<NodeCheckBox>(id);
    target->on_value_changed = std::bind(&NodePanelStroke::handle_checkbox,
        this, setting, std::placeholders::_2);
    Canvas::I->m_current_brush.get()->*prop = target->checked;
}

void NodePanelStroke::handle_checkbox(
    pp::app::BrushStrokeBoolSetting setting,
    bool value)
{
    const auto plan = pp::app::plan_brush_stroke_bool_setting(setting, value);
    execute_stroke_control_plan(plan);
}

void NodePanelStroke::execute_stroke_control_plan(const pp::app::BrushStrokeControlPlan& plan)
{
    const auto status = pp::panopainter::execute_legacy_brush_stroke_control_plan(*this, plan);
    if (!status.ok()) {
        LOG("Brush stroke control action failed: %s", status.message);
    }
}

void NodePanelStroke::close_popup_overlay_handles() noexcept
{
    pp::panopainter::close_legacy_overlay_handles_and_reset(
        *this,
        m_popup_overlay_handle,
        m_tick_overlay_handle);
}

kEventResult NodePanelStroke::handle_event(Event* e)
{
    switch (e->m_type)
    {
    case kEventType::MouseUpL:
        if (!m_mouse_inside)
        {
            close_popup_overlay_handles();
        }
        break;
    default:
        return kEventResult::Available;
        break;
    }
    return kEventResult::Available;
}
