#!/usr/bin/perl 
use 5.014 ; use strict ; use warnings  ;
use feature qw [ say ] ;
use Time::HiRes qw[gettimeofday tv_interval] ; 
use Term::ANSIColor qw [ :constants color ] ; $Term::ANSIColor::AUTORESET = 1 ; 
use File::Spec::Functions qw[ catfile splitdir rel2abs updir ] ; 
use Getopt::Std ; 
use List::Util qw [ max min sum sum0 reduce ] ;
use Cwd qw [ getcwd abs_path ] ;

my $time_start = [ gettimeofday ] ; 
getopts '.:g:x:0:' , \my%o ; 
$o{g} //= '' ; # どんなデータを出力させるか(get)
$o{'.'} //= '' ; # ピリオドで始まる隠しファイルについての処理(0が指定されると、たどらない)
push @ARGV , $o{x} if defined $o{x} ; # 引数がオプションで  与えられた場合の処理
my $start_dir = $ARGV [0] // "." ; # 先頭のディレクトリ 
my $I = catfile q[] , q[] ;
chdir $start_dir or do { say STDERR "Seems no such a directory ``$start_dir''" ; exit -1 }  ;

# -- 関数 head_trim の定義 : state でさらに短くなるかも。-- 
my $d0 = ( getcwd ) . $I ;
$d0 = (getcwd ). $I unless exists $o{g} && $o{g} =~ m/a/ ; 
$d0 = '' if $o{g} =~ m/A/ ;
sub head_trim ( $ ) { return $_[0] =~ s/^\Q$d0\E//r =~ s/\ /\\ /gr }

my @gg = ( map { [ split /-/, $_ ] } split /,/ , $o{g} =~ s/[Aadlx]//gr );   # コンマ区切り ハイフン結合ペアの取り出し
our %g1 = map { $_ ->[0] , 1 } grep { @ { $_ } == 1 } @gg ; # ペアではないもの
our %g2 = map { $_->[0] ."-" .$_->[1] , 1 } grep { @ { $_ } == 2 } @gg ; # ペアのもの
our @S ; #　$S[depth][maxdepth]の集計表となる。
our @Sq ;  # $S_ln [ depth ]  # 各リンクファイルの情報を取り出す。(確か)
my @Ex ; # 例の保管 $Ex[$dep][$depM] が 具体的なディレクトリ名 (後で網羅的に格納するかも)
my @Ex2 ; # 例の保管 $Ex[$dep][$depM] が 具体的なディレクトリ名 (後で網羅的に格納するかも)

& main ; 
exit 0 ;

END{
  print RESET "" ; 
  my $s = tv_interval $time_start , [ gettimeofday ] ; 
  say STDERR BOLD FAINT " --  " , "Process time: " , 
    sprintf( "%.6f", $s ) , " second" . ($s>1 ? 's' : '' ) . ". ($0)"  ; 
}

sub open_dir_error_message ( $ ) { 
  say STDERR FAINT BOLD YELLOW "Cannot open the directory `$_[0]' so skipped." ;
}

sub main () {
  $SIG{INT} = sub { say GREEN getcwd ; & output (\@S, \@Sq ) } ;
  & node_proc ( 0 , '.' ) ; # <- 再帰的な関数呼び出しが中でされる。
  & output (\@S, \@Sq ) unless exists $o{g} && $o{g} =~ m/x/ ;
}

sub node_proc ( $ $ )  {  # 第１引数は、元の指定ディレクトリからの深さであり、# 返り値は、そこで ( 経験した最大の深さ , 最大深さを達成したディレクトリ名 ) 。
  my $dep = $_[0] ; # 深さ
  my $dn = $_[1] ; #head_trim(getcwd);  # dir name
  my $dnM = $dn ; # 最も深いところにあるディレクトリ名が保管されることになる。
  my $depM = $dep ; # 最大深さの記録用。
  my @dirs ; 
  opendir my $dh , '.' or do { open_dir_error_message ( abs_path "." ) ; return () } ; 
  my @dirs0 = sort grep { ! /\A\.{1,2}\Z/ && -d $_ } readdir $dh ;  # <-- - sort は -g が無いときは不要である
  for ( @dirs0 ) { 
    do { push @dirs , $_ ; next } if ! -l $_  ;
    say join "\t" , $o{g} =~ m/d/ ? () : "link:", head_trim (getcwd).$I.$_ if $o{g} =~ m/l/;#exists $o{g} && $o{g} =~ m/l/;
    ++ $Sq [ $dep + 1 ] ;   
  }
  @dirs = grep { ! m/\A\./ } @dirs if 0 eq $o{'.'} ; # 隠しファイルに関する処理 \A
  for ( @dirs ) { 
    #say "'$o{'.'}'" , $_ unless ! /^\./ ;
    next unless chdir $_ ; 
    #$depM = max $depM , & node_proc ( $dep + 1 ) ; # <-- 再帰的な呼び出し
    #$depM = max $depM , & node_proc ( $dep + 1 , "$dn$I$_" ) ; # <-- 再帰的な呼び出し
    my ($depM1,$dnM1) = & node_proc ( $dep + 1 , "$dn$I$_" ) ; # 1はここでは一時的を意味する。
    ( $depM, $dnM ) = ($depM1, $dnM1) if $depM1 > $depM ; 
    chdir $dh or die ; # ここで戻れないのは重大
  }
  #closedir $dh ;
  $S [ $dep ] [ $depM ] ++ ;
  $Ex [ $dep ] [ $depM ] //= $dnM ;
  $Ex2[ $dep ] [ $depM ] = $dnM ;
  say join "\t" , $o{g} =~ m/d/ ? () : $dep, head_trim getcwd if $g1{$dep}  ; 
  say join "\t" , $o{g} =~ m/d/ ? () : "$dep-$depM", head_trim getcwd if  $g2{"$dep-$depM"} ; 
  return $depM , $dnM  ; # <- - $dn であっているか
}


sub output ($$) {  # 引数 \@S と \@Sq が引数となる。
  my $asum = 0 ; # ファイル数の合計
  my @out = ( '', 1 .. $#{$_[0]} , '+' , '++' ) ;  # 0を1に。
  push @out , MAGENTA "Symbolic_link_dir" if sum0 map { $_ // 0 } @{$_[1]} ; # f ;
  push @out , "examples" ;
  say join "\t" , @out ;
  for ( 1 .. $#{$_[0]} ) {  # 0を1に。
    $_[0]->[$_][$_] //= 0 ; # unless exists $o{0} && $o{0} eq "." ; # 対角成分に対する処理
    for my $i ( $_ .. $#{$_[0]} ) { $_[0]->[$_][$i] //= '' } ; 
    #my @out = ( $_ , map { ! defined $_ ? '' : $_ eq '' ? FAINT 0 : $_ } @{$_[0]->[$_]} ) ; 
    my @out = ( $_ , map { ! defined $_ ? '' : $_ eq '' ? FAINT 0 : $_ } do { my @t = @{$_[0]->[$_]} ; @t[1..@t-1] } ) ; 
    push @out , FAINT my $rsum = sum0 map { $_ || 0 } @{$_[0]->[$_]}   ; 
    push @out , $asum += $rsum ;
    push @out , MAGENTA "+$_[1]->[$_]" if $_[1]->[$_] ;
    push @out , substr $Ex [$_][$_] // '  ' , 2 ; # 先頭の2文字は 通常 './'' または '  ' になるはずなので、それを除去。
    push @out , substr $Ex2[$_][$_] // '  ' , 2 ; 
    say join "\t" , @out ; 
  }
}

## ヘルプの扱い
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 [dirname]
   
   指定されたディレクトリから、i階層下に潜ったところに、
   j階層下までディレクトリを持つディレクトリが何個あるのかを示す。
   縦方向がiで、横方向がjに対応する。
   +と表記された列は、i階層の合計値を示す。++は累積和。
   シンボリックリンクのディレクトリは辿らない。存在する場合は、その数を出力する。

  オプション: 

    -x dirname : 引数dirnameとして与えるディレクトリ名をオプションとして渡す。
    -. 0 : 隠しファイルを辿らない。
    -g N1-N2 ; iがN1, jがN2に相当するディレクトリ名を出力する。N1-N2の書式は コンマ(,)で連結が可能
    -g ...[dx] ; xを指定文字列に含むことで表の出力を抑制する。dがある場合は深さ情報を抑制する。
    -g ...[Aa] : aの有無でディレクトリの表示が変わる。あれば、指定ディレクトリ名から表示する。Aを含めば、絶対パスとなる。
    -g ...l  : シンボリックリンクのディレクトリを出力する。 


  その他の注意: 
    - Ctrl+C では途中結果を出すのみで，停止しない。Ctrl+\で停止する。

  開発上の注意 : 
    * chdir ".." が意図通りに動作しないことがあったので、opendirを使った動作とした。
