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

our $VERSION = '0.0.7'; # ed788a27f375179
our %IRSSI = (
    authors	=> 'Nei',
    name	=> 'account_expando',
    description	=> 'show account information when available',
    license	=> 'ISC',
   );
die "This script requires Irssi 1.3 or later"
    if (Irssi::parse_special('$abiversion')||0) < 36;

# inspired by account-notify.pl by Mike Quin

# Usage
# =====
# after loading the script, several expandos are provided for your
# usage (see below). You have to add them to your /format pubmsg and
# related formats, wherever you want to see the account indicator
#
# To ease the installation, the script provides several convenience
# functions to install the expandos to a defined preset:
#
#   /script exec Irssi::Script::account_expando::install
#   /script exec Irssi::Script::account_expando::install_marker
#   /script exec Irssi::Script::account_expando::install_accountonly
#   /script exec Irssi::Script::account_expando::uninstall
#
# Do not forget to /save if you are happy with the installation.
#
# Note that due to the Irssi /format system, this script naturally
# conflicts with nickcolor.pl, nm.pl and similar scripts that use
# /format
#
# You can use this script together with nm2.pl (and
# nickcolor_expando.pl) if your nm2.pl script version is 2.2 or
# higher. In that case, distribute the a, A, and i style indicators
# into the neat_style and neat_action_style formats

# Expandos
# ========
# The following expandos are available:
#
# $account_tag
# * whether the IRCv3 account message-tag was used to deliver the
#   account
#
# $has_account
# * an indicator whether the current nick has an account or not (or
#   unknown)
#
# $maybe_account
# * contains the formatted account name if available (and it is not
#   equal to the nick, but see Options)
#
# $nick_or_account
# * contains the account name if available, otherwise the nick name
#
# $ae_space
# * contains a space when $has_account contains a value
#

# Options
# =======
# /set no_account_networks <list>
# * list of networks without accounts, the expandos will remain empty
#   on them
#
# /set account_hide_if_equal <ON|OFF>
# * whether the $maybe_account expando should remain empty if the nick
#   is equal to the account
#
# /format account_(no_)tag <string>
# * the values to show in the $account_tag expando
#
# /format account_present <string>
# /format account_not_present <string>
# /format account_unknown <string>
# * the values to show in the $has_account expando
#
# /format account_display <string>
# * how to render the account name in the $maybe_account expando
#     $0 : the account
#

use constant CAP_ACCOUNT_TAG => 'account-tag';

our ($nick, $from_tag, $account);
our ($has_account, $maybe_account, $nick_or_account);

my %S;
my %no_account;
my %account_tag;

sub lc1459 {
    my $x = shift;
    $x =~ y/][\\^/}{|~/;
    lc $x
}

sub expando_has_account {
    if (not defined $account) {
	''
    } elsif ($account eq '*') {
	$S{not_present}
    } elsif (length $account) {
	$S{present}
    } else {
	$S{unknown}
    }
}

sub expando_maybe_account {
    if (not length $account) {
	''
    } elsif ($S{hide_equal} && lc1459($account) eq lc1459($nick)) {
	''
    } elsif (length $account && $account ne '*') {
	Irssi::parse_special($S{display}, $account)
    }
}

sub expando_nick_or_account {
    if (defined $account && length $account && $account ne '*') {
	$account
    } elsif (defined $nick) {
	$nick
    } else {
	'¿'
    }
}

sub expando_account_tag {
    if (not defined $from_tag) {
	''
    } elsif ($from_tag) {
	$S{tag}
    } else {
	$S{no_tag}
    }
}

sub expando_ae_space {
    my ($server) = @_;
    unless ($server) {
	''
    } elsif (exists $no_account{ $server->{chatnet} } || !$server->can('isupport') || !defined $server->isupport('whox')) {
	''
    } else {
	' '
    }
}

sub sig_expando_incoming ($server, $line, $in_nick, $address, $tags_str) {
    return unless defined $in_nick;

    my ($_from_tag, $_account);
    if (exists $no_account{ $server->{chatnet} } || !$server->can('isupport') || !defined $server->isupport('whox')) {
	$_from_tag = undef;
	$_account = undef;
    } elsif ($account_tag{ $server->{tag} }) {
	$_from_tag = 1;
	my $tags = Irssi::Irc::parse_message_tags($tags_str // '');
	$_account = $tags->{account} // '*';
    } else {
	$_from_tag = 0;
	my (undef, $nick) = $server->nicks_get_same($in_nick);
	$_account = $nick->{account};
    }
    local $nick = $in_nick;
    local $from_tag = $_from_tag;
    local $account = $_account;
    &Irssi::signal_continue;
}

sub request_cap_account_tag ($server) {
    return unless $server->can('irc_server_cap_toggle');

    $server->irc_server_cap_toggle(CAP_ACCOUNT_TAG, 1);
}

sub sig_disconnected ($server) {
    delete $account_tag{ $server->{tag} };
}

sub sig_cap_ack ($server) {
    $account_tag{ $server->{tag} } = 1;
}

sub sig_cap_deack ($server) {
    $account_tag{ $server->{tag} } = 0;
}

sub _get_format {
    Irssi::current_theme->get_format(__PACKAGE__, @_);
}

sub update_formats {
    $S{tag}	    = Irssi::format_string_expand(_get_format('account_tag'));
    $S{no_tag}	    = Irssi::format_string_expand(_get_format('account_no_tag'));
    $S{present}	    = Irssi::format_string_expand(_get_format('account_present'));
    $S{not_present} = Irssi::format_string_expand(_get_format('account_not_present'));
    $S{unknown}	    = Irssi::format_string_expand(_get_format('account_unknown'));
    $S{display}	    = Irssi::format_string_expand(_get_format('account_display'));
}

sub setup_changed {
    $S{hide_equal} = Irssi::settings_get_bool('account_hide_if_equal');
    %no_account = map { ($_ => 1) } map { (split ',', $_) } split ' ',
	Irssi::settings_get_str('no_account_networks');
}

sub init {
    update_formats();
    setup_changed();
    for my $server (Irssi::servers) {
	request_cap_account_tag($server);
	if (grep { $_ eq CAP_ACCOUNT_TAG() } @{ $server->{cap_active} || [] }) {
	    sig_cap_ack($server);
	}
    }
}

Irssi::theme_register([
  'account_tag'		=> 'T',
  'account_no_tag'	=> 'C',
  'account_present'	=> '%Gª%n',
  'account_not_present'	=> '%r~%n',
  'account_unknown'	=> '%Y?%n',
  'account_display'	=> '%k∵$0%n',
 ]);

Irssi::settings_add_str('misc', 'no_account_networks', 'EFNet IRCnet');
Irssi::settings_add_bool('misc', 'account_hide_if_equal', 1);

Irssi::signal_add({
    'server event tags' => 'sig_expando_incoming',
    'event connected' => 'request_cap_account_tag',
    'server disconnected' => 'sig_disconnected',
    'server cap ack ' . CAP_ACCOUNT_TAG() => 'sig_cap_ack',
    'server cap ack -' . CAP_ACCOUNT_TAG() => 'sig_cap_deack',
});
Irssi::signal_add_last({
    'setup changed'  => 'setup_changed',
    'theme changed'  => 'update_formats',
    'command format' => 'update_formats',
});

Irssi::expando_create('account_tag'	=> \&expando_account_tag, { 'server incoming' => 'none' });
Irssi::expando_create('has_account'	=> \&expando_has_account, { 'server incoming' => 'none' });
Irssi::expando_create('maybe_account'	=> \&expando_maybe_account, { 'server incoming' => 'none' });
Irssi::expando_create('nick_or_account' => \&expando_nick_or_account, { 'server incoming' => 'none' });
Irssi::expando_create('ae_space'        => \&expando_ae_space, { 'window changed' => 'none',
								 'window connect changed' => 'window',
								 'window server changed' => 'window' });

init();

my %formats = (
    action_public	   => [4, '{pubaction '      ,'$0',''                      ,'}','$1' ],
    action_public_channel  => [4, '{pubaction '      ,'$0','{msgchannel $1}'       ,'}','$2' ],
    action_private	   => [4, '{pvtaction '      ,'$0',''                      ,'}','$2' ],
    action_private_query   => [4, '{pvtaction_query ','$0',''                      ,'}','$2' ],

    notice_public	   => [6, '{notice '         ,'$0','{pubnotice_channel $1}','}','$2' ],
    notice_private	   => [6, '{notice '         ,'$0','{pvtnotice_host $1}'   ,'}','$2' ],
    #                          * *                   * #  *                   *

    msg_private		   => [2, '{privmsg '        ,''  ,''             ,'$0','' ,' $1'            ,'}','$2' ],
    msg_private_query	   => [2, '{privmsgnick '    ,''  ,''             ,'$0','' ,''               ,'}','$2' ],
    pubmsg_me		   => [0, '{pubmsgmenick '   ,'$2',' {menick '    ,'$0','}',''               ,'}','$1' ],
    pubmsg_me_channel	   => [0, '{pubmsgmenick '   ,'$3',' {menick '    ,'$0','}','{msgchannel $1}','}','$2' ],
    pubmsg_hilight	   => [0, '{pubmsghinick $0 ','$3',' '            ,'$1', '','',              ,'}','$2' ],
    pubmsg_hilight_channel => [0, '{pubmsghinick $0 ','$4',' '            ,'$1', '','{msgchannel $2}','}','$3' ],
    pubmsg		   => [0, '{pubmsgnick '     ,'$2',' {pubnick '   ,'$0','}',''               ,'}','$1' ],
    pubmsg_channel	   => [0, '{pubmsgnick '     ,'$3',' {pubnick '   ,'$0','}','{msgchannel $1}','}','$2' ],
    #                          * *                   *    *               * #  *   *                 *   *

    ctcp_reply		   => [8, 'CTCP {hilight $0} reply from '   ,'{nick '    ,'$1', '}',''                        ,': $2' ],
    ctcp_reply_channel	   => [8, 'CTCP {hilight $0} reply from '   ,'{nick '    ,'$1', '}',' in channel {channel $3}',': $2' ],
    ctcp_ping_reply	   => [8, 'CTCP {hilight PING} reply from ' ,'{nick '    ,'$0', '}',''                        ,': $1.$[-3.0]2 seconds' ],

    ctcp_requested	   => [8, '{ctcp '                          ,'{hilight ' ,'$0', '}',' {comment $1} requested CTCP {hilight $2} from {nick $4}}'        ,': $3' ],
    ctcp_requested_unknown => [8, '{ctcp '                          ,'{hilight ' ,'$0', '}',' {comment $1} requested unknown CTCP {hilight $2} from {nick $4}}',': $3' ],
   );

sub do_install ($impl, $ext) {
    Irssi::signal_remove('command format' => 'update_formats');
    Irssi::signal_remove('theme changed'  => 'update_formats');
    for my $fmt (sort keys %formats) {
	my $fs = join '', $impl->(@{ $formats{$fmt} });
	Irssi::command("^format $fmt $fs");
    }
    $ext->();
    Irssi::signal_add_last({
	'theme changed'  => 'update_formats',
	'command format' => 'update_formats',
    });
}

sub install {
    do_install(sub ($t, @fs) {
		   $fs[ 1 ]
		       = '$has_account' . $fs[ 1 ];

		   $fs[ $t <= 2 ? 4 : $t <= 6 ? 1 : 3 ]
		       .= '$maybe_account';

		   @fs
	       }, sub {
		   Irssi::command('^format neat_style  , , pi , , c , t , a , , ');
		   Irssi::command('^format neat_action_style  , pi , , ta , ');
	       });
}

sub install_marker {
    do_install(sub ($t, @fs) {
		   $fs[ 1 ]
		       = '$has_account' . $fs[ 1 ];

		   @fs
	       }, sub {
		   Irssi::command('^format neat_style  , , pi , , c , t , , , ');
		   Irssi::command('^format neat_action_style  , pi , , t , ');
	       });
}

sub install_accountonly {
    do_install(sub ($t, @fs) {
		   $fs[ 1 ]
		       = '$has_account' . $fs[ 1 ];

		   $fs[ $t <= 2 ? 3 : $t <= 6 ? 1 : 2 ]
		       = '$nick_or_account';

		   @fs
	       }, sub {
		   Irssi::command('^format neat_style  , , pi , , c , At , , , ');
		   Irssi::command('^format neat_action_style  , pi , , At , ');
	       });
}

sub uninstall {
    do_install(sub ($t, @fs) {
		   @fs
	       }, sub {
		   Irssi::command('^format neat_style  , , p , , c , t , , , ');
		   Irssi::command('^format neat_action_style  , p , , t , ');
	       });
}
