/*
 * 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-wl-shell.c
 * Title : wl_shell implementation
 */

#include "config.h"

#include <string.h>
#include <assert.h>
#include "compositor.h"
#include "shared/helpers.h"
#include "fx-surface-extension-server-protocol.h"
#include "weston-fx-shell.h"
#include "fx-client.h"
#include "fx-shell.h"
#include "fx-seat.h"
#include "fx-wl-shell.h"

#define WD_WL_SHELL_PROTOCOL_VERSION 1

/* Typedefs */
enum fx_shell_wl_shell_surface_state {
    FX_SHELL_SURFACE_NONE,
    FX_SHELL_SURFACE_TOPLEVEL,
    FX_SHELL_SURFACE_FULLSCREEN,
    FX_SHELL_SURFACE_TRANSIENT,
    FX_SHELL_SURFACE_POPUP,
};
struct fx_shell_wl_shell_surface {
    struct wl_resource *resource;
    struct weston_fx_shell *weston_fx_shell;
    struct wl_display *display;
    struct fx_shell_surface *shell_surface;
    struct fx_shell_surface *parent_shell_surface;
    bool added;
    struct fx_shell_seat *popup_shell_seat;
    enum fx_shell_wl_shell_surface_state state;
    struct wl_listener wl_surface_resource_destroy_listener;
};

/* Procedures */
static void
fx_shell_wl_shell_surface_set_size(
        struct fx_shell_surface *shell_surface,
        void *user_data,
        int32_t width,
        int32_t height )
{
    struct fx_shell_wl_shell_surface *wl_shell_surface = user_data;
    struct weston_surface *surface =
        fx_shell_surface_get_surface( wl_shell_surface->shell_surface );


    if ((surface->width == width && surface->height == height) ||
            (width == 0 && height == 0))
        return;

    wl_shell_surface_send_configure(
            wl_shell_surface->resource,
            WL_SHELL_SURFACE_RESIZE_NONE,
            width,
            height );
}
static void
fx_wl_shell_surface_maybe_ungrab(
        struct fx_shell_wl_shell_surface *wl_shell_surface )
{
    if ( wl_shell_surface->state != FX_SHELL_SURFACE_POPUP ||
            !fx_shell_surface_get_grab( wl_shell_surface->shell_surface ) ) {
        return;
    }
    fx_shell_surface_popup_ungrab(
            wl_shell_surface->shell_surface,
            wl_shell_surface->popup_shell_seat );
    wl_shell_surface->popup_shell_seat = NULL;
}

static void
fx_shell_wl_shell_surface_committed(
        struct fx_shell_surface *shell_surface,
        void *user_data,
        int32_t sx,
        int32_t sy )
{
    struct fx_shell_wl_shell_surface *wl_shell_surface = user_data;
    struct weston_surface *surface =
        fx_shell_surface_get_surface( shell_surface );

    if (surface->buffer_ref.buffer == NULL) {
        fx_wl_shell_surface_maybe_ungrab( wl_shell_surface );
    }

    if ( wl_shell_surface->added ) {
        weston_fx_shell_api_committed(
                wl_shell_surface->weston_fx_shell,
                wl_shell_surface->shell_surface,
                sx,
                sy );
    }
}
static void
fx_wl_shell_surface_close(
        struct fx_shell_surface *shell_surface,
        void *user_data )
{
    struct fx_shell_wl_shell_surface *wl_shell_surface = user_data;

    if ( wl_shell_surface->state == FX_SHELL_SURFACE_POPUP ) {
        wl_shell_surface_send_popup_done( wl_shell_surface->resource );
    }
}

static void
fx_shell_wl_shell_change_state(
        struct fx_shell_wl_shell_surface *wl_shell_surface,
        enum fx_shell_wl_shell_surface_state state,
        struct fx_shell_surface *parent_shell_surface,
        int32_t x,
        int32_t y )
{
    bool to_add = ( parent_shell_surface == NULL );

    assert( state != FX_SHELL_SURFACE_NONE );

    if ( to_add && wl_shell_surface->added ) {
        wl_shell_surface->state = state;
        return;
    }

    if ( wl_shell_surface->state != state ) {
        if ( wl_shell_surface->state == FX_SHELL_SURFACE_POPUP ) {
            fx_wl_shell_surface_maybe_ungrab( wl_shell_surface );
        }
        if ( to_add ) {
            fx_shell_surface_unset_relative_to(
                    wl_shell_surface->shell_surface );
            weston_fx_shell_api_surface_added(
                    wl_shell_surface->weston_fx_shell,
                    wl_shell_surface->shell_surface );
        } else if ( wl_shell_surface->added ) {
            weston_fx_shell_api_surface_removed(
                    wl_shell_surface->weston_fx_shell,
                    wl_shell_surface->shell_surface );
        }

        wl_shell_surface->state = state;
        wl_shell_surface->added = to_add;
    }

    if ( parent_shell_surface != NULL ) {
        fx_shell_surface_set_relative_to(
                wl_shell_surface->shell_surface,
                parent_shell_surface,
                x,
                y,
                false );
    }
}
static void
fx_shell_wl_shell_surface_destroy(
        struct fx_shell_surface *shell_surface,
        void *user_data )
{
    struct fx_shell_wl_shell_surface *wl_shell_surface = user_data;

    wl_list_remove(
            &wl_shell_surface->wl_surface_resource_destroy_listener.link );

    fx_wl_shell_surface_maybe_ungrab( wl_shell_surface );
    fx_shell_surface_unset_relative_to(
            wl_shell_surface->shell_surface );
    if ( wl_shell_surface->added ) {
        weston_fx_shell_api_surface_removed(
                wl_shell_surface->weston_fx_shell,
                wl_shell_surface->shell_surface );
    }

    free( wl_shell_surface );
}

static void
fx_shell_wl_shell_surface_protocol_pong(
        struct wl_client *client,
        struct wl_resource *resource,
        uint32_t serial )
{
}
static void
fx_shell_wl_shell_surface_protocol_move(
        struct wl_client *client,
        struct wl_resource *resource,
        struct wl_resource *seat_resource,
        uint32_t serial )
{
}
static void
fx_shell_wl_shell_surface_protocol_resize(
        struct wl_client *client,
        struct wl_resource *resource,
        struct wl_resource *seat_resource,
        uint32_t serial,
        uint32_t edges )
{
}

static void
fx_shell_wl_shell_surface_protocol_set_toplevel(
        struct wl_client *client,
        struct wl_resource *resource )
{
    struct fx_shell_surface *shell_surface
        = wl_resource_get_user_data( resource );
    struct fx_shell_wl_shell_surface *wl_shell_surface
        = fx_shell_surface_get_implementation_data( shell_surface );

    fx_shell_wl_shell_change_state(
            wl_shell_surface,
            FX_SHELL_SURFACE_TOPLEVEL,
            NULL,
            0,
            0 );
    if ( wl_shell_surface->parent_shell_surface == NULL ) {
        return;
    }
    wl_shell_surface->parent_shell_surface = NULL;
    weston_fx_shell_api_set_parent(
            wl_shell_surface->weston_fx_shell,
            wl_shell_surface->shell_surface,
            NULL);
}
static void
fx_shell_wl_shell_surface_protocol_set_transient(
        struct wl_client *client,
        struct wl_resource *resource,
        struct wl_resource *parent_resource,
        int x,
        int y,
        uint32_t flags)
{
    struct fx_shell_surface *shell_surface =
        wl_resource_get_user_data( resource );
    struct weston_surface *parent =
        wl_resource_get_user_data( parent_resource );
    struct fx_shell_surface *parent_shell_surface;
    struct fx_shell_wl_shell_surface *wl_shell_surface =
        fx_shell_surface_get_implementation_data( shell_surface );

    if (! fx_shell_surface_is_shell_surface( parent ) ) {
        return;
    }

    parent_shell_surface = fx_shell_surface_get_shell_surface( parent );
    if (flags & WL_SHELL_SURFACE_TRANSIENT_INACTIVE) {
        fx_shell_wl_shell_change_state(
                wl_shell_surface,
                FX_SHELL_SURFACE_TRANSIENT,
                parent_shell_surface,
                x,
                y );
    } else {
        fx_shell_wl_shell_change_state(
                wl_shell_surface,
                FX_SHELL_SURFACE_TOPLEVEL,
                NULL,
                0,
                0 );
        if ( parent_shell_surface ) {
            fx_shell_surface_set_transient(
                    wl_shell_surface->shell_surface,
                    parent_shell_surface,
                    x,
                    y );
        }
    }
}
static void
fx_shell_wl_shell_surface_protocol_set_fullscreen(
        struct wl_client *client,
        struct wl_resource *resource,
        uint32_t method,
        uint32_t framerate,
        struct wl_resource *output_resource )
{
    struct fx_shell_surface *shell_surface
        = wl_resource_get_user_data( resource );
    struct fx_shell_wl_shell_surface *wl_shell_surface
        = fx_shell_surface_get_implementation_data( shell_surface );

    struct weston_output *output = NULL;

    if ( output_resource != NULL ) {
        output = weston_output_from_resource(output_resource);
    }

    fx_shell_wl_shell_change_state(
            wl_shell_surface,
            FX_SHELL_SURFACE_FULLSCREEN,
            NULL,
            0,
            0 );

    weston_fx_shell_api_fullscreen_requested(
            wl_shell_surface->weston_fx_shell,
            shell_surface,
            true,
            output );
}
static void
fx_shell_wl_shell_surface_protocol_set_popup(
        struct wl_client *client,
        struct wl_resource *resource,
        struct wl_resource *seat_resource,
        uint32_t serial,
        struct wl_resource *parent_resource,
        int32_t x,
        int32_t y,
        uint32_t flags )
{
    struct fx_shell_surface *shell_surface
        = wl_resource_get_user_data( resource );
    struct weston_seat *seat = wl_resource_get_user_data( seat_resource );
    struct fx_shell_seat *shell_seat = fx_shell_seat_from_seat( seat );
    struct weston_surface *parent =
        wl_resource_get_user_data( parent_resource );
    struct fx_shell_surface *parent_shell_surface;
    struct fx_shell_wl_shell_surface *wl_shell_surface
        = fx_shell_surface_get_implementation_data( shell_surface );

    if ( seat != NULL && shell_seat == NULL ) {
        wl_client_post_no_memory( client );
        return;
    }
    if ( !fx_shell_surface_is_shell_surface( parent ) ) {
        return;
    }
    parent_shell_surface = fx_shell_surface_get_shell_surface( parent );

    fx_shell_wl_shell_change_state(
            wl_shell_surface,
            FX_SHELL_SURFACE_POPUP,
            parent_shell_surface,
            x,
            y );
    fx_shell_surface_popup_grab(
            shell_surface,
            shell_seat,
            serial );
    wl_shell_surface->popup_shell_seat = shell_seat;
}
static void
fx_shell_wl_shell_surface_protocol_set_maximized(
        struct wl_client *client,
        struct wl_resource *resource,
        struct wl_resource *output_resource )
{
}
static void
fx_shell_wl_shell_surface_protocol_set_title(
        struct wl_client *client,
        struct wl_resource *resource,
        const char *title )
{
}
static void
fx_shell_wl_shell_surface_protocol_set_class(
        struct wl_client *client,
        struct wl_resource *resource,
        const char *class )
{
}

static const struct wl_shell_surface_interface
fx_shell_wl_shell_surface_implementation = {
    /* not implemented */
    .pong = fx_shell_wl_shell_surface_protocol_pong,   
    .move = fx_shell_wl_shell_surface_protocol_move,
    .resize = fx_shell_wl_shell_surface_protocol_resize,

    .set_toplevel = fx_shell_wl_shell_surface_protocol_set_toplevel,

    /* not implemented */
    .set_transient = fx_shell_wl_shell_surface_protocol_set_transient,

    .set_fullscreen = fx_shell_wl_shell_surface_protocol_set_fullscreen,

    /* not implemented */
    .set_popup = fx_shell_wl_shell_surface_protocol_set_popup,
    .set_maximized = fx_shell_wl_shell_surface_protocol_set_maximized,
    .set_title = fx_shell_wl_shell_surface_protocol_set_title,
    .set_class = fx_shell_wl_shell_surface_protocol_set_class
};
static const struct fx_shell_surface_implementation
fx_shell_wl_shell_surface_internal_implementation = {
    .set_size = fx_shell_wl_shell_surface_set_size,
    .committed = fx_shell_wl_shell_surface_committed,
    .close = fx_wl_shell_surface_close,

    .destroy = fx_shell_wl_shell_surface_destroy,
};
static void
wl_surface_resource_destroyed(
        struct wl_listener *listener,
        void *data )
{
    struct fx_shell_wl_shell_surface *wl_shell_surface =
        wl_container_of(listener, wl_shell_surface,
                wl_surface_resource_destroy_listener);

    /* the wl_shell_surface spec says that wl_shell_surfaces are to be
     * destroyed automatically when the wl_surface is destroyed. */
    fx_shell_surface_destroy( wl_shell_surface->shell_surface );
}

static void
fx_shell_wl_shell_protocol_get_shell_surface(
        struct wl_client *client,
        struct wl_resource *resource,
        uint32_t id,
        struct wl_resource *surface_resource )
{
    struct fx_shell_client *shell_client
        = wl_resource_get_user_data( resource );
    struct weston_surface *surface
        = wl_resource_get_user_data( surface_resource );
    struct fx_shell_wl_shell_surface *wl_shell_surface;

    if ( weston_surface_set_role(
                surface,
                "wl_shell_surface",
                resource,
                WL_SHELL_ERROR_ROLE ) < 0 ) {
        return;
    }
    wl_shell_surface = zalloc( sizeof( struct fx_shell_wl_shell_surface ) );
    if ( wl_shell_surface == NULL ) {
        wl_client_post_no_memory( client );
        return;
    }
    wl_shell_surface->weston_fx_shell =
        fx_shell_client_get_weston_fx_shell( shell_client );
    wl_shell_surface->display =
        weston_fx_shell_get_display( wl_shell_surface->weston_fx_shell);
    wl_shell_surface->shell_surface =
        fx_shell_surface_create(
                wl_shell_surface->weston_fx_shell,
                shell_client,
                surface,
                &fx_shell_wl_shell_surface_internal_implementation,
                wl_shell_surface );
    if ( wl_shell_surface->shell_surface == NULL ) {
        free( wl_shell_surface );
        return;
    }
    wl_shell_surface->wl_surface_resource_destroy_listener.notify
        = wl_surface_resource_destroyed;
    wl_resource_add_destroy_listener(
            surface->resource,
            &wl_shell_surface->wl_surface_resource_destroy_listener );

    wl_shell_surface->resource =
        fx_shell_surface_add_resource(
                wl_shell_surface->shell_surface,
                &wl_shell_surface_interface,
                &fx_shell_wl_shell_surface_implementation,
                id,
                NULL );
}

static const struct wl_shell_interface fx_shell_wl_shell_implementation = {
    fx_shell_wl_shell_protocol_get_shell_surface
};
static void
fx_shell_wl_shell_bind(
        struct wl_client *client,
        void *data,
        uint32_t version,
        uint32_t id )
{
    struct weston_fx_shell *weston_fx_shell = data;

    ( void ) fx_shell_client_create(
            weston_fx_shell,
            client,
            NULL,
            &wl_shell_interface,
            &fx_shell_wl_shell_implementation,
            version,
            id );
}

struct wl_global *
fx_shell_wl_shell_create(
        struct weston_fx_shell* weston_fx_shell,
        struct wl_display *display )
{
    return wl_global_create(
                display,
                &wl_shell_interface,
                WD_WL_SHELL_PROTOCOL_VERSION,
                weston_fx_shell,
                fx_shell_wl_shell_bind );
    return 0;
}
