#!/usr/bin/env perl
#
# needs package perl-json
#

use IO::Socket::UNIX;
use JSON;
use Encode;
use Getopt::Long;

use constant MAGIC_STRING => 'i3-ipc';

use constant TYPE_COMMAND => 0;
use constant TYPE_GET_WORKSPACES => 1;
use constant TYPE_GET_TREE => 4;

#
# ipc functions
#
sub message {
    my ($type, $content) = @_;
    my $payload = '';

    if ($content) {
        $payload = encode_utf8($content);
    }

    return(MAGIC_STRING . pack('LL', length($payload), $type) . $payload);
}

sub ipc_request {
    my ($sock, $type, $content) = @_;

    # send request
    $sock->write(message($type, $content));

    # read response header
    my $header = '';
    $sock->read($header, length(MAGIC_STRING) + 4 + 4);

    # get payload length
    my ($len, $type) = unpack('LL', substr($header, length(MAGIC_STRING)));

    # read payload
    my $payload = '';
    $sock->read($payload, $len);

    # return decoded json
    return decode_json $payload;
}

#
# tree functions
#
sub get_node_by_name {
    my ($ref, $name) = @_;

    # search for a direct child node by name
    foreach my $node (@{${${ref}}{'nodes'}}) {
        if (${${node}}{'name'} eq $name) {
            return $node;
        }
    }
}

sub pop_window_array {
    my ($ref, $arr) = @_;
    my @node_types = qw(nodes floating_nodes);

    # iterate through all child nodes
    foreach my $node_type (@node_types) {
        foreach my $node (@{${${ref}}{$node_type}}) {
            # if child node is not a window
            if (${${node}}{'window'} eq undef) {
                # go one level down
                pop_window_array($node, \@{$arr});
            } else {
                # add window id to array
                push(@{$arr}, ${${node}}{'id'});
            }
        }
    }
}

sub get_focused_node {
    my $ref = shift;
    my $ret = '';
    my @node_types = qw(nodes floating_nodes);

    # iterate through all child nodes
    foreach my $node_type (@node_types) {
        foreach my $node (@{${${ref}}{$node_type}}) {
            # if child node is not a window
            if (${${node}}{'window'} eq undef) {
                # go one level down
                $ret = get_focused_node($node);
                if ($ret != '') {
                    return $ret;
                }
            } else {
                # return focused window id
                if (${${node}}{'focused'}) {
                    return ${${node}}{'id'};
                }
            }
        }
    }
    return '';
}

#
# main
#

my $reverse;
GetOptions("reverse" => \$reverse);

# get socket path
chomp(my $path = qx(i3 --get-socketpath));

# open connection
my $sock = IO::Socket::UNIX->new(Peer => $path);

# get current workspaces
my $response = '';
$response = ipc_request($sock, TYPE_GET_WORKSPACES);

# get focused output and workspace
my $i3output = '';
my $i3workspace = '';
foreach my $node (@{$response}) {
    if (${$node}{'focused'}) {
        $i3output = ${$node}{output};
        $i3workspace = ${$node}{num};
    }
}

# get layout tree
my $response = '';
$response = ipc_request($sock, TYPE_GET_TREE);

my @windows, $focused, $prev, $next;
if (${$response}{'name'} eq 'root') {
    my $output = get_node_by_name($response, $i3output);
    my $content = get_node_by_name($output, 'content');
    my $workspace = get_node_by_name($content, $i3workspace);

    # populate window array
    pop_window_array($workspace, \@windows);

    # get focused window
    $focused = get_focused_node($workspace);

    # find window key for focused window
    my $focused_key;
    while (my ($key, $val) = each @windows) {
        if ($val eq $focused) {

            # get previous window
            if ($key == 0) {
                $prev = $windows[$#windows];
            } else {
                $prev = $windows[$key-1];
            }

            # get next window
            if ($key == $#windows) {
                $next = $windows[0];
            } else {
                $next = $windows[$key+1];
            }
        }
    }

}

#print $prev . "\n" . $focused . "\n" . $next . "\n";

my $response = '';
if ($reverse) {
    # set focus to previous window
    $response = ipc_request($sock, TYPE_COMMAND, '[con_id="' . $prev . '"] focus');
} else {
    # set focus to next window
    $response = ipc_request($sock, TYPE_COMMAND, '[con_id="' . $next . '"] focus');
}

close($sock);

# exit with ipc command status
if (${$response}[0]{'success'}) {
    exit(0);
} else {
    exit(1);
}

