#include "pointer.hpp"
#include <wayfire/bindings-repository.hpp>
#include "cursor.hpp"
#include "pointing-device.hpp"
#include "input-manager.hpp"
#include "wayfire/scene.hpp"
#include "wayfire/signal-definitions.hpp"

#include <wayfire/debug.hpp>
#include <wayfire/util/log.hpp>
#include <wayfire/core.hpp>
#include <wayfire/output-layout.hpp>

wf::pointer_t::pointer_t(nonstd::observer_ptr<wf::input_manager_t> input,
    nonstd::observer_ptr<seat_t> seat)
{
    this->input = input;
    this->seat  = seat;

    on_root_node_updated = [=] (wf::scene::root_node_update_signal *data)
    {
        if (!(data->flags & wf::scene::update_flag::INPUT_STATE))
        {
            return;
        }

        if (grabbed_node && !is_grabbed_node_alive(grabbed_node))
        {
            grab_surface(nullptr);
        }

        update_cursor_position(get_current_time());
    };

    wf::get_core().scene()->connect(&on_root_node_updated);
}

wf::pointer_t::~pointer_t()
{}

bool wf::pointer_t::has_pressed_buttons() const
{
    return this->count_pressed_buttons > 0;
}

/* ------------------------- Cursor focus functions ------------------------- */
void wf::pointer_t::set_enable_focus(bool enabled)
{
    this->focus_enabled_count += enabled ? 1 : -1;
    if (focus_enabled_count > 1)
    {
        LOGI("LogicalPointer enabled more times than disabled?");
    }

    // reset grab
    if (!focus_enabled())
    {
        grab_surface(nullptr);
        this->update_cursor_focus(nullptr);
    } else
    {
        update_cursor_position(get_current_time());
    }
}

bool wf::pointer_t::focus_enabled() const
{
    return this->focus_enabled_count > 0;
}

void wf::pointer_t::update_cursor_position(int64_t time_msec)
{
    wf::pointf_t gc   = seat->priv->cursor->get_cursor_position();
    const auto& scene = wf::get_core().scene();
    auto isec    = scene->find_node_at(gc);
    auto hovered = isec ? isec->node->shared_from_this() : nullptr;

    /* If we have an active grab, continue sending events to the grabbed
     * surface even if the pointer goes outside of it. This enables DnD to work
     * correctly, and also lets the user for ex. grab a scrollbar and move
     * their mouse freely. */
    auto kind = get_current_grab_kind();
    bool sticky_grab = (kind == input_grab_kind_t::IMPLICIT) ||
        (kind == input_grab_kind_t::DND) ||
        ((kind == input_grab_kind_t::EXPLICIT) && has_pressed_buttons());
    if (sticky_grab)
    {
        maybe_retarget_grab(hovered.get(), kind);
    } else if (this->focus_enabled())
    {
        update_cursor_focus(hovered);
    }

    this->send_motion(time_msec);
    seat->priv->update_drag_icon();
}

void wf::pointer_t::send_leave_to_focus(wf::scene::node_ptr old_focus)
{
    if (old_focus)
    {
        if (!old_focus->wants_raw_input())
        {
            // Make a copy of sent buttons: it could happen that we switch focus multiple times if we have
            // multiple grabs on multiple outputs.
            auto send_release = this->currently_sent_buttons;
            for (auto button : send_release)
            {
                LOGC(POINTER, "force-release button ", button);
                wlr_pointer_button_event event;
                event.pointer   = NULL;
                event.button    = button;
                event.state     = WL_POINTER_BUTTON_STATE_RELEASED;
                event.time_msec = wf::get_current_time();
                old_focus->pointer_interaction().handle_pointer_button(event,
                    input_grab_kind_t::NONE);
            }
        }

        old_focus->pointer_interaction().handle_pointer_leave(input_grab_kind_t::NONE);
        this->last_focus_coords = {};
    }
}

void wf::pointer_t::transfer_grab(scene::node_ptr node)
{
    if (node == cursor_focus)
    {
        set_grab(node, input_grab_kind_t::EXPLICIT);
        LOGC(POINTER, "transfer grab ", cursor_focus.get(), " -> ", node.get(), ": do nothing");
        // Node might already be focused, in case for example there was no input surface when the grab node
        // was added to the scenegraph.
        return;
    }

    LOGC(POINTER, "transfer grab ", cursor_focus.get(), " -> ", node.get());
    auto old_focus = std::move(cursor_focus);
    cursor_focus = node;
    send_leave_to_focus(old_focus);
    set_grab(node, input_grab_kind_t::EXPLICIT);

    // Send pointer_enter to the grab
    send_enter_to_focus();
    if (!node->wants_raw_input())
    {
        currently_sent_buttons.clear();
    }

    set_grab(node, input_grab_kind_t::EXPLICIT);
}

void wf::pointer_t::retarget_grab(wf::scene::node_ptr node, input_grab_kind_t kind)
{
    if ((node == cursor_focus) || !node)
    {
        return;
    }

    LOGC(POINTER, "retarget grab ", cursor_focus.get(), " -> ", node.get());
    auto old_focus    = std::move(cursor_focus);
    auto sent_buttons = currently_sent_buttons;
    cursor_focus = node;

    if (old_focus)
    {
        old_focus->pointer_interaction().handle_pointer_leave(kind);
    }

    send_enter_to_focus();
    // Force a motion event to the new target on the same cursor update.
    last_focus_coords = {};
    currently_sent_buttons = sent_buttons;

    set_grab(node, kind);
}

bool wf::pointer_t::maybe_retarget_grab(nonstd::observer_ptr<wf::scene::node_t> new_focus,
    input_grab_kind_t kind)
{
    if (!grabbed_node || !new_focus || (grabbed_node.get() == new_focus.get()))
    {
        return false;
    }

    auto gc = wf::get_core().get_cursor_position();
    if (!grabbed_node->pointer_interaction().can_retarget_pointer_grab(kind, new_focus, gc))
    {
        return false;
    }

    retarget_grab(new_focus->shared_from_this(), kind);
    return true;
}

void wf::pointer_t::send_enter_to_focus()
{
    auto gc    = wf::get_core().get_cursor_position();
    auto local = get_node_local_coords(cursor_focus.get(), gc);
    cursor_focus->pointer_interaction().handle_pointer_enter(local, get_current_grab_kind());

    this->last_focus_coords = local;
}

void wf::pointer_t::update_cursor_focus(wf::scene::node_ptr new_focus)
{
    if (cursor_focus == new_focus)
    {
        return;
    }

    LOGC(POINTER, "Change cursor focus ", cursor_focus.get(), " -> ", new_focus.get());
    auto old_focus = std::move(cursor_focus);
    cursor_focus = new_focus;

    send_leave_to_focus(old_focus);
    currently_sent_buttons.clear();

    if (cursor_focus)
    {
        send_enter_to_focus();
    } else
    {
        // If there is focused surface, we should reset the cursor image to
        // avoid the last cursor image getting stuck outside of its surface.
        wf::get_core().set_cursor("default");
    }

    wf::pointer_focus_changed_signal ev;
    ev.new_focus = cursor_focus;
    wf::get_core().emit(&ev);
}

wf::scene::node_ptr wf::pointer_t::get_focus() const
{
    return this->cursor_focus;
}

/* -------------------------- Implicit grab --------------------------------- */
void wf::pointer_t::grab_surface(wf::scene::node_ptr node)
{
    if ((node == grabbed_node) &&
        ((node != nullptr) || (current_grab_kind == input_grab_kind_t::NONE)))
    {
        return;
    }

    if (node)
    {
        set_grab(node, seat->priv->is_drag_active() ? input_grab_kind_t::DND : input_grab_kind_t::IMPLICIT);
        return;
    }

    /* End grab */
    set_grab(nullptr, input_grab_kind_t::NONE);
    update_cursor_position(get_current_time());
}

void wf::pointer_t::set_grab(wf::scene::node_ptr node, input_grab_kind_t kind)
{
    grabbed_node = node;
    current_grab_kind = node ? kind : input_grab_kind_t::NONE;
}

wf::input_grab_kind_t wf::pointer_t::get_current_grab_kind() const
{
    if (!grabbed_node)
    {
        return input_grab_kind_t::NONE;
    }

    if ((current_grab_kind == input_grab_kind_t::IMPLICIT) && seat->priv->is_drag_active())
    {
        return input_grab_kind_t::DND;
    }

    return current_grab_kind;
}

/* ----------------------- Input event processing --------------------------- */
void wf::pointer_t::handle_pointer_button(wlr_pointer_button_event *ev,
    input_event_processing_mode_t mode)
{
    seat->priv->break_mod_bindings();
    bool handled_in_binding = (mode != input_event_processing_mode_t::FULL);

    if (ev->state == WL_POINTER_BUTTON_STATE_PRESSED)
    {
        count_pressed_buttons++;
        if (count_pressed_buttons == 1)
        {
            /* Focus only the first click, since then we also start an implicit
             * grab, and we don't want to suddenly change the output */
            auto gc     = seat->priv->cursor->get_cursor_position();
            auto output = wf::get_core().output_layout->find_closest_output(gc);
            seat->focus_output(output);
        }

        handled_in_binding |= wf::get_core().bindings->handle_button(
            wf::buttonbinding_t{seat->priv->get_modifiers(), ev->button});
    } else
    {
        count_pressed_buttons--;
    }

    send_button(ev, handled_in_binding);
    if (!handled_in_binding)
    {
        check_implicit_grab();
    }
}

void wf::pointer_t::check_implicit_grab()
{
    /* start a button held grab, so that the window will receive all the
     * subsequent events, no matter what happens */
    if ((count_pressed_buttons >= 1) && cursor_focus &&
        (current_grab_kind == input_grab_kind_t::NONE))
    {
        grab_surface(cursor_focus);
    }

    /* end the button held grab. We need to to this here after we have send
     * the last button release event, so that buttons don't get stuck in clients */
    if ((count_pressed_buttons == 0) &&
        (current_grab_kind == input_grab_kind_t::IMPLICIT))
    {
        grab_surface(nullptr);
    }
}

void wf::pointer_t::send_button(wlr_pointer_button_event *ev, bool has_binding)
{
    /* Clients do not receive buttons for bindings */
    if (has_binding)
    {
        return;
    }

    if (cursor_focus)
    {
        auto kind = get_current_grab_kind();
        if ((ev->state == WL_POINTER_BUTTON_STATE_PRESSED) && cursor_focus)
        {
            LOGC(POINTER, "normal button press ", ev->button);
            this->currently_sent_buttons.insert(ev->button);
            cursor_focus->pointer_interaction().handle_pointer_button(*ev, kind);
        } else if ((ev->state == WL_POINTER_BUTTON_STATE_RELEASED) &&
                   (currently_sent_buttons.count(ev->button) || cursor_focus->wants_raw_input()))
        {
            LOGC(POINTER, "normal button release ", ev->button);
            if (currently_sent_buttons.count(ev->button))
            {
                this->currently_sent_buttons.erase(currently_sent_buttons.find(ev->button));
            }

            cursor_focus->pointer_interaction().handle_pointer_button(*ev, kind);
        } else
        {
            LOGC(POINTER, "ignoring button event ", ev->button, " ", ev->state);
            // Ignore buttons which the client has not received.
            // These are potentially buttons which were grabbed.
        }
    } else
    {
        LOGC(POINTER, "ignoring button event (null focus) ", ev->button, " ", ev->state);
    }
}

void wf::pointer_t::send_motion(uint32_t time_msec)
{
    if (cursor_focus)
    {
        auto kind  = get_current_grab_kind();
        auto gc    = wf::get_core().get_cursor_position();
        auto local = get_node_local_coords(cursor_focus.get(), gc);

        if (!last_focus_coords.has_value() ||
            (local.x != last_focus_coords->x) || (local.y != last_focus_coords->y))
        {
            // NB: the pointer motion could go to a grab, which could trigger scenegraph changes, which
            // trigger re-focus. We need to set the last focus coordinates early, so that we break out of
            // infinite loops.
            last_focus_coords = local;
            cursor_focus->pointer_interaction().handle_pointer_motion(local, time_msec, kind);
        }
    }
}

void wf::pointer_t::handle_pointer_motion(wlr_pointer_motion_event *ev,
    input_event_processing_mode_t mode)
{
    /* XXX: maybe warp directly? */
    wlr_cursor_move(seat->priv->cursor->cursor, &ev->pointer->base, ev->delta_x, ev->delta_y);
    update_cursor_position(ev->time_msec);
}

void wf::pointer_t::handle_pointer_motion_absolute(
    wlr_pointer_motion_absolute_event *ev, input_event_processing_mode_t mode)
{
    // next coordinates
    double cx, cy;
    wlr_cursor_absolute_to_layout_coords(seat->priv->cursor->cursor, &ev->pointer->base,
        ev->x, ev->y, &cx, &cy);

    // send relative motion
    double dx = cx - seat->priv->cursor->cursor->x;
    double dy = cy - seat->priv->cursor->cursor->y;
    wlr_relative_pointer_manager_v1_send_relative_motion(
        wf::get_core().protocols.relative_pointer, seat->seat,
        (uint64_t)ev->time_msec * 1000, dx, dy, dx, dy);

    // TODO: indirection via wf_cursor
    wlr_cursor_warp_closest(seat->priv->cursor->cursor, NULL, cx, cy);
    update_cursor_position(ev->time_msec);
}

void wf::pointer_t::handle_pointer_axis(wlr_pointer_axis_event *ev,
    input_event_processing_mode_t mode)
{
    bool handled_in_binding = wf::get_core().bindings->handle_axis(
        seat->priv->get_modifiers(), ev);
    seat->priv->break_mod_bindings();

    /* Do not send scroll events to clients if an axis binding has used up the
     * event */
    if (handled_in_binding)
    {
        return;
    }

    /* Calculate speed settings */
    wf::pointing_device_t *pd = (wf::pointing_device_t*)ev->pointer->base.data;
    double mult = pd->get_scroll_speed(&ev->pointer->base, ev->source == WL_POINTER_AXIS_SOURCE_FINGER);

    ev->delta *= mult;
    ev->delta_discrete *= mult;

    if (cursor_focus)
    {
        cursor_focus->pointer_interaction().handle_pointer_axis(*ev);
    }
}

void wf::pointer_t::handle_pointer_swipe_begin(wlr_pointer_swipe_begin_event *ev,
    input_event_processing_mode_t mode)
{
    wlr_pointer_gestures_v1_send_swipe_begin(
        wf::get_core().protocols.pointer_gestures, seat->seat,
        ev->time_msec, ev->fingers);
}

void wf::pointer_t::handle_pointer_swipe_update(
    wlr_pointer_swipe_update_event *ev, input_event_processing_mode_t mode)
{
    wlr_pointer_gestures_v1_send_swipe_update(
        wf::get_core().protocols.pointer_gestures, seat->seat,
        ev->time_msec, ev->dx, ev->dy);
}

void wf::pointer_t::handle_pointer_swipe_end(wlr_pointer_swipe_end_event *ev,
    input_event_processing_mode_t mode)
{
    wlr_pointer_gestures_v1_send_swipe_end(
        wf::get_core().protocols.pointer_gestures, seat->seat,
        ev->time_msec, ev->cancelled);
}

void wf::pointer_t::handle_pointer_pinch_begin(wlr_pointer_pinch_begin_event *ev,
    input_event_processing_mode_t mode)
{
    wlr_pointer_gestures_v1_send_pinch_begin(
        wf::get_core().protocols.pointer_gestures, seat->seat,
        ev->time_msec, ev->fingers);
}

void wf::pointer_t::handle_pointer_pinch_update(
    wlr_pointer_pinch_update_event *ev, input_event_processing_mode_t mode)
{
    wlr_pointer_gestures_v1_send_pinch_update(
        wf::get_core().protocols.pointer_gestures, seat->seat,
        ev->time_msec, ev->dx, ev->dy, ev->scale, ev->rotation);
}

void wf::pointer_t::handle_pointer_pinch_end(wlr_pointer_pinch_end_event *ev,
    input_event_processing_mode_t mode)
{
    wlr_pointer_gestures_v1_send_pinch_end(
        wf::get_core().protocols.pointer_gestures, seat->seat,
        ev->time_msec, ev->cancelled);
}

void wf::pointer_t::handle_pointer_hold_begin(wlr_pointer_hold_begin_event *ev,
    input_event_processing_mode_t mode)
{
    wlr_pointer_gestures_v1_send_hold_begin(
        wf::get_core().protocols.pointer_gestures, seat->seat,
        ev->time_msec, ev->fingers);
}

void wf::pointer_t::handle_pointer_hold_end(wlr_pointer_hold_end_event *ev,
    input_event_processing_mode_t mode)
{
    wlr_pointer_gestures_v1_send_hold_end(
        wf::get_core().protocols.pointer_gestures, seat->seat,
        ev->time_msec, ev->cancelled);
}

void wf::pointer_t::handle_pointer_frame()
{
    wlr_seat_pointer_notify_frame(seat->seat);
}
