# mt-toc Ver.0.2
#      by N.Yamamoto

package MT::Plugin::TOC;

use strict;
use MT::Template::Context;
use MT::Util qw(remove_html decode_html);

#use warnings;
#use CGI::Carp;

MT::Template::Context->add_tag(TOCInit => \&TOCInit );
MT::Template::Context->add_conditional_tag(IfTOC => \&IfTOC );
MT::Template::Context->add_tag(TOCSet => \&TOCSet);
MT::Template::Context->add_tag(TOCCounter => \&TOCCounter );
MT::Template::Context->add_container_tag(TOC => \&TOC);
MT::Template::Context->add_container_tag(TOCItems => \&TOCItems);
MT::Template::Context->add_container_tag(TOCItemBody => \&TOCItemBody);
MT::Template::Context->add_tag(TOCItem => \&TOCItem);
MT::Template::Context->add_tag(TOCDumper => \&TOCDumper);

sub TOCInit {
  my ( $ctx, $args ) = @_;
  my $max = _tag_compile($ctx,$args->{max_depth}||'') || 6;
  $ctx->stash('mttoc_template');
  $ctx->stash('mttoc_maxdepth', $max);
  $ctx->stash('mttoc_eid', $ctx->stash('entry')->id);
  $ctx->stash('mttoc_lastvalue', '' );
  $ctx->stash('mttoc_index',     [] );
  $ctx->stash('mttoc_counter',   [0, 0]);
  $ctx->stash('mttoc_depth',     0  );
  $ctx->stash('mttoc_active',    0  );
  $ctx->stash('mttoc_safedepth',    0  );
  my $ignore = _tag_compile($ctx,$args->{ignore}||'');
  $ctx->stash('mttoc_ignore', $ignore ? qr($ignore) : undef );
  '';
}

sub IfTOC {
  my ( $ctx ) = @_;
  ( (($ctx->stash('mttoc_eid') || -1) == $ctx->stash('entry')->id)
    &&
    $ctx->stash('mttoc_active') )
    ? 1 : 0;
}

sub TOCSet {
  my ( $ctx, $args ) = @_;
  my $line = _tag_compile($ctx, $args->{value} || '');
  my $depth = _tag_compile($ctx,$args->{depth} || '');
  return '' unless ( $line && $depth );
  return '' if ( $depth > ($ctx->stash('mttoc_maxdepth')||0) );
  my $match = _tag_compile($ctx, $args->{match}||'');
  my $ignore = $ctx->stash('mttoc_ignore');
  if ( $ignore && $match && ( $match =~ /$ignore/ ) ) {
    $ctx->stash('mttoc_active',0);
    return '';
  } else {
    $ctx->stash('mttoc_active',1);
  }
  return '' unless IfTOC($ctx);

  # トップレベルの深さ
  $ctx->stash('mttoc_depth',$depth)
    if ( ! $ctx->stash('mttoc_depth' ) );

  # 第2要素が直前要素の深さ．第2要素以降が深さ毎のカウンタのスタック
  my $counter = $ctx->stash('mttoc_counter');
  my $prevdepth = shift(@$counter) || $depth;

  my $index = $ctx->stash('mttoc_index');
  if ( $depth > $prevdepth ) {
    for (my $i=$prevdepth; $i<$depth; $i++ ) {
      push(@$counter,1);
      push(@$index,[$i,undef,undef])
	if ($i!=$prevdepth);  # 苦し紛れの飛ばし対処
    }
  } else {
    if ( $depth < $prevdepth ) {
      for (my $i=($prevdepth-$depth); $i>0; $i-- ) {
	return $ctx->error("Illegal Depth.")
	  unless (@$counter);                 # 先頭要素よりも浅い場合
	pop(@$counter);
      }
    }
    print STDERR "COUNTER: " . join(" ", @$counter) . "\n";
    push(@$counter, pop(@$counter)+1);
  }

  push(@$index, [ $depth, remove_html($line), join(' ',@$counter) ]);
  #TOCDumper($ctx);
  unshift(@$counter,$depth);
  '';
}

sub TOCCounter {
  my ( $ctx, $args ) = @_;
  my $glue = $args->{glue} || '';
  my $counter = $ctx->stash('mttoc_counter');
  if ( $counter ) {
    my @counter = @$counter;
    shift(@counter);
    join( $glue, @counter );
  } else {
    $counter = $ctx->stash('mttoc_index')->[0][2];
    $counter =~ s/ /$glue/g;
    $counter;
  }
}

sub TOC {
  my ( $ctx, $args ) =@_;
  my $builder = $ctx->stash('builder');
  my $tokens  = $ctx->stash('tokens');
  my $index = $ctx->stash('mttoc_index');
  $ctx->stash('mttoc_counter', undef);
  $ctx->stash('mttoc_template',$tokens);

  $builder->build($ctx,$tokens)
    or return $ctx->error($ctx->errstr);
}

sub TOCItems {
  my ( $ctx, $args ) =@_;
  my $builder = $ctx->stash('builder');
  my $tokens  = $ctx->stash('tokens');
  my $index = $ctx->stash('mttoc_index');
  my $depth = $ctx->stash('mttoc_depth');
  return '' if ( $depth > ($ctx->stash('mttoc_maxdepth')||0) );
  return '' unless ($ctx->stash('mttoc_template'));
  my $res = '';
  while ( my $item = $index->[0] ) {
    if ( $item->[0] < $depth ) {
      unshift(@$index,undef); # 脱出後，すぐに shift される．
      $ctx->stash('mttoc_depth', $depth-1);
      last;
    }

    $res .= $builder->build($ctx,$tokens)
      or return $ctx->error($ctx->errstr);

    # ItemBody で shift する方が効率は良いが，万が一 ItemBody がない場
    # 合に無限ループしてしまうので．
    shift(@$index);
  }
  $res;
}

sub TOCItemBody {
  my ( $ctx, $args ) =@_;
  my $builder = $ctx->stash('builder');
  my $tokens  = $ctx->stash('tokens');
  my $index = $ctx->stash('mttoc_index');
  my $depth = $ctx->stash('mttoc_depth');

  my $res = '';
  if (defined $index->[0][1]) {
    $res = $builder->build($ctx,$tokens)
      or return $ctx->error($ctx->errstr);
  }

  if ( $index->[1] && ( $index->[1][0] > $depth) ) {
    shift(@$index);
    $ctx->stash('mttoc_depth',$depth+1);
    $res .= $builder->build($ctx,$ctx->stash('mttoc_template'))
      or return $ctx->error($ctx->errstr);
  }
  $res;
}

sub TOCItem {
  my ( $ctx, $args ) =@_;
  $ctx->stash('mttoc_index')->[0][1];
}

sub TOCDumper {
  my ( $ctx, $args ) =@_;
  my $index = $ctx->stash('mttoc_index');
}

sub _tag_compile ($$) {
  my($ctx, $str, $decode) = @_;
  my $build = $ctx->stash('builder');
  $str = decode_html($str) if ($decode);

  if ( ($str =~ m/\<MT.*?\>/g) ||
       ($str =~ s/\[(MT(.*?))\]/<$1>/g) ) {
    my $tokens = $build->compile($ctx, $str)
      or die $build->errstr;
    defined($str = $build->build($ctx, $tokens))
      or die $build->errstr;
  }
  $str;
}

1;

