#! env perl

#  expskip 
#    Producec by Toshiyuki Shimono, Tokyo., 2016-01 ~ 07 , 2018-3
#    最初は、ファイルの先頭と最後の3行のみを出していたが、
#    途中を指数関数的な行番号を出すようにしてみた。
#     ファイル名は headtail, pickall, expskip と変遷している。

use 5.014 ; use warnings ;
use Getopt::Std ; getopts "b:e:f:gtp:qzA:B:E",\my%o ; 
use Term::ANSIColor qw[ color :constants ] ; $Term::ANSIColor::AUTORESET = 1 ; 
use FindBin qw [ $Script ] ; 
eval "use PerlIO::gzip;1" or die "Can't import PerlIO::gzip despite -z instruction. ($Script)\n" if $o{z} ; 

sub lineOut ( $ ) ; # lineOutのような関数名いくつかのうち、どれかが使われる。
sub eachFile ( $ ) ; 
sub niceNumber ( $ ) ; 
$| = 1 ; 
my (@nums0, @nums) ;  
$o{b} //= 10 ; # 基数の指定
$o{e} //= 2 ;  # 最初と最後のそれぞれ何行を出力するか
$o{f} //= 1 ;  # 開始行を指定する。 
$o{A} //= 0 ;  # 合致する行の何行後までさらに続けて出力するか 
$o{B} //= 0 ;  # 合致する行の何行前にさかのぼって続けて出力するか

& Init ; 
& traverse ;  
exit 0 ;

# (出力する)書式の指定

sub Init ( ) { 

  sub lineOutSub ( $ ) ; # lineOutのような関数名いくつかのうち、どれかが使われる。
  sub lineOutColon ( $ ) { $_[0]->[0], ":\t", $_[0]->[1] }   # 行番号にコロン(:) を付加して出力
  sub lineOutG ( $ ) { GREEN $_[0]->[0] , RESET "\t", $_[0]->[1] }   
  sub lineOutQ ( $ ) { $_[0]->[1] }   
  sub lineOutTime ( $ ) { sprintf("%02d:%02d:%02d\t", @{[localtime]}[2,1,0]), lineOutSub $_[0] }
  * lineOutSub = $o{g} ? * lineOutG : $o{q} ? * lineOutQ : * lineOutColon ; 
  * lineOut = $o{t}? *lineOutTime : * lineOutSub ;

  select * STDERR if $o{E} ; #<-- BOLD DARKを STDERRの後に追加するとどうなるか?
  @nums0 = do { 
   my %t =(0=>['Inf'],1=>[1],2=>[1,2,4,8],5=>[1,2,5],7=>[1,1.5,2,3,5,7],8=>[1,1.5,2,3,5,8],9=>[1..$o{b}-1]) ; 
   my @t = @{$t{$o{p}//1}} ; 
   grep {$_ < $o{b} || $_ == 'Inf'} @t 
  } ;
}

sub traverse ( ) { 
  my $fnFlag = @ARGV > 1 ; 
  while ( 1 ) { 
    my $fn = shift @ARGV  ; # ファイル名
    my $fh ; # ファイルハンドル
    if ( defined $fn ) { 
        open $fh , "<" , $fn or warn "File `$fn' does not open." and next ; 
    } else { 
        $fh = *STDIN ;
    }
    binmode $fh , ":gzip(autopop)" if $o{z}  ; 
    say $fn if $fnFlag ;
    eachFile $fh ; 
    close $fh ; 
    last if ! @ARGV  ;
    print "\n" ; # ファイル間の空行
  }
}

sub eachFile ( $ ) { 
  #$. = 0 ;
  @nums = @nums0 ;
  my $fh = $_[0] ; 
  my $rd ; # この数が正なら出力する。キリの良い数などのトリガーで一定値が格納されて、1ずつ減る仕組み。
  my @stockLines ; # いくつかの行の、文字列を格納する。
  * flag_E = $o{E} ? sub { print STDOUT $_ } :sub {} ; # -E の時はSTDERRに出しつつも、そのままteeのように出す
  # 最初の方は、まず一定量読む。
  if ( $o{e} > 0 ) { 
    while ( <$fh> ) { 
     & flag_E ;
     push @stockLines , [ $. , $_ ]  ; 
     last if $. >= $o{e} ; 
    }
  }
  print lineOut $_ for @stockLines ;

  # 条件に一致するもののみ出力する。
  while ( <$fh> ) { 
     & flag_E ;
     push @stockLines , [ $. , $_ ]  ; # pushとshiftを対にしてFIFOのような仕組み
     my $lf = shift @stockLines ; 
     $rd = $o{A}+1 if niceNumber $lf->[0] ;
     print lineOut $lf if $rd -- > 0  && $lf->[0] > $o{e} ; 
  }
  print lineOut $_ for grep { $_->[0] > $o{e} } @stockLines ;   # 最後に残っているものを書き出す
}

# 関数
sub niceNumber ( $ ) { 
    my $head = shift @nums ;    # 数珠を回すようなイメージで処理をする
    while ( $head < $_[0] ) { push @nums , $head * $o{b} ; $head = shift @nums }
    if ( $head < $_[0] + 1 ) { push @nums , $head * $o{b} ; return $_[0] >= $o{f} } # $_[0]がfloor $head に一致
    unshift @nums, $head ; 
    return $_[0] >= $head - $o{B} && $_[0] <= $head + $o{A} && $_[0] >= $o{f} ;  
}

## ヘルプの扱い
sub VERSION_MESSAGE {}
sub HELP_MESSAGE {
    use FindBin qw[ $Script ] ; 
    $ARGV[1] //= '' ;
    open my $FH , '<' , $0 ;
    while(<$FH>){
        s/\$0/$Script/g ;
        print $_ if s/^=head1// .. s/^=cut// and $ARGV[1] =~ /^o(p(t(i(o(ns?)?)?)?)?)?$/i ? m/^\s+\-/ : 1;
    }
    close $FH ;
    exit 0 ;
}

=encoding utf8

=head1

 $0 [-z] [-A 0] [-B 0] [-1289] [-f 1] [-n 3]  

   大きなテキストファイルの全体を把握しやすいように、
   途中の適当な数の行数の位置、及び最後の数行を表示する。

   途中は 10, 20, 50 , 100, 200, 500 , 1000 .. 行目など切りの良い数字の行数で表示をする。
   その途中の行番号については、開始行を -f num のように指定することが出来る。

オプション: 
   -b num : 10倍ごと、という指定を変更する。たとえば、2や3、1.5などを指定できる。
   -e num : 最初と最後のそれぞれ何行を必ず出力するか。デフォルトでは3 。0 も指定可能。(Edge)
   -f num : 開始行の指定
   -g : 行番号が緑色で出力される。
   -q : 行番号無しで出力する。( -q が無いと、行番号が : の列の前に出力される。)
   -z ; 入力は、gzip 形式と仮定する。
   -B num きりの良い数の何行前から表示するか。連続表示に用いる。
   -A num きりの良い数の何行後まで表示するか。連続表示に用いる。

   -p 0 : キリの良い数では出力しない。単に、3行もしくは、-n で指定された行数を、最初と最後のみ出力。
   -p 1 : キリの良い数を、1, 10, 100 ,1000 .. 行に限定する。
   -p 2 : キリの良い数を 1, 2, 4, 8, 10, 20, 40, 80... とする。
   -p 7 : キリの良い数を 1, 1.5 , 2, 3, 5 , 7 ... の1倍、10倍、100倍.. とする。
   -p 8 : キリの良い数を 1, 1.5 , 2, 3, 5 , 8 ... の1倍、10倍、100倍.. とする。
   -p 9 : キリの良い数を 上1桁以外が全て0の数と見なす。

   -E ; 入力を標準出力にそのまま通過させつつ、通常の機能で出力させるものは、標準エラー出力に出す。時に便利機能。
   -t ; 出力時の時刻情報を 行頭に付加。

  --help : この $0 のヘルプメッセージを出す。  perldoc -t $0 | cat でもほぼ同じ。
  --help opt : オプションのみのヘルプを出す。opt以外でも options と先頭が1文字以上一致すれば良い。

 開発上のメモ: 
   * キーボードからの入力待ちの場合は、-tで検出して、ALRMシグナルで受付を促すようにしたい。

=cut
