#!/usr/bin/perl 
use 5.014 ; use warnings ; 
use Time::HiRes qw [ gettimeofday tv_interval ] ;
my ${ dt_start } = [ gettimeofday ] ; 
use Getopt::Std ; getopts '=12:c:i:s:' , \my %o  ; 
use Term::ANSIColor qw[ color :constants ] ; $Term::ANSIColor::AUTORESET = 1 ;
use FindBin qw[ $Script ] ; 
use autodie qw [ open ] ;
use List::Util qw [ max ] ; 

* d3 = exists $o{','} && $o{','} eq 0 ? sub{$_[0]} : sub { $_[0] =~ s/(?<=\d)(?=(\d\d\d)+($|\D))/,/gr } ;
my $time0 = time ; 
my $help = 0  ; # オンラインヘルプの文面の表示をしたか否か。
$o{i} //= "\t" ;
my $readLines = 0 ; # 読み取った行数
my $sec = $o{'@'} // 15 ; # 何秒おきにアラームを発生させるか

$SIG{INT} = sub { exit } ;
$SIG{ALRM} = sub { 
  my $n = $.  =~ s/(?<=\d)(?=(\d\d\d)+($|\D))/,/gr ; # 3桁ごとに区切る。
  say STDERR GREEN "$n lines read ($Script). " , scalar localtime ; 
  alarm $sec 
} ; 
alarm $sec ;

my %f1 ; # 各行の内容の文字列から単純に参照される頻度表になる。(層分割の時は、一段階だけ多次元化する)
my @f2 ; # 変数の %f1 の頻度の数についての頻度を集計したものになる。(層分割の時は、一段階だけ多次元化する)

if ( $o{'='} ) { 
  $_ = <> ; 
  chomp ; 
  my @F = split /$o{i}/ , $_ , -1 ; 
  my $z ; 
  for ( 0 .. $#F ) { do { $z = $_ + 1 ; last } if $o{c} eq $F[$_] } 
  $o{c} = defined $z ? $z : undef ; 
}

if ( ! defined $o{c} ) { 
  while ( <> ) {
    $readLines ++ ;
    chomp ; 
    $f1 { $_ } ++ ;
  }
  $f2 [ $f1{$_} ] ++ for keys %f1 ;  # **1 
  # say STDERR BOLD FAINT ITALIC "--" unless ($o{2}//'') eq 0 ; 
  say join ',' , map { $o{1}||$f2[$_]!=1 ? "${_}x$f2[$_]" : $_ } grep { defined $f2[$_] } 1 .. $#f2 ; 
} 
else 
{ 
  my $pos = $o{c} > 0 ? $o{c} - 1 : $o{c} ; # -c N において N<0 の場合は後ろから何番目のように考える。
  while ( <> ) { 
    $readLines ++ ;
    chomp ; 
    my @F = split $o{i} , $_ , -1 ; 
    my $k = splice @F , $pos , 1  ; # 前はlabelと表記していたが、簡潔にするためkの一文字にした。
    $_ = join $o{i} , @F ;
    $f1 { $k } { $_ } ++ ; 
  }
  for my $k ( keys %f1 ) { ## ← 上記の **1 とは意味の異なるので、トリッキーである。注意。
    for my $rest ( keys %{ $f1 { $k } } ) { 
      $f2 [ $f1{$k}{$rest} ] { $k } ++ ;
    }
  }
  # say STDERR BOLD FAINT ITALIC "--" unless ($o{2}//'') eq 0 ; 
  
  my @keg1 = keys %f1 ; 
  my @kmst = map {my $k = $_ ; max 0, grep { defined $f2[$_]{$keg1[$k]} } 1 .. $#f2 } 0 .. $#keg1 ; # k-mostのつもり
  #say STDERR CYAN $#f2 ; 
  #say STDERR RED join " " , @kmst ;
  $o{s} //= '' ; # 出力するキーの値のソート方法を指定する。
  my @keg2 = $o{s} eq 'n' ? sort {$a <=> $b} @keg1 : 
         $o{s} eq 'm' ? map{ $keg1[$_] } sort { $kmst[$b] <=> $kmst[$a] } 0 .. $#keg1 :
         sort @keg1 ; 

  for my $k ( @keg2 ) { 
    my @tmp = grep { defined $f2 [ $_ ] { $k } } 1 .. $#f2 ; # 各キー値の多重度に対して、度数が定義されたものを抽出。
    say "$k\t" , join ',' , map { $o{1} || $f2[$_]{$k} != 1 ? "${_}x$f2[$_]{$k}" : $_ } @tmp ; 
  }
}

exit ;

END{
  exit if $help ;
  my $procsec = sprintf "%.5f", tv_interval ${ dt_start } ; #time - $time0 ; # このプログラムの処理にかかった秒数。比較する2個の時刻は秒単位なので、±1秒未満の誤差は発生する。
  $readLines //= $. ; # Ctrl+Cの連打で必要となる処理。
  return if ($o{2}//'') eq 0 ; 
  my $linenumeral = $readLines > 1 ? 'lines' : 'line' ; 
  print STDERR BOLD FAINT ITALIC & d3 ( $readLines ) . " $linenumeral read. " ; 
  my $s = tv_interval $dt_start , [ gettimeofday ] ; 
  say STDERR BOLD FAINT ITALIC " -- $Script ; " . $procsec . " sec. in process" ;
}

## ヘルプの扱い
sub VERSION_MESSAGE {}
sub HELP_MESSAGE {
  use FindBin qw[ $Script ] ; 
  $help = 1 ;
  $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 inputfile 
  $0 < inuptfile 
  cat inputfile | $0 

同じ文字列の行の発生の様子を調べる。全行を入力から読み取り、「全く同じ内容の行が
m重に発生したということがn回発生した」様子を全てのmに対して集計して、mxn の
表示の仕方で(xは小文字のエックスであり、その前後に数値を配置する)、コンマ区切りで
表示する。入力がN行であり、全ての行の内容が異なる場合には1xNと出力される。

オプションに関して :

  -c N : N は数である。指定されると、タブ文字区切りの左からN列目ごとに「層分け」する。(負の数も指定可能。次のオプションも参照。)
  -=   : 入力の1行目を、データの実質ではなくて、変数名と見なす。これにより「-c 変数名」の指定が可能となる。
  -1   : 出力で "x1" の部分を表示するようにする。-1の指定が無い場合、"x1"は省略される。
  -2 0 : 入力行数や処理時間などの2次情報を、標準エラー出力に出力しない。

  -s n : -cでキーとなる列が指定された場合に、キーを数値と見なして、昇順にソートして出力する。
  -s m : -cでキーとなる列が指定された場合に、各キーの値において、最も重なりの値が大きい順(降順)に出力する。

  --help : このオンラインヘルプの文面を表示する。

その他 : 
  * x と , を使った表記の出力の仕方もある。層分割したときに、表のようにしたいかも。-p でプレゼン用に変えたい。
  * 区切り文字を指定できるようにする。
  * -= を使うことで、-c 変数名のよう使い方が出来るようにする。

=cut
