package YATT::Class::Tcl;
use strict;
use warnings FATAL => qw(all);
use Tcl;

use base qw(YATT::Class::Configurable);
use YATT::Fields (qw(tcl)
		  , ['^cf_myname'  => '::yatt::perl']
		  , ['cf_tclinit' => 1]);

use YATT::Util::Symbol;

foreach my $meth (qw(Eval EvalFile Init call icall invoke result
		     CreateCommand DeleteCommand
		     SetResult AppendResult AppendElement ResetResult
		     SplitList
		     SetVar SetVar2 GetVar GetVar2
		     UnsetVar UnsetVar2
		     return_ref delete_ref )) {
  *{globref(MY, $meth)} = sub {
    my MY $self = shift;
    $self->{tcl}->$meth(@_);
  };
}

#
# Expose tcl commands (in ::*) as methods.
#
sub AUTOLOAD {
  my $method = our $AUTOLOAD;
  $method =~ s/.*:://;
  (my MY $self) = @_;
  our ($sub, %NEGATIVE_CACHE);
  if (exists $NEGATIVE_CACHE{$method} or not do {
    if (my @found = $self->{tcl}->invoke(info => commands => $method)) {
      $sub = sub {
	my MY $self = shift;
	$self->{tcl}->invoke($method, @_);
      };
    } else {
      undef $NEGATIVE_CACHE{$method}
    }
   }) {
    die "No such method: $method";
  }
  *{globref(class($self), $method)} = $sub;
  goto &$sub;
}

sub new {
  my MY $self = shift->SUPER::new(@_);
  $self->{tcl}->Init if $self->{cf_tclinit};
  if (my $myname = $self->myname) {
    # 決め打ちはどうかとも思うが、そもそもこの tcl interp は
    # この YATT::Class::Tcl インスタンスに固有だから、衝突のしようがない。
    $self->{tcl}->Eval(<<'END');
namespace eval ::yatt {
proc nslist nsname {
    set q [namespace qualifier $nsname]
    if {$q eq ""} {
	return [list $nsname]
    } else {
	linsert [nslist $q] end [namespace tail $nsname]
    }
}

proc ensure-nslist nslist {
    if {![llength $nslist]} {
	list
    } else {
	list namespace eval [lindex $nslist 0] \
	    [ensure-nslist [lrange $nslist 1 end]]
    }
}

proc ensure-ns nsname {
  uplevel #0 [ensure-nslist [nslist [namespace qualifier $nsname]]]
}

}

END

    $self->{tcl}->invoke('yatt::ensure-ns', $myname);
    $self->{tcl}->CreateCommand($myname, \&perl_dispatch, $self);
  }
  $self
}

sub before_configure {
  my MY $self = shift;
  $self->{tcl} = new Tcl;
  $self
}

sub perl_dispatch {
  (my MY $self, my ($tcl, undef, $method)) = splice @_, 0, 4;
  $tcl->ResetResult;
  my @result = $self->$method(@_); # To make debugger happy.
  $tcl->AppendResult(map {defined $_ ? $_ : ""} @result);
}

sub MainLoop {
  (my MY $self, my $varname) = @_;
  $self->{tcl}->invoke(vwait => $varname || 'forever');
}

sub lexpand {
  (my MY $tcl, my ($list, $n)) = @_;
  $tcl->{tcl}->invoke(lrange => $list, defined $n ? $n : 0, 'end');
}

sub lexpand_if {
  (my MY $tcl, my ($typename, $list)) = @_;
  return if $tcl->{tcl}->lindex($list, 0) ne $typename;
  # 先頭が一致するなら、残りを返す。
  $tcl->lexpand($list, 1);
}

1;
