#!/usr/bin/perl -w
# takes a linear output from cilly, and mangles it to be executable
# this is necessary because we might not have closed braces due to crashes
# also, we need to decide for each function call, whether to leave the call in place
# or to 


#TODO Handle returns and gotos

use strict;
use warnings;
use constant {TRUE => 1,FALSE => 0};
use constant {CI_DEFAULT_STATE => 0,
	      CI_CALL_FOUND => 1,
	      CI_CALL_SETUP => 2,
	      CI_DONE_SETUP => 3,
	      CI_INLINED_CALL => 4,
	      CI_NON_INLINED_CALL => 5};

my %stateName = (
    0 => "CI_DEFAULT_STATE",
    1 => "CI_CALL_FOUND",
    2 => "CI_CALL_SETUP",
    3 => "CI_DONE_SETUP",
    4 => "CI_INLINED_CALL",
    5 => "CI_NON_INLINED_CALL");


my $spacesPerIndent = 2;
main();

sub stripIndent {
    my ($line, @bad) = @_;
    die "Too many arguments " if @bad;

    #the final /s makes . match newlines
    $line =~ m/^\s*(.*)/s;
    return $1;
}


sub isOpenBrace {
    my ($line, @bad) = @_;
    die "Too many arguments " if @bad;

    return $line =~ m/^\s*{/;
}

sub isCloseBrace {
    my ($line, @bad) = @_;
    die "Too many arguments " if @bad;

    return $line =~ m/^\s*}/;
}

sub isMatchingCloseBrace {
    my ($line, $level, @bad) = @_;
    die "Too many arguments " if @bad;

    return isCloseBrace($line) && (indentLevel($line) == $level);
}


sub indentLevel {
    my ($line, @bad) = @_;
    die "Too many arguments " if @bad;

# see http://stackoverflow.com/questions/3916852/how-can-i-count-the-amount-of-spaces-at-the-start-of-a-string-in-perl
    my $numLeadingSpaces = length( ($line =~ m/^( *)/)[0] );
    return ($numLeadingSpaces / $spacesPerIndent);
}

sub closeBraces {
    my ($lines, @bad) = @_;
    die "Too many arguments " if @bad;
    my $lastLine = @$lines[-1];
    my @closingBraces = printCloseBraces(indentLevel($lastLine),0);
    push $lines, @closingBraces;
}


sub indentSpaces {
    my ($i, @bad) = @_;
    die "Too many arguments " if @bad;
    
    return " " x ($i * $spacesPerIndent);
}
    
sub indentLine {
    my ($i, $line, @bad) = @_;
    die "Too many arguments " if @bad;

    my $stripped = stripIndent($line);
    my $indentation = indentSpaces($i);
    return $indentation . $stripped;
}

sub printCloseBraces {
    my ($initial,$final,@bad) = @_;
    die "Too many arguments " if @bad;

    my @newlines;
    for (my $i = $initial; $i > $final; $i--){
	#always close an indent at one level lower
	my $newline = indentLine ($i-1, "}\n") ;
	push @newlines, $newline;
    }
    return @newlines;
}


#given a piece of code, instantiate the calls relevent for that code
#if something is inlined, then we need to create temporary variables for
#it; otherwise, we need to create the formal call
sub instantiateCalls {
    my ($arrayRef,@bad) = @_;
    die "Too many arguments " if @bad;
    
    my @output;
    my $state = CI_DEFAULT_STATE;
    my $indentDepth;
    my $currentCall;
    my @callSetup;
    my @inlineCallBody;
        
    foreach my $line (@$arrayRef) {
	#print "$stateName{$state}: $line" if ($recursive);
	if ($state == CI_DEFAULT_STATE) {
	    #just to be clean, undef anything that 
	    #isn't defined in the default state
	    #we could move this to every time that we go into default state
	    # but that is more error prone.
	    undef $indentDepth;
	    undef $currentCall;
	    undef @callSetup;
	    undef @inlineCallBody;
	    	
	    if ($line =~ m/^\s*\/\/call/){
		$currentCall = $line;
		$state = CI_CALL_FOUND;
	    } else {
		push @output, $line;
		$state = CI_DEFAULT_STATE;
	    }
	} 

	#collect the 
	elsif ($state == CI_CALL_FOUND) {
	    #the first line after is an open brace
	    isOpenBrace($line) or die "not an open brace";
	    $indentDepth = indentLevel($line);
	    push @callSetup, $line;
	    $state = CI_CALL_SETUP;
	}

	elsif ($state == CI_CALL_SETUP) {
	    push @callSetup, $line;
	    if ($line =~ m/^\s*\/\/done setup/){
		$state = CI_DONE_SETUP;
	    } else {
		$state = CI_CALL_SETUP;
	    }
	}

	#either we immediadly follow done with enter
	#or we are in a call we cannot inline
	elsif ($state == CI_DONE_SETUP) {
	    if ($line =~ m/^\s*\/\/enter/) {
		push @callSetup, $line;
		$state = CI_INLINED_CALL;
	    } else {
		$currentCall =~  m/^\s*\/\/call(.*)/;
		my $actualCall = "$1";
		my $indentedCall = indentLine($indentDepth, $actualCall);
		push @output, "$indentedCall\n";
		if (isMatchingCloseBrace($line,$indentDepth)){
		    $state = CI_DEFAULT_STATE;
		} else {
		    $state = CI_NON_INLINED_CALL;
		}
	    }
	}

	elsif ($state == CI_NON_INLINED_CALL) {
	    if (isMatchingCloseBrace($line,$indentDepth)){
		$state = CI_DEFAULT_STATE;
	    } else {
		$state = CI_NON_INLINED_CALL;
	    }
	}
	
	elsif($state == CI_INLINED_CALL) {
	    if (isMatchingCloseBrace($line,$indentDepth)){
		push @inlineCallBody, $line;

		my @reduced = instantiateCalls(\@inlineCallBody);
		push @output, $currentCall;
		push @output, @callSetup;
		push @output, @reduced;
		$state = CI_DEFAULT_STATE;

	    } else {
		push @inlineCallBody, $line;
		$state = CI_INLINED_CALL;
	    }
	}
	
	else { 
	    die "improper state";
	}
    }
    return @output;
}

sub main {
    my ($infilename, @bad) = @ARGV;
    die "Too many arguments " if @bad;
    die "missing filename " unless $infilename;

    # print "$infilename\n";

    open INFILE, "<", $infilename or die $!;
    my @infile = <INFILE>;

    closeBraces(\@infile);
    my @instantiatedCalls = instantiateCalls(\@infile);

    
    foreach my $line (@instantiatedCalls){
	print $line;
    }
}



