use strict;
use warnings;
use experimental 'signatures';
use Irssi;

our $VERSION = '0.3'; # f275633fe5f97dd
our %IRSSI = (
    authors     => 'Nei',
    name        => 'soju',
    description => 'soju.im helper script',
    license     => 'ISC',
   );
die 'This script requires Irssi 1.3 or later'
    if (Irssi::parse_special('$abiversion')||0) < 33;

use constant CAP_BOUNCER_NETWORKS        => 'soju.im/bouncer-networks';
use constant CAP_BOUNCER_NETWORKS_NOTIFY => 'soju.im/bouncer-networks-notify';
use constant CAP_BATCH                   => 'batch';

use constant BATCH_OPEN_ID   => 0;
use constant BATCH_OPEN_TYPE => 1;

use Irssi::Irc;

my %soju_nets;
my %redir_batch;
my %batch_open;

sub run_next ($subname, $arg) {
    Irssi::timeout_add_once(10, $subname, $arg);
}

sub argparse ($data) {
    $data =~ s/^:// ? ($data) : do {
	my @args = split ' :', $data, 2;
	unshift @args, split ' ', shift @args;
	@args
    }
}

sub reconnect_find_chatnet ($chatnet) {
    my @reconnects = grep { $_->{chatnet} eq $chatnet } Irssi::reconnects;
    Irssi::print("[soju_debug/$chatnet] found the following reconnects: " . (join ', ', map { $_->{tag} } @reconnects) )
	    if @reconnects;
    return $reconnects[0] if @reconnects;
    return;
}

#####  batch handling  #############################################################################

sub redirect_batch ($server, $start, $type) {
    push $redir_batch{$server->{tag}}{$start}->@*, $type;
}

sub batch_redirect_active ($server, $event, $data, $nick, $address) {
    my $r = $batch_open{$server->{tag}}//[];
    my $type;
    $type = $r->[-1][BATCH_OPEN_TYPE] if $r->@*;
    if ($type) {
	Irssi::signal_emit("redir $type $event", $server, $data, $nick, $address);
	return 1;
    }
    return;
}


sub redir_batch_retrieve ($server, $start) {
    return unless $start;
    my $r = $redir_batch{$server->{tag}}//+{};
    my $s = $r->{$start}//[];
    shift $s->@*
}

sub open_batch ($server, $id, $type) {
    push $batch_open{$server->{tag}}->@*, [ $id, $type ];
    if ($type) {
	Irssi::signal_emit("redir $type batch open", $server, $id, '', '');
    }
}

sub close_batch ($server, $id) {
    while (($batch_open{$server->{tag}}//[])->@*) {
	my $r = pop $batch_open{$server->{tag}}->@*;
	my ($rid, $type) = $r->@*;
	if ($type) {
	    Irssi::signal_emit("redir $type batch close", $server, $rid, '', '');
	}
	last if $id eq $rid;
    }
}

sub event_batch ($server, $data, $nick, $address) {
    my ($batchid, $start, @rest) = argparse($data);
    if ($batchid =~ s/^[+]//) {
	my $type = redir_batch_retrieve($server, $start);
	open_batch($server, $batchid, $type);
    } elsif ($batchid =~ s/^-//) {
	close_batch($server, $batchid);
    }
}

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

sub sync_bnc ($server) {
    return unless exists $server->{cap_supported}{+CAP_BOUNCER_NETWORKS};

    if (defined $server->isupport('bouncer_netid')) {
	# we're in a singlenet connection
    } else {
	my @missing_caps = grep { my $c = $_; not grep { $c eq $_ } $server->{cap_active}->@* }
	    (CAP_BOUNCER_NETWORKS_NOTIFY, CAP_BOUNCER_NETWORKS, CAP_BATCH);
	$server->send_raw_now("CAP REQ :@missing_caps")
	    if @missing_caps;
	redirect_batch($server, +CAP_BOUNCER_NETWORKS, 'auto');
	$server->send_raw_now('BOUNCER LISTNETWORKS');
    }
}

sub event_bouncer ($server, $data, $nick, $address) {
    return if batch_redirect_active($server, 'bouncer', $data, $nick, $address);

    if ($data =~ s/(\S+)(?:\s|$)//) {
	Irssi::signal_emit("event bouncer \L$1", $server, $data, $nick, $address);
    } else {
	$server->print(undef, MSGLEVEL_CRAP, "BOUNCER $data");
    }
}

sub bnc_update_config ($server, $netid, $newconfig) {
    my $config = $soju_nets{$server->{tag}}{$netid} //= +{};
    %$config = (%$config, %$newconfig);
    $config
}

sub bnc_autoconfig ($server, $netid, $newconfig) {
    my $config = bnc_update_config($server, $netid, $newconfig);

    my $client = Irssi::settings_get_str('soju_client_id');
    my $netname = "$server->{tag}|$config->{name}";
    $netname =~ s/[^a-zA-Z0-9|_-]+/_/g;
    my $username = "$server->{username}/$config->{name}\@$client";
    my $net = Irssi::chatnet_find($netname);
    unless ($net && $net->{username} eq $username) {
	Irssi::command("^network add "
		       ."-user $username "
		       ."$netname");
    }
    if ($config->{state} eq 'connected') {
	unless (Irssi::server_find_chatnet($netname)) {
	    Irssi::print("[soju_debug/$netname] Found a soju connected event for $netname / $netid");
	    Irssi::print("[soju_debug/$netname] Current Irssi connection state:");
	    Irssi::command("server");
	    Irssi::print("[soju_debug/$netname] Currently configured Irssi networks:");
	    Irssi::command("network");
	    Irssi::print("[soju_debug/$netname] ===================================");
	    my $reconnect = reconnect_find_chatnet($netname);
	    if ($reconnect) {
		Irssi::print("[soju_debug/$netname] reconnecting $reconnect->{tag}");
		Irssi::command("reconnect $reconnect->{tag}");
	    } else {
		Irssi::command("^server add "
			       ."-network $netname "
			       ."-".($server->{use_tls} ? "" : "no")."tls "
			       ."$server->{address} "
			       ."$server->{port} "
			       ."$server->{password}");
		Irssi::print("[soju_debug/$netname] connecting $netname");
		Irssi::command("connect $netname");
	    }
	}
    } elsif ($config->{state} eq 'disconnected') {
	my $s = Irssi::server_find_chatnet($netname);
	if ($s) {
	    $s->command('disconnect');
	} else {
	    my $r = reconnect_find_chatnet($netname);
	    Irssi::command("disconnect $r->{tag}")
		    if $r;
	}
    } elsif ($config->{state} eq 'connecting') {
	$server->printformat('', MSGLEVEL_CRAP, 'soju_connecting', $config->{name}, $netid, $config->{address}, $config->{port});
    }
    if ($config->{error}) {
	$server->printformat('', MSGLEVEL_CRAP, 'soju_connection_error', $config->{name}, $config->{error},
			     $netid // '(unknown netid)', $config->{address} // '(unknown address)', $config->{port} // '(unknown port)');
    }
}

sub event_bouncer_network ($server, $data, $, $) {
    my ($netid, $configstr) = argparse($data);
    my $config = Irssi::Irc::parse_message_tags($configstr);
    bnc_autoconfig($server, $netid, $config);
}

sub redir_auto_bouncer ($server, $data, $nick, $address) {
    if ($data =~ s/(\S+)(?:\s|$)//) {
	Irssi::signal_emit("redir auto bouncer \L$1", $server, $data, $nick, $address);
    } else {
	# unknown, ignore
    }
}

sub redir_auto_bouncer_network ($server, $data, $, $) {
    my ($netid, $configstr) = argparse($data);
    my $config = Irssi::Irc::parse_message_tags($configstr);
    bnc_autoconfig($server, $netid, $config);
}

# sub redir_auto_batch_open ($server, $data, $, $) {
# }

# sub redir_auto_batch_close ($server, $data, $, $) {
# }

sub redir_listnet_bouncer ($server, $data, $nick, $address) {
    if ($data =~ s/(\S+)(?:\s|$)//) {
	Irssi::signal_emit("redir listnet bouncer \L$1", $server, $data, $nick, $address);
    } else {
	# unknown, ignore
    }
}

sub redir_listnet_bouncer_network ($server, $data, $, $) {
    my ($netid, $configstr) = argparse($data);
    my $newconfig = Irssi::Irc::parse_message_tags($configstr);
    my $config = bnc_update_config($server, $netid, $newconfig);
    my %config_opts = %$config;
    delete @config_opts{qw(host port name tls state)};
    my $settings = join ', ', $config->{state}, ($config->{tls} ? 'tls' : ()),
	map { "$_: $config_opts{$_}" } sort keys %config_opts;
    $server->printformat('', MSGLEVEL_CRAP, 'soju_server_line',
			 $netid, $config->{host}, $config->{port} // '', $config->{name} // '', $settings // '');
}

sub redir_listnet_batch_open ($server, $data, $, $) {
    $server->printformat('', MSGLEVEL_CRAP, 'soju_server_header');
}

sub redir_listnet_batch_close ($server, $data, $, $) {
    $server->printformat('', MSGLEVEL_CRAP, 'soju_server_footer');
}

sub bnc_disconnect_all ($tag) {
    return if Irssi::server_find_tag($tag);
    for my $s (Irssi::servers) {
	if ($s->{chatnet} =~ /^\Q$tag\E\|/) {
	    $s->command('disconnect');
	}
    }
}

sub server_disconnected ($server) {
    if (exists $server->{cap_supported}{+CAP_BOUNCER_NETWORKS}) {

	if (defined $server->isupport('bouncer_netid')) {
	    # we're in a singlenet connection
	} else {
	    run_next('bnc_disconnect_all', $server->{tag});
	}
    }

    delete $_->{$server->{tag}} for
	(\(%soju_nets, %redir_batch, %batch_open));
}

sub is_soju_master ($server) {
    $server->isa('Irssi::Irc::Server')
	&& exists $server->{cap_supported}{+CAP_BOUNCER_NETWORKS}
	&& !defined $server->isupport('bouncer_netid')
}

sub is_soju_parent ($server, $subserver) {
    is_soju_master($server)
	&& $server->{address} eq $subserver->{address}
	&& $subserver->{chatnet} =~ /^\Q$server->{tag}\E\|/
}

sub find_soju ($server, $complain) {
    if (exists $server->{cap_supported}{+CAP_BOUNCER_NETWORKS}) {
	if (defined $server->isupport('bouncer_netid')) {
	    my @sojus = grep { is_soju_parent($_, $server) } Irssi::servers;
	    return $sojus[0] if @sojus == 1;
	} else {
	    return $server;
	}
    } else {
	my @sojus = grep { is_soju_master($_) } Irssi::servers;
	return $sojus[0] if @sojus == 1;
    }
    if ($complain) {
	print CLIENTERROR 'Active server is not a soju connection';
    }
    return;
}

sub cmd_soju ($data, $server, $witem) {
    if ($data =~ /^-/) {
	my @tags = map { $_->{tag} } Irssi::servers;
	my $tag_re = join '|', map { "\Q$_" } sort { length $b <=> length $a } @tags;
	if ($data =~ s/^-($tag_re)(?:\s+|$)//i) {
	    $server = Irssi::server_find_tag($1);
	}
    }
    Irssi::command_runsub('soju', $data, $server, $witem);
}

sub cmd_soju_network ($data, $server, $witem) {
    $server = find_soju($server, 1)
	or return;
    redirect_batch($server, +CAP_BOUNCER_NETWORKS, 'listnet');
    $server->send_raw_now('BOUNCER LISTNETWORKS');
}

sub event_motd_start ($server, $, $, $) {
    sync_bnc($server);
}

sub init {
    for my $s (Irssi::servers) {
	next unless $s->isa('Irssi::Irc::Server');
	sync_bnc($s);
    }
}

# Irssi::signal_register({'complete command ' => [qw[glistptr_char* Irssi::UI::Window string string intptr]]});

Irssi::settings_add_str('soju', 'soju_client_id', 'irssi');

Irssi::signal_add({
    # at this point, 005 is finished
    'event 375'                     => 'event_motd_start',
    'event 422'                     => 'event_motd_start', # no motd
    'server disconnected'           => 'server_disconnected',
    'event batch'                   => 'event_batch',
    'event bouncer'                 => 'event_bouncer',
    'event bouncer network'         => 'event_bouncer_network',
    # 'redir auto batch open'       => 'redir_auto_batch_open',
    'redir auto bouncer'            => 'redir_auto_bouncer',
    'redir auto bouncer network'    => 'redir_auto_bouncer_network',
    # 'redir auto batch close'      => 'redir_auto_batch_close',
    'redir listnet batch open'      => 'redir_listnet_batch_open',
    'redir listnet bouncer'         => 'redir_listnet_bouncer',
    'redir listnet bouncer network' => 'redir_listnet_bouncer_network',
    'redir listnet batch close'     => 'redir_listnet_batch_close',
});

Irssi::theme_register([
    'soju_connecting'       => 'Connecting to {server $0}',
    'soju_connection_error' => 'Error in connection {server $0} {reason $1}',
    'soju_server_header'    => '%##  Soju Server          Port  Network    Settings',
    'soju_server_line'      => '%#%|$[!2]0 $[!20]1 $[5]2 $[10]3 %|$4',
    'soju_server_footer'    => '',
   ]);

Irssi::command_bind({
    'soju'         => 'cmd_soju',
    'soju network' => 'cmd_soju_network',
});

init();
