#!/usr/bin/perl -W

# parses the config_t structure in global.h and generates a command sequence
# used to configure the avr 

# ParseStruct.pm is in bin/ directory, I was unlucky with:
# push (@INC, 'bin'); push (@INC, '.');
# therefore the bin:: hack:
use bin::ParseStruct;

# TODO: make it possible to accept 
# more than one in-files (config.txt tables.txt)
# and use STDOUT as output
if (@ARGV != 3) {
    print "usage:\n";
    print "\t make_conf global.h in-data out-commands\n\n";
    exit;
}


my ($headerfilename, $configtxt, $mttfilename ) = @ARGV;

check_mcd_txt();

$psobj = ParseStruct->new();
$psobj->wantedstructs( 'config_t' );
$psobj->datasource( $headerfilename );
$psobj->readstructs();
my $vars = $psobj->getstructvars( 'config_t' );

#print "hash created\n";

open(DATA, "<$configtxt") or die "can't open $configtxt";
open(OF, ">$mttfilename");

print OF "Mantt\n";

varvaldictionary_init();

# now match the data file with the entries in the hash to get the offset
# todo: merge this with make_table.pl  ...

my $warning_cnt = 0;
my $error_cnt = 0;
my $line_number=0;
while($l = <DATA>) {
    chomp $l;
    $line_number++;

	my $parsedline = parseline( $l );

	if( ! defined $parsedline){
		print STDERR "ERROR @ line $line_number: can't understand \"$l\"\n";
		$error_cnt += 1;
	next;
    }

	my $varname =  $parsedline->{varname};
	
    if ( ! defined $varname ){
		# we still need to write the comments...
		write_config_mcd_txt_comment( $parsedline );
		next;
	}
	
	my $varvalue = write_config_mcd_txt( $parsedline );
	
	if (defined($vars->{$varname})) {
#	    print "var=$varname, value=$varvalue, offset=$vars{$varname}\n";

	    # we use explicite positioning, the posl auto increment could be utilized, but
	    # a second more or less doesn't matter here.
	    printf(OF "g%02Xc%02X\n", $vars->{$varname}, $varvalue);

	    if (defined($var_status{$varname})) {
			print STDERR "INFO @ line $line_number: \"$varname\" already assigned, old value=$var_status{$varname}, new value=$varvalue\n";
			$var_status{$varname}=$varvalue;
	    } else {
			$var_status{$varname}=$varvalue;
	    }

	} else {
	    print STDERR "WARNING @ line $line_number: variable \"$varname\" not found in config_t\n";
	    $warning_cnt += 1;
	}
}
print OF "mcs\n";  # config save everything
print OF "bye\n";  # goodbye
my $var_count = keys %$vars;

# check if all variables from config_t was assigned
foreach $n (sort keys %$vars) {
    if (!defined($var_status{$n})) {
		print STDERR "WARNING: variable \"$n\" in config_t not found in \"$configtxt\"\n";
		$warning_cnt += 1;
    }
} 

print STDERR "done, $var_count variables:"
	. $psobj->errors() . " config_t errors, "
	. " $error_cnt config.txt errors, $warning_cnt warnings\n";

close DATA;
close OF;

# useful to hide real values from users by applying symbolic names
# TODO: this should only apply when varname ends in '_channel' ?
# TODO: do we want to print which was the previous (conflicting) config variable ? 
sub varvaldictionary
{
	my $rawdictionary = shift;
	my $rawval = shift; $rawval = hex($rawval);

	my $retval = 16 * $rawval;
	my ($dictkey_inverting) = ($rawdictionary =~ /^([\w\d]+)\_$/);
	
	my $l = length($dictkey_inverting);
	my $i_or_not_i = substr($dictkey_inverting, $l-1, 1);
	# my ($dictkey, $inverting) = ($dictkey_inverting =~ /^\s*([\w\d]+?)(i?)\_$/ );
	my $dictkey;
	if($i_or_not_i eq 'i') {
		$dictkey = substr($dictkey_inverting, 0, $l-1);
		$retval += 128;
	} else {
		$dictkey = $dictkey_inverting;
	}

	my $logmsg = ""; ;
	if(exists $varvaldictionary{$dictkey}){
		# 16 * for easy reading and fast firmware operation 
		# (8* would be enough at this time)		
		$retval += $varvaldictionary{$dictkey};		
	} else {
		$logmsg .= "WARNING: INVALID OUTPUT NAME, no dictionary for $dictkey \n";
	}
	my $conflictmsg = watch_conflict($retval, $rawdictionary, $rawval);
	
	$logmsg .= "rawdictionary: $rawdictionary" . $rawval 
				. "=> $retval\n"	.$conflictmsg;
	print STDERR $logmsg;
	return $retval;
}

# ===========================
# returns warning message if it's already used, or ""
sub watch_conflict
{
	my $output = shift;
	my $d1 = shift;	
	my $d2 = shift;
	
	return "" if ($d1 =~ /DISABLED/);
	my $tolog = "";
	if(defined $output_already_used{$output}){
		$tolog = "WARNING: CONFLICTING OUTPUT. Previously used "
		. $output_already_used{$output} . " times, this can't be right. "
		. $d1 . $d2 . "\n";
	}
	
	$output_already_used{$output} ++;
	return $tolog;
}

sub varvaldictionary_init
{
# the .259 defines match the given CS signal bitposition!
	%varvaldictionary =	(
	INJFET => 0,
	SPECFET => 1,
	I259 => 2,	
	S259 => 6,
	P259 => 7,
	DISABLED => 15
	);

}

# =====================
# if 'mcd.txt' file is found, we will read the values,
# so write_config_mcd_txt() can
# return instead of the config.txt values 
# and write output to config_mcd.txt
# This way it is possible to update the commented config.txt
# with values read back from the controller (that have no comments :-).
sub check_mcd_txt
{
	my $filename = "etc/mcd.txt";
	if( ! open( MCD_TXT, "<$filename") ){
		print STDERR "Could not read $filename. This is OK. Data from ECU mcd output saved to mcd.txt file is merged automatically so easy to see what's changed in config in the ECU (eg. changed from MegaTunix or menu) and comments are carried over as well (merged, commented result written into config_mcd.txt).";
		return;
	}

	# global variable, will be checked from sub write_config_mcd_txt()
	$config_mcd_txt_outputfilename = "etc/config_mcd.txt";
	if( ! open( CONFIG_MCD_TXT, ">$config_mcd_txt_outputfilename" )){
		print STDERR "Could not open $config_mcd_txt_outputfilename for writing";
		undef $config_mcd_txt_outputfilename;
	}
	
	print STDERR "Values from $filename take precedence.\nMake sure this is intentional ($filename is not old)\n";
	
	my $l;
	while($l = <MCD_TXT>) {
		chomp $l;
		my $parsedline = parseline( $l );
		if( ! defined  $parsedline){
			next;
		}

		my $varname = $parsedline->{varname};
		if ( defined $varname ){
			$mcdtxt->{$varname} = $parsedline;
		}
	}

}

# ================
sub write_config_mcd_txt_comment
{
	if( ! defined $config_mcd_txt_outputfilename ){
		# nothing to do
		return;		
	}
	my $parsedline = shift;

	if (! defined $parsedline || ! defined $parsedline->{comment} ){
		return;
	}
	
	print CONFIG_MCD_TXT $parsedline->{comment} ."\n";
}
# =================
# updates value if necessary and writes to output
# returns the effective varvalue
sub write_config_mcd_txt
{
	my $parsedline = shift;
	if( ! defined $parsedline ){
		return undef;
	}
	
	if( ! defined $config_mcd_txt_outputfilename ){
		# nothing special to do
		return $parsedline->{varvalue};		
	}

	my $varname = $parsedline->{varname};
	my $varvalue = $parsedline->{varvalue};
	
	my $powerline = $mcdtxt->{$varname}; # this might take effect
	
	# check if there is a change:
	my $uncooked = $parsedline->{uncooked};
	if( defined $powerline ){ # maybe update
		# print STDERR "powerline=$powerline\n";
		if( defined $powerline->{varvalue} ){
			# print STDERR "powerline varvalue: $powerline->{varvalue}\n";
		
			if( ! defined $varvalue 
			|| $varvalue != $powerline->{varvalue} ){			
				my $uc = $powerline->{uncooked};
				my $vv = $powerline->{varvalue};
				print STDERR "updating variable $varname : $uncooked => $uc\n";
				$varvalue = $vv;
				$uncooked = $uc;
			}
		}
	}
	
	print CONFIG_MCD_TXT $varname . '=' . $uncooked . $parsedline->{comment} ."\n";
	return $varvalue;
}
# ==============
# parses lines like:
# # comment
# key=f4 # key->value and comment
# key=I259i_7  # comment
# result is a hashref that might not have ->{varname} key
sub parseline
{
	my $l = shift;
	
	my $ret;
	# comments allowed: (empty line, //, #, ;)
	# this is a bit tricky...
	my ($interesting, $commentsign, $comment) 
		= ($l =~ /^([\s\w\.\_\[\]=]*)(\/\/|\#|;)(.*)?$/ );

	if( ! defined $commentsign ){
		$interesting = $l;
	} else {
		# put back some whitespace that tries to escape truth
		if( $interesting =~ /(\s+)$/ ){
			$commentsign = $1 . $commentsign;
		}
	}

	# print STDERR "interesting=$interesting\n";

	my ($part1, $precommentwhitespace)
		= ( $interesting =~ /^\s*(.*)?(\s*)$/ );
		
	if( defined $commentsign ){
		$ret->{comment} = $precommentwhitespace. $commentsign . $comment;
	} else {
		$ret->{comment} = '';
	}

	if( $part1 eq '' ){
		return $ret;
	}

	# print STDERR "part1=$part1\n";
	my ($varname, $arraysize, $uncooked) 
		= ( $part1 =~ /(\w+(\[\w+\])?)=(\S+)/ );

	if( ! defined $varname || !defined $uncooked){
		print STDERR "Strange line: $l\n";
		return undef;
	}

	# match a name=FF or name[1]=FF or name=I259i_7
	my ($varvaldictionary, $varvalue)
		= ( $uncooked =~ /([\w]*\_)?([a-fA-F0-9]{1,2})\b/ );

	if( ! defined $varvalue ){
		print STDERR "Strange line: close, but....: $l\n";
		return $ret;
	}

	if(defined $varvaldictionary && $varvaldictionary ne ''){
		$varvalue = varvaldictionary($varvaldictionary, $varvalue);
	} else {
		$varvalue = hex($varvalue);
	}

	$ret->{varname} = $varname;
	$ret->{varvalue} = $varvalue;
	$ret->{uncooked} = $uncooked;
	return $ret;
}

