The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package # hide from PAUSE
App::YTDL::YTData;

use warnings;
use strict;
use 5.010000;

use Exporter qw( import );
our @EXPORT_OK = qw( get_data get_new_video_url choose_from_list_and_add_to_info wrapper_get );

use File::Which            qw( which );
use IPC::System::Simple    qw( capture );
use JSON                   qw( decode_json );
use LWP::UserAgent         qw();
use Term::ANSIScreen       qw( :screen );
use Term::Choose           qw( choose );
use Term::ReadLine::Simple qw();
use Try::Tiny              qw( try catch );

use if $^O eq 'MSWin32', 'Win32::Console::ANSI';

use App::YTDL::GenericFunc qw( sec_to_time insert_sep );

sub HIDE_CURSOR () { "\e[?25l" }
sub SHOW_CURSOR () { "\e[?25h" }


sub wrapper_get {
    my ( $opt, $info, $url ) = @_;
    my $show_progress = 1;
    my $ua = LWP::UserAgent->new( agent => $opt->{useragent}, timeout => $opt->{timeout}, show_progress => $show_progress );
    my ( $retry, $continue ) = ( 'Retry', 'Continue' );
    my $choice;
    my $res;
    while ( 1 ) {
        $choice = $continue;
        try {
            $res = $ua->get( $url );
            die $res->status_line, ': ', $url if ! $res->is_success;
        }
        catch {
            my $prompt = "\n$_\n";
            $choice = choose(
                [ undef, $retry, $continue ],
                { prompt => $prompt, layout => 3, clear_screen => 0, undef => 'Exit' }
            );
        };
        exit if ! $choice;
        redo if $choice eq $retry;
        return $res;
    }
}


sub get_new_video_url {
    my ( $opt, $info, $video_id ) = @_;
    my $fmt = $info->{$video_id}{fmt};
    my $youtube_dl = which( 'youtube-dl' ) // 'youtube-dl';
    my @cmd = ( $youtube_dl );
    push @cmd, '--user-agent', $opt->{useragent} if defined $opt->{useragent};
    push @cmd, '--socket-timeout', $opt->{timeout};
    #push @cmd, '-v';
    push @cmd, '--format', $fmt, '--get-url', '--', $video_id;
    my ( $retry, $continue ) = ( 'Retry', 'Continue' );
    my $choice;
    my $video_url;
    while ( 1 ) {
        $choice = $continue;
        try {
            $video_url = capture( @cmd );
        }
        catch {
            my $prompt = "\n$_\n";
            $choice = choose(
                [ undef, $retry, $continue ],
                { prompt => $prompt, layout => 3, clear_screen => 0, undef => 'Exit' }
            );
        };
        exit if ! $choice;
        redo if $choice eq $retry;
        return $video_url;
    }
}


sub get_data {
    my ( $opt, $info, $video_id ) = @_;
    my $youtube_dl = which( 'youtube-dl' ) // 'youtube-dl';
    my @cmd = ( $youtube_dl );
    push @cmd, '--user-agent', $opt->{useragent} if defined $opt->{useragent};
    push @cmd, '--socket-timeout', $opt->{timeout};
    #push @cmd, '-v';
    push @cmd, '--dump-json', '--', $video_id;
    my $capture;
    my $message = "** GET download info: ";
    my $spinner;
    try {
        require Term::Twiddle;
        $spinner = Term::Twiddle->new();
    }
    catch {
        $spinner = undef;
    };
    my ( $retry, $continue ) = ( 'Retry', 'Continue' );
    my $choice;

    while ( 1 ) {
        $choice = $continue;
        try {
            if ( $spinner ) {
                print HIDE_CURSOR;
                print $message;
                $spinner->thingy( [ "[\\]", "[|]", "[/]", "[-]" ] );
                $spinner->start;
                $capture = capture( @cmd );
                $spinner->stop;
                print "\r", clline;
                print $message . "done.\n";
                print SHOW_CURSOR;
            }
            else {
                print $message . "...";
                $capture = capture( @cmd );
                print "\r", clline;
                print $message . "done.\n";
            }
        }
        catch {
            my $prompt = "\n$_\n";
            $choice = choose(
                [ undef, $retry, $continue ],
                { prompt => $prompt, layout => 3, clear_screen => 0, undef => 'Exit' }
            );
        };
        exit if ! $choice;
        redo if $choice eq $retry;
        last;
    }
    $opt->{up}++; ##
    my @json = split /\n+/, $capture;
    my $is_list = @json > 1 ? 1 : 0;
    my $list_id;
    my $playlist_id = $info->{$video_id}{playlist_id};
    if ( $is_list ) {
        delete $info->{$video_id};
        $list_id = 'OT_' . $video_id;
    }
    else {
        $list_id = $info->{$video_id}{list_id};
    }
    my $ids;
    my $tmp = {};
    for my $json ( @json ) {
        my $h_ref = decode_json( $json );
        my $fmt_list;
        my $formats;
        for my $format ( @{$h_ref->{formats}} ) {
            my $format_id = $format->{format_id};           # fmt
            push @$fmt_list, $format_id;
            $formats->{$format_id}{ext}         = $format->{ext};
            $formats->{$format_id}{format}      = $format->{format};
            $formats->{$format_id}{format_note} = $format->{format_note};
            $formats->{$format_id}{height}      = $format->{height};
            $formats->{$format_id}{width}       = $format->{width};
            $formats->{$format_id}{url}         = $format->{url};
        }
        if ( $is_list ) {
            $video_id = $h_ref->{id} // $h_ref->{title};
        }
        push @$ids, $video_id;
        $tmp->{$video_id} = {
            video_id        => $video_id,
            id              => $h_ref->{id},
            #age_limit       => $h_ref->{age_limit},
            #annotations     => $h_ref->{annotations},
            author          => $h_ref->{uploader},          # author user
            categories      => $h_ref->{categories},
            channel_id      => $h_ref->{uploader_id},       # channel_id
            description     => $h_ref->{description},
            default_fmt     => $h_ref->{format_id},
            dislike_count   => $h_ref->{dislike_count},
            duration_raw    => $h_ref->{duration},          # duration_raw
            extractor       => $h_ref->{extractor},
            extractor_key   => $h_ref->{extractor_key},
            #fulltitle       => $h_ref->{fulltitle},
            like_count      => $h_ref->{like_count},
            playlist        => $h_ref->{playlist},
            playlist_id     => $playlist_id,
            #playlist_index  => $h_ref->{playlist_index},
            published_raw   => $h_ref->{upload_date},       # published_raw
            #stitle          => $h_ref->{stitle},
            title           => $h_ref->{title},
            view_count      => $h_ref->{view_count},
        };
        $tmp->{$video_id}{fmt_to_info} = $formats;
        $tmp->{$video_id}{fmt_list}    = $fmt_list;
        $tmp->{$video_id}{list_id}     = $list_id;
        $tmp = _prepare_info_hash( $tmp, $video_id );
        if ( defined $tmp->{$video_id}{extractor_key} && $tmp->{$video_id}{extractor_key} =~ /^youtube\z/i) {
            $tmp->{$video_id}{youtube} = 1;
        }
    }
    if ( $is_list ) {
        $info = choose_ids_from_list( $opt, $info, $tmp, $ids );
    }
    else {
        my ( $video_id ) = keys %$tmp;
        $info->{$video_id} = $tmp->{$video_id};
    }
    return $info;
}


sub choose_from_list_and_add_to_info {
    my ( $opt, $info, $tmp, $ids ) = @_;
    my $nr = 2;
    my $regexp;
    my $c;
    FILTER: while ( 1 ) {
        my @video_print_list;
        my @tmp_video_ids;
        my @video_ids = grep { $_ ne $opt->{back} }
                    sort {    ( $tmp->{$a}{published} // '' ) cmp ( $tmp->{$b}{published} // '' )
                           || ( $tmp->{$a}{title}     // '' ) cmp ( $tmp->{$b}{title}     // '' ) } @$ids;
        VIDEO_ID: for my $video_id ( @video_ids, $opt->{back} ) {
            ( my $title = $tmp->{$video_id}{title} ) =~ s/\s+/ /g;
            $title =~ s/^\s+|\s+\z//g;
            if ( length $regexp && $title !~ /$regexp/i ) {
                next VIDEO_ID if $video_id ne $opt->{back};
            }
            push @video_print_list, sprintf "%11s | %7s  %10s  %s", $video_id, $tmp->{$video_id}{duration},
                                                                    $tmp->{$video_id}{published}, $title;
            push @tmp_video_ids, $video_id;
        }
        my @pre = ( 'FILTER' . ( $c == $nr ? ' (last if empty)' : '' ) );
        my @idx = choose(
            [ @pre, @video_print_list ],
            { prompt => 'Your choice: ', layout => 3, index => 1, clear_screen => 1, no_spacebar => [ 0, $#video_print_list + @pre ] }
        );
        return if ! @idx || ! defined $idx[0];
        return if $idx[-1] == $#video_print_list + @pre;
        if ( $idx[0] == 0 ) {
            my $trs = Term::ReadLine::Simple->new();
            $regexp = $trs->readline( "Regexp: " );
            $c++ if defined $regexp && $regexp eq '';
            last FILTER if $c > $nr;
            next FILTER;
        }
        for my $i ( @idx ) {
            $i -= @pre;
            my $video_id = $tmp_video_ids[$i];
            $info->{$video_id} = $tmp->{$video_id};
        }
        last FILTER;
    }
    #return $info;
}


sub _prepare_info_hash {
    my ( $info, $video_id ) = @_;
    if ( defined $info->{$video_id}{duration_raw} ) {
        if ( $info->{$video_id}{duration_raw} =~ /^[0-9]+\z/ ) {
            $info->{$video_id}{duration} = sec_to_time( $info->{$video_id}{duration_raw}, 1 );
        }
        else {
            $info->{$video_id}{duration} = $info->{$video_id}{duration_raw};
        }
    }
    if ( $info->{$video_id}{published_raw} ) {
        if ( $info->{$video_id}{published_raw} =~ /^(\d{4})(\d{2})(\d{2})\z/ ) {
            $info->{$video_id}{published} = $1 . '-' . $2 . '-' . $3;
        }
        else {
            $info->{$video_id}{published} = $info->{$video_id}{published_raw};
        }
    }
    if ( $info->{$video_id}{channel_id} ) {
        if ( ! $info->{$video_id}{author} ) {
            $info->{$video_id}{author} = $info->{$video_id}{channel_id};
        }
        elsif ( $info->{$video_id}{author} ne $info->{$video_id}{channel_id} ) {
            $info->{$video_id}{author} .= ' (' . $info->{$video_id}{channel_id} . ')';
        }
    }
    if ( $info->{$video_id}{like_count} && $info->{$video_id}{dislike_count} ) {
        $info->{$video_id}{raters} = $info->{$video_id}{like_count} + $info->{$video_id}{dislike_count};
        $info->{$video_id}{avg_rating} = $info->{$video_id}{like_count} * 5 / $info->{$video_id}{raters};
        $info->{$video_id}{avg_rating} = sprintf "%.2f", $info->{$video_id}{avg_rating};
        $info->{$video_id}{raters} = insert_sep( $info->{$video_id}{raters} );
    }
    if ( $info->{$video_id}{view_count} ) {
        $info->{$video_id}{view_count} = insert_sep( $info->{$video_id}{view_count} );
    }
    if ( defined $info->{$video_id}{extractor} || defined $info->{$video_id}{extractor_key} ) {
        $info->{$video_id}{extractor}     = $info->{$video_id}{extractor_key} if ! defined $info->{$video_id}{extractor};
        $info->{$video_id}{extractor_key} = $info->{$video_id}{extractor}     if ! defined $info->{$video_id}{extractor_key};
    }
    return $info;
}





1;


__END__