#include <pipewire/keys.h>
#include <gtkmm.h>

#include "mixer-control.hpp"
#include "icon-select.hpp"

#define ICON(volume) icon_from_range(volume_icons, volume)

MixerControl::MixerControl(WpPipewireObject *obj, WayfireMixer *parent_widget)
{
    object = obj;
    parent = parent_widget;
}

MixerControl::~MixerControl()
{
    mute_conn.disconnect();
    middle_conn.disconnect();
    right_conn.disconnect();
    for (auto signal : signals)
    {
        signal.disconnect();
    }
}

void MixerControl::init()
{
    guint32 id = wp_proxy_get_bound_id(WP_PROXY(object));

    // build layout

    add_css_class("mixer-control");
    button.set_child(volume_icon);
    button.add_css_class("mute-toggle");
    button.add_css_class("flat");
    volume_icon.add_css_class("default-icon");

    scale.set_range(0.0, 1.0);
    scale.set_size_request(slider_length, 0);

    const gchar *name;
    // try to find a name to display
    name = wp_pipewire_object_get_property(object, PW_KEY_NODE_DESCRIPTION);
    if (!name)
    {
        name = wp_pipewire_object_get_property(object, PW_KEY_NODE_NICK);
    }

    if (!name)
    {
        name = wp_pipewire_object_get_property(object, PW_KEY_NODE_NAME);
    }

    if (!name)
    {
        name = "Unnamed";
    }

    label.set_text(Glib::ustring(name));
    // this causes the label’s *minimum* size to be 0, causing it to not request any space,
    // thus having the column’s width be dictated solely by the scale’s size request
    label.set_max_width_chars(0);
    label.set_ellipsize(Pango::EllipsizeMode::END);

    set_tooltip_text(name);

    update_icons_pos();

    // setup user interactions

    signals.push_back(mute_conn = button.signal_toggled().connect(
        [this, id] ()
    {
        // if the menu was popped up because of an external change
        // and is now changed manually, don’t hide
        parent->button->popup();
        ignore = true;
        // disregarding the return value prevents cases where ignore is read at an ncorrect time,
        // with the only problem of having one update ignored if something goes very wrong
        WpCommon::get().set_mute(id, button.get_active());
    }));

    scale.set_user_changed_callback(
        [this, id] ()
    {
        parent->button->popup();
        ignore = true;
        WpCommon::get().set_volume(id, scale.get_target_value());
    });

    auto scroll_gesture = Gtk::EventControllerScroll::create();
    signals.push_back(scroll_gesture->signal_scroll().connect([=] (double dx, double dy)
    {
        dy = parent->invert_scroll ? dy : dy * -1; // for the same scrolling as volume widget, which we will
                                                   // agree it is more intuitive for more people
        double change = 0;
        const double SCROLL_MULT = 0.2; // corrects the scrolling to have the default scroll sensitivity as 1
        if (scroll_gesture->get_unit() == Gdk::ScrollUnit::WHEEL)
        {
            // +- number of clicks.
            change = (dy * parent->scroll_sensitivity * SCROLL_MULT) / 10;
        } else
        {
            // Number of pixels expected to have scrolled. usually in 100s
            change = (dy * parent->scroll_sensitivity * SCROLL_MULT) / 100;
        }

        WpCommon::get().set_volume(id, scale.get_target_value() + change);
        return true;
    }, true));
    scroll_gesture->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL);
    add_controller(scroll_gesture);

    middle_click_mute = Gtk::GestureClick::create();
    right_click_mute  = Gtk::GestureClick::create();
    middle_click_mute->set_button(2);
    right_click_mute->set_button(3);
    add_controller(middle_click_mute);
    add_controller(right_click_mute);

    update_gestures();

    // initialise the values
    auto v = WpCommon::get().get_volume_and_mute(id);
    set_scale_target_value(v.first);
    set_btn_status_no_callbk(v.second);

    update_icon();
}

void MixerControl::set_btn_status_no_callbk(bool state)
{
    mute_conn.block(true);
    button.set_active(state);
    mute_conn.block(false);
}

void MixerControl::set_scale_target_value(double volume)
{
    scale.set_target_value(volume);
}

void MixerControl::update_icon()
{
    if (button.get_active())
    {
        add_css_class("muted");
        volume_icon.set_from_icon_name(ICON(0)); // mute
        return;
    }

    remove_css_class("muted");
    volume_icon.set_from_icon_name(ICON(get_scale_target_value()));
}

double MixerControl::get_scale_target_value()
{
    return scale.get_target_value();
}

// attaches elements to the grid at the appropriate place
void MixerControl::update_icons_pos()
{
    static WfOption<bool> icons_on_left{"panel/mixer_icons_on_left"};

    if (icons_on_left)
    {
        attach(label, 1, 0);
        attach(scale, 1, 1);
        attach(button, 0, 1);
    } else
    {
        attach(label, 0, 0);
        attach(scale, 0, 1);
        attach(button, 1, 1);
    }
}

void MixerControl::update_gestures()
{
    // the setting says that it is for quick target, but let’s have all the muting consistent
    static WfOption<std::string> str_wp_right_click_action{"panel/mixer_right_click_action"};
    static WfOption<std::string> str_wp_middle_click_action{"panel/mixer_middle_click_action"};

    auto mute_action =
        [&] (int count, double x, double y)
    {
        button.set_active(!button.get_active());
    };

    if (str_wp_middle_click_action.value() == "mute_quick_target")
    {
        middle_conn = middle_click_mute->signal_pressed().connect(mute_action);
    }

    if (str_wp_right_click_action.value() == "mute_quick_target")
    {
        right_conn = right_click_mute->signal_pressed().connect(mute_action);
    }
}

void MixerControl::handle_config_reload()
{
    remove(label);
    remove(scale);
    remove(button);
    middle_conn.disconnect();
    right_conn.disconnect();
    update_icons_pos();
    update_gestures();
    scale.set_size_request(slider_length);
}

// used to make a copy to the face of the widget
std::unique_ptr<MixerControl> MixerControl::copy()
{
    return std::make_unique<MixerControl>(object, parent);
}

MixerControlDevice::~MixerControlDevice()
{
    def_conn.disconnect();
}

void MixerControlDevice::init()
{
    add_css_class("mixer-control");
    default_btn.add_css_class("flat");
    default_btn.add_css_class("default-button");

    is_def_icon.set_from_icon_name("emblem-default");
    default_btn.set_child(is_def_icon);
    is_def_icon.add_css_class("default-icon");

    // we are not using ToggleButton groups because on_default_nodes_changed
    // will be called anyway to set the status of all devices
    def_conn = default_btn.signal_clicked().connect(
        [this] ()
    {
        // keep the button down when it is selected to prevent inconsistency of visuals with actual state
        if (default_btn.get_active() == false)
        {
            set_def_status_no_callbk(true);
            return;
        }

        WpCommon::get().set_default(object);
    });

    MixerControl::init();
}

void MixerControlDevice::set_def_status_no_callbk(bool state)
{
    if (state)
    {
        add_css_class("default");
    } else
    {
        remove_css_class("default");
    }

    def_conn.block(true);
    default_btn.set_active(state);
    def_conn.block(false);
}

void MixerControlDevice::update_icons_pos()
{
    MixerControl::update_icons_pos();
    static WfOption<bool> icons_on_left{"panel/mixer_icons_on_left"};

    if (icons_on_left)
    {
        attach(default_btn, 0, 0);
    } else
    {
        attach(default_btn, 1, 0);
    }
}

void MixerControlDevice::handle_config_reload()
{
    remove(default_btn);
    MixerControl::handle_config_reload();
}
