#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • https://build.i3wm.org/docs/testsuite.html
#   (or docs/testsuite)
#
# • https://build.i3wm.org/docs/lib-i3test.html
#   (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • https://build.i3wm.org/docs/ipc.html
#   (or docs/ipc)
#
# • https://i3wm.org/downloads/modern_perl_a4.pdf
#   (unless you are already familiar with Perl)
#
# Test dragging containers.

my ($width, $height) = (1000, 500);

my $config = <<"EOT";
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1

focus_follows_mouse no
floating_modifier Mod1

# 2 side by side outputs
fake-outputs ${width}x${height}+0+0P,${width}x${height}+${width}+0

bar {
    output primary
}
EOT
use i3test i3_autostart => 0;
use i3test::XTEST;
my $pid = launch_with_config($config);

sub start_drag {
    my ($pos_x, $pos_y) = @_;
    die "Drag outside of bounds!" unless $pos_x < $width * 2 && $pos_y < $height;

    $x->root->warp_pointer($pos_x, $pos_y);
    sync_with_i3;

    xtest_key_press(64); # Alt_L
    xtest_button_press(1, $pos_x, $pos_y);
    xtest_sync_with_i3;
}

sub end_drag {
    my ($pos_x, $pos_y) = @_;
    die "Drag outside of bounds!" unless $pos_x < $width * 2 && $pos_y < $height;

    $x->root->warp_pointer($pos_x, $pos_y);
    sync_with_i3;

    xtest_button_release(1, $pos_x, $pos_y);
    xtest_key_release(64); # Alt_L
    xtest_sync_with_i3;
}

my ($ws1, $ws2);
my ($A, $B, $tmp);
my ($A_id, $B_id);

sub move_subtest {
    my ($cb, $win) = @_;

    my @events = events_for($cb, 'window');
    my @move = grep { $_->{change} eq 'move' } @events;

    is(scalar @move, 1, 'Received 1 window::move event');
    is($move[0]->{container}->{window}, $A->{id}, "window id matches");
}

###############################################################################
# Drag floating container onto an empty workspace.
###############################################################################

$ws2 = fresh_workspace(output => 1);
$ws1 = fresh_workspace(output => 0);
$A = open_floating_window(rect => [ 30, 30, 50, 50 ]);

start_drag(40, 40);
end_drag(1050, 50);

is($x->input_focus, $A->id, 'Floating window moved to the right workspace');
is($ws2, focused_ws, 'Empty workspace focused after floating window dragged to it');

###############################################################################
# Drag tiling container onto an empty workspace.
###############################################################################

subtest "Dragging tiling container onto an empty workspace produces move event", \&move_subtest,
sub {

$ws2 = fresh_workspace(output => 1);
$ws1 = fresh_workspace(output => 0);
$A = open_window;

start_drag(50, 50);
end_drag(1050, 50);

is($x->input_focus, $A->id, 'Tiling window moved to the right workspace');
is($ws2, focused_ws, 'Empty workspace focused after tiling window dragged to it');
is(@{get_ws_content($ws1)}, 0, 'No container left in ws1');
is(@{get_ws_content($ws2)}, 1, 'One container in ws2');

};

###############################################################################
# Swap-drag tiling container onto an empty workspace.
###############################################################################

subtest "Swap tiling container with an empty workspace does nothing", sub {

$ws2 = fresh_workspace(output => 1);
$ws1 = fresh_workspace(output => 0);
$A = open_window;

xtest_key_press(50); # Shift
start_drag(50, 50);
end_drag(1050, 50);
xtest_key_release(50); # Shift

is($x->input_focus, $A->id, 'Tiling window still focused');
is($ws1, focused_ws, 'Same workspace focused');
is(@{get_ws_content($ws1)}, 1, 'One container still in ws1');
is(@{get_ws_content($ws2)}, 0, 'No container in ws2');

};

###############################################################################
# Drag tiling container onto a container that closes before the drag is
# complete.
###############################################################################

$ws1 = fresh_workspace(output => 0);
$A = open_window;
open_window;

start_drag(600, 300);  # Start dragging the second window.

# Try to place it on the first window.
$x->root->warp_pointer(50, 50);
sync_with_i3;

cmd '[id=' . $A->id . '] kill';
sync_with_i3;
end_drag(50, 50);

is(@{get_ws_content($ws1)}, 1, 'One container left in ws1');

###############################################################################
# Drag tiling container onto a tiling container on an other workspace.
###############################################################################

subtest "Dragging tiling container onto a tiling container on an other workspace produces move event", \&move_subtest,
sub {

$ws2 = fresh_workspace(output => 1);
open_window;
$B_id = get_focused($ws2);
$ws1 = fresh_workspace(output => 0);
$A = open_window;
$A_id = get_focused($ws1);

start_drag(50, 50);
end_drag(1500, 250);  # Center of right output, inner region.

is($ws2, focused_ws, 'Workspace focused after tiling window dragged to it');
$ws2 = get_ws($ws2);
is($ws2->{focus}[0], $A_id, 'A focused first, dragged container kept focus');
is($ws2->{focus}[1], $B_id, 'B focused second');

};

###############################################################################
# Swap-drag tiling container onto a tiling container on an other workspace.
###############################################################################

subtest "Swap tiling container with a tiling container on an other workspace produces move event", sub {

$ws2 = fresh_workspace(output => 1);
open_window;
$B_id = get_focused($ws2);
$ws1 = fresh_workspace(output => 0);
$A = open_window;
$A_id = get_focused($ws1);

xtest_key_press(50); # Shift
start_drag(50, 50);
end_drag(1500, 250);  # Center of right output, inner region.
xtest_key_release(50); # Shift

is($ws2, focused_ws, 'Workspace focused after tiling window dragged to it');
$ws2 = get_ws($ws2);
is($ws2->{focus}[0], $A_id, 'A focused first, dragged container kept focus');
$ws1 = get_ws($ws1);
is($ws1->{focus}[0], $B_id, 'B now in first workspace');

};

###############################################################################
# Drag tiling container onto a floating container on an other workspace.
###############################################################################

subtest "Dragging tiling container onto a floating container on an other workspace produces move event", \&move_subtest,
sub {

$ws2 = fresh_workspace(output => 1);
open_floating_window;
$B_id = get_focused($ws2);
$ws1 = fresh_workspace(output => 0);
$A = open_window;
$A_id = get_focused($ws1);

start_drag(50, 50);
end_drag(1500, 250);

is($ws2, focused_ws, 'Workspace with one floating container focused after tiling window dragged to it');
$ws2 = get_ws($ws2);
is($ws2->{focus}[0], $A_id, 'A focused first, dragged container kept focus');
is($ws2->{floating_nodes}[0]->{nodes}[0]->{id}, $B_id, 'B exists & floating');

};

###############################################################################
# Drag tiling container onto a bar.
###############################################################################

subtest "Dragging tiling container onto a bar produces move event", \&move_subtest,
sub {

$ws1 = fresh_workspace(output => 0);
open_window;
$B_id = get_focused($ws1);
$ws2 = fresh_workspace(output => 1);
$A = open_window;
$A_id = get_focused($ws2);

start_drag(1500, 250);
end_drag(1, 498);  # Bar on bottom of left output.

is($ws1, focused_ws, 'Workspace focused after tiling window dragged to its bar');
$ws1 = get_ws($ws1);
is($ws1->{focus}[0], $A_id, 'B focused first, dragged container kept focus');
is($ws1->{focus}[1], $B_id, 'A focused second');

};

###############################################################################
# Drag an unfocused tiling container onto it's self.
###############################################################################

$ws1 = fresh_workspace(output => 0);
open_window;
$A_id = get_focused($ws1);
open_window;
$B_id = get_focused($ws1);

start_drag(50, 50);
end_drag(450, 450);

$ws1 = get_ws($ws1);
is($ws1->{focus}[0], $B_id, 'B focused first, kept focus');
is($ws1->{focus}[1], $A_id, 'A focused second, unfocused dragged container didn\'t gain focus');

###############################################################################
# Drag an unfocused tiling container onto an occupied workspace.
###############################################################################

subtest "Dragging unfocused tiling container onto an occupied workspace produces move event", \&move_subtest,
sub {

$ws1 = fresh_workspace(output => 0);
$A = open_window;
$A_id = get_focused($ws1);
$ws2 = fresh_workspace(output => 1);
open_window;
$B_id = get_focused($ws2);

start_drag(50, 50);
end_drag(1500, 250);  # Center of right output, inner region.

is($ws2, focused_ws, 'Workspace remained focused after dragging unfocused container');
$ws2 = get_ws($ws2);
is($ws2->{focus}[0], $B_id, 'B focused first, kept focus');
is($ws2->{focus}[1], $A_id, 'A focused second, unfocused container didn\'t steal focus');

};

###############################################################################
# Drag fullscreen container onto window in same workspace.
###############################################################################

$ws1 = fresh_workspace(output => 0);
open_window;
$A = open_window;
cmd 'fullscreen enable';

start_drag(900, 100);  # Second window
end_drag(50, 50);  # To first window

is($ws1, focused_ws, 'Workspace remained focused after dragging fullscreen container');
is_num_fullscreen($ws1, 1, 'Container still fullscreened');
is($x->input_focus, $A->id, 'Fullscreen container still focused');

###############################################################################
# Drag unfocused fullscreen container onto window in other workspace.
###############################################################################

subtest "Dragging unfocused fullscreen container onto window in other workspace produces move event", \&move_subtest,
sub {

$ws1 = fresh_workspace(output => 0);
$A = open_window;
cmd 'fullscreen enable';
$ws2 = fresh_workspace(output => 1);
open_window;
open_window;

start_drag(900, 100);
end_drag(1000 + 500 * 0.15 + 10, 200);  # left of leftmost window

is($ws2, focused_ws, 'Workspace still focused after dragging fullscreen container to it');
is_num_fullscreen($ws1, 0, 'No fullscreen container in first workspace');
is_num_fullscreen($ws2, 1, 'Moved container still fullscreened');
is($x->input_focus, $A->id, 'Fullscreen container now focused');
$ws2 = get_ws($ws2);
is($ws2->{nodes}->[0]->{window}, $A->id, 'Fullscreen container now leftmost window in second workspace');

};

###############################################################################
# Drag unfocused fullscreen container onto left outer region of window in
# other workspace. The container shouldn't end up in $ws2 because it was
# dragged onto the outer region of the leftmost window. We must also check
# that the focus remains on the other window.
###############################################################################

subtest "Dragging unfocused fullscreen container onto left outer region of window in other workspace produces move event", \&move_subtest,
sub {

$ws1 = fresh_workspace(output => 0);
open_window for (1..3);
$A = open_window;
$tmp = get_focused($ws1);
cmd 'fullscreen enable';
$ws2 = fresh_workspace(output => 1);
$B = open_window;

start_drag(990, 100);  # rightmost of $ws1
end_drag(1004, 100);  # outer region of window of $ws2

is($ws2, focused_ws, 'Workspace still focused after dragging fullscreen container to it');
is_num_fullscreen($ws1, 1, 'Fullscreen container still in first workspace');
is_num_fullscreen($ws2, 0, 'No fullscreen container in second workspace');
is($x->input_focus, $B->id, 'Window of second workspace still has focus');
is(get_focused($ws1), $tmp, 'Fullscreen container still focused in first workspace');
$ws1 = get_ws($ws1);
is($ws1->{nodes}->[3]->{window}, $A->id, 'Fullscreen container still rightmost window in first workspace');

};

exit_gracefully($pid);

###############################################################################

done_testing;
