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::YTInfo;

use warnings;
use strict;
use 5.010000;

use Exporter qw( import );
our @EXPORT_OK = qw( get_download_infos );

use Encode                qw( decode_utf8 );
use File::Spec::Functions qw( catfile );
use List::Util            qw( max );

use List::MoreUtils   qw( any );
use LWP::UserAgent    qw();
use Term::ANSIScreen  qw( :cursor :screen );
use Term::Choose      qw( choose );
use Text::LineFold    qw();
use Try::Tiny         qw( try catch );
use Unicode::GCString qw();
use URI               qw();
use URI::Escape       qw( uri_unescape );

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

use App::YTDL::YTConfig    qw( map_fmt_to_quality );
use App::YTDL::YTData      qw( get_data );
use App::YTDL::YTXML       qw( xml_to_entry_node );
use App::YTDL::GenericFunc qw( term_size unicode_trim encode_stdout_lax );

use constant {
    QUIT   => -1,
    DELETE => -2,
    APPEND => -3,
    REDO   => -4,
};


sub get_download_infos {
    my ( $opt, $info ) = @_;
    my ( $cols, $rows ) = term_size();
    print "\n\n\n", '=' x $cols, "\n\n", "\n" x $rows;
    print locate( 1, 1 ), cldown;
    say 'Dir  : ', $opt->{yt_video_dir};
    say 'Agent: ', $opt->{useragent} // '';
    print "\n";
    my @video_ids = sort {
           ( $info->{$b}{extractor}     // ''  ) cmp ( $info->{$a}{extractor}     // ''  )
        || ( $info->{$a}{list_id}       // ''  ) cmp ( $info->{$b}{list_id}       // ''  )
        || ( $info->{$a}{published_raw} // '0' ) cmp ( $info->{$b}{published_raw} // '0' )
        || ( $info->{$a}{title}         // ''  ) cmp ( $info->{$b}{title}         // ''  )
    } keys %$info;
    my $fmt;
    my $count = 0;
    $opt->{up} = 0;
    VIDEO:
    while ( @video_ids ) {
        my $video_id = shift @video_ids;
        my ( $print_array, $key_len, $failed );
        try {
            $info = _get_print_info( $opt, $info, $video_id ) if $info->{$video_id}{youtube};
            ( $info, $print_array, $key_len ) = _format_print_info( $opt, $info, $video_id );
            print "\n";
            $opt->{up}++;
            binmode STDOUT, ':pop';
            print for map { encode_stdout_lax( $_ ) } @$print_array;
            binmode STDOUT, ':encoding(console_out)';
            $opt->{up} += @$print_array;
            print "\n";
            $opt->{up}++;
        }
        catch {
            say "$video_id - $_";
            choose( [ 'Press ENTER' ], { prompt => '' } );
            delete  $info->{$video_id};
            $failed = 1;
        };
        next VIDEO if $failed;
        if ( $info->{$video_id}{youtube} ) {
            my $status = $info->{$video_id}{status};
            if ( ! defined $status || $status ne 'ok' ) {
                _status_not_ok( $opt, $info, $video_id );
                next VIDEO;
            }
        }
        try { #
            my $edit;
            ( $info, $fmt, $edit ) = _choose_quality( $opt, $info, $fmt, $video_id );
            print up( $opt->{up} ), cldown;
            $opt->{up} = 0;
            if ( defined $edit ) {
                if ( $edit == QUIT ) {
                    print locate( 1, 1 ), cldown;
                    say "Quit";
                    exit;
                }
                if ( $edit == DELETE ) {
                    delete  $info->{$video_id};
                    if ( ! @video_ids ) {
                        print up( 2 ), cldown;
                        print "\n";
                    }
                }
                push    @video_ids, $video_id if $edit == APPEND;
                unshift @video_ids, $video_id if $edit == REDO;
            }
            else {
                $info->{$video_id}{video_url} = $info->{$video_id}{fmt_to_info}{$fmt}{url};
                $info->{$video_id}{file_name} = catfile( $opt->{yt_video_dir}, _get_filename( $opt, $info, $fmt, $video_id ) );
                $info->{$video_id}{count}     = ++$count;
                $info->{$video_id}{fmt}       = $fmt;
                $print_array->[0] =~ s/\n\z/ ($fmt)\n/;
                unshift @$print_array, sprintf "%*.*s : %s\n", $key_len, $key_len, 'video', $count;
                binmode STDOUT, ':pop';
                print for map { encode_stdout_lax( $_ ) } @$print_array;
                binmode STDOUT, ':encoding(console_out)';
                print "\n";
            }
        }
        catch {
            say "$video_id - $_";
            choose( [ 'Press ENTER' ], { prompt => '' } );
            delete  $info->{$video_id};
        };
    }
    print "\n";
    return $info, $count;
}


sub _status_not_ok {
    my ( $opt, $info, $video_id ) = @_;
    $info->{$video_id}{status} //= 'undefined';
    my @keys    = ( 'title', 'video_id', 'status', 'errorcode', 'reason' );
    my $key_len = 10;
    my $s_tab   = $key_len + length( ' : ' );
    my $maxcols =  ( term_size() )[0] - $opt->{right_margin};
    my $col_max = $maxcols > $opt->{max_info_width} ? $opt->{max_info_width} : $maxcols;
    my $lf = Text::LineFold->new( %{$opt->{linefold}} );
    $lf->config( 'ColMax', $col_max );
    my $prompt = '  Status NOT OK!' . "\n\n";
    my $print_array;
    for my $key ( @keys ) {
        next if ! $info->{$video_id}{$key};
        $info->{$video_id}{$key} =~ s/\n+/\n/g;
        $info->{$video_id}{$key} =~ s/^\s+//;
        ( my $kk = $key ) =~ s/_/ /g;
        my $pr_key = sprintf "%*.*s : ", $key_len, $key_len, $kk;
        $prompt .= $lf->fold( '' , ' ' x $s_tab, $pr_key . $info->{$video_id}{$key} );
    }
    choose( [ 'Press ENTER' ], { prompt => $prompt } );
    delete $info->{$video_id};
    print up( $opt->{up} ), cldown;
    $opt->{up} = 0;
}


sub _get_print_info {
    my ( $opt, $info, $video_id ) = @_;
    $info = get_data( $opt, $info, $video_id );
    my $ua = LWP::UserAgent->new( agent => $opt->{useragent}, show_progress => 1 );
    my $info_url = URI->new( 'https://www.youtube.com/get_video_info' );
    $info_url->query_form( 'video_id' => $video_id );
    my $res = $ua->get( $info_url->as_string );
    die "$res->status_line: $info_url" if ! $res->is_success;
    $opt->{up}++;
    for my $item ( split /&/, $res->decoded_content ) {
        my ( $key, $value ) = split /=/, $item;
        if ( defined $value && $key =~ /^(?:title|keywords|reason|status)\z/ ) {
            $info->{$video_id}{$key} = decode_utf8( uri_unescape( $value ) );
            $info->{$video_id}{$key} =~ s/\+/ /g;
            $info->{$video_id}{$key} =~ s/(?<=\p{Word}),(?=\p{Word})/, /g if $key eq 'keywords';
        }
    }
    return $info;
}


sub _format_print_info {
    my ( $opt, $info, $video_id ) = @_;
    my @keys = ( qw( title video_id ) );
    push @keys, 'extractor' if ! $info->{$video_id}{youtube};
    push @keys, qw( author duration raters avg_rating view_count published content description keywords );
    for my $key ( @keys ) {
        next if ! defined $info->{$video_id}{$key};
        $info->{$video_id}{$key} =~ s/\R/ /g;
    }
    my $key_len = 13;
    my $s_tab = $key_len + length( ' : ' );
    my ( $maxcols, $maxrows ) = term_size();
    $maxcols -= $opt->{right_margin};
    my $col_max = $maxcols > $opt->{max_info_width} ? $opt->{max_info_width} : $maxcols;
    my $lf = Text::LineFold->new( %{$opt->{linefold}} );
    $lf->config( 'ColMax', $col_max );
    my $print_array;
    for my $key ( @keys ) {
        next if ! $info->{$video_id}{$key};
        $info->{$video_id}{$key} =~ s/\n+/\n/g;
        $info->{$video_id}{$key} =~ s/^\s+//;
        ( my $kk = $key ) =~ s/_/ /g;
        my $pr_key = sprintf "%*.*s : ", $key_len, $key_len, $kk;
        my $text = $lf->fold( '' , ' ' x $s_tab, $pr_key . $info->{$video_id}{$key} );
        $text =~ s/\R+\z//;
        for my $val ( split /\R+/, $text ) {
            push @$print_array, $val . "\n";
        }
    }
    if ( $opt->{auto_width} ) {
        my $ratio = @$print_array / $maxrows;
        my $begin = 0.70;
        my $end   = 1.50;
        my $step  = 0.0125;
        my $div   = ( $end - $begin ) / $step + 1;
        my $plus;
        if ( $ratio >= $begin ) {
            $ratio = $end if $ratio > $end;
            $plus = int( ( ( $maxcols - $col_max ) / $div ) * ( ( $ratio - $begin  ) / $step + 1 ) );
        }
        if ( $plus ) {
            $col_max += $plus;
            $lf->config( 'ColMax', $col_max );
            $print_array = [];
            for my $key ( @keys ) {
                next if ! $info->{$video_id}{$key};
                ( my $kk = $key ) =~ s/_/ /g;
                my $pr_key = sprintf "%*.*s : ", $key_len, $key_len, $kk;
                my $text = $lf->fold( '' , ' ' x $s_tab, $pr_key . $info->{$video_id}{$key} );
                $text =~ s/\R+\z//;
                for my $val ( split /\R+/, $text ) {
                    push @$print_array, $val . "\n";
                }
            }
        }
        if ( @$print_array > ( $maxrows - 6 ) ) {
            $col_max = $maxcols;
            $lf->config( 'ColMax', $col_max );
            $print_array = [];
            for my $key ( @keys ) {
                next if ! $info->{$video_id}{$key};
                ( my $kk = $key ) =~ s/_/ /g;
                my $pr_key = sprintf "%*.*s : ", $key_len, $key_len, $kk;
                my $text = $lf->fold( '' , ' ' x $s_tab, $pr_key . $info->{$video_id}{$key} );
                $text =~ s/\R+\z//;
                for my $val ( split /\R+/, $text ) {
                    push @$print_array, $val . "\n";
                }
            }
        }
    }
    return $info, $print_array, $key_len;
}


sub _get_filename {
    my ( $opt, $info, $fmt, $video_id ) = @_;
    my $title  = $info->{$video_id}{title};
    my $suffix = $info->{$video_id}{fmt_to_info}{$fmt}{ext};
    my $gcs_suff = Unicode::GCString->new( $suffix );
    my $gcs_fmt  = Unicode::GCString->new( $fmt );
    my $len = $opt->{max_len_f_name} - ( $gcs_suff->columns() + $gcs_fmt->columns() + 2 );
    my $file_name = unicode_trim( $title, $len );
    $file_name = $file_name . '_' . $fmt . '.' . $suffix;
    $file_name =~ s/\s/_/g;
    $file_name =~ s/^\s+|\s+\z//g;
    $file_name =~ s/^\.+//;
    #$file_name =~ s/[^\p{Word}.()]/-/g;
    $file_name =~ s/["\/\\:*?<>|]/-/g;
    # NTFS and FAT unsupported characters:  / \ : " * ? < > |
    return $file_name;
}


sub _choose_quality {
    my ( $opt, $info, $fmt, $video_id ) = @_;
    my @avail_fmts = @{$info->{$video_id}{fmt_list}};
    if ( ! @avail_fmts ) {
        my $ref = map_fmt_to_quality;
        for my $fmt ( sort keys %$ref ) {
            push @avail_fmts, $fmt if $info->{$video_id}{fmt_to_info}{$fmt}{url};
        }
        if ( ! @avail_fmts ) {
            my $prompt = 'video_id ' . $video_id . ': Error in fetching available fmts.' . "\n";
            $prompt .= 'Skipping video "' . $info->{$video_id}{title} . '".';
            choose( [ 'Press Enter to continue' ], { prompt => $prompt } );
            $fmt = DELETE;
            return $info, $fmt;
        }
    }
    my $skip_pq = $opt->{auto_quality} == 3 && ! $info->{$video_id}{youtube} ? 1 : 0;
    my ( $fmt_ok, $edit );
    if ( $opt->{auto_quality} == 4 ) {
        if ( defined $info->{$video_id}{default_fmt} ) {
            $fmt = $info->{$video_id}{default_fmt};
            $fmt_ok = 1;
        }
    }
    elsif ( $opt->{auto_quality} == 3 ) {
        my @pref_qualities = @{$opt->{preferred}//[]};
        if ( ! @pref_qualities ) {
            print "\n";
            $opt->{up}++;
            say 'video_id: ' . $video_id . ' - no preferred qualities found!';
            $opt->{up}++;
        }
        else {
            for my $pq ( @pref_qualities ) {
                if ( any{ $pq eq $_ } @avail_fmts ) {
                    $fmt = $pq;
                    $fmt_ok = 1;
                    last;
                }
            }
            if ( ! $fmt_ok ) {
                print "\n";
                $opt->{up}++;
                say 'video_id: ' . $video_id . ' - no matches between preferred fmts and available fmts!';
                $opt->{up}++;
            }
        }
    }
    elsif ( $opt->{auto_quality} == 2 || $skip_pq ) {
        if ( ! defined $opt->{aq} ) {
            ( $fmt, $edit ) = _set_fmt( $opt, $info, $video_id );
            $opt->{aq} = $fmt if $fmt >= 0;
            $fmt_ok = 1;
        }
        elsif ( any{ $_ eq $opt->{aq} } @avail_fmts ) {
            $fmt = $opt->{aq};
            $fmt_ok = 1;
        }
    }
    elsif ( $opt->{auto_quality} == 1 ) {
        if ( $info->{$video_id}{list_id} ) {
            my $aq = $info->{$video_id}{list_id};
            if ( ! defined $opt->{$aq} ) {
                ( $fmt, $edit ) = _set_fmt( $opt, $info, $video_id );
                if ( defined $fmt ) {
                    $opt->{$aq} = $fmt;
                    $fmt_ok = 1;
                }
            }
            elsif ( any{ $_ eq $opt->{$aq} } @avail_fmts ) {
                $fmt = $opt->{$aq};
                $fmt_ok = 1;
            }
        }
    }
    if ( ! $fmt_ok ) {
        ( $fmt, $edit ) = _set_fmt( $opt, $info, $video_id );
    }
    if ( ! defined $edit && ! $info->{$video_id}{fmt_to_info}{$fmt}{url} ) {
        my $prompt = 'video_id "' . $video_id . '": fmt ' . $fmt . ' not supported.';
        choose( [ 'Press ENTER to continue' ], { prompt => $prompt } );
        $edit = DELETE;
    }
    return $info, $fmt, $edit;
}


sub _set_fmt {
    my ( $opt, $info, $video_id ) = @_;
    my ( @choices, @format_ids );
    if ( $info->{$video_id}{youtube} ) {
        for my $fmt ( sort { $a <=> $b } @{$info->{$video_id}{fmt_list}} ) {
            push @choices, $info->{$video_id}{fmt_to_info}{$fmt}{format} . ' ' . $info->{$video_id}{fmt_to_info}{$fmt}{ext};
            push @format_ids, $fmt;
        }
    }
    else {
        for my $fmt ( sort @{$info->{$video_id}{fmt_list}} ) {
            push @choices, $info->{$video_id}{fmt_to_info}{$fmt}{format} . ' ' . $info->{$video_id}{fmt_to_info}{$fmt}{ext};
            push @format_ids, $fmt;
        }
    }
    my @pre = ( undef );
    print "\n";
    $opt->{up}++;
    my $fmt_res_idx = choose(
        [ @pre, @choices ],
        { prompt => 'Your choice: ', index => 1, order => 0, undef => 'Menu' }
    );
    my $fmt;
    my $edit;
    if ( ! $fmt_res_idx ) {
        my ( $delete, $append, $redo ) = ( 'Delete', 'Append', 'Redo' );
        my $choice = choose(
            [ undef, $delete, $append, $redo ],
            { prompt => 'Your choice: ', undef => $opt->{quit} }
        );
        $edit = QUIT   if ! defined $choice;
        $edit = DELETE if $choice eq $delete;
        $edit = APPEND if $choice eq $append;
        $edit = REDO   if $choice eq $redo;
    }
    else {
        $fmt_res_idx--;
        $fmt = $format_ids[$fmt_res_idx];
    }
    return $fmt, $edit;
}



1;


__END__