package ExtUtils::XSBuilder::TypeMap;

use strict;
use warnings FATAL => 'all';

use ExtUtils::XSBuilder::FunctionMap ();
use ExtUtils::XSBuilder::CallbackMap ();
use ExtUtils::XSBuilder::StructureMap ();
use ExtUtils::XSBuilder::MapUtil qw(list_first function_table structure_table callback_table callback_hash);
use Data::Dumper ;

our @ISA = qw(ExtUtils::XSBuilder::MapBase);

sub new {
    my $class = shift;
    my $self = bless { INCLUDE => [], wrapxs => shift }, $class;

    $self->{function_map}  = ExtUtils::XSBuilder::FunctionMap ->new ($self -> {wrapxs}),
    $self->{structure_map} = ExtUtils::XSBuilder::StructureMap->new ($self -> {wrapxs}),
    $self->{callback_map}  = ExtUtils::XSBuilder::CallbackMap ->new ($self -> {wrapxs}),

    $self->get;
    $self;
}

my %special = map { $_, 1 } qw(UNDEFINED NOTIMPL CALLBACK);

sub special {
    my($self, $class) = @_;
    return $special{$class};
}

sub function_map  { shift->{function_map}->get  }
sub structure_map { shift->{structure_map}->get }
sub callback_map  { shift->{callback_map}->get }

sub parse {
    my($self, $fh, $map) = @_;

    while ($fh->readline) {
        if (/E=/) {
            my %args = $self->parse_keywords($_);
            while (my($key,$val) = each %args) {
                push @{ $self->{$key} }, $val;
            }
            next;
        }

        my @aliases;
        my($type, $class, $typemapid, $aliastypes, $malloctype) = split /\s*\|\s*/, $_, 5;
        if (!$typemapid && $class)
            {
            if ($class =~ /::/) {
                $typemapid = 'T_PTROBJ';
                }
            else {
                $typemapid = "T_$class";
            }
        }
        $class ||= 'UNDEFINED';

        if ($type =~ s/^struct\s+(.*)/$1/) {
            push @aliases,
              "$type *",        "const $type *",
              $type,            "const $type", 
              "struct $type",   "const struct $type",
              "struct $type *", "const struct $type *",
              "$type **",       "const $type **" ;

            my $cname = $class;
            if ($cname =~ s/::/__/g) {
                push @{ $self->{typedefs} }, [$type, $cname];
            }
        }
        elsif ($type =~ /_t$/) {
            push @aliases, $type, "$type *", "const $type *";
        }
        else {
            push @aliases, $type;
        }

        my $t = { class => $class,
                  typemapid => $typemapid } ;
        $t -> {aliastypes} = [ split (/\s*,\s*/, $aliastypes) ]  if ($aliastypes) ;
        $t -> {malloctype} = $malloctype if ($malloctype) ;

        for (@aliases) {
            $map->{$_} = $t ; 
        }
    }
}

sub get {
    my $self = shift;

    $self->{map} ||= $self->parse_map_files;
}

my $ignore = join '|', qw{
ap_LINK ap_HOOK _ UINT union._
union.block_hdr cleanup process_chain
iovec struct.rlimit Sigfunc in_addr_t
};

sub should_ignore {
    my($self, $type) = @_;
    return 1 if $type =~ /^($ignore)/o;
}

sub is_callback {
    my($self, $type) = @_;
    return 1 if $type =~ /\(/ and $type =~ /\)/; #XXX: callback
}

sub exists {
    my($self, $type) = @_;

    return 1 if $self->is_callback($type) || $self->should_ignore($type);

    $type =~ s/\[\d+\]$//; #char foo[64]

    return exists $self->get->{$type};
}

sub map_type {
    my($self, $type, $quiet) = @_;
    my $t = $self->get->{$type};
    my $class = $t -> {class} ;

    unless ($class and ! $self->special($class))
        {
        print "WARNING: Type '$type' not in mapfile\n" if (!$quiet);
        return undef ;
        }
    if ($class =~ /(.*?)::$/) {
        return $1 ;
    }
    if ($class =~ /::/) {
        return $class;
    }
    else {
        return $type;
    }
}

sub map_malloc_type {
    my($self, $type) = @_;
    my $t = $self->get->{$type};
    return $t -> {malloctype} ;
}

sub map_class {
    my($self, $type) = @_;
    my $t = $self->get->{$type};
    my $class = $t -> {class} ;

    return unless $class and ! $self->special($class);
    if ($class =~ /(.*?)::$/) {
        return $1 ;
    }
    return $class ;
}

sub null_type {
    my($self, $type) = @_;
    my $t = $self->get->{$type};
    my $class = $t -> {class} ;

    if ($class =~ /^[INU]V/) {
        return '0';
    }
    elsif ($class =~ /^(U_)?CHAR$/) {
        return '0'; # xsubpp seems to mangle q{'\0'}
    }
    else {
        return 'NULL';
    }
}

sub can_map {
    my $self = shift;
    my $map = shift;
    my $return_type = shift ;

    if (!$self->map_type($return_type))
        {
        print "WARNING: Cannot map return type $return_type for function ", $map->{name} || '???', "\n" ;
        return undef ;
        }

    return 1 if ($map->{argspec}) ;

    for (@_) {
        if (!$self->map_type($_))
            {
            print "WARNING: Cannot map type $_ for function ", $map->{name} || '???', "\n" ;
            return undef ;
            }
    }

    return 1;
}

sub map_arg {
    my($self, $arg) = @_;
    #print Dumper ($arg), 'map ', $self->map_type($arg->{type}), "\n" ;
    return {
       name    => $arg->{name},
       default => $arg->{default},
       type    => $self->map_type($arg->{type}) || $arg->{type},
       rtype   => $arg->{type},
       class   => $self->{map}->{$arg->{type}}->{class} || "",
    }
}

sub map_args {
    my($self, $func, $entry) = @_;

    #my $entry = $self->function_map->{ $func->{name} };
    my $argspec = $entry->{argspec};
    my $args = [];
    my $retargs = [];

    if ($argspec) {
        $entry->{orig_args} = [ map $_->{name}, @{ $func->{args} } ];

        #print "argspec ", Dumper($argspec) ;
        for my $arg (@$argspec) {
            my $default;
            my $return  ;
            if ($arg =~ /^<(.*?)$/) {
                $arg = $1 ;
                $return = 1 ;
                }
            
            ($arg, $default) = split /=/, $arg, 2;
            my($type, $name) ;
            if ($arg =~ /^(.+)\s*:\s*(.+)$/)
                {
                $type = $1 ;
                $name = $2 ;
                }

            #my($type, $name) = split /:(?:[^:])/, $arg, 2;

            my $arghash ;
            if ($type and $name) {
                $arghash = {
                   name => $name,
                   type => $type,
                   default => $default,
                };
            }
            else {
                my $e = list_first { $_->{name} eq $arg } @{ $func->{args} };
                if ($e) {
                    $arghash = { %$e, default => $default};
                }
                elsif ($arg eq '...') {
                    $arghash = { name => '...', type => 'SV *'};
                }
                else {
                    warn "bad argspec: $func->{name} ($arg)\n", Dumper ($func->{args}) ;
                }
            }
            if ($arghash){
                if ($return) {
                    $arghash -> {return} = 1 ;
                    $arghash -> {type} =~ s/\s*\*$// ;
                    push @$retargs, $arghash  ;
                } 
                else {
                    push @$args, $arghash  ;
                }
            }
        }
    }
    else {
        $args = $func->{args};
    }

    return ([ map $self->map_arg($_), @$args ], [ map $self->map_arg($_), @$retargs ]) ;
}

# ============================================================================

sub map_cb_or_func {
    my($self, $func, $map, $class) = @_;

    return unless $map;

    return unless $self->can_map($map, $func->{return_type} || 'void',
                                 map $_->{type}, @{ $func->{args} });
    my ($mfargs, $mfretargs) = $self->map_args($func, $map) ;

    my $mf = {
       name        => $func->{name},
       comment     => $func->{comment},
       return_type => $self->map_type($map->{return_type} ||
                                      $func->{return_type} || 'void'),
       args        => $mfargs,
       retargs     => $mfretargs,
       perl_name   => $map->{name},
    };

    for (qw(dispatch argspec dispatch_argspec orig_args prefix)) {
        $mf->{$_} = $map->{$_};
    }

    $mf->{class} = $class if ($class) ;

    unless ($mf->{class}) {
        $mf->{class} = $map->{class} || $self->first_class($mf);
        #print "GUESS class=$mf->{class} for $mf->{name}\n";
    }

    $mf->{prefix} ||= $self -> {function_map} -> guess_prefix($mf);

    $mf->{module} = $map->{module} || $mf->{class};

    $mf;
}

# ============================================================================

sub map_function {
    my($self, $func) = @_;

    my $map = $self->function_map->{ $func->{name} };
    return unless $map;

    return $self -> map_cb_or_func ($func, $map) ;
}

# ============================================================================

sub map_callback {
    my($self, $callb, $class) = @_;

    my $name = $callb -> {type} ;
    my $callback = callback_hash ($self -> {wrapxs}) -> {$name} ;
    #print $callb -> {name} || '???' ,"   $name -> ", $callback || '-', "\n" ;
    return unless $callback;

    my $map = $self->callback_map->{ $name };
    #print "$name -> map=", $map || '-', "\n" ;
    return unless $map;

    my $cb = $self -> map_cb_or_func ($callback, $map, $class) ;

    return unless $cb ;

    my $orig_args = $cb -> {orig_args} ;
    $orig_args = [ map $_->{name}, @{ $cb->{args} } ] if (!$orig_args) ;
    
    my %args    = map { $_->{name} => $_ } @{ $cb->{args} } ;
    my %retargs = map { $_->{name} => $_ } @{ $cb->{retargs} } ;

    #print "mcb ", Dumper($cb), " cba ", Dumper($callback->{args}) , " args ", Dumper(\%args) ;

    $cb -> {orig_args} = [ map ($retargs{$_}?"\&$_":(($args{$_}{type} !~ /::/) || ($args{$_}{rtype} =~ /\*$/)?
                                         $_:"*$_"), @{ $orig_args }) ];

    my $cbargs      = [ { type => $class, name => '__self'} ] ;
    push @$cbargs, @{ $cb->{args} } if (@{ $cb->{args}}) ;
    $cb->{args} = $cbargs ;

    #print 'func', Dumper($callback), 'map', Dumper($map), 'cb', Dumper($cb) ;

    return $cb ;
}

# ============================================================================

sub map_structure {
    my($self, $struct) = @_;

    my($class, @elts);
    my $stype = $struct->{type};

    return unless ($class = $self->map_type($stype)) ;

    my $module = $self->{structure_map}->{MODULES}->{$stype} || $class ;
    for my $e (@{ $struct->{elts} }) {
        my($name, $type) = ($e->{name}, $e->{type});
        my $rtype;
        my $mapping ;

        if (!exists ($self->structure_map->{$stype}->{$name}))
            {
            if (!$name)
                {
                print "WARNING: The following struct element is not in mapfile and has no name\n", Dumper ($e) ;
                }
            else
                {
                print "WARNING: $name not in mapfile\n" ;
                }
            next ;
            }
        if (!($mapping = $self->structure_map->{$stype}->{$name}))
            {
            print "WARNING: $stype for $name not in mapfile\n" ;
            next ;
            }
        my $mallocmap = $self->structure_map->{$stype}{-malloc} ;
        my $freemap   = $self->structure_map->{$stype}{-free} ;

        #print 'mapping: ', Dumper($mapping, $type) ;

        if ($rtype = $self->map_type($type, 1)) {
            #print "rtype=$rtype\n" ;
            my $malloctype = $self->map_malloc_type($type) ;
            push @elts, {
               name    => $name,
               perl_name    => $mapping -> {perl_name} || $name,
               comment => $e -> {comment},
               type    => $mapping -> {type} || $rtype,
               rtype   => $type,
               default => $self->null_type($type),
               pool    => $self->class_pool($class),
               class   => $self->{map}->{$type}{class} || "",
               $malloctype?(malloc  => $mallocmap -> {$malloctype}):(), 
               $malloctype?(free    => $freemap -> {$malloctype}):(), 
            };
    
            #print Dumper($elts[-1], $stype, $mallocmap, $self->map_malloc_type($type)) ;
        }
        elsif ($rtype = $self->map_callback($e, $class)) {
            push @elts, {
               name    => $name,
               perl_name    => $mapping -> {perl_name} || $name,
               func    => { %$rtype, name => $name, perl_name => $rtype->{alias} || $name, module => $module, dispatch => "(*__self->$name)", comment => $e -> {comment}},
               rtype   => $type,
               default => 'NULL',
               #pool    => $self->class_pool($class),
               class   => $class || "",
               callback => 1,
            };
        }
        else
            {
            print "WARNING: Type '$type' for struct memeber '$name' in not in types mapfile\n" ;
            }

    }

    return {
       module       => $module,
       class        => $class,
       type         => $stype,
       elts         => \@elts,
       has_new      => $self->structure_map->{$stype}->{'new'}?1:0,
       has_private  => $self->structure_map->{$stype}->{'private'}?1:0,
       comment      => $struct -> {comment},

    };
}

sub destructor {
    my($self, $prefix) = @_;
    $self->function_map->{$prefix . 'DESTROY'};
}


sub first_class_ok { 1 } ;

sub first_class {
    my($self, $func) = @_;
    my $map = $self->get ;

    for my $e (@{ $func->{args} }) {
        ###next unless $e->{type} =~ /::/;
        # use map -> rtype to catch class::
        next unless $map->{$e->{rtype}}{class} =~ /::/;
        
        #there are alot of util functions that take an APR::Pool
        #that do not belong in the APR::Pool class
        ###next if (!$self -> first_class_ok ($func, $e)) ;
        next if $e->{type} eq 'APR::Pool' and $func->{name} !~ /^apr_pool/;
        return $1 if ($e->{type} =~ /^(.*?)::$/) ;
        return $e->{type};
    }

    return $func->{name} =~ /^apr_/ ? 'APR' : 'Apache';
}

sub check {
    my $self = shift;

    my(@types, @missing, %seen);

    for my $entry (@{ structure_table($self -> {wrapxs}) }) {
        push @types, map $_->{type}, @{ $entry->{elts} } ;
        my $type = $entry -> {stype} || $entry->{type} ;
        push @types, $type =~/^struct\s+/?$type:"struct $type" ;
    }

    for my $entry (@{ function_table($self -> {wrapxs}) }) {
        push @types, grep { not $seen{$_}++ }
          ($entry->{return_type},
           map $_->{type}, @{ $entry->{args} })
    }

    #printf "%d types\n", scalar @types;

    for my $type (@types) {
        $type =~ s/\s*(\*\s*)+$// ;
        $type =~ s/const\s*// ;
        #$type =~ s/struct\s*// ;
        push @missing, $type unless ($self->exists($type) || $type eq 'new'  || $type eq 'private') ;
    }

    return @missing ? \@missing : undef;
}

#look for Apache/APR structures that do not exist in structure.map
my %ignore_check = map { $_,1 } qw{
module_struct cmd_how kill_conditions
regex_t regmatch_t pthread_mutex_t
unsigned void va_list ... iovec char int long const
gid_t uid_t time_t pid_t size_t
sockaddr hostent
SV
};

sub check_exists {
    my $self = shift;

    my %structures = map { my $t = $_->{type}; $t =~ s/^struct\s+// ; ($_->{type} => 1, $t => 1) } @{ structure_table($self) };
    my @missing = ();
    my %seen;
    #print Data::Dumper -> Dump ([\%structures, structure_table($self)]) ;

    for my $name (keys %{ $self->{map} }) {
        1 while $name =~ s/^\w+\s+(\w+)/$1/;
        $name =~ s/\s+\**.*$//;
        next if $seen{$name}++ or $structures{$name} or $ignore_check{$name};
        push @missing, $name;
    }

    return @missing ? \@missing : undef;
}


sub checkmaps {

    my $self = shift ;
    my %result ;
    $result{missing_functions}   = $self->{function_map} -> check ;
    $result{obsolete_functions}  = $self->{function_map} -> check_exists ;
    $result{missing_callbacks}   = $self->{callback_map} -> check ;
    $result{obsolete_callbacks}  = $self->{callback_map} -> check_exists ;
    $result{missing_structures}  = $self->{structure_map} -> check ;
    $result{obsolete_structures} = $self->{structure_map} -> check_exists ;
    $result{missing_types}       = $self-> check ;
    $result{obsolete_types}      = $self-> check_exists ;

    return \%result ;
}

sub writemaps {

    my $self = shift ;
    my $result = shift ;
    my $prefix = shift ;
    $self->{function_map}  -> write_map_file ($result -> {missing_functions}, $prefix) ;
    $self->{callback_map}  -> write_map_file ($result -> {missing_callbacks}, $prefix) ;
    $self->{structure_map} -> write_map_file ($result -> {missing_structures}, $prefix) ;
    $self -> write_map_file ($result -> {missing_types}) ;
}


sub write {
    my ($self, $fh, $newentries) = @_ ;

    my %types ;
    foreach my $type (@$newentries)
        {
        $type =~ s/\s*(\*\s*)+$// ;
        $type =~ s/const\s*// ;
        #$type =~ s/struct\s*// ;
        $types{$type} = 1 ;
        }
    
    foreach my $type (sort keys %types)
        {
        $fh -> print ("$type\t|\n") ;
        }
    }


#XXX: generate this
my %class_pools = map {
    (my $f = "mpxs_${_}_pool") =~ s/:/_/g;
    $_, $f;
} qw{
   Apache::RequestRec Apache::Connection Apache::URI
};

sub class_pool : lvalue {
    my($self, $class) = @_;
    $class_pools{$class};
}


sub h_wrap {
    my($self, $file, $code) = @_;

    $file = $self -> {wrapxs} -> h_filename_prefix . $file;

    my $h_def = uc "${file}_h";
    my $preamble = "\#ifndef $h_def\n\#define $h_def\n\n";
    my $postamble = "\n\#endif /* $h_def */\n";

    return ("$file.h", $preamble . $code . $postamble);
}

sub typedefs_code {
    my $self = shift;
    my $map = $self->get;
    my %seen;

    my $file = $self -> {wrapxs} -> h_filename_prefix . 'typedefs';
    my $h_def = uc "${file}_h";
    my $code = "";
    my @includes ;

    for (@includes, @{ $self->{INCLUDE} }) {
        $code .= qq{\#include "$_"\n}
    }

    for my $t (@{ $self->{typedefs} }) {
        next if $seen{ $t->[1] }++;
        my $class = $t->[1] ;
        $class =~ s/__$// ;
        $code .= "typedef $t->[0] * $class;\n";
    }

    $code .= "typedef void * PTR;\n";
    $code .= "#if PERL_VERSION > 5\n";
    $code .= "typedef char * PV;\n";
    $code .= "#endif\n";
    $code .= "typedef char * PVnull;\n";

    $code .= q{
#ifndef pTHX_
#define pTHX_
#endif
#ifndef aTHX_
#define aTHX_
#endif
#ifndef pTHX
#define pTHX
#endif
#ifndef aTHX
#define aTHX
#endif

#ifndef XSprePUSH
#define XSprePUSH (sp = PL_stack_base + ax - 1)
#endif

} ;

    $self->h_wrap('typedefs', $code);
}

sub sv_convert_code {
    my $self = shift;
    my $map = $self->get;
    my %seen;
    my $cnvprefix =  $self -> {wrapxs} -> my_cnv_prefix ;
    my $typemap_code = $self -> typemap_code ($cnvprefix);
    my $code = q{
    
#ifndef aTHX_
/* let it work with 5.005 */
#define aTHX_
#endif
} ;    

    while (my($ctype, $t) = each %$map) {
        my $ptype = $t -> {class} ;
        next if $self->special($ptype);
        next if ($ctype =~ /\s/)  ;
        my $class = $ptype;
        my $tmcode ;

        $ptype =~ s/:/_/g ;
        $ptype =~ s/__$// ;
        $class =~ s/::$// ;
        next if $seen{$ptype}++;

        if ($typemap_code -> {$t -> {typemapid}}) {
            my $alias;
            my $expect = "expecting an $class derived object";
            my $croak  = "argument is not a blessed reference";

            #Perl -> C
            my $define = "${cnvprefix}sv2_$ptype";

            if ($tmcode = $typemap_code -> {$t -> {typemapid}}{perl2c})
                {
                $code .= "#define $define(sv) " . eval (qq[qq[$tmcode]]) . "\n" ;
                }
            else
                {
                print "WARNING no convert code for $t -> {typemapid}\n" ;
                }
            if ($alias = $t -> {typealiases}[0]) {
                $code .= "#define ${cnvprefix}sv2_$alias $define\n\n";
            }

            #C -> Perl
            $define = "${cnvprefix}${ptype}_2obj";
            if ($tmcode = $typemap_code -> {$t -> {typemapid}}{c2perl})
                {
                $code .= "#define $define(ptr) " . eval (qq[qq[$tmcode]]) . "\n" ;
                }
            else
                {
                print "WARNING no convert code for $t -> {typemapid}\n" ;
                }
            if ($alias) {
                $code .= "#define ${cnvprefix}${alias}_2obj $define\n\n";
            }

            #Create
            $define = "${cnvprefix}${ptype}_create_obj";
            if ($tmcode = $typemap_code -> {$t -> {typemapid}}{create})
                {
                $code .= "#define $define(p,sv,rv,alloc) " . eval (qq[qq[$tmcode]]) . "\n" ;
                }

            if ($alias) {
                $code .= "#define ${cnvprefix}${alias}_2obj $define\n\n";
            }
            #Destroy
            $define = "${cnvprefix}${ptype}_free_obj";
            if ($tmcode = $typemap_code -> {$t -> {typemapid}}{destroy})
                {
                $code .= "#define $define(ptr) " . eval (qq[qq[$tmcode]]) . "\n" ;
                }

            if ($alias) {
                $code .= "#define ${cnvprefix}${alias}_2obj $define\n\n";
            }
        }
        else {
            if (($ptype =~ /^(\wV)$/) && $ptype ne 'SV') {
                my $class = $1;
                my $alias ;

                #Perl -> C
                my $define = "${cnvprefix}sv2_$ctype";

                $code .= "#define $define(sv) ($ctype)Sv$class(sv)\n\n";

                if ($alias = $t -> {typealiases}[0]) {
                    $code .= "#define ${cnvprefix}sv2_$alias $define\n\n";
                }
                #C -> Perl
                $define = "${cnvprefix}${ctype}_2obj";
                my $lcclass = lc($class) ;
                my $l = $class eq 'PV'?',0':'' ;

                $code .= "#define $define(v) sv_2mortal(newSV$lcclass(v$l))\n\n";

                if ($alias) {
                    $code .= "#define ${cnvprefix}${alias}_2obj $define\n\n";
                }
            }
        }
    }

    $code .= "#define ${cnvprefix}sv2_SV(sv) (sv)\n\n";
    $code .= "#define ${cnvprefix}SV_2obj(x) (x)\n\n";
    $code .= "#define ${cnvprefix}sv2_SVPTR(sv) (sv)\n\n";
    $code .= "#define ${cnvprefix}SVPTR_2obj(x) (x==NULL?&PL_sv_undef:sv_2mortal(SvREFCNT_inc(x)))\n\n";
    $code .= "#define ${cnvprefix}sv2_PV(sv) (SvPV(sv, PL_na))\n\n";
    $code .= "#define ${cnvprefix}PV_2obj(x) (sv_2mortal(newSVpv(x, 0)))\n\n";
    $code .= "#define ${cnvprefix}sv2_PVnull(sv) (SvOK(sv)?SvPV(sv, PL_na):NULL)\n\n";
    $code .= "#define ${cnvprefix}PVnull_2obj(x) (x==NULL?&PL_sv_undef:sv_2mortal(newSVpv(x, 0)))\n\n";
    $code .= "#define ${cnvprefix}sv2_IV(sv) SvIV(sv)\n\n";
    $code .= "#define ${cnvprefix}IV_2obj(x) sv_2mortal(newSViv(x))\n\n";
    $code .= "#define ${cnvprefix}sv2_NV(sv) SvNV(sv)\n\n";
    $code .= "#define ${cnvprefix}NV_2obj(x) sv_2mortal(newSVnv(x))\n\n";
    $code .= "#define ${cnvprefix}sv2_UV(sv) SvUV(sv)\n\n";
    $code .= "#define ${cnvprefix}UV_2obj(x) sv_2mortal(newSVuv(x))\n\n";
    $code .= "#define ${cnvprefix}sv2_PTR(sv) (SvROK(sv)?((void *)SvIV(SvRV(sv))):NULL)\n\n";
    $code .= "#define ${cnvprefix}PTR_2obj(x) (x?newRV_noinc(newSViv ((IV)x)):&PL_sv_undef)\n\n";
    $code .= "#define ${cnvprefix}sv2_CHAR(sv) (char)SvNV(sv)\n\n";
    $code .= "#define ${cnvprefix}CHAR_2obj(x) sv_2mortal(newSVnv(x))\n\n";
    $code .= "#define ${cnvprefix}sv2_AVREF(sv) (AV*)SvRV(sv)\n\n";
    $code .= "#define ${cnvprefix}AVREF_2obj(x) (x?sv_2mortal(newRV((SV*)x)):&PL_sv_undef)\n\n";
    $code .= "#define ${cnvprefix}sv2_HVREF(sv) (HV*)SvRV(sv)\n\n";
    $code .= "#define ${cnvprefix}HVREF_2obj(x) (x?sv_2mortal(newRV((SV*)x)):&PL_sv_undef)\n\n";

    $self->h_wrap('sv_convert', $code);
}

# ============================================================================

# NOTE: 'INPUT' code must not be ended with a ;


sub typemap_code

    {
    my $self = shift ;
    my $cnvprefix = shift ;

    return 
        {
        'T_MAGICHASH_SV' => 
            {
            'OUTPUT' => '    if ($var -> _perlsv) $arg = $var -> _perlsv ; else $arg = &sv_undef ;',

            'c2perl' => '(ptr->_perlsv?ptr->_perlsv:&sv_undef)',

            'INPUT' =>
q[    {
    MAGIC * mg ;
    if ((mg = mg_find (SvRV($arg), '~')))
        $var = *(($type *)(mg -> mg_ptr)) ;
    else
        croak (\"$var is not of type $type\") ;
    }
],

            'perl2c' =>
q[(SvOK(sv)?((SvROK(sv) && SvMAGICAL(SvRV(sv))) \\\\
|| (Perl_croak(aTHX_ "$croak ($expect)"),0) ? \\\\
*(($ctype **)(mg_find (SvRV(sv), '~') -> mg_ptr))  : ($ctype *)NULL):($ctype *)NULL)
],

            'create' => 
q[  sv = (SV *)newHV () ; \\\\
    p = alloc ; \\\\
    memset (p, 0, sizeof($ctype)) ; \\\\
    sv_magic ((SV *)sv, NULL, '~', (char *)&p, sizeof (p)) ; \\\\
    rv = p -> _perlsv = newRV_noinc ((SV *)sv) ; \\\\
    sv_bless (rv, gv_stashpv ("$class", 0)) ; 
    
],
            'destroy' => '    free(ptr)',
            },

        'T_PTROBJ' => 
            {
            'c2perl' => '    sv_setref_pv(sv_newmortal(), "$class", (void*)ptr)',

            'perl2c' =>
q[(SvOK(sv)?((SvROK(sv) && (SvTYPE(SvRV(sv)) == SVt_PVMG)) \\\\
|| (Perl_croak(aTHX_ "$croak ($expect)"),0) ? \\\\
($ctype *)SvIV((SV*)SvRV(sv)) : ($ctype *)NULL):($ctype *)NULL)
],

            'create' => 
q[   rv = newSViv(0) ; \\\\
    sv = newSVrv (rv, "$class") ; \\\\
    SvUPGRADE(sv, SVt_PVIV) ; \\\\
    SvGROW(sv, sizeof (*p)) ;  \\\\
    p = ($ctype *)SvPVX(sv) ;\\\\
    memset(p, 0, sizeof (*p)) ;\\\\
    SvIVX(sv) = (IV)p ;\\\\
    SvIOK_on(sv) ;\\\\
    SvPOK_on(sv) ;
],

            },
        'T_AVREF' => 
            {
            'OUTPUT' => "        \$arg = SvREFCNT_inc (${cnvprefix}AVREF_2obj(\$var));",
            'INPUT'  => "        \$var = ${cnvprefix}sv2_AVREF(\$arg)",
            },
        'T_HVREF' => 
            {
            'OUTPUT' => "        \$arg = SvREFCNT_inc (${cnvprefix}HVREF_2obj(\$var));",
            'INPUT'  => "        \$var = ${cnvprefix}sv2_HVREF(\$arg)",
            },
        'T_SVPTR' => 
            {
            'OUTPUT' => "        \$arg = SvREFCNT_inc (${cnvprefix}SVPTR_2obj(\$var));",
            'INPUT'  => "        \$var = (\$type)${cnvprefix}sv2_SVPTR(\$arg)",
            },
        'T_PVnull' => 
            {
            'OUTPUT' => "        \$arg = SvREFCNT_inc (${cnvprefix}PVnull_2obj(\$var));",
            'INPUT'  => "        \$var = (\$type)${cnvprefix}sv2_PVnull(\$arg)",
            },

        },
    }




1;
__END__