/*
 * (C) Copyright 2014, Fuji Xerox Co.,Ltd.  All rights reserved.
 * File name : fx-shell-controller.c
 * Title : shell controller from gimlet.
 */
#include "config.h"

#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <linux/input.h>
#include <sys/mman.h>
#include "compositor-fx-fbdev.h"
#include "shared/helpers.h"
#include "fx-shell-controller-server-protocol.h"
#include "fx-screen-share.h"
#include "fx-seat.h"
#include "fx-shell.h"
#include "fx-shell-controller.h"

/* Typedefs */
struct fx_shell_controller_key_info {
    struct xkb_keymap* keymap;
    uint32_t key;
    enum wl_keyboard_key_state state;
};
struct fx_shell_controller_seat {
    struct weston_seat base;
    struct wl_listener seat_released;
    struct fx_shell *
        shell;
};
struct fx_shell_controller_screen_share {
    struct wl_list
        link;
    struct screen_share*
        screen_share;
    char*
        section_name;
};
struct fx_shell_controller {
    struct wl_resource *
        resource;
    struct wl_listener
        destroy_listener;
    struct fx_shell_controller_seat*
        shell_controller_seat;
    struct fx_shell *
        shell;

    struct wl_listener
        key_listener;
    struct wl_listener
        touch_listener;
    struct wl_listener
        button_listener;

    const struct weston_compositor_fx_fbdev_api*
        fx_fbdev_api;
    struct wl_list
        screen_share_list;
    struct fx_screen_share_listener
        listener;
    const struct weston_fx_screen_share_api*
        screen_share_api;
    int connected;

    struct wl_list
        *shell_controller_listeners;

    struct wl_event_source *
        send_key_event_source;
    struct wl_array
        key_infos;
    struct wl_array
        keys;
    bool
        wake_enabled;
} ;

/* Procedures */
static struct fx_shell_controller_seat *
shell_controller_seat_create( struct fx_shell *shell )
{
    struct fx_shell_controller_seat *shell_controller_seat;

    shell_controller_seat = zalloc(sizeof *shell_controller_seat);
    if (shell_controller_seat == NULL)
        return NULL;

    weston_seat_init(
            &shell_controller_seat->base,
            shell->compositor,
            FX_SHELL_SEAT_NAME );
/*
    shell_controller_seat->seat_released.notify = seat_released;
    wl_signal_add(
            &shell_controller_seat->base.released_signal,
            &shell_controller_seat->seat_released );
*/
    shell_controller_seat->shell = shell;

    weston_seat_init_touch( &shell_controller_seat->base );
    weston_seat_init_keyboard( &shell_controller_seat->base, NULL );
    return shell_controller_seat;
}
static void
send_key( void* data )
{
    struct fx_shell_controller *shell_controller = data;
    struct weston_keyboard* keyboard = weston_seat_get_keyboard(
            &shell_controller->shell_controller_seat->base );
    struct timespec time;

    if ( keyboard->focus != NULL ) {
        struct fx_shell_controller_key_info* key_info;

        weston_compositor_get_time(&time);
        wl_array_for_each(key_info, &shell_controller->key_infos) {
            uint32_t *k, *end;

            if ( key_info->keymap != NULL ) {
                if ( key_info->keymap != keyboard->xkb_info->keymap ) {
                    weston_seat_update_keymap(
                            &shell_controller->shell_controller_seat->base,
                            key_info->keymap );
                }
                xkb_keymap_unref( key_info->keymap );
            }
            notify_key(
                    &shell_controller->shell_controller_seat->base,
                    &time,
                    key_info->key,
                    key_info->state,
                    STATE_UPDATE_AUTOMATIC );

            if ( key_info->state == WL_KEYBOARD_KEY_STATE_PRESSED ) {
                k = wl_array_add(&shell_controller->keys, sizeof *k);
                *k = key_info->key;
            } else {
                end = shell_controller->keys.data + shell_controller->keys.size;
                for (k = shell_controller->keys.data; k < end; k++) {
                    if (*k == key_info->key) {
                        *k = *--end;
                        break;
                    }
                }
                shell_controller->keys.size
                    = (void *) end - shell_controller->keys.data;
            }
        }
    }
    shell_controller->key_infos.size = 0;
    shell_controller->send_key_event_source = NULL;
}
static bool
contains_key(
        struct fx_shell_controller *shell_controller,
        uint32_t key )
{
    uint32_t *k, *end;
    
    end = shell_controller->keys.data + shell_controller->keys.size;
    for (k = shell_controller->keys.data; k < end; k++) {
        if (*k == key) return true;
    }
    return false;
}
static void
keyboard_grab_key(
        struct weston_keyboard_grab *grab,
        const struct timespec *time,
        uint32_t key,
        enum wl_keyboard_key_state state )
{
}
static void
keyboard_grab_modifiers(
        struct weston_keyboard_grab *grab,
        uint32_t serial,
        uint32_t mods_depressed,
        uint32_t mods_latched,
        uint32_t mods_locked,
        uint32_t group )
{
}
static void
keyboard_grab_cancel( struct weston_keyboard_grab *grab )
{
}
static void
pointer_grab_focus( struct weston_pointer_grab *grab )
{
}
static void
pointer_grab_motion(
        struct weston_pointer_grab *grab,
        const struct timespec *time,
        struct weston_pointer_motion_event *event )
{
}
static void
pointer_grab_button(
        struct weston_pointer_grab *grab,
        const struct timespec *time,
        uint32_t button,
        enum wl_pointer_button_state state )
{
}
static void
pointer_grab_axis(
        struct weston_pointer_grab *grab,
        const struct timespec *time,
        struct weston_pointer_axis_event *event )
{
}
static void
pointer_grab_axis_source(
        struct weston_pointer_grab *grab,
        uint32_t source )
{
}
static void
pointer_grab_frame( struct weston_pointer_grab *grab )
{
}
static void
pointer_grab_cancel( struct weston_pointer_grab *grab )
{
}
static void
touch_grab_down(
        struct weston_touch_grab *grab,
        const struct timespec *time,
        int touch_id,
        wl_fixed_t sx,
        wl_fixed_t sy )
{
}
static void
touch_grab_up(
        struct weston_touch_grab *grab,
        const struct timespec *time,
        int touch_id )
{
}
static void
touch_grab_motion(
        struct weston_touch_grab *grab,
        const struct timespec *time,
        int touch_id,
        wl_fixed_t sx,
        wl_fixed_t sy )
{
}
static void
touch_grab_frame( struct weston_touch_grab *grab )
{
}
static void
touch_grab_cancel( struct weston_touch_grab *grab )
{
}
static const struct weston_keyboard_grab_interface g_keyboard_grab_interface = {
    .key = keyboard_grab_key,
    .modifiers = keyboard_grab_modifiers,
    .cancel = keyboard_grab_cancel,
};
static const struct weston_pointer_grab_interface g_pointer_grab_interface = {
    .focus = pointer_grab_focus,
    .motion = pointer_grab_motion,
    .button = pointer_grab_button,
    .axis = pointer_grab_axis,
    .axis_source = pointer_grab_axis_source,
    .frame = pointer_grab_frame,
    .cancel = pointer_grab_cancel,
};
static const struct weston_touch_grab_interface g_touch_grab_interface = {
    .down = touch_grab_down,
    .up = touch_grab_up,
    .motion = touch_grab_motion,
    .frame = touch_grab_frame,
    .cancel = touch_grab_cancel,
};
static struct weston_keyboard_grab g_keyboard_grab = {
    .interface = &g_keyboard_grab_interface,
};
static struct weston_pointer_grab g_pointer_grab = {
    .interface = &g_pointer_grab_interface,
};
static struct weston_touch_grab g_touch_grab = {
    .interface = &g_touch_grab_interface,
};

static void
send_session( struct fx_shell_controller *shell_controller ) 
{
    fx_shell_controller_send_session(
            shell_controller->resource,
            shell_controller->shell->compositor->session_active );
}
static void
send_screen_share( struct fx_shell_controller *shell_controller ) 
{
    fx_shell_controller_send_screen_share(
            shell_controller->resource,
            shell_controller->connected );
}
static void
connection(
        struct fx_screen_share_listener *listener,
        int connected )
{
    struct fx_shell_controller *shell_controller
        = container_of( listener, struct fx_shell_controller, listener );

    shell_controller->connected = connected;
    send_screen_share( shell_controller );
}
static void
send_wake(
        struct fx_shell_controller *shell_controller,
        struct fx_shell_seat *shell_seat )
{
    if ( !shell_controller->wake_enabled ) return;
    if ( fx_shell_seat_disabled( shell_seat ) ) return;

    fx_shell_controller_send_wake( shell_controller->resource );
}
static void
key_notify(
        struct wl_listener *listener,
        void *data )
{
    struct fx_shell_controller *shell_controller
        = container_of( listener, struct fx_shell_controller, key_listener );
    va_list *argp = data;
    struct weston_seat *seat = va_arg( *argp, struct weston_seat* );
    uint32_t key = va_arg( *argp, uint32_t );
    enum wl_keyboard_key_state state = va_arg( *argp, enum wl_keyboard_key_state );
    struct fx_shell_seat *shell_seat = fx_shell_seat_from_seat( seat );
    struct weston_keyboard* keyboard = weston_seat_get_keyboard( seat );
    struct weston_keyboard* shell_controller_keyboard =
        weston_seat_get_keyboard(
                &shell_controller->shell_controller_seat->base );
    struct fx_shell_controller_key_info* key_info;

    send_wake(
            shell_controller,
            shell_seat );

    if ( seat == &shell_controller->shell_controller_seat->base ) return;

    if ( state == WL_KEYBOARD_KEY_STATE_PRESSED ||
            !contains_key(
                shell_controller,
                key ) ) {
        if ( fx_shell_seat_disabled( shell_seat ) ) return;

        if ( shell_controller_keyboard->focus == NULL ) return;

        if ( keyboard->focus == shell_controller_keyboard->focus ) return;
    }
    key_info = wl_array_add(
            &shell_controller->key_infos,
            sizeof ( *key_info ) );
    key_info->keymap = keyboard->xkb_info->keymap != NULL ?
        xkb_keymap_ref( keyboard->xkb_info->keymap ) : NULL;
    key_info->key = key;
    key_info->state = state;

    if ( shell_controller->send_key_event_source == NULL ) {
        shell_controller->send_key_event_source = wl_event_loop_add_idle(
                wl_display_get_event_loop(
                    shell_controller->shell->compositor->wl_display ),
                send_key,
                shell_controller );
    }
}
static void
button_notify(
        struct wl_listener *listener,
        void *data )
{
#if 0
    struct fx_shell_controller *shell_controller
        = container_of( listener, struct fx_shell_controller, button_listener );
    va_list *argp = data;
    struct weston_seat *seat = va_arg( *argp, struct weston_seat* );
    int button = va_arg( *argp, int );
    wl_fixed_t x = va_arg( *argp, wl_fixed_t );
    wl_fixed_t y = va_arg( *argp, wl_fixed_t );
    enum wl_pointer_button_state state = va_arg( *argp, enum wl_pointer_button_state );
    struct fx_shell_seat *shell_seat = fx_shell_seat_from_seat( seat );
    struct wl_resource* resource;

    if ( fx_shell_seat_disabled( shell_seat ) ) return;

    if ( button == BTN_LEFT )
    {
        wl_resource_for_each(resource, &seat->base_resource_list) {
            if ( wl_resource_get_client( resource )
                    == wl_resource_get_client( shell_controller->resource ) ) {
                fx_shell_controller_send_touch(
                        shell_controller->resource,
                        0,
                        x,
                        y,
                        state == WL_POINTER_BUTTON_STATE_RELEASED ?
                        FX_SHELL_CONTROLLER_ACTION_RELEASE :
                        FX_SHELL_CONTROLLER_ACTION_PRESS );
                break;
            }
        }
    }
#endif
    struct fx_shell_controller *shell_controller
        = container_of( listener, struct fx_shell_controller, button_listener );
   
    va_list *argp = data;
    struct weston_seat *seat = va_arg( *argp, struct weston_seat* );
    struct fx_shell_seat *shell_seat = fx_shell_seat_from_seat( seat );
    
    send_wake(
            shell_controller,
            shell_seat );
}
static void
touch_notify(
        struct wl_listener *listener,
        void *data )
{
#if 0
    struct fx_shell_controller *shell_controller
        = container_of( listener, struct fx_shell_controller, touch_listener );
    va_list *argp = data;
    struct weston_seat *seat = va_arg( *argp, struct weston_seat* );
    int touch_id = va_arg( *argp, int );
    wl_fixed_t x = va_arg( *argp, wl_fixed_t );
    wl_fixed_t y = va_arg( *argp, wl_fixed_t );
    int touch_type = va_arg( *argp, int );
    struct fx_shell_seat *shell_seat = fx_shell_seat_from_seat( seat );
    struct wl_resource* resource;

    if ( fx_shell_seat_disabled( shell_seat ) ) return;

    if ( touch_type == WL_TOUCH_DOWN || touch_type == WL_TOUCH_UP )
    {
        wl_resource_for_each(resource, &seat->base_resource_list) {
            if ( wl_resource_get_client( resource )
                    == wl_resource_get_client( shell_controller->resource ) ) {
                fx_shell_controller_send_touch(
                        shell_controller->resource,
                        touch_id,
                        x,
                        y,
                        touch_type = WL_TOUCH_UP ?
                        FX_SHELL_CONTROLLER_ACTION_RELEASE :
                        FX_SHELL_CONTROLLER_ACTION_PRESS );
                break;
            }
        }
    }
#endif
    struct fx_shell_controller *shell_controller
        = container_of( listener, struct fx_shell_controller, touch_listener );
    va_list *argp = data;
    struct weston_seat *seat = va_arg( *argp, struct weston_seat* );
    struct fx_shell_seat *shell_seat = fx_shell_seat_from_seat( seat );
    
    send_wake(
            shell_controller,
            shell_seat );
}
static void
set_session(
        struct wl_client *client,
        struct wl_resource *resource,
        int32_t active )
{
    struct fx_shell_controller *shell_controller
        = wl_resource_get_user_data( resource );

    if ( shell_controller->fx_fbdev_api == NULL ) {
        shell_controller->fx_fbdev_api = weston_compositor_fx_fbdev_get_api(
                shell_controller->shell->compositor );
        if ( shell_controller->fx_fbdev_api == NULL ) {
            weston_log( "set_session warning: api is not detected\n" );
            return;
        }
    }
    shell_controller->shell->compositor->session_active = active;
    shell_controller->fx_fbdev_api->session_notify(
            shell_controller->shell->compositor );
    send_session( shell_controller );
}
static struct fx_shell_controller_screen_share*
get_screen_share(
        struct fx_shell_controller *shell_controller,
        const char* section_name )
{
    struct fx_shell_controller_screen_share* shell_controller_screen_share;

    wl_list_for_each(
            shell_controller_screen_share,
            &shell_controller->screen_share_list,
            link
    ) {
        if ( strcmp(
                    shell_controller_screen_share->section_name,
                    section_name ) == 0 ) {
            return shell_controller_screen_share;
        }
    }
    return NULL;
}
static void
set_screen_share(
        struct wl_client *client,
        struct wl_resource *resource,
        int32_t on,
        const char* section_name )
{
    struct fx_shell_controller *shell_controller
        = wl_resource_get_user_data( resource );
    struct fx_shell_controller_screen_share *shell_controller_screen_share;

    if ( shell_controller->screen_share_api == NULL ) {
        shell_controller->screen_share_api = weston_fx_screen_share_get_api( 
                shell_controller->shell->compositor );
        if ( shell_controller->screen_share_api == NULL ) {
            weston_log( "set_screen_share warning: api is not detected\n" );
            return;
        }
    }

    shell_controller_screen_share = get_screen_share(
            shell_controller,
            section_name );
    if ( on ) {
        if ( shell_controller_screen_share == NULL ) {
            shell_controller_screen_share =
                zalloc( sizeof ( *shell_controller_screen_share ) );

            if ( shell_controller_screen_share == NULL ) {
                weston_log( "set_screen_share no memory\n" );
                return;
            }
            shell_controller_screen_share->screen_share = 
                shell_controller->screen_share_api->create(
                    shell_controller->shell->compositor,
                    section_name,
                    &shell_controller->listener );
            shell_controller_screen_share->section_name
                = strdup( section_name );
            wl_list_insert(
                    &shell_controller->screen_share_list,
                    &shell_controller_screen_share->link );
        }
    } else {
        if ( shell_controller_screen_share != NULL ) {
            wl_list_remove( &shell_controller_screen_share->link );
            shell_controller->screen_share_api->destroy(
                    shell_controller_screen_share->screen_share );

            free( shell_controller_screen_share->section_name );
            free( shell_controller_screen_share );
        }
    }
}
static void
set_seat_disabled(
        struct wl_client *client,
        struct wl_resource *resource,
        struct wl_resource *seat_resource,
        int32_t disabled )
{
    struct weston_seat *seat = wl_resource_get_user_data(seat_resource);
    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 );
    struct fx_shell_seat *shell_seat = fx_shell_seat_from_seat( seat );

    if ( disabled ) {
        if ( keyboard != NULL ) {
            weston_keyboard_start_grab(
                    keyboard,
                    &g_keyboard_grab );
        }
        if ( pointer != NULL ) {
            weston_pointer_start_grab(
                    pointer,
                    &g_pointer_grab );
        }
        if ( touch != NULL ) {
            weston_touch_start_grab(
                    touch,
                    &g_touch_grab );
        }
    } else {
        if ( keyboard != NULL ) {
            weston_keyboard_end_grab( keyboard );
        }
        if ( pointer != NULL ) {
            weston_pointer_end_grab( pointer );
        }
        if ( touch != NULL ) {
            weston_touch_end_grab( touch );
        }
    }
    fx_shell_seat_set_disabled(
            shell_seat,
            disabled );
}
static void
set_screen_enabled(
        struct wl_client *client,
        struct wl_resource *resource,
        int32_t enable )
{
    struct fx_shell_controller *shell_controller
        = wl_resource_get_user_data( resource );

    if ( shell_controller->fx_fbdev_api == NULL ) {
        shell_controller->fx_fbdev_api = weston_compositor_fx_fbdev_get_api(
                shell_controller->shell->compositor );
        if ( shell_controller->fx_fbdev_api == NULL ) {
            weston_log( "set_screen_enabled warning: api is not detected\n" );
            return;
        }
    }
    shell_controller->fx_fbdev_api->set_screen_enabled(
            shell_controller->shell->compositor,
            enable );
}
static void
write_image_to_fb(
        struct wl_client *client,
        struct wl_resource *resource,
        int fd,
        int32_t format,
        int32_t width,
        int32_t height,
        int32_t rowstride_bytes )
{
    struct fx_shell_controller *shell_controller
        = wl_resource_get_user_data( resource );
    pixman_image_t* image;
    void* bits;

    if ( shell_controller->fx_fbdev_api == NULL ) {
        shell_controller->fx_fbdev_api = weston_compositor_fx_fbdev_get_api(
                shell_controller->shell->compositor );
        if ( shell_controller->fx_fbdev_api == NULL ) {
            weston_log( "write_image_to_fb warning: api is not detected\n" );
            close( fd );
            return;
	}
    }
    bits = mmap(
            NULL,
            rowstride_bytes * height,
            PROT_READ | PROT_WRITE,
            MAP_SHARED,
            fd,
            0 );
    if ( bits == MAP_FAILED ) {
        close( fd );
        return;
    }
    close( fd );
    image = pixman_image_create_bits_no_clear(
            format,
            width,
            height,
            bits,
            rowstride_bytes );
    if ( image == NULL ) {
        ( void ) munmap(
                bits,
                rowstride_bytes * height );
    }
    shell_controller->fx_fbdev_api->write_image_to_fb(
            shell_controller->shell->compositor,
            image );
    pixman_image_unref( image );
    ( void ) munmap(
            bits,
            rowstride_bytes * height );
}

static void
set_wake_enabled(
        struct wl_client *client,
        struct wl_resource *resource,
        int32_t enable )
{
    struct fx_shell_controller *shell_controller
        = wl_resource_get_user_data( resource );
    
    shell_controller->wake_enabled = enable;
}
        
static void
shell_controller_destroyed(
        struct wl_listener *listener,
        void *data )
{
    struct fx_shell_controller *shell_controller
        = container_of(listener, struct fx_shell_controller, destroy_listener);
    struct fx_shell_controller_screen_share* shell_controller_screen_share;
    struct fx_shell_controller_screen_share* next;

    weston_seat_release( &shell_controller->shell_controller_seat->base );

    if ( shell_controller->send_key_event_source != NULL ) {
        wl_event_source_remove( shell_controller->send_key_event_source );
    }
    wl_array_release( &shell_controller->key_infos );
    wl_array_release( &shell_controller->keys );
    free( shell_controller->shell_controller_seat );
    wl_list_for_each_safe(
            shell_controller_screen_share,
            next,
            &shell_controller->screen_share_list,
            link
    ) {
        free( shell_controller_screen_share->section_name );
        free( shell_controller_screen_share );
    }
    free( shell_controller );
}

static struct fx_shell_controller_interface fx_shell_controller_implementation
= {
    set_session,
    set_screen_share,
    set_seat_disabled,
    set_screen_enabled,
    write_image_to_fb,
    set_wake_enabled,
};
static void 
bind_fx_shell_controller(
        struct wl_client *client,
        void *data,
        uint32_t version,
        uint32_t id )
{
    struct fx_shell *shell = data;
    struct fx_shell_controller * shell_controller;

    shell_controller = zalloc( sizeof ( *shell_controller ) );

    if ( shell_controller == NULL ) {
        return;
    }
    shell_controller->resource = wl_resource_create(
            client,
            &fx_shell_controller_interface,
            1,
            id );
    wl_resource_set_implementation(
            shell_controller->resource,
            &fx_shell_controller_implementation,
            shell_controller,
            NULL );
    shell_controller->destroy_listener.notify = shell_controller_destroyed;
    wl_client_add_destroy_listener(
            client,
            &shell_controller->destroy_listener );
    shell_controller->shell_controller_seat =
        shell_controller_seat_create( shell );

    shell_controller->shell = shell;

    send_session( shell_controller );

    shell_controller->key_listener.notify = key_notify;
    wl_signal_add(
            &shell->compositor->key_signal,
            &shell_controller->key_listener );
    shell_controller->touch_listener.notify = touch_notify;
    wl_signal_add(
            &shell->compositor->touch_signal,
            &shell_controller->touch_listener );
    shell_controller->button_listener.notify = button_notify;
    wl_signal_add(
            &shell->compositor->button_signal,
            &shell_controller->button_listener );

    wl_list_init( &shell_controller->screen_share_list );

    shell_controller->listener.connection = connection;
    wl_array_init( &shell_controller->key_infos );
    wl_array_init( &shell_controller->keys );
}

struct wl_global*
fx_shell_controller_create( struct fx_shell* shell )
{
    return wl_global_create(
            shell->compositor->wl_display,
            &fx_shell_controller_interface,
            1,
            shell,
            bind_fx_shell_controller );
}

