#!/usr/bin/perl

use IPC::Run 'run';

#   For each include line in the file:
#     Write the file out to a temporary file, less that line.
#     Compile.
#     If it succeeds, then it was an unnecessary include.
sub test_unnecessary_inclusion($$$$$)
{
    my $lines_ref = shift;
    my @lines = @{$lines_ref};
    my $line = shift;
    my $lines = @lines;
    my $source_file = shift;
    my $argv_ref = shift;
    my @argv = @{$argv_ref};
    my $source_index = shift;
    my $i;
    my $tmp;

    open(my $fh, ">", $source_file) or die("Failed to open $source_file for writing.\n");
    for ($i = 0; $i < $line; $i++)
    {
	print $fh "$lines[$i]\n";
    }
    print $fh "//$lines[$line]\n";
    for ($i = $line+1; $i < $lines; $i++)
    {
	print $fh "$lines[$i]\n";
    }
    close($fh);

    $tmp = $argv[$source_index];
    $argv[$source_index] = $source_file;
    run [ "gcc",
          "-Werror=implicit-function-declaration",
	  "-Werror=missing-prototypes",
	  "-Werror=missing-declarations",
          @argv ], ">&", \my $stdout;
    $argv[$source_index] = $tmp;
    close($stdout);

    if ($? == 0) {
	print "Unneccessary?: $line: $lines[$line]\n";
    }
    unlink($source_file);
}

#   For each include line in the file:
#     If that include line is in the local source directories
#     Compile that header file alone.
#     If it fails, flag it.
sub test_header_independence($$$)
{
    my $line = shift;
    my $argv_ref = shift;
    my @argv = @{$argv_ref};
    my $argc = @argv;
    my $source_index = shift;
    my $i;
    my $tmp;

    if ($line !~ m/#include "(.*?)"/)
    {
	return;
    }

    my $filename = $1;

    if (open(my $fh, "$filename"))
    {
	# Filename is in the current dir
    } else {
	close($fh);
	for ($i = 0; $i < $argc; $i++)
	{
	    if ($argv[$i] =~ m/-I(.*)/)
	    {
		my $dir = $1;
		if (open($fh, "$dir/$filename")) {
		    $filename = "$dir/$filename";
		    goto FOUND;
		}
                close($fh);
	    }
	}
	# Didn't find it. It must be a system header. Ignore it.
	return;
    }
  FOUND:
    close($fh);
    
    $tmp = $argv[$source_index];
    $argv[$source_index] = $filename;
    run [ "gcc", @argv ], ">&", \my $stdout;
    #system("gcc", @argv);
    $argv[$source_index] = $tmp;
    close($stdout);

    if ($? != 0) {
	print "Non-independent?: $filename\n";
    }
}

#------------------------------------------------------------------------
# main starts here

# Collect the args
my @argv = @ARGV;
my $argc = @ARGV;
my $i;
my $source_file;
my $source_index;

# Run the build.
#print "Vanilla compile:\n";
system("gcc", @ARGV);

# If it fails to build, exit with an error.
if ($? != 0) {
    die "Build failed. Header testing aborted.\n";
}

# Extract the source filename from the command line.
for ($i = 0; $i < $argc; $i++)
{
    if ($argv[$i] eq "-o")
    {
	$i++;
    }
    elsif ($argv[$i] eq "-c")
    {
	$i++;
	while ($i < $argc && $argv[$i] =~ m/^-.*/)
	{
	    $i++;
	}
	$source_index = $i;
	$source_file = $argv[$source_index];
    }
}
if (!defined $source_file)
{
    die("Failed to extract source filename from commandline.\n");
}
#print "Source file is: $source_file\n";

# Read the file in.
my @lines;
open(my $fh, "<", $source_file) or die("Failed to open $source_file\n");
while (<$fh>) {
    chomp;
    push @lines, $_;
}
close $fh;

# Replace -o with /dev/null
for ($i = 0; $i < $argc; $i++)
{
    if ($argv[$i] eq "-o")
    {
	$argv[$i+1] = "/dev/null";
    }
}

my $n = @lines;
#print "Searching:\n";
# Search the file for #includes
for ($i = 0; $i < $n; $i++)
{
    if ($lines[$i] =~ m/\#include/)
    {
	#print "Inclusion: $i/$n\n";
	test_unnecessary_inclusion(\@lines,
                                   $i,
                                   $source_file."-hhcc.c",
                                   \@argv,
                                   $source_index);
    }
}

for ($i = 0; $i < $n; $i++)
{
    if ($lines[$i] =~ m/\#include/)
    {
	#print "Independence: $i/$n\n";
	test_header_independence($lines[$i],
                                 \@argv,
                                 $source_index);
    }
}
