#!perl
use strict;
use warnings;
use 5.020;
use experimental 'postderef';
use Net::Twitter;
use Path::Tiny;
use JSON;
use Try::Tiny;
use Safe::Isa;
use open ':std',':locale';

use Data::Printer;

sub get_twitter {
    my $config_file = $0 =~ s{\.[^.]+$}{.json}r;
    my $conf = decode_json(path($config_file)->slurp_raw);
    return Net::Twitter->new(traits=>[
        'API::RESTv1_1',
        'AutoCursor',
        'AutoCursor' => {
            max_calls => 15,
            force_cursor => 1,
            array_accessor => 'users',
            methods => [qw(friends followers)],
        },
    ],%$conf);
}

sub fetch_lists_info {
    my ($tw) = @_;

    my $lists = $tw->list_ownerships({
        count => 200,
    });
    my %lists_info = map {
        $_->{id} => {
            name => $_->{name},
        },
    } $lists->{lists}->@*;

    for my $list_id (sort keys %lists_info) {
        my $members = $tw->list_members({
            list_id => $list_id,
            count => 2000,
            skip_status => 1,
            include_entities => 0,
        });
        $lists_info{$list_id}->{members}={
            map { $_->{id} => 1 } $members->{users}->@*,
        };
    }
    return \%lists_info;
}

sub fetch_friends_info {
    my ($tw) = @_;

    my $friends = $tw->friends({
        count => 200,
        skip_status => 1,
        include_user_entities => 0,
    });

    my %friends_info = map {
        $_->{id} => { name => $_->{name} }
    } $friends->@*;

    return \%friends_info;
}

sub cache_file { $0 =~ s{\.[^.]+$}{-cache.json}r };

sub load_info {
    return try { decode_json(path(cache_file)->slurp_raw) };
}

sub save_info {
    path(cache_file)->spew_raw(encode_json({
        lists=>$_[0],
        friends=>$_[1],
    }));
    return;
}

sub to_list {
    sort {
        $a->{name} cmp $b->{name}
    } map {
        { id => $_, $_[0]->{$_}->%* }
    } keys $_[0]->%*
}

sub set_display_name {
    my $max=0;
    for my $e ($_[0]->@*) {
        $e->{dn} = sprintf '%s (%d)',$e->@{qw(name id)};
        my $l = length($e->{dn});
        $max = $l if $max<$l;
    }
    return $max;
}

sub print_friends_lists_matrix {
    my ($li,$fi) = @_;

    my @lists = to_list($li);
    my $list_width = set_display_name(\@lists);
    my @friends = to_list($fi);
    my $friend_width = set_display_name(\@friends);

    print ' ' x ($friend_width+1);
    for my $l (@lists) {
        print $l->{dn},' ';
    }
    print "\n";

    for my $f (@friends) {
        printf '%-*s ',$friend_width,$f->{dn};
        for my $l (@lists) {
            printf '%-*s ',
                length($l->{dn}),
                ( $l->{members}->{$f->{id}}
                  ? '*' : '_' );
        }
        print "\n";
    }
}

sub parse_friends_lists_matrix {
    my ($fn) = @_;

    my @lines = path($fn)->lines_utf8;
    my @list_ids = $lines[0] =~ m{\((\d+)\)}g;
    shift @lines;

    my %lists;
    for my $l (@lines) {
        $l =~ s{\A.*?\((\d+)\)\s+}{} or next;
        my $friend_id = $1;
        
    }
}

my $tw = get_twitter;
try {
    my $lists_info;
    my $friends_info;
    my $info = load_info();
    if ($info) {
        ($lists_info,$friends_info) = $info->@{qw(lists friends)},
    }
    else {
        $lists_info = fetch_lists_info($tw);
        $friends_info = fetch_friends_info($tw);
        save_info($lists_info,$friends_info);
    }
    print_friends_lists_matrix($lists_info,$friends_info);
} catch {
    if ($_->$_isa('Net::Twitter::Error')) {
        my $limit = $tw->rate_limit_status;
        p $limit;
    }
    else { local $@=$_;die }
};
