#!/usr/bin/perl -w

# Copyright (C) CNRS, INRIA, Université Bordeaux 1, Télécom SudParis
# See COPYING in top-level directory.

use File::Temp qw/ tempfile tempdir /;
use File::Copy;
use Getopt::Long;

my $prefix;
my $exec_prefix;
my $libdir;
my $includedir;
my $bindir;
my $create_plugin_options = "";

BEGIN {
    $prefix="@prefix@";
    $exec_prefix="@exec_prefix@";
    $libdir="@libdir@";
    $includedir="@includedir@";
    $bindir="@bindir@";
}

sub parse_options {
    my $backtrace="";
    GetOptions ('backtrace|b' => \$backtrace);
    if($backtrace ne "") {
	$create_plugin_options .= "-b";
    }
}

parse_options();

my $indent_fortran="$prefix/bin/eztrace_indent_fortran";

# format a source file so that function prototypes
# are properly formatted (eg. everything on line line)
sub format_source_file( $ ) {
    my $fname = shift;

    my $fh;
    my $filename;

    my $suffix;
    if( $fname =~ m/.+\.c$/) {
	$suffix=".c";
    } elsif( $fname =~ m/.+\.cpp$/) {
	$suffix=".cpp";
    } elsif( $fname =~ m/.+\.f$/) {
	# todo .f77, .f90, .F, etc.
	$suffix=".f";
    }
    ($fh, $filename) = tempfile(SUFFIX=>$suffix);

    copy($fname, $filename) or die "Copy failed: $!";

    if($suffix eq ".c") {
	`indent -npsl --line-length10000  $filename`;
    }

    if($suffix eq ".cpp") {
	`indent -npsl --line-length10000  $filename`;
    }

    if($suffix eq ".f") {
	`$indent_fortran $fname $filename`;
    }

    return $filename;
}

# return the prototype of function $fname extracted from $filename
sub extract_prototype( $$ ) {
    my $fname=shift;
    my $filename=shift;

    # in some cases, fname main contain (). Remove these parenthesis
    $fname =~ s/\(\s*\)//;

    # don't instrument the main function
    if($fname eq "main") {
	return "";
    }

    # format the source code
    my $new_filename=format_source_file($filename);

    # extract symbols
    my $program_language;
    my $symbols="";
    if ($new_filename =~ m/.\.c/) {
	$program_language="C";
	$symbols=`ctags --c-kinds=pf --fields=+S -x $new_filename`;
    } elsif ($new_filename =~ m/.\.cpp/) {
	$program_language="CPP";
	$symbols=`ctags --c++-kinds=pf --fields=+S -x $new_filename`;
    } elsif ($new_filename =~ m/.\.f/) {
	$prototype = "void ".$fname."()";
	$symbols=`ctags --fortran-kinds=pfis --fields=+S -x $new_filename`;
    } else {
	# example: f90, c++, etc.
	# not implemented
	printf "Cannot extract information about function %s: file format not supported\n", $fname;
	printf "\tAssuming the function prototype is void %s()\n", $fname;
	$prototype = "void ".$fname."()";
	unlink($new_filename);
	return $prototype;
    }

    my $fortran_fname=$fname;
    $fortran_fname=~ s/_$//;
    my @symbol_list = split('\n', $symbols);

    foreach $symbol(@symbol_list) {
	# each line should look like this:
	# <foo_func> function 22 <source_file.c> <void foo_func (int len)>
	if($symbol =~ m/^($fname)\s+(function)\s+\d+\s+\S+\s+(.+)/) {
	    # fname was found. return the corresponding prototype
	    ($prototype) = ($symbol =~ m/$fname\s+function\s+\d+\s+\S+\s+(.+)$/);
	    unlink($new_filename);
	    # remove any /* ... */ comment that we might have found
	    $prototype =~ s/\/\*.*\*\///g;
	    return $prototype;
	}

# for fortran files, each line looks like this
# <foo_func>      subroutine   95 <source_file.f> subroutine <foo_func (arg1, arg2)>
	if($symbol =~ m/^($fortran_fname)\s+subroutine/) {
	    # fname was found. return the corresponding prototype
	    printf "$fname found in a subroutine\n";
	    ($prototype) = ($symbol =~ m/$fortran_fname\s+subroutine\s+\d+\s+\S+\s+(.+)$/);
	    unlink($new_filename);
	    # remove any /* ... */ comment that we might have found
	    $prototype =~ s/\/\*.*\*\///g;

	    # replace ^subroutine with void
	    $prototype =~ s/^subroutine $fortran_fname/void $fname/;

	    $prototype =~ s/>//g;

	    # cast each parameter to int*
	    $prototype =~ s/\(\s*(\S+)/\(int *$1/;
	    $prototype =~ s/,/, int */g;
	    if($prototype !~ m/\(/) {
		printf("Proto doesnt contain parenthesis!\n");
		$prototype .= "( )";
	    }
	    return $prototype;
	}
    }

    # not found
    unlink($new_filename);
    return "";
}

if (@ARGV<1) {
    printf "Usage: eztrace_plugin_generator <binary_file>\n";
    exit 1;
}
my $input_file=$ARGV[0];
my $input_file_parsed=$input_file;
$input_file_parsed =~ s/\//_/g;
$input_file_parsed =~ s/-/_/g;
$input_file_parsed =~ s/\./_/g;

my $output_file=$input_file_parsed.".tpl";
my $plugin_dir="plugin_".$input_file_parsed;
my $module_name=$input_file_parsed;
$module_name =~ s/\./_/g;

open($output_handle, '>', $output_file)
    or die "Couldn't open $output_file for writing!";

printf $output_handle "BEGIN_MODULE\n";
printf $output_handle "NAME %s\n", $module_name;
printf $output_handle "DESC \"Module for the %s program\"\n", $input_file;
printf $output_handle "\n";
printf $output_handle "# In this section, insert the #include/#define directives that are needed for\n";
printf $output_handle "# compiling\n";
printf $output_handle "BEGIN_INCLUDE\n";
printf $output_handle "\n";
printf $output_handle "END_INCLUDE\n\n";
printf $output_handle "\n";
printf $output_handle "# In this section, insert the CFLAGS that are needed for compiling\n";
printf $output_handle "BEGIN_CFLAGS\n";
printf $output_handle "\n";
printf $output_handle "END_CFLAGS\n";
printf $output_handle "\n";
printf $output_handle "# In this section, insert the LDFLAGS that are needed for compiling\n";
printf $output_handle "BEGIN_LDFLAGS\n";
printf $output_handle "\n";
printf $output_handle "END_LDFLAGS\n";
printf $output_handle "\n";


my $symbols=`nm --demangle --defined -l $input_file`;
my @symbol_list = split('\n', $symbols);
my $nb_symbols=0;

printf "Creating the plugin script %s\n", $output_file;
foreach $symbol(@symbol_list) {
    if( $symbol =~ m/\s+T\s+(.+)\s+(.+)/ ) {
	($fname, $file) = ( $symbol =~ m/\s+T\s+(\S+)\s+(.+):/ );
	my $proto=extract_prototype($fname, $file);
	if ($proto ne "") {
	    $nb_symbols++;
	    printf "\tFound '%s'\n", $proto;
	    printf $output_handle "%s\n\n", $proto;
	}
    }
}

printf $output_handle "END_MODULE\n";
close($output_handle);

if ( $nb_symbols == 0) {
	# No symbol found
	printf "No symbol found!\n";
	printf "Make sure the program contains debugging symbols (ie. compile it with -the -g option)\n";
	exit ;
}
printf "%d symbols found\n", $nb_symbols;

printf "\nGenerating the plugin...\n";
printf "\t\$ %s/eztrace_create_plugin %s -o %s %s\n", $bindir, $create_plugin_options, $plugin_dir, $output_file;
`$bindir/eztrace_create_plugin $create_plugin_options -o $plugin_dir $output_file`;
if ( $? != 0) {
    die "Plugin creation failed !\n";
}

printf "\nCompiling the plugin...\n";
printf "\t\$ make -C %s\n", $plugin_dir;
`make -C $plugin_dir`;
if ( $? != 0) {
    die "Compilation failed ! Please fix the ".$output_file." template file and re-run the eztrace_create_plugin command\n";
}


printf "\nYou can now use the generated plugin by setting the following environment variables:\n";
printf "\t\$ export EZTRACE_LIBRARY_PATH=\$EZTRACE_LIBRARY_PATH:%s\n", `readlink -f $plugin_dir`;
