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

use warnings;
use strict;
use 5.010000;

use Exporter qw( import );
our @EXPORT_OK = qw( map_fmt_to_quality read_config_file options );

use File::Spec::Functions qw( catfile );
use File::Temp            qw();
use FindBin               qw( $RealBin $RealScript );
use List::Util            qw( max );
use Pod::Usage            qw( pod2usage );

use JSON                   qw();
use Term::Choose           qw( choose );
use Term::ReadLine::Simple qw();
use Text::LineFold         qw();


use App::YTDL::GenericFunc qw( term_size print_hash encode_fs choose_a_dir choose_a_number insert_sep );


sub _fmts_sorted {
    return [ 13, 17, 36, 5, 6, 34, 35, 18, 22, 37, 38, 82 .. 85, 43 .. 46, 100 .. 102, 139 .. 141, 160, 133 .. 138, 264, 171 .. 172, 242 .. 248, 271 .. 272];
}


sub map_fmt_to_quality {
    return {
        13 => ' 176x144  3GP',
        17 => ' 176x144  3GP',
        36 => ' 320x240  3GP',

         5 => ' 360x240  FLV', # 400
         6 => ' 480x270  FLV',
        34 => ' 640x360  FLV',
        35 => ' 854x480  FLV',

        18 => ' 640x360  MP4',
        22 => '1280x720  MP4',
        37 => '1920x1080 MP4',
        38 => '4096x3072 MP4',

        43 => ' 640x360  WebM',
        44 => ' 854x480  WebM',
        45 => '1280x720  WebM',
        46 => '1920x1080 WebM',

        82 => ' 640x360  MP4_3D',
        83 => ' 854x480  MP4_3D',
        84 => '1280x720  MP4_3D',
        85 => '1920x1080 MP4_3D',

        100 => ' 640x360  WebM_3D',
        101 => ' 854x480  WebM_3D',
        102 => '1280x720  WebM_3D',

        139 => 'DASH audio   48  M4A',
        140 => 'DASH audio  128  M4A',
        142 => 'DASH audio  256  M4A',


        133 => 'DASH video  240  MP4',
        134 => 'DASH video  360  MP4',
        135 => 'DASH video  480  MP4',
        136 => 'DASH video  720  MP4',
        137 => 'DASH video 1080  MP4',
        138 => 'DASH video 2160  MP4',

        160 => 'DASH video  144  MP4',
        264 => 'DASH video 1440  MP4',

        171 => 'DASH audio   48 WebM',
        172 => 'DASH audio  256 WebM',

        242 => 'DASH video  240 WebM',
        243 => 'DASH video  360 WebM',
        244 => 'DASH video  480 WebM',
        245 => 'DASH video  480 WebM',
        246 => 'DASH video  480 WebM',
        247 => 'DASH video  720 WebM',
        248 => 'DASH video 1080 WebM',
        271 => 'DASH video 1440 WebM',
        272 => 'DASH video 2160 WebM',

    };
}


sub options {
    my ( $opt ) = @_;
    my $help         = "  HELP";
    my $show_path    = "  PATH";
    my $useragent    = "- UserAgent";
    my $overwrite    = "- Overwrite files";
    my $auto_fmt     = "- Set auto quality";
    my $preferred    = "- Preferred qualities";
    my $retries      = "- Download retries";
    my $timeout      = "- Timeout";
    my $logging      = "- Enable logging";
    my $info_width   = "- Max info width";
    my $auto_width   = "- Enable auto width";
    my $filename_len = "- Max filename length";
    my $len_kb_sec   = "- Digits 'k/s'";
    my $yt_video_dir = "- Video directory";
    my %c_hash = (
        $help         => 'show_help_text',
        $show_path    => 'show_path',
        $useragent    => 'useragent',
        $overwrite    => 'overwrite',
        $auto_fmt     => 'auto_quality',
        $preferred    => 'preferred',
        $retries      => 'retries',
        $timeout      => 'timeout',
        $logging      => 'log_info',
        $info_width   => 'max_info_width',
        $auto_width   => 'auto_width',
        $filename_len => 'max_len_f_name',
        $len_kb_sec   => 'kb_sec_len',
        $yt_video_dir => 'yt_video_dir',
    );
    my @choices = (
        $help,
        $show_path,
        $useragent,
        $overwrite,
        $auto_fmt,
        $preferred,
        $retries,
        $timeout,
        $logging,
        $info_width,
        $auto_width,
        $filename_len,
        $len_kb_sec,
        $yt_video_dir,
    );
    my $continue = '  ' . $opt->{continue};
    my $quit     = '  ' . $opt->{quit};

    OPTION: while ( 1 ) {
        # Choose
        print "\n";
        my $c_key = choose(
            [ undef, $continue, @choices ],
            { prompt => "Options:", layout => 3, clear_screen => 1, undef => $quit }
        );
        if ( ! defined $c_key ) {
            _write_config_file( $opt, $opt->{config_file}, values %c_hash ) if $opt->{change};
            exit();
        }
        if ( $c_key eq $continue ) {
            _write_config_file( $opt, $opt->{config_file}, values %c_hash ) if $opt->{change};
            delete $opt->{change};
            last OPTION;
        }
        my $choice = $c_hash{$c_key};
        if ( $choice eq "show_help_text" ) {
            pod2usage( { -exitval => 'NOEXIT', -verbose => 2 } );
        }
        elsif ( $choice eq "show_path" ) {
            my $version      = '  version  ';
            my $bin          = '    bin    ';
            my $yt_video_dir = ' video dir ';
            my $log_file     = ' log file  ';
            my $config_file  = 'config file';
            my $path = {
                $version      => $main::VERSION,
                $bin          => catfile( $RealBin, $RealScript ),
                $yt_video_dir => $opt->{yt_video_dir},
                $log_file     => $opt->{log_file},
                $config_file  => $opt->{config_file},
            };
            my $keys = [ $version, $bin, $yt_video_dir, $log_file, $config_file ];
            print_hash( $path, { keys => $keys, preface => ' Close with ENTER' } );
        }
        elsif ( $choice eq "useragent" ) {
            my $prompt = 'Set the UserAgent: ';
            _local_read_line( $opt, $choice, $prompt );
            $opt->{useragent} = 'Mozilla/5.0' if $opt->{useragent} eq '';
            $opt->{useragent} = ''            if $opt->{useragent} eq '""';
        }
        elsif ( $choice eq "overwrite" ) {
            my $prompt = 'Overwrite files';
            _opt_yes_no( $opt, $choice, $prompt );
        }
        elsif ( $choice eq "log_info" ) {
            my $prompt = 'Enable info-logging';
            _opt_yes_no( $opt, $choice, $prompt );
        }
        elsif ( $choice eq "auto_quality" ) {
            my $list = [
                'choose always manually',
                'keep choice for the respective Playlist/Channel if possible',
                'keep choice always if possible',
                'use preferred qualities',
                'use always default (best) quality',
            ];
            _opt_choose_from_list( $opt, $choice, $list );
        }
        elsif ( $choice eq "preferred" ) {
            _opt_choose_a_list( $opt, $choice, map_fmt_to_quality(), _fmts_sorted() );
        }
        elsif ( $choice eq "retries" ) {
            my $prompt = 'Download retries';
            my $digits = 3;
            _opt_number_range( $opt, $choice, $prompt, 3 )
        }
        elsif ( $choice eq "timeout" ) {
            my $prompt = 'Connection timeout (s)';
            my $digits = 3;
            _opt_number_range( $opt, $choice, $prompt, 3 )
        }
        elsif ( $choice eq "max_info_width" ) {
            my $prompt = 'Maximum Info width';
            my $digits = 3;
            _opt_number_range( $opt, $choice, $prompt, 3 )
        }
        elsif ( $choice eq "auto_width" ) {
            my $prompt = 'Enable auto width';
            _opt_yes_no( $opt, $choice, $prompt );
        }
        elsif ( $choice eq "max_len_f_name" ) {
            my $prompt = 'Maximum filename length';
            my $digits = 3;
            _opt_number_range( $opt, $choice, $prompt, 3 )
        }
        elsif ( $choice eq "kb_sec_len" ) {
            my ( $min, $max ) = ( 3, 9 );
            my $prompt = 'Digits for "k/s" (download speed)';
            _opt_number( $opt, $choice, $prompt, $min, $max );
        }
        elsif ( $choice eq "yt_video_dir" ) {
            my $prompt = 'Video directory';
            _opt_choose_a_directory( $opt, $choice, $prompt );
        }
        else { die $choice }
    }
    return $opt;
}

sub _opt_choose_a_directory {
    my( $opt, $choice, $prompt ) = @_;
    my $new_dir = choose_a_dir( { dir => $opt->{$choice} } );
    return if ! defined $new_dir;
    if ( $new_dir ne $opt->{$choice} ) {
        if ( ! eval {
            my $fh = File::Temp->new( TEMPLATE => 'XXXXXXXXXXXXXXX', UNLINK => 1, DIR => $new_dir );
            1 }
        ) {
            print "$@";
            choose( [ 'Press Enter:' ], { prompt => '' } );
        }
        else {
            $opt->{$choice} = $new_dir;
            $opt->{change}++;
        }
    }
}


sub _local_read_line {
    my ( $opt, $section, $prompt ) = @_;
    my $current = $opt->{$section} // '';
    my $trs = Term::ReadLine::Simple->new();
    # Readline
    my $string = $trs->readline( $prompt, { default => $current } );
    $opt->{$section} = $string;
    $opt->{change}++;
    return;
}


sub _opt_yes_no {
    my ( $opt, $section, $prompt ) = @_;
    my ( $yes, $no ) = ( 'YES', 'NO' );
    my $current = $opt->{$section} ? $yes : $no;
    # Choose
    my $choice = choose(
        [ undef, $yes, $no ],
        { prompt => $prompt . ' [' . $current . ']:', layout => 1, undef => $opt->{s_back} }
    );
    return if ! defined $choice;
    $opt->{$section} = $choice eq $yes ? 1 : 0;
    $opt->{change}++;
    return;
}


sub _opt_number_range {
    my ( $opt, $section, $prompt, $digits ) = @_;
    my $current = $opt->{$section};
    $current = insert_sep( $current ); # $opt->{thsd_sep}
    # Choose_a_number
    my $choice = choose_a_number( $digits, { name => $prompt, current => $current } );
    return if ! defined $choice;
    $opt->{$section} = $choice eq '--' ? undef : $choice;
    $opt->{change}++;
    return;
}



sub _opt_number {
    my ( $opt, $section, $prompt, $min, $max ) = @_;
    my $current = $opt->{$section};
    # Choose
    my $choice = choose(
        [ undef, $min .. $max ],
        { prompt => $prompt . ' [' . $current . ']:', layout => 1, justify => 1, order => 0, undef => $opt->{s_back} }
    );
    return if ! defined $choice;
    $opt->{$section} = $choice;
    $opt->{change}++;
    return;
}

sub _opt_choose_from_list {
    my ( $opt, $section, $list ) = @_;
    my @options = ();
    my $len = length( scalar @$list );
    for my $i ( 0 .. $#$list ) {
        push @options, sprintf "%*d => %s", $len, $i, $list->[$i];
    }
    my $prompt = "$section [" . ( $opt->{$section} // '--' ) . "]";
    my $value = choose( [ undef, @options ], { prompt => $prompt, layout => 3, undef => $opt->{s_back} } );
    return if ! defined $value;
    $value = ( split / => /, $value )[0];
    $opt->{$section} = $value;
    $opt->{change}++;
    return;
}

sub _opt_choose_a_list {
    my ( $opt, $section, $ref, $keys ) = @_;
    my $available = [];
    my $len_key = max map length, @$keys;
    for my $key ( @$keys ) {
        push @$available, sprintf "%*d => %s", $len_key, $key, $ref->{$key};
    }
    my $current = $opt->{$section} // [];
    my $new     = [];
    my $key_cur = 'Current > ';
    my $key_new = '    New > ';
    my $l_k     = length $key_cur > length $key_new ? length $key_cur : length $key_new;
    my $lf      = Text::LineFold->new( %{$opt->{line_fold}} );
    $lf->config( 'ColMax', ( term_size() )[0] );
    while ( 1 ) {
        my $prompt = $key_cur . join( ', ', @$current ) . "\n";
        $prompt   .= $key_new . join( ', ', @$new )     . "\n\n";
        $prompt   .= 'Choose:';
        # Choose
        my $val = choose(
            [ undef, $opt->{confirm}, map( "  $_", @$available ) ],
            { prompt => $prompt, lf => [0,$l_k], layout => 3, clear_screen => 1, undef => $opt->{back} }
        );
        if ( ! defined $val ) {
            if ( @$new ) {
                $new = [];
                next;
            }
            else {
                return;
            }
        }
        if ( $val eq $opt->{confirm} ) {
            if ( @$new ) {
                $opt->{$section} = $new;
                $opt->{change}++;
            }
            return;
        }
        $val =~ s/^\s+//;
        $val = ( split / => /, $val )[0];
        push @$new, $val;
    }
}

sub _write_config_file {
    my ( $opt, $file, @keys ) = @_;
    my $tmp = {};
    for my $section ( sort @keys ) {
        $tmp->{$section} = $opt->{$section};
    }
    _write_json( $file, $tmp );
}


sub read_config_file {
    my ( $opt, $file ) = @_;
    my $tmp = _read_json( $file );
    for my $section ( keys %$tmp ) {
        $opt->{$section} = $tmp->{$section};
    }
    return $opt;
}


sub _write_json {
    my ( $file, $h_ref ) = @_;
    my $json = JSON::XS->new->pretty->encode( $h_ref );
    open my $fh, '>', encode_fs( $file ) or die $!;
    print $fh $json;
    close $fh;
}


sub _read_json {
    my ( $file ) = @_;
    return {} if ! -f encode_fs( $file );
    open my $fh, '<', encode_fs( $file ) or die $!;
    my $json = do { local $/; <$fh> };
    close $fh;
    my $h_ref = JSON::XS->new->pretty->decode( $json ) if $json;
    return $h_ref;
}


1;


__END__