/*
 * Copyright © 2010-2012 Intel Corporation
 * Copyright © 2011-2012 Collabora, Ltd.
 * Copyright © 2013 Raspberry Pi Foundation
 * Copyright © 2016 Quentin "Sardem FF7" Glidic
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
/*
 * (C) Copyright 2014, Fuji Xerox Co.,Ltd.  All rights reserved.
 * File name : fx-seat.h
 * Title : seat surface
 */

#include "config.h"

#include <assert.h>

#include <wayland-server.h>

#include "compositor.h"
#include "zalloc.h"
#include "shared/timespec-util.h"

#include "fx-surface.h"
#include "fx-seat.h"

/* Typedefs */
struct fx_shell_seat {
    struct wl_listener seat_destroy_listener;
    struct weston_seat *seat;
    struct {
        struct weston_keyboard_grab keyboard;
        struct weston_pointer_grab pointer;
        struct weston_touch_grab touch;
        bool initial_up;
        struct wl_client *client;
        struct wl_list surfaces;
    } popup_grab;
    bool disabled;
};

/* Procedures */
static void fx_shell_seat_popup_grab_end( struct fx_shell_seat *shell_seat );

static void
fx_shell_seat_popup_grab_keyboard_key(
        struct weston_keyboard_grab *grab,
        const struct timespec *time,
        uint32_t key,
        enum wl_keyboard_key_state state )
{
    weston_keyboard_send_key(
            grab->keyboard,
            time,
            key,
            state );
}

static void
fx_shell_seat_popup_grab_keyboard_modifiers(
        struct weston_keyboard_grab *grab,
        uint32_t serial,
        uint32_t mods_depressed,
        uint32_t mods_latched,
        uint32_t mods_locked,
        uint32_t group )
{
    weston_keyboard_send_modifiers(
            grab->keyboard,
            serial,
            mods_depressed,
            mods_latched,
            mods_locked,
            group );
}

static void
fx_shell_seat_popup_grab_keyboard_cancel( struct weston_keyboard_grab *grab )
{
    struct fx_shell_seat* shell_seat =
        wl_container_of(grab, shell_seat, popup_grab.keyboard);

    fx_shell_seat_popup_grab_end( shell_seat );
}

static const struct weston_keyboard_grab_interface fx_shell_seat_keyboard_popup_grab_interface = {
   .key = fx_shell_seat_popup_grab_keyboard_key,
   .modifiers = fx_shell_seat_popup_grab_keyboard_modifiers,
   .cancel = fx_shell_seat_popup_grab_keyboard_cancel,
};

static void
fx_shell_seat_popup_grab_pointer_focus( struct weston_pointer_grab *grab )
{
    struct fx_shell_seat* shell_seat =
        wl_container_of(grab, shell_seat, popup_grab.pointer);
    struct weston_pointer *pointer = grab->pointer;
    struct weston_view *view;
    wl_fixed_t sx, sy;

    view = weston_compositor_pick_view(
            pointer->seat->compositor,
            pointer->x,
            pointer->y,
            &sx,
            &sy );
    if ( view != NULL &&
            view->surface->resource != NULL &&
            wl_resource_get_client( view->surface->resource ) ==
            shell_seat->popup_grab.client ) {
        weston_pointer_set_focus(
                pointer,
                view,
                sx,
                sy );
    } else {
        weston_pointer_clear_focus( pointer );
    }
}

static void
fx_shell_seat_popup_grab_pointer_motion(
        struct weston_pointer_grab *grab,
        const struct timespec *time,
        struct weston_pointer_motion_event *event )
{
    weston_pointer_send_motion(
            grab->pointer,
            time,
            event );
}

static void
fx_shell_seat_popup_grab_pointer_button(
        struct weston_pointer_grab *grab,
        const struct timespec *time,
        uint32_t button,
        enum wl_pointer_button_state state )
{
    struct fx_shell_seat* shell_seat =
        wl_container_of(grab, shell_seat, popup_grab.pointer);
    struct weston_pointer *pointer = grab->pointer;
    bool initial_up = shell_seat->popup_grab.initial_up;

    if ( state == WL_POINTER_BUTTON_STATE_RELEASED ) {
        shell_seat->popup_grab.initial_up = true;
    }

    if ( weston_pointer_has_focus_resource( pointer ) ) {
        weston_pointer_send_button(
                pointer,
                time,
                button,
                state );
    } else if ( state == WL_POINTER_BUTTON_STATE_RELEASED &&
            (initial_up ||
             (timespec_sub_to_msec(time, &grab->pointer->grab_time) > 500) ) )
    {
        fx_shell_seat_popup_grab_end( shell_seat );
    }
}

static void
fx_shell_seat_popup_grab_pointer_axis(
        struct weston_pointer_grab *grab,
        const struct timespec *time,
        struct weston_pointer_axis_event *event )
{
    weston_pointer_send_axis(
            grab->pointer,
            time,
            event );
}

static void
fx_shell_seat_popup_grab_pointer_axis_source(
        struct weston_pointer_grab *grab,
        uint32_t source )
{
    weston_pointer_send_axis_source(
            grab->pointer,
            source );
}

static void
fx_shell_seat_popup_grab_pointer_frame( struct weston_pointer_grab *grab )
{
    weston_pointer_send_frame( grab->pointer );
}

static void
fx_shell_seat_popup_grab_pointer_cancel( struct weston_pointer_grab *grab )
{
    struct fx_shell_seat* shell_seat =
        wl_container_of(grab, shell_seat, popup_grab.pointer);

    fx_shell_seat_popup_grab_end( shell_seat );
}

static const struct weston_pointer_grab_interface fx_shell_seat_pointer_popup_grab_interface = {
   .focus = fx_shell_seat_popup_grab_pointer_focus,
   .motion = fx_shell_seat_popup_grab_pointer_motion,
   .button = fx_shell_seat_popup_grab_pointer_button,
   .axis = fx_shell_seat_popup_grab_pointer_axis,
   .axis_source = fx_shell_seat_popup_grab_pointer_axis_source,
   .frame = fx_shell_seat_popup_grab_pointer_frame,
   .cancel = fx_shell_seat_popup_grab_pointer_cancel,
};

static void
fx_shell_seat_popup_grab_touch_down(
        struct weston_touch_grab *grab,
        const struct timespec *time,
        int touch_id,
        wl_fixed_t sx,
        wl_fixed_t sy )
{
    weston_touch_send_down(
            grab->touch,
            time,
            touch_id,
            sx,
            sy );
}

static void
fx_shell_seat_popup_grab_touch_up(
        struct weston_touch_grab *grab,
        const struct timespec *time,
        int touch_id )
{
    weston_touch_send_up(
            grab->touch,
            time,
            touch_id );
}

static void
fx_shell_seat_popup_grab_touch_motion(
        struct weston_touch_grab *grab,
        const struct timespec *time,
        int touch_id,
        wl_fixed_t sx,
        wl_fixed_t sy )
{
    weston_touch_send_motion(
            grab->touch,
            time,
            touch_id,
            sx,
            sy );
}

static void
fx_shell_seat_popup_grab_touch_frame( struct weston_touch_grab *grab )
{
    weston_touch_send_frame( grab->touch );
}

static void
fx_shell_seat_popup_grab_touch_cancel( struct weston_touch_grab *grab )
{
    struct fx_shell_seat *shell_seat =
        wl_container_of(grab, shell_seat, popup_grab.touch);

    fx_shell_seat_popup_grab_end( shell_seat );
}

static const struct weston_touch_grab_interface fx_shell_seat_touch_popup_grab_interface = {
   .down = fx_shell_seat_popup_grab_touch_down,
   .up = fx_shell_seat_popup_grab_touch_up,
   .motion = fx_shell_seat_popup_grab_touch_motion,
   .frame = fx_shell_seat_popup_grab_touch_frame,
   .cancel = fx_shell_seat_popup_grab_touch_cancel,
};

static void
fx_shell_seat_destroy(
        struct wl_listener *listener,
        void *data )
{
    struct fx_shell_seat *shell_seat =
        wl_container_of(listener, shell_seat, seat_destroy_listener);

    free( shell_seat );
}

struct fx_shell_seat *
fx_shell_seat_from_seat( struct weston_seat *seat )
{
    struct wl_listener *listener;
    struct fx_shell_seat *shell_seat;

    if ( seat == NULL ) return NULL;

    listener = wl_signal_get(
            &seat->destroy_signal,
            fx_shell_seat_destroy );
    if ( listener != NULL ) {
        return wl_container_of(
                listener,
                shell_seat,
                seat_destroy_listener );
    }

    shell_seat = zalloc( sizeof ( struct fx_shell_seat ) );
    if ( shell_seat == NULL ) {
        return NULL;
    }

    shell_seat->seat = seat;

    shell_seat->seat_destroy_listener.notify = fx_shell_seat_destroy;
    wl_signal_add(
            &seat->destroy_signal,
            &shell_seat->seat_destroy_listener );

    shell_seat->popup_grab.keyboard.interface =
        &fx_shell_seat_keyboard_popup_grab_interface;
    shell_seat->popup_grab.pointer.interface =
        &fx_shell_seat_pointer_popup_grab_interface;
    shell_seat->popup_grab.touch.interface =
        &fx_shell_seat_touch_popup_grab_interface;
    wl_list_init( &shell_seat->popup_grab.surfaces );

    return shell_seat;
}

bool
fx_shell_seat_popup_grab_start(
        struct fx_shell_seat *shell_seat,
        struct wl_client *client,
        uint32_t serial )
{
    assert( shell_seat == NULL ||  shell_seat->popup_grab.client == NULL ||
            shell_seat->popup_grab.client == client );

    struct weston_seat *seat = shell_seat != NULL ? shell_seat->seat : NULL;
    /* weston_seat_get_* functions can properly handle a NULL wseat */
    struct weston_keyboard *keyboard = weston_seat_get_keyboard( seat );
    struct weston_pointer *pointer = weston_seat_get_pointer( seat );
    struct weston_touch *touch = weston_seat_get_touch( seat );

    if ( ( keyboard == NULL || keyboard->grab_serial != serial ) &&
            ( pointer == NULL || pointer->grab_serial != serial ) &&
            ( touch == NULL || touch->grab_serial != serial ) ) {
        return false;
    }

    if ( keyboard != NULL &&
            keyboard->grab->interface !=
            &fx_shell_seat_keyboard_popup_grab_interface ) {
        weston_keyboard_start_grab(
                keyboard,
                &shell_seat->popup_grab.keyboard );
    }
    if ( pointer != NULL &&
            pointer->grab->interface !=
            &fx_shell_seat_pointer_popup_grab_interface ) {
        weston_pointer_start_grab(
                pointer,
                &shell_seat->popup_grab.pointer );
    }
    if ( touch != NULL &&
            touch->grab->interface !=
            &fx_shell_seat_touch_popup_grab_interface  ) {
        weston_touch_start_grab(
                touch,
                &shell_seat->popup_grab.touch );
    }
    shell_seat->popup_grab.initial_up =
        ( pointer == NULL || pointer->button_count == 0 );
    shell_seat->popup_grab.client = client;

    return true;
}

static void
fx_shell_seat_popup_grab_end( struct fx_shell_seat *shell_seat )
{
    struct weston_keyboard *keyboard =
        weston_seat_get_keyboard( shell_seat->seat );
    struct weston_pointer *pointer =
        weston_seat_get_pointer( shell_seat->seat );
    struct weston_touch *touch =
        weston_seat_get_touch( shell_seat->seat );

    while ( !wl_list_empty( &shell_seat->popup_grab.surfaces ) ) {
        struct wl_list *link = shell_seat->popup_grab.surfaces.prev;
        struct fx_shell_surface *shell_surface =
            fx_shell_surface_from_grab_link( link );

        wl_list_remove( link );
        wl_list_init( link );
        fx_shell_surface_popup_dismiss( shell_surface );
    }

    if ( keyboard != NULL &&
            keyboard->grab->interface ==
            &fx_shell_seat_keyboard_popup_grab_interface ) {
        weston_keyboard_end_grab( keyboard );
    }

    if ( pointer != NULL &&
            pointer->grab->interface ==
            &fx_shell_seat_pointer_popup_grab_interface ) {
        weston_pointer_end_grab( pointer );
    }

    if ( touch != NULL &&
            touch->grab->interface ==
            &fx_shell_seat_touch_popup_grab_interface ) {
        weston_touch_end_grab( touch );
    }

    shell_seat->popup_grab.client = NULL;
}
void
fx_shell_seat_popup_grab_add_surface(
        struct fx_shell_seat *shell_seat,
        struct wl_list *link )
{
    assert( shell_seat->popup_grab.client != NULL );

    wl_list_insert(
            &shell_seat->popup_grab.surfaces,
            link );
}
void
fx_shell_seat_popup_grab_remove_surface(
        struct fx_shell_seat *shell_seat,
        struct wl_list *link )
{
    assert( shell_seat->popup_grab.client != NULL );

    wl_list_remove( link );
    wl_list_init( link );
    if ( wl_list_empty( &shell_seat->popup_grab.surfaces )) {
        fx_shell_seat_popup_grab_end( shell_seat );
    }
}

void
fx_shell_seat_set_disabled(
        struct fx_shell_seat *shell_seat,
        bool disabled )
{
    shell_seat->disabled = disabled;
}
bool
fx_shell_seat_disabled(
        struct fx_shell_seat *shell_seat )
{
    return shell_seat->disabled;
}
