#!/usr/bin/perl -w
#############################################################################
## Name:        script/make_v_cback.pl
## Purpose:     Create the v_cback_def.h include
## Author:      Mattia Barbon
## Modified by:
## Created:     19/08/2007
## RCS-ID:      $Id: make_v_cback.pl 2264 2007-11-05 23:23:35Z mbarbon $
## Copyright:   (c) 2007 Mattia Barbon
## Licence:     This program is free software; you can redistribute it and/or
##              modify it under the same terms as Perl itself
#############################################################################

use strict;
use Data::Dumper;

my @macros =
  qw(DEC_V_CBACK_BOOL__BOOL
     DEF_V_CBACK_BOOL__BOOL

     DEC_V_CBACK_BOOL__INT
     DEF_V_CBACK_BOOL__INT
     DEF_V_CBACK_BOOL__INT_pure

     DEC_V_CBACK_BOOL__WXVARIANT_UINT_UINT
     DEF_V_CBACK_BOOL__WXVARIANT_UINT_UINT_pure

     DEC_V_CBACK_BOOL__SIZET
     DEF_V_CBACK_BOOL__SIZET
     DEF_V_CBACK_BOOL__SIZET_pure

     DEC_V_CBACK_BOOL__SIZET_SIZET
     DEF_V_CBACK_BOOL__SIZET_SIZET
     DEF_V_CBACK_BOOL__SIZET_SIZET_pure

     DEC_V_CBACK_BOOL__VOID
     DEC_V_CBACK_BOOL__VOID_const
     DEF_V_CBACK_BOOL__VOID
     DEF_V_CBACK_BOOL__VOID_const
     DEF_V_CBACK_BOOL__VOID_pure

     DEC_V_CBACK_BOOL__INT_INT
     DEC_V_CBACK_BOOL__INT_INT_const
     DEF_V_CBACK_BOOL__INT_INT
     DEF_V_CBACK_BOOL__INT_INT_pure
     DEF_V_CBACK_BOOL__INT_INT_const
     DEF_V_CBACK_BOOL__INT_INT_const_pure

     DEC_V_CBACK_DOUBLE__INT_INT
     DEC_V_CBACK_DOUBLE__INT_INT_const
     DEF_V_CBACK_DOUBLE__INT_INT
     DEF_V_CBACK_DOUBLE__INT_INT_pure
     DEF_V_CBACK_DOUBLE__INT_INT_const
     DEF_V_CBACK_DOUBLE__INT_INT_const_pure

     DEC_V_CBACK_INT__LONG_LONG
     DEC_V_CBACK_INT__LONG_LONG_const
     DEF_V_CBACK_INT__LONG_LONG
     DEF_V_CBACK_INT__LONG_LONG_pure
     DEF_V_CBACK_INT__LONG_LONG_const

     DEC_V_CBACK_INT__VOID
     DEF_V_CBACK_INT__VOID
     DEF_V_CBACK_INT__VOID_pure

     DEC_V_CBACK_LONG__INT_INT
     DEC_V_CBACK_LONG__INT_INT_const
     DEF_V_CBACK_LONG__INT_INT
     DEF_V_CBACK_LONG__INT_INT_pure
     DEF_V_CBACK_LONG__INT_INT_const
     DEF_V_CBACK_LONG__INT_INT_const_pure

     DEC_V_CBACK_UINT__VOID
     DEC_V_CBACK_UINT__VOID_const
     DEF_V_CBACK_UINT__VOID
     DEF_V_CBACK_UINT__VOID_const
     DEF_V_CBACK_UINT__VOID_pure
     DEF_V_CBACK_UINT__VOID_const_pure

     DEC_V_CBACK_VOID__INT_INT_LONG
     DEF_V_CBACK_VOID__INT_INT_LONG
     DEF_V_CBACK_VOID__INT_INT_LONG_pure

     DEC_V_CBACK_VOID__mWXVARIANT_UINT_UINT_const
     DEF_V_CBACK_VOID__mWXVARIANT_UINT_UINT_const_pure

     DEC_V_CBACK_VOID__SIZET_SIZET_const
     DEF_V_CBACK_VOID__SIZET_SIZET_const

     DEC_V_CBACK_WXCOORD__VOID_const
     DEF_V_CBACK_WXCOORD__VOID_const
     DEF_V_CBACK_WXCOORD__VOID_const_pure

     DEC_V_CBACK_WXCOORD__SIZET
     DEC_V_CBACK_WXCOORD__SIZET_const
     DEF_V_CBACK_WXCOORD__SIZET
     DEF_V_CBACK_WXCOORD__SIZET_const
     DEF_V_CBACK_WXCOORD__SIZET_pure
     DEF_V_CBACK_WXCOORD__SIZET_const_pure

     DEC_V_CBACK_WXSTRING__WXSTRING
     DEF_V_CBACK_WXSTRING__WXSTRING

     DEC_V_CBACK_WXSTRING__UINT
     DEC_V_CBACK_WXSTRING__UINT_const
     DEF_V_CBACK_WXSTRING__UINT
     DEF_V_CBACK_WXSTRING__UINT_const_pure
     );
my %type_map =
  ( BOOL    => [ 'bool',    'SvTRUE( ret )', 'return false',
                 'bool p%d', 'b', 'p%d', 'p%d' ],
    SIZET   => [ 'size_t',  'SvIV( ret )', 'return 0',
                 'size_t p%d', 'L', 'p%d', 'p%d' ],
    LONG    => [ 'long',    'SvIV( ret )', 'return 0',
                 'long p%d', 'l', 'p%d', 'p%d' ],
    INT     => [ 'int',     'SvIV( ret )', 'return 0',
                 'int p%d', 'i', 'p%d', 'p%d' ],
    UINT    => [ 'unsigned int', 'SvUV( ret )', 'return 0',
                 'unsigned int p%d', 'I', 'p%d', 'p%d' ],
    WXCOORD => [ 'wxCoord', 'SvIV( ret )', 'return 0',
                 'wxCoord p%d', 'l', 'p%d', 'p%d' ],
    DOUBLE  => [ 'double',  'SvNV( ret )', 'return 0.0', ],
    VOID    => [ 'void',    ';',         , 'return',
                 ],
    WXSTRING=> [ 'wxString','wxPli_sv_2_wxString( aTHX_ ret )', 'return wxEmptyString',
                 'const wxString& p%d', 'P', '&p%d', 'p%d' ],
    WXVARIANT=> [ 'wxVariant','wxPli_sv_2_wxvariant( aTHX_ ret )', 'return wxVariant()',
                 'const wxVariant& p%d', 'q', '&p%d, "Wx::Variant"', 'p%d' ],
    mWXVARIANT=> [ 'wxVariant','wxPli_sv_2_wxvariant( aTHX_ ret )', 'return wxVariant()',
                 'wxVariant& p%d', 'q', '&p%d, "Wx::Variant"', 'p%d' ],
    );
my %const_map =
  ( 0       => 'wxPli_NOCONST',
    1       => 'wxPli_CONST',
    );

my %emitted;
my @todo = map [ parse_macro( $_, \%type_map ) ], @macros;

print <<'EOT';
// GENERATED FILE, DO NOT EDIT

#ifndef _WXPERL_V_CBACK_DEF_H
#define _WXPERL_V_CBACK_DEF_H

EOT

foreach my $todo ( @todo ) {
    my $args = join '_', @{$todo->[2]};
    my( $c_args, $p_args, $b_args, $tymap ) = macro_call_args( $todo );

    if( $todo->[0] eq 'DEC' && $todo->[1] eq 'VOID' ) {
        my $name = sprintf 'DEC_V_CBACK_VOID__%s_', $args;
        next if $emitted{$name};
        $emitted{$name} = 1;

        printf <<'EOT',
#define %s( RET, METHOD, CONST ) \
    void METHOD(%s) CONST

EOT
        $name, $c_args;
    } elsif( $todo->[0] eq 'DEC' ) {
        my $name = sprintf 'DEC_V_CBACK_ANY__%s_', $args;
        next if $emitted{$name};
        $emitted{$name} = 1;

        printf <<'EOT',
#define %s( RET, METHOD, CONST ) \
    RET METHOD(%s) CONST

EOT
        $name, $c_args;
    } elsif( $todo->[0] eq 'DEF' && $todo->[1] eq 'VOID' ) {
        my $name = sprintf 'DEF_V_CBACK_VOID__%s_', $args;
        next if $emitted{$name};
        $emitted{$name} = 1;

        printf <<'EOT',
#define %s( RET, CVT, CLASS, CALLBASE, METHOD, CONST ) \
    void CLASS::METHOD(%s) CONST \
    {                                                                         \
        dTHX;                                                                 \
        if( wxPliFCback( aTHX_ &m_callback, #METHOD ) )                       \
        {                                                                     \
            wxPliCCback( aTHX_ &m_callback, G_SCALAR|G_DISCARD,               \
                         %s%s );                              \
        }                                                                     \
        else                                                                  \
            CALLBASE;                                                         \
    }

EOT
            $name, $c_args, $tymap, ( $p_args ? ", $p_args" : '' );
    } elsif( $todo->[0] eq 'DEF' ) {
        my $name = sprintf 'DEF_V_CBACK_ANY__%s_', $args;
        next if $emitted{$name};
        $emitted{$name} = 1;

        printf <<'EOT',
#define %s( RET, CVT, CLASS, CALLBASE, METHOD, CONST ) \
    RET CLASS::METHOD(%s) CONST                           \
    {                                                                         \
        dTHX;                                                                 \
        if( wxPliFCback( aTHX_ &m_callback, #METHOD ) )                       \
        {                                                                     \
            wxAutoSV ret( aTHX_ wxPliCCback( aTHX_ &m_callback, G_SCALAR,     \
                                             %s%s ) );                      \
            return CVT;                                                       \
        }                                                                     \
        else                                                                  \
            CALLBASE;                                                         \
    }

EOT
            $name, $c_args, $tymap, ( $p_args ? ", $p_args" : '' );
    }
}

foreach my $todo ( @todo ) {
    my $args = join '_', @{$todo->[2]};
    my( $c_args, $p_args, $b_args, $tymap ) = macro_call_args( $todo );

    my $const = $todo->[3]->{const} ? '_const' : '';
    my $pure = $todo->[3]->{pure} ? '_pure' : '';

    die 'No type name for ', $todo->[1]
        unless $type_map{$todo->[1]}[0];
    die 'No type conversion for ', $todo->[1]
        unless $type_map{$todo->[1]}[1];

    if( $todo->[0] eq 'DEC' && $todo->[1] eq 'VOID' ) {
        printf <<'EOT',
#define DEC_V_CBACK_VOID__%s%s( METHOD ) \
    DEC_V_CBACK_VOID__%s_( %s, METHOD, %s )

EOT
            $args, $const, $args, $type_map{$todo->[1]}[0],
            $const_map{$todo->[3]->{const}};
    } elsif( $todo->[0] eq 'DEC' ) {
        printf <<'EOT',
#define DEC_V_CBACK_%s__%s%s( METHOD ) \
    DEC_V_CBACK_ANY__%s_( %s, METHOD, %s )

EOT
            $todo->[1], $args, $const, $args, $type_map{$todo->[1]}[0],
            $const_map{$todo->[3]->{const}};
    } elsif( $todo->[0] eq 'DEF' && $todo->[1] eq 'VOID' ) {
        my $callbase = sprintf 'BASE::METHOD(%s)', $b_args;
        die 'No default value for pure function ', $todo->[1]
            if $todo->[3]{pure} && !$type_map{$todo->[1]}[2];

        printf <<'EOT',
#define DEF_V_CBACK_VOID__%s%s%s( CLASS, BASE, METHOD ) \
    DEF_V_CBACK_VOID__%s_( %s, %s, CLASS, %s, METHOD, %s )

EOT
            $args, $const, $pure, $args, $type_map{$todo->[1]}[0],
            $type_map{$todo->[1]}[1],
            ( $todo->[3]{pure} ? $type_map{$todo->[1]}[2] : $callbase ),
            $const_map{$todo->[3]->{const}};
    } elsif( $todo->[0] eq 'DEF' ) {
        my $callbase = sprintf 'return BASE::METHOD(%s)', $b_args;
        die 'No default value for pure function ', $todo->[1]
            if $todo->[3]{pure} && !$type_map{$todo->[1]}[2];

        printf <<'EOT',
#define DEF_V_CBACK_%s__%s%s%s( CLASS, BASE, METHOD ) \
    DEF_V_CBACK_ANY__%s_( %s, %s, CLASS, %s, METHOD, %s )

EOT
            $todo->[1], $args, $const, $pure, $args, $type_map{$todo->[1]}[0],
            $type_map{$todo->[1]}[1],
            ( $todo->[3]{pure} ? $type_map{$todo->[1]}[2] : $callbase ),
            $const_map{$todo->[3]->{const}};
    }
}

print <<'EOT';

#endif

EOT

sub parse_macro {
    my( $macro, $types ) = @_;
    my( $type, $ret, @args, %flags );

    $flags{$_} = 0 foreach qw(const pure);

    my $tmp = $macro;
    $tmp =~ s/_const// and $flags{const} = 1;
    $tmp =~ s/_pure//  and $flags{pure} = 1;

    $tmp =~ s/^DE([CF])_V_CBACK// and $type = 'DE' . $1;
    $tmp =~ s/^_([A-Za-z]+)__//   and $ret = $1;

    @args = split '_', $tmp;

    die "Unable to parse '$macro'" unless @args && $ret;
    $types->{$_} or die "invalid type $_ in '$macro'" foreach $ret, @args;

    return ( $type, $ret, \@args, \%flags );
}

sub macro_call_args {
    my( $todo ) = @_;

    my( $c_args, $p_args, $b_args, $tymap );
    if( $todo->[2][0] eq 'VOID' ) {
        $c_args = $p_args = $b_args = '';
        $tymap = 'NULL';
    } else {
        my $c = 0;
        my( @cargs, @pargs, @bargs );
        foreach my $idx ( 0 .. $#{$todo->[2]} ) {
            my $type = $todo->[2][$idx];
            die 'Incomplete type definition for ', $type
              unless    $type_map{$type}[3]
                     && $type_map{$type}[4]
                     && $type_map{$type}[5];
            $cargs[$idx] = sprintf $type_map{$type}[3], $idx + 1;
            $tymap .= $type_map{$type}[4];
            $pargs[$idx] = sprintf $type_map{$type}[5], $idx + 1;
            $bargs[$idx] = sprintf $type_map{$type}[6], $idx + 1;
        }
        $c_args = ' ' . join( ', ', @cargs ) . ' ';
        $p_args = join( ', ', @pargs );
        $b_args = join( ', ', @bargs );
        $tymap  = qq{"$tymap"};
    }

    return ( $c_args, $p_args, $b_args, $tymap );
}
