/*
 * 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-surface.h
 * Title : shell surface
 */

#include "config.h"

#include <string.h>
#include <assert.h>
#include "compositor.h"
#include "shared/helpers.h"
#include "fx-shell.h"
#include "fx-client.h"
#include "fx-seat.h"
#include "fx-surface.h"

/* Typedefs */
struct fx_shell_view {
    struct wl_list
        link;
    struct weston_view *
        view;
    struct fx_shell_view *
        parent_shell_view;
    struct wl_list
        children_list;
    struct wl_list
        children_link;
};

struct fx_shell_surface {
    struct weston_fx_shell *
        weston_fx_shell;
    struct fx_shell_client *
        shell_client;
    struct wl_list
        client_link;
    const struct fx_shell_surface_implementation *
        implementation;
    void *
        implementation_data;
    void *
        user_data;
    struct weston_surface *
        surface;
    struct wl_list
        view_list;
    struct weston_position
        buffer_move;
    struct wl_listener
        surface_commit_listener;
    struct wl_listener
        surface_destroy_listener;
    struct wl_listener
        client_destroy_listener;
    struct wl_list
        children_list;

    struct wl_list
        resource_list;
    bool
        has_geometry;
    struct weston_geometry
        geometry;
    struct {
        struct fx_shell_surface *
            parent_shell_surface;
        struct wl_list
            children_link;
        struct weston_position
            position;
        bool
            use_geometry;
        struct fx_shell_surface *
            transient_parent_shell_surface;
    };
    struct {
        struct wl_list grab_link;
    };

    int flags;
    bool
        shadow_focus;
    bool
        anim;
    int layer_index;
};

/* Procedures */
static void
fx_shell_surface_update_view_position(
        struct fx_shell_surface *shell_surface )
{
    struct fx_shell_view *shell_view;
    int32_t x, y;

    x = shell_surface->position.x;
    y = shell_surface->position.y;

    if ( shell_surface->use_geometry ) {
        struct fx_shell_surface *parent_shell_surface =
            fx_shell_surface_get_parent( shell_surface );
        struct weston_geometry geometry, parent_geometry;

        geometry = fx_shell_surface_get_geometry( shell_surface );
        parent_geometry = fx_shell_surface_get_geometry( parent_shell_surface );

        x += parent_geometry.x - geometry.x;
        y += parent_geometry.y - geometry.y;
    }
    wl_list_for_each(shell_view, &shell_surface->view_list, link)
        weston_view_set_position( shell_view->view, x, y );
}
static void
fx_shell_surface_update_view_postion_relative_to_transient(
        struct fx_shell_surface *shell_surface )
{
    struct fx_shell_view *shell_view;
    struct weston_position offset;

    offset.x = offset.y = 0;
    wl_list_for_each(
            shell_view,
            &shell_surface->transient_parent_shell_surface->view_list,
            link ) {
        float x, y;

        weston_view_to_global_float(
                shell_view->view,
                0,
                0,
                &x,
                &y );
        offset.x = MAX( x, offset.x );
        offset.y = MAX( y, offset.y );
    }
    wl_list_for_each(shell_view, &shell_surface->view_list, link)
        weston_view_set_position(
                shell_view->view,
                offset.x + shell_surface->position.x,
                offset.y + shell_surface->position.y );
    shell_surface->transient_parent_shell_surface = NULL;
}
static void
fx_shell_view_destroy( struct fx_shell_view *shell_view )
{
    struct fx_shell_view *child_shell_view, *tmp;

    wl_list_for_each_safe(
            child_shell_view,
            tmp,
            &shell_view->children_list,
            children_link ) {
        fx_shell_view_destroy( child_shell_view );
    }

    wl_list_remove( &shell_view->children_link );
    wl_list_remove( &shell_view->link );

    weston_view_damage_below( shell_view->view );
    if ( shell_view->parent_shell_view != NULL ) {
        weston_view_destroy( shell_view->view );
    }

    free( shell_view );
}
void
fx_shell_surface_destroy( struct fx_shell_surface *shell_surface )
{
    struct fx_shell_view *view, *next_view;
    struct fx_shell_surface *child, *next_child;

    wl_list_remove( &shell_surface->surface_commit_listener.link );
    wl_list_remove( &shell_surface->surface_destroy_listener.link );
    wl_list_remove( &shell_surface->client_destroy_listener.link );
    if ( !wl_list_empty( &shell_surface->resource_list ) ) {
        struct wl_resource *resource, *tmp;
        wl_resource_for_each_safe(
                resource, tmp, &shell_surface->resource_list ) {
            wl_resource_set_user_data(
                    resource,
                    NULL );
            wl_list_remove( wl_resource_get_link( resource ) );
        }
    }
    shell_surface->implementation->destroy(
            shell_surface,
            shell_surface->implementation_data );

    shell_surface->surface->committed = NULL;
    shell_surface->surface->committed_private = NULL;

    fx_shell_surface_unset_relative_to( shell_surface );
    wl_list_remove( &shell_surface->client_link );

    wl_list_for_each_safe( child, next_child,
            &shell_surface->children_list,
            children_link ) {
        fx_shell_surface_unset_relative_to(child);
    }

    wl_list_for_each_safe( view, next_view, &shell_surface->view_list, link ) {
        fx_shell_view_destroy( view );
    }

    free(shell_surface);
}
static void
fx_shell_surface_surface_committed(
        struct wl_listener *listener,
        void *data )
{
    struct fx_shell_surface *shell_surface =
        wl_container_of(listener, shell_surface, surface_commit_listener);

    if ( shell_surface->surface->width == 0
            || shell_surface->surface->height == 0 ) {
        return;
    }
    if ( shell_surface->transient_parent_shell_surface != NULL ) {
        fx_shell_surface_update_view_postion_relative_to_transient(
                shell_surface );
    }
    if ( shell_surface->implementation->committed != NULL )
        shell_surface->implementation->committed(
                shell_surface,
                shell_surface->implementation_data,
                shell_surface->buffer_move.x,
                shell_surface->buffer_move.y);

    if ( shell_surface->parent_shell_surface != NULL ) {
        struct fx_shell_view *shell_view;

        wl_list_for_each( shell_view, &shell_surface->view_list, link ) {
            weston_view_set_transform_parent(
                    shell_view->view,
                    shell_view->parent_shell_view->view);
            fx_shell_view_propagate_layer( shell_view->parent_shell_view );
        }
        fx_shell_surface_update_view_position( shell_surface );
    }

    if ( !wl_list_empty( &shell_surface->children_list ) ) {
        struct fx_shell_surface *child;

        wl_list_for_each( child, &shell_surface->children_list, children_link )
            fx_shell_surface_update_view_position( child );
    }

    shell_surface->buffer_move.x = 0;
    shell_surface->buffer_move.y = 0;
}
static void
fx_shell_surface_surface_destroyed(
        struct wl_listener *listener,
        void *data )
{
    struct fx_shell_surface *surface
        = wl_container_of(listener, surface, surface_destroy_listener);

    fx_shell_surface_destroy(surface);
}
static void
fx_shell_surface_resource_destroy( struct wl_resource *resource )
{
    struct fx_shell_surface *shell_surface
        = wl_resource_get_user_data( resource );

    if ( shell_surface != NULL ) {
        fx_shell_surface_destroy( shell_surface );
    }
}
static void
fx_shell_surface_committed(
        struct weston_surface *surface,
        int32_t sx,
        int32_t sy )
{
    struct fx_shell_surface *shell_surface = surface->committed_private;

    shell_surface->buffer_move.x = sx;
    shell_surface->buffer_move.y = sy;
}
static void
fx_shell_surface_client_destroyed(
        struct wl_listener *listener,
        void *data )
{
    struct fx_shell_surface *shell_surface =
        wl_container_of(listener, shell_surface, client_destroy_listener);

    fx_shell_surface_destroy( shell_surface );
}

struct fx_shell_surface *
fx_shell_surface_create(
        struct weston_fx_shell *weston_fx_shell,
        struct fx_shell_client *shell_client,
        struct weston_surface *surface,
        const struct fx_shell_surface_implementation *implementation,
        void *implementation_data )
{
    struct fx_shell_surface *shell_surface;

    assert( implementation->destroy != NULL );

    shell_surface = zalloc( sizeof ( struct fx_shell_surface ) );
    if ( shell_surface == NULL ) {
        if ( shell_client != NULL ) {
            wl_client_post_no_memory(
                    fx_shell_client_get_client( shell_client ) );
        }
        return NULL;
    }
    shell_surface->weston_fx_shell = weston_fx_shell;
    shell_surface->implementation = implementation;
    shell_surface->implementation_data = implementation_data;
    shell_surface->surface = surface;

    shell_surface->shell_client = shell_client;
    shell_surface->client_destroy_listener.notify =
        fx_shell_surface_client_destroyed;
    fx_shell_client_add_destroy_listener(
            shell_client,
            &shell_surface->client_destroy_listener );

    surface->committed = fx_shell_surface_committed;
    surface->committed_private = shell_surface;

    shell_surface->surface = surface;

    shell_surface->surface_commit_listener.notify =
        fx_shell_surface_surface_committed;
    wl_signal_add(
            &shell_surface->surface->commit_signal,
            &shell_surface->surface_commit_listener );
    shell_surface->surface_destroy_listener.notify =
        fx_shell_surface_surface_destroyed;
    wl_signal_add(
            &shell_surface->surface->destroy_signal,
            &shell_surface->surface_destroy_listener );

    wl_list_init( &shell_surface->client_link );
    wl_list_init( &shell_surface->resource_list );
    wl_list_init( &shell_surface->children_list );
    wl_list_init( &shell_surface->children_link );
    wl_list_init( &shell_surface->view_list );
    wl_list_init( &shell_surface->grab_link );

    return shell_surface;
}

struct wl_resource *
fx_shell_surface_add_resource(
        struct fx_shell_surface *shell_surface,
        const struct wl_interface *interface,
        const void *implementation,
        uint32_t id,
        wl_resource_destroy_func_t destroy )
{
    struct wl_resource *client_resource =
        fx_shell_client_get_resource( shell_surface->shell_client );
    struct wl_client *client  =
        fx_shell_client_get_client( shell_surface->shell_client );
    struct wl_resource *resource;

    resource
        = wl_resource_create(
            client,
            interface,
            wl_resource_get_version( client_resource ),
            id );
    if ( resource == NULL ) {
        wl_client_post_no_memory( client );
        fx_shell_surface_destroy( shell_surface );
        return NULL;
    }
    if ( destroy == NULL ) {
        destroy = fx_shell_surface_resource_destroy;
    }
    wl_resource_set_implementation(
            resource,
            implementation,
            shell_surface,
            destroy );
    wl_list_insert(
            &shell_surface->resource_list,
            wl_resource_get_link( resource ) );

    return resource;
}
WL_EXPORT bool
fx_shell_surface_is_shell_surface( struct weston_surface *surface )
{
    return surface->committed == fx_shell_surface_committed;
}
WL_EXPORT struct fx_shell_surface *
fx_shell_surface_get_shell_surface( struct weston_surface *surface )
{
    if (!fx_shell_surface_is_shell_surface( surface ) ) {
        return NULL;
    }
    return surface->committed_private;
}
struct fx_shell_surface *
fx_shell_surface_from_grab_link( struct wl_list *grab_link )
{
    struct fx_shell_surface *shell_surface =
        wl_container_of( grab_link, shell_surface, grab_link  );

    return shell_surface;
}
WL_EXPORT void
fx_shell_surface_set_user_data(
    struct fx_shell_surface* shell_surface,
    void *user_data )
{
    shell_surface->user_data = user_data;
}
struct fx_shell_view *
fx_shell_surface_create_shell_view( struct fx_shell_surface *shell_surface )
{
    struct wl_client *client =
        fx_shell_client_get_client( shell_surface->shell_client );
    struct fx_shell_view *shell_view, *child_shell_view;
    struct weston_view *view;
    struct fx_shell_surface *child;

    view = weston_view_create( shell_surface->surface );
    if ( view == NULL ) {
        if ( client != NULL ) {
            wl_client_post_no_memory( client );
        }
        return NULL;
    }
    shell_view = zalloc( sizeof ( struct fx_shell_view ) );
    if ( shell_view == NULL ) {
        if ( client != NULL ) {
            wl_client_post_no_memory( client );
        }
        return NULL;
    }

    shell_view->view = view;
    wl_list_init( &shell_view->children_list );
    wl_list_init( &shell_view->children_link);
    wl_list_insert(
            shell_surface->view_list.prev,
            &shell_view->link );

    wl_list_for_each( child, &shell_surface->children_list, children_link ) {
        child_shell_view = fx_shell_surface_create_shell_view( child );
        if ( child_shell_view == NULL ) {
            fx_shell_view_destroy( shell_view );
            return NULL;
        }

        child_shell_view->parent_shell_view = shell_view;
        wl_list_insert(
                shell_view->children_list.prev,
                &child_shell_view->children_link );
    }

    return shell_view;
}
WL_EXPORT struct weston_view *
fx_shell_surface_create_view( struct fx_shell_surface* shell_surface )
{
    struct fx_shell_view *shell_view;

    shell_view = fx_shell_surface_create_shell_view( shell_surface );
    if ( shell_view == NULL ) {
        return NULL;
    }

    return shell_view->view;
}
WL_EXPORT void
fx_shell_surface_unlink_view( struct weston_view *view )
{
    struct fx_shell_surface *shell_surface;
    struct fx_shell_view *shell_view;

    if ( !fx_shell_surface_is_shell_surface( view->surface ) ) {
        return;
    }

    shell_surface = fx_shell_surface_get_shell_surface( view->surface );
    wl_list_for_each( shell_view, &shell_surface->view_list, link ) {
        if ( shell_view->view == view ) {
            fx_shell_view_destroy( shell_view );
            return;
        }
    }
}
WL_EXPORT void
fx_shell_view_propagate_layer( struct fx_shell_view* shell_view )
{
    struct fx_shell_view *child_view;
    struct wl_list *link = &shell_view->view->layer_link.link;

    wl_list_for_each_reverse(
            child_view, &shell_view->children_list, children_link ) {
        struct weston_layer_entry *prev =
            wl_container_of( link->prev, prev, link );

        if ( prev == &child_view->view->layer_link ) {
            continue;
        }

        child_view->view->is_mapped = true;
        weston_view_damage_below( child_view->view );
        weston_view_geometry_dirty( child_view->view );
        weston_layer_entry_remove( &child_view->view->layer_link );
        weston_layer_entry_insert(
                prev,
                &child_view->view->layer_link );
        weston_view_geometry_dirty( child_view->view );
        weston_surface_damage( child_view->view->surface );
        weston_view_update_transform( child_view->view );

        fx_shell_view_propagate_layer( child_view );
    }
}
WL_EXPORT void
fx_shell_surface_propagate_layer( struct fx_shell_surface *shell_surface )
{
    struct fx_shell_view *shell_view;

    wl_list_for_each( shell_view, &shell_surface->view_list, link )
        fx_shell_view_propagate_layer( shell_view );
}
WL_EXPORT void
fx_shell_surface_set_fullscreen(
    struct fx_shell_surface *shell_surface,
    bool fullscreen )
{
    if ( shell_surface->implementation->set_fullscreen != NULL )
        shell_surface->implementation->set_fullscreen(
            shell_surface,
            shell_surface->implementation_data,
            fullscreen );
}
WL_EXPORT void
fx_shell_surface_set_size(
        struct fx_shell_surface *shell_surface,
        int32_t width,
        int32_t height )
{
	if ( shell_surface->implementation->set_size != NULL ) {
		shell_surface->implementation->set_size(
                shell_surface,
                shell_surface->implementation_data,
                width,
                height );
    }
}
WL_EXPORT void
fx_shell_surface_close( struct fx_shell_surface *shell_surface )
{
    if ( shell_surface->implementation->close != NULL ) {
        shell_surface->implementation->close(
                shell_surface,
                shell_surface->implementation_data );
    }
}
struct fx_shell_surface *
fx_shell_surface_get_parent( struct fx_shell_surface *shell_surface )
{
    return shell_surface->parent_shell_surface;
}
bool
fx_shell_surface_get_grab( struct fx_shell_surface *shell_surface )
{
    return !wl_list_empty( &shell_surface->grab_link );
}
struct wl_list *
fx_shell_surface_get_client_link( struct fx_shell_surface *shell_surface )
{
    return &shell_surface->client_link;
}
const struct fx_shell_surface_implementation *
fx_shell_surface_get_implementation(
        struct fx_shell_surface *shell_surface )
{
    return shell_surface->implementation;
}
void *
fx_shell_surface_get_implementation_data(
        struct fx_shell_surface *shell_surface)
{
    return shell_surface->implementation_data;
}
void
fx_shell_surface_set_relative_to(
        struct fx_shell_surface *shell_surface,
        struct fx_shell_surface *parent_shell_surface,
        int32_t x,
        int32_t y,
        bool use_geometry )
{
    struct fx_shell_view *shell_view, *parent_shell_view;
    struct wl_list *link, *tmp;

    assert( parent_shell_surface );

    shell_surface->position.x = x;
    shell_surface->position.y = y;
    shell_surface->use_geometry = use_geometry;
    
    if ( shell_surface->parent_shell_surface == parent_shell_surface ) {
        return;
    }
    
    shell_surface->parent_shell_surface = parent_shell_surface;
    wl_list_remove( &shell_surface->children_link );
    
    wl_list_insert(
            shell_surface->parent_shell_surface->children_list.prev,
            &shell_surface->children_link );

    link = shell_surface->view_list.next;
    tmp = link->next;
    wl_list_for_each(
            parent_shell_view,
            &parent_shell_surface->view_list,
            link ) {
        if ( link == &shell_surface->view_list ) {
            shell_view = fx_shell_surface_create_shell_view( shell_surface );
            if ( shell_view == NULL ) {
                return;
            }
            tmp = &shell_surface->view_list;
        } else {
            shell_view = wl_container_of( link, shell_view, link );
            wl_list_remove( &shell_view->children_link );
        }
        shell_view->parent_shell_view = parent_shell_view;
        wl_list_insert(
                parent_shell_view->children_list.prev,
                &shell_view->children_link );
        fx_shell_view_propagate_layer( shell_view );

        link = tmp;
        tmp = link->next;
    }
    for ( ; link != &shell_surface->view_list; link = tmp, tmp = link->next ) {
        shell_view = wl_container_of( link, shell_view, link );
        fx_shell_view_destroy( shell_view );
    }
}
void
fx_shell_surface_unset_relative_to( struct fx_shell_surface *shell_surface )
{
    struct fx_shell_view *shell_view, *tmp;

    if ( shell_surface->parent_shell_surface == NULL ) {
        return;
    }
    shell_surface->parent_shell_surface = NULL;
    wl_list_remove( &shell_surface->children_link );
    wl_list_init( &shell_surface->children_link );
    wl_list_for_each_safe( shell_view, tmp, &shell_surface->view_list, link ) {
        fx_shell_view_destroy( shell_view );
    }
}
void
fx_shell_surface_set_transient(
        struct fx_shell_surface *shell_surface,
        struct fx_shell_surface *transient_parent_shell_surface,
        int32_t x,
        int32_t y )
{
    assert( transient_parent_shell_surface );

    shell_surface->position.x = x;
    shell_surface->position.y = y;
    shell_surface->transient_parent_shell_surface
        = transient_parent_shell_surface;
}
void
fx_shell_surface_popup_grab(
        struct fx_shell_surface *shell_surface,
        struct fx_shell_seat *shell_seat,
        uint32_t serial )
{
    struct wl_client *client =
        fx_shell_client_get_client( shell_surface->shell_client );
    
    if ( fx_shell_seat_popup_grab_start(
                shell_seat,
                client,
                serial ) ) {
        fx_shell_seat_popup_grab_add_surface(
                shell_seat,
                &shell_surface->grab_link );
    } else {
        fx_shell_surface_popup_dismiss( shell_surface );
    }
}
void
fx_shell_surface_popup_ungrab(
        struct fx_shell_surface *shell_surface,
        struct fx_shell_seat *shell_seat )
{
    fx_shell_seat_popup_grab_remove_surface(
            shell_seat,
            &shell_surface->grab_link );
}
void
fx_shell_surface_popup_dismiss( struct fx_shell_surface *shell_surface )
{
    struct fx_shell_view *shell_view, *tmp;

    wl_list_for_each_safe( shell_view, tmp, &shell_surface->view_list, link ) {
        fx_shell_view_destroy( shell_view );
    }
    wl_list_remove( &shell_surface->grab_link );
    wl_list_init( &shell_surface->grab_link );
    fx_shell_surface_close( shell_surface );
}
WL_EXPORT struct fx_shell_client *
fx_shell_surface_get_client(struct fx_shell_surface *shell_surface)
{
    return shell_surface->shell_client;
}
WL_EXPORT void *
fx_shell_surface_get_user_data( struct fx_shell_surface *shell_surface )
{
    return shell_surface->user_data;
}
WL_EXPORT struct weston_surface *
fx_shell_surface_get_surface( struct fx_shell_surface *shell_surface )
{
    return shell_surface->surface;
}
WL_EXPORT bool
fx_shell_surface_get_fullscreen( struct fx_shell_surface *shell_surface )
{
    if ( shell_surface->implementation->get_fullscreen == NULL) return false;

    return shell_surface->implementation->get_fullscreen(
        shell_surface,
        shell_surface->implementation_data );
}
WL_EXPORT struct weston_geometry
fx_shell_surface_get_geometry( struct fx_shell_surface *shell_surface )
{
    if ( shell_surface->has_geometry )
        return shell_surface->geometry;
    return weston_surface_get_bounding_box( shell_surface->surface );
}
int
fx_shell_surafce_get_flags( struct fx_shell_surface *shell_surface )
{
    return shell_surface->flags;
}
void
fx_shell_surafce_set_flags(
        struct fx_shell_surface *shell_surface,
        int flags )
{
    shell_surface->flags = flags;
}
bool
fx_shell_surafce_get_shadow_focus(
        struct fx_shell_surface *shell_surface )
{
    return shell_surface->shadow_focus;
}
void
fx_shell_surafce_set_shadow_focus(
        struct fx_shell_surface *shell_surface,
        bool shadow_focus )
{
    shell_surface->shadow_focus = shadow_focus;
}
bool
fx_shell_surafce_get_anim_enabled(
        struct fx_shell_surface *shell_surface )
{
    return shell_surface->anim;
}
void
fx_shell_surafce_set_anim_enabled(
        struct fx_shell_surface *shell_surface,
        bool anim )
{
    shell_surface->anim = anim;
}
int
fx_shell_surafce_get_layer_index(
        struct fx_shell_surface *shell_surface )
{
    return shell_surface->layer_index;
}
void
fx_shell_surafce_set_layer_index(
        struct fx_shell_surface *shell_surface,
        int layer_index )
{
    shell_surface->layer_index = layer_index;
}

