package Inline::Tcl;

use strict;
use Carp;
require Inline;
require DynaLoader;
require Exporter;

use vars qw(@ISA $VERSION @EXPORT_OK);
@ISA = qw(Inline DynaLoader Exporter);
$VERSION = '0.05';

@EXPORT_OK = qw(eval_tcl);

#==============================================================================
# Load (and initialize) the Tcl Interpreter
#==============================================================================
sub dl_load_flags { 0x01 }
Inline::Tcl->bootstrap($VERSION);

#==============================================================================
# Allow 'use Inline::Tcl qw(eval_tcl)'
#==============================================================================
sub import {
    Inline::Tcl->export_to_level(1,@_);
}

#==============================================================================
# Provide an overridden function for evaluating Tcl code
#==============================================================================
sub eval_tcl {
  #croak "Invalid use of eval_tcl()." .
  #  " See 'perldoc Inline::Tcl' for details";
  if (scalar @_ == 1) {
    return _eval_tcl(@_);
  }
  elsif ((scalar @_ < 3) or not (ref $_[2] =~ /::/)) {
    return _eval_tcl_function(@_);
  }
  else {
    croak "Invalid use of eval_tcl()." .
      " See 'perldoc Inline::Tcl' for details";
  }
}

#==============================================================================
# Register Tcl.pm as a valid Inline language
#==============================================================================
sub register {
    return {
	    language => 'Tcl',
	    aliases => ['tcl', 'tk'],
	    type => 'interpreted',
	    suffix => 'tcldat',
	   };
}

#==============================================================================
# Validate the Tcl config options
#==============================================================================
sub validate {
    my $o = shift;

    $o->{Tcl} = {};
    $o->{Tcl}{AUTO_INCLUDE} = {};
    $o->{Tcl}{PRIVATE_PREFIXES} = [];
    $o->{Tcl}{built} = 0;
    $o->{Tcl}{loaded} = 0;

    while (@_) {
	my ($key, $value) = (shift, shift);

	if ($key eq 'AUTO_INCLUDE') {
	    add_string($o->{Tcl}{AUTO_INCLUDE}, $key, $value, '');
	    warn "AUTO_INCLUDE has not been implemented yet!\n";
	}
	elsif ($key eq 'PRIVATE_PREFIXES') {
	    add_list($o->{Tcl}, $key, $value, []);
	}
	else {
	    croak "$key is not a valid config option for Tcl\n";
	}
	next;
    }
}

sub add_list {
    my ($ref, $key, $value, $default) = @_;
    $value = [$value] unless ref $value;
    croak usage_validate($key) unless ref($value) eq 'ARRAY';
    for (@$value) {
	if (defined $_) {
	    push @{$ref->{$key}}, $_;
	}
	else {
	    $ref->{$key} = $default;
	}
    }
}

sub add_string {
    my ($ref, $key, $value, $default) = @_;
    $value = [$value] unless ref $value;
    croak usage_validate($key) unless ref($value) eq 'ARRAY';
    for (@$value) {
	if (defined $_) {
	    $ref->{$key} .= ' ' . $_;
	}
	else {
	    $ref->{$key} = $default;
	}
    }
}

sub add_text {
    my ($ref, $key, $value, $default) = @_;
    $value = [$value] unless ref $value;
    croak usage_validate($key) unless ref($value) eq 'ARRAY';
    for (@$value) {
	if (defined $_) {
	    chomp;
	    $ref->{$key} .= $_ . "\n";
	}
	else {
	    $ref->{$key} = $default;
	}
    }
}

###########################################################################
# Print a short information section if PRINT_INFO is enabled.
###########################################################################
sub info {
    my $o = shift;
    my $info =  "";

    $o->build unless $o->{Tcl}{built};
    $o->load unless $o->{Tcl}{loaded};

    my @functions = @{$o->{Tcl}{namespace}{functions}||{}};
    $info .= "The following Tcl functions have been bound to Perl:\n"
      if @functions;
    for my $function (sort @functions) {
	$info .= "\tdef $function()\n";
    }

    return $info;
}

###########################################################################
# Use Tcl to Parse the code, then extract all newly created functions
# and save them for future loading
###########################################################################
sub build {
    my $o = shift;
    return if $o->{Tcl}{built};
    #print "Build\n";
    #print "SEP: (@{$o->{Tcl}{PRIVATE_PREFIXES}})\n";
    my $result = _eval_tcl($o->{code});
    croak "Couldn't parse your Tcl code.\n" 
      unless $result;

    my %namespace = _Inline_parse_tcl_namespace();

    my @filtered;
    for my $func (@{$namespace{functions}}) {
	my $private = 0;
	for my $prefix (@{$o->{Tcl}{PRIVATE_PREFIXES}}) {
	    ++$private and last
	      if substr($func, 0, length($prefix)) eq $prefix;
	}
	push @filtered, $func
	  unless $private;
    }
    $namespace{functions} = \@filtered;

    warn "No functions found!"
      unless ((length @{$namespace{functions}}) > 0 );

    require Data::Dumper;
    local $Data::Dumper::Terse = 1;
    local $Data::Dumper::Indent = 1;
    my $namespace = Data::Dumper::Dumper(\%namespace);

    # if all was successful
    $o->mkpath("$o->{install_lib}/auto/$o->{modpname}");

    open TCLDAT, "> $o->{location}" or
      croak "Inline::Tcl couldn't write parse information!";
    print TCLDAT <<END;
%namespace = %{$namespace};
END
    close TCLDAT;

    $o->{Tcl}{built}++;
}

#==============================================================================
# Load and Run the Tcl Code, then export all functions from the tcldat 
# file into the caller's namespace
#==============================================================================
sub load {
    #print "LOAD\n";
    my $o = shift;
    return if $o->{Tcl}{loaded};
    open TCLDAT, $o->{location} or 
      croak "Couldn't open parse info!";
    my $tcldat = join '', <TCLDAT>;
    close TCLDAT;

    eval <<END;
;package Inline::Tcl::namespace;
no strict;
$tcldat
END

    croak "Unable to parse $o->{location}\n$@\n" if $@;

    $o->{Tcl}{namespace} = \%Inline::Tcl::namespace::namespace;
    delete $main::{Inline::Tcl::namespace::};
    $o->{Tcl}{loaded}++;

    my $result = _eval_tcl($o->{code});

    # bind some perl functions to the caller's namespace
    for my $function (@{$o->{Tcl}{namespace}{functions}}) {
      my $s = "*::" . "$o->{pkg}";
      $s .= "::$function = sub { ";
      $s .= "Inline::Tcl::_eval_tcl_function";
      $s .= "(__PACKAGE__,\"$function\", \@_) }";
      #print "$s\n";
      eval $s;
      croak $@ if $@;
    }
}

1;

__END__
