#!/usr/bin/env perl

# Needs zenity and mplayer.
# (commands can be changed in %CMDS).

use 5.18.0;

BEGIN {
    use File::Basename;
    unshift @INC, dirname $0;
    $0 = basename $0;
}

use utf8;

use JSON 'decode_json';

use Fish::Utility;
use Fish::Utility_l qw, slurp slurp_try false forkk def list pushr ,; 
use Fish::Opt 'opt';
use Fish::Class 'o', 'class';
Fish::Utility->verbose_cmds(0);

# to and cc are lists.
class mail => [qw, from to cc message_id is_known_sender ,];

sub process(_);
sub mail_obj(_);

my $DBUS = "/tmp/dbus-session-bus-address";
my $FILE_IDS = '/tmp/.new-mail-ids';

my $STRING = "ঈ %s « %s";
my $TO_JOIN = ", ";

my %CMDS = (
    sound => "mplayer $ENV{HOME}/d/sounds/mail",

    # Timeout doesn't work right. It doesn't affect how long the notify
    # stays up. But it's necessary, with value of 1, or else zenity
    # doesn't return.
    notify => 'zenity --notification --timeout 1 --text %s',
);

my $USAGE = "Usage: $0 [-t to test] [-w for show warnings]";
my $IDS_LIMIT_RESTART = 50;

my %ACCT_NICKNAMES = (
    'allen@netherrealm.net'             => 'me',
    'otheraccount@gmail.com'            => 'other account',
);

my $opt = opt {
    h   => 'f',
    t   => 'f',
    w   => 'f',
} or error($USAGE);

info($USAGE), 
    exit if $opt->h;

my $g = o(
    show_warnings => ($opt->w // 0),
    known => 0,
    done_ids => {},
);

{
    my @json;

    restore_dbus();

    my $known = 0;
    my %done_ids = $opt->t ? () : get_done_ids();
    $g->done_ids(\%done_ids);

    my $json = get_data({ test => $opt->t });

    # can die.
    my $data = decode_json $json;

    ref $data eq 'ARRAY' or 
        error 'Bad input';

    $known |= process mail_obj for @$data;
    $g->known($known);

    # forks.
    play_sound() if $known;
}

# tell mutt whether to beep.
$| = 1; # autoflush.

printf "%d", $g->known ? 1 : 0; # printf means no punctuation magic

exit;

# - - - -

sub mail_obj(_) {
    my $mail = shift;

    (defined $mail and ref $mail eq 'HASH') or 
        return war 'Bad input (inner)';
 
    my $mo = mail->new;
    for my $k (list $mo->_->keysr) { # from, to, etc.
        my $v = $mail->{$k} //
            return war 'Bad input: missing field', BR $k;
        $mo->$k($v);
    }

    $mo

}

sub process(_) {
    my $mo = shift;

    return 0 unless $mo;

    my $known = 0;

    my $to_ary = $mo->to;
    if (my $cc_ary = $mo->cc) {
        pushr $to_ary for list $cc_ary;
    }


    my @to = map { $ACCT_NICKNAMES{$_} // s, @.+ ,,xr } @$to_ary;

    return 0 unless $mo->is_known_sender;

    my $msg_id = $mo->message_id;

    return 0 if exists $g->done_ids->{$msg_id};

    $known = 1;

    my $fh_ids = safeopen ">>$FILE_IDS";
    say $fh_ids $msg_id;

    my $to_str = join $TO_JOIN, @to;
    my $notify_str = sprintf $STRING, $to_str, $mo->from;

    # forks.
    notify($notify_str);

    $known
}

sub get_done_ids {
    return () unless -e $FILE_IDS;

    my @ids = split m, \n ,x, slurp $FILE_IDS;

    if (@ids > $IDS_LIMIT_RESTART) {
        safeopen ">$FILE_IDS";
        return ();
    }

    map { $_ => 1 } @ids
}

sub restore_dbus {
    # If we lose connection to dbus, e.g. running mutt in a screen which is
    # continually detached/attached, this can help to make zenity still work.

    if (my $c = slurp_try $DBUS) {
        $ENV{DBUS_SESSION_BUS_ADDRESS} = $c;
    }
}

# Some commands don't like '&', so fork inside notify() and play_sound().

sub notify {
    my $str = shift;
    my $n = $CMDS{notify} //
        return iwar;

    # this is the kid.
    if (false forkk) {
        my $cmd = sprintf $n, shell_quote $str;
        # zenity has warnings and non-zero exit (so silence them)
        sys $cmd,, { verbose => 0, die => 0, quiet => 1, killerr => 1}; 
        exit;
    }
}

sub play_sound {
    my $cmd = $CMDS{sound} // 
        return iwar;

    # this is the kid.
    if (false forkk) {
        sys $cmd, { quiet => 1, killerr => 1, die => 0 };
        exit;
    }
}

sub get_data {
    my $opt = shift;
    my $json;
    if ($opt->{test}) {
        local $/ = undef;
        $json = <DATA>;
    }
    else {
        my @json;
        while (def my $in = <STDIN>) {
            last if $in eq ".\n";
            push @json, strip $in;
        }
        $json = join ' ', @json;
    }

    $json
}

__DATA__
[
 { 
  "from": "friend@friend.net",
  "to": [ "allen@netherrealm.net", "him@her.com" ],
  "cc": [ "her@him.com" ],
  "is_known_sender": "1",
  "message_id": "<bsdbfadf>"
 },
 {
  "from": "AvoidWrinkles@basepurebella.work",
  "to": [ "otheraccount@gmail.com", "him@her.com" ],
  "cc": [],
  "is_known_sender": "0",
  "message_id": "<asdbfadf>"
 },
 {
  "from": "friend@friend.net",
  "to": [ "otheraccount@gmail.com" ],
  "cc": [ "her@him.com" ],
  "is_known_sender": "1",
  "message_id": "<asdbfadf>"
 }
]
