#!/usr/bin/perl -w

# Perl script that reads the same glade files that megatunix uses for custom defined gui.

# By adding:
#   rs232 handling
#   config file parsing
#   .datamap file parsing
# it should be possible to get it to communicate with the genboard. Thus we'll have
# a very simple tuning interface that works now, and later on the gui can be used in 
# megatunix.

# http://gtk2-perl.sourceforge.net/doc/gtk2-perl-tut/index.html
# http://www.gnome.org/~newren/tutorials/developing-with-gnome/html/index.html (examples in appendix C)

use Glib qw(TRUE FALSE);
use Gtk2 '-init';    # The '-init' means Gtk2->init gets called initially
use Gtk2::GladeXML;
use bin::ParseStruct;
use Class::Struct;
use Device::SerialPort;
use Math::Expression; #perl -MCPAN -e 'install Math::Expression'
use threads;
use threads::shared;

#use strict;
use vars qw($main_window);


if (defined($ARGV[1])) {
    $PORT = $ARGV[1];
} else {
    $PORT = "";
}

if ($PORT eq "") {
    $PORT = "/dev/ttyS0";
}

$ob = Device::SerialPort->new ($PORT) || die "Can't open $PORT: $!";
$ob->baudrate(9600)   || die "failed setting baudrate";
$ob->parity("none")    || die "failed setting parity";
$ob->databits(8)       || die "failed setting databits";
$ob->handshake("none") || die "failed setting handshake";
$ob->binary(T);
$ob->read_char_time(500);
$ob->read_const_time(500);
$ob->write_settings    || die "no settings";
$tmout = 500;

struct Datamap => {
    section => '$', # same as widget name
    var_name => '$', # used for config_t
    var_offset => '$', # used for config_t

    # Datamap keys
    page => '$',
    offset => '$',
    bit_shift => '$',
    bit_val => '$',
    bit_mask => '$',
    dl_conv_expr => '$',
    ul_conv_expr => '$',
    handler => '$',
};


# run from firmware/ directory
my $global_h_file = "global.h";
my $glade_file = "doc/megatunix/Gui/genboard_knock.glade";

my $rs232_sig : shared = 0;
my $rs232_cmd : shared = 0;


sub bitmask_button_handler {
    my ($widget, undef) = @_;
    my $name = $widget->get_name;

    if ($widget->get_active) {  # there is one selected button, 
		     # do nothing unless this is the handler for the selected button

	# bit fiddling: bitmask & shifting of the value such that the untouched bits at the memory location are preserved
	if (defined($widgets{$name}->bit_shift) && defined($widgets{$name}->bit_mask)
	    && defined($widgets{$name}->bit_val)) {

	    my $bit_shift = $widgets{$name}->bit_shift;
	    my $bit_mask = $widgets{$name}->bit_mask;
	    my $value = $widgets{$name}->bit_val;

	    my $offset = get_var_offset($name);
	    my $current_value = readmem($offset);

	    $current_value &= ~$bit_mask;
	    $current_value |= (($value << $bit_shift) & $bit_mask);
	    $value = $current_value;
	
	    writemem($offset, $value);
	}
    }
}

sub spin_button_handler {
    my ($widget, undef) = @_;
    my $name = $widget->get_name;

    my $value = $widget->get_value; 
    $value =~ s/,/\./;  # . instead of ,

    if (defined($widgets{$name}->dl_conv_expr)) {
	$value = parse_expr($value, $widgets{$name}->dl_conv_expr);
    }

    my $offset = get_var_offset($name);

    # bit fiddling: bitmask & shifting of the value such that the untouched bits at the memory location are preserved
    if (defined($widgets{$name}->bit_shift) && defined($widgets{$name}->bit_mask)) {
	my $bit_shift = $widgets{$name}->bit_shift;
	my $bit_mask = $widgets{$name}->bit_mask;
	my $current_value = readmem($offset);

	$current_value &= ~$bit_mask;
	$current_value |= (($value << $bit_shift) & $bit_mask);
	$value = $current_value;
    }

    writemem($offset, $value);
}

sub std_button_handler {
    my ($widget, undef) = @_;
    my $name = $widget->get_name;
#    print "button: $name\n";
    
    if ($name eq "topwindow" || $name eq "knock_exit_button") {
	leave();
    } elsif ($name eq "knock_get_data_button") {
	if (defined($tmr_ret_val) && $tmr_ret_val) {
	    Glib::Source->remove($tmr_ret_val);
	      undef $tmr_ret_val;
	  }

	get_vars_from_ecu();
	init_widgets();
    } elsif ($name eq "knock_save_button") {
	comm_send('mcs');
    } elsif ($name eq "knock_start_rt_button") {
	if (!defined($tmr_ret_val)) {
	    my $timer = Glib::Timeout;
	    $tmr_ret_val = $timer->add(100,\&progress_timeout);
	}
    } elsif ($name eq "knock_stop_rt_button") {
	if (defined($tmr_ret_val) && $tmr_ret_val) {
	    Glib::Source->remove($tmr_ret_val);
	      undef $tmr_ret_val;
	  }
    }
}

$main_window = Gtk2::GladeXML->new($glade_file);
Gtk2::Gdk::Threads->init;

# talk to ecu and get memory layout of config variables
menu_chitchat();
get_vars_from_ecu();
#parse_config();  # this is faster than querying the ecu, but then we dont get the data, thus only good for development!

parse_datamap_file();
#TODO: check if some widgets wasn't assigned


# setup widgets to the values of the variables in the avr
init_widgets();

$main_window->signal_autoconnect_from_package('main');


my $t = threads->create(\&rs232_handler);



Gtk2->main;


sub progress_timeout { 

    # signal rs232 thread
    { lock $rs232_sig; cond_signal $rs232_sig; }

    # this is just fun
#    my $the_widget = $main_window->get_widget('knock_status_kvalue_bar');
#    my $t = $the_widget->get_fraction + 0.01;
#    if ($t >= 1) { $t = 0; }
#    $the_widget->set_fraction($t);

    return ( TRUE ) ; 
}

sub rs232_handler {
#    print "thread started\n";
    while (1) {
	{ lock $rs232_sig; cond_wait ($rs232_sig); $rs232_sig = 0; }

	comm_send("mde04");
	$ob->read_const_time($tmout);

	my @expected_header = ("\r", "\n", "k", "=");
	my $len = 19;

	my $buf = "";
	my $cnt = 0;
	my $status = "ok";

	do {
	    # first read a byte
	    my ($data_cnt_in, $data) = comm_recv(1);

	    # then check if it was a success
	    if ($data_cnt_in != 1) {
		$status = "failure";
		$cnt = $len; # terminate do-while loop
	    } else {
		if ($cnt == 0) { comm_send("mde00"); }  # a single stream please

		if ($cnt < @expected_header) {
		    if ($expected_header[$cnt] eq $data) {
			$buf .= $data;
			$cnt++;
		    } else {
			printf("Error in header of runtime data: expectedh=%02x, data=%02x, cnt=%d\n", ord($expected_header[$cnt]), ord($data), $cnt);
			$buf = "";
			$cnt = 0;
		    }
		} else {
		    $buf .= $data;
		    $cnt++;
		}
	    }
	} while ($cnt < $len);

	$recv_cnt++;

	if ($status eq "ok") {
	    my $the_widget = $main_window->get_widget('rt_status_cnt');
	    Gtk2::Gdk::Threads->enter;
	    $the_widget->set_label($recv_cnt);
	    Gtk2::Gdk::Threads->leave;

	    update_runtime_widgets($buf);
	} else {
	    print STDERR "reading of RT variables unsuccessful\n";
	}
    }
}


sub parse_datamap_file {
    $glade_file =~ /^(.+)glade$/;
    my $datamap_file = $1 . "datamap";

    open IF, "<$datamap_file" or die "ERROR, could not open '$datamap_file' for reading: $!";

    print STDERR "loading datamap file\n";
    my $w = "";
    while (my $l = <IF>) {
	chomp($l);
	if ($l =~ /^\s*\[(\w+)\]/) {
	    $w = $1;
	    $widgets{$w} = Datamap->new();
	    $widgets{$w}->section($w);
	} elsif ($l =~ /^\s*#varname\s*=\s*([\w\.]+)\s*$/ ) {
	    my $varname = $1;
	    $widgets{$w}->var_name($varname);
	    $widgets{$w}->var_offset(get_var_offset_from_ecu($varname));
        } elsif ($l =~ /^\s*(page|offset|bit_shift|bit_val|bitmask|dl_conv_expr|ul_conv_expr|handler)=([\w\s\*\+-\/]+)$/) {
	    my $key = $1;
	    my $value = $2;

	    if ($key =~ /page/) {
		$widgets{$w}->page($value);
	    } elsif  ($key =~ /offset/) {
		$widgets{$w}->offset($value);
	    } elsif  ($key =~ /bit_shift/) {
		$widgets{$w}->bit_shift(int $value);
	    } elsif  ($key =~ /bit_val/) {
		$widgets{$w}->bit_val(int $value);
	    } elsif  ($key =~ /bitmask/) {
		$widgets{$w}->bit_mask(int $value);
	    } elsif  ($key =~ /dl_conv_expr/) {
		$widgets{$w}->dl_conv_expr($value);
	    } elsif  ($key =~ /ul_conv_expr/) {
		$widgets{$w}->ul_conv_expr($value);
	    } elsif  ($key =~ /handler/) {
		$widgets{$w}->handler($value);
	    }
        }
    }
}

sub parse_config {
    my $psobj = ParseStruct->new();
    $psobj->wantedstructs( 'config_t' );
    $psobj->datasource( $global_h_file );
    $psobj->readstructs();

    my $vars = $psobj->getstructvars( 'config_t' );

    foreach my $name (keys %$vars) {
	$mcd_offset{$name} = $vars->{$name};
	$memory_map[$vars->{$name}] = 0;
    }
}

sub get_var_offset {
    my $w = shift;

    if (defined($widgets{$w}->var_offset)) {
	return $widgets{$w}->var_offset;
    } else {
	print STDERR "Oops, '$w' has no offset in config!\n";
	return -1;
    }
}

sub menu_chitchat {
# say hi to the ecu
    comm_send('Man'); 
    $b = srl_wait(8);
    if ($b =~ /Hello>/) {
	print "AVR said hello\n";
    } else {
	print "No hello from AVR\n";
    }
#    comm_send('mst51msp01');
}

sub get_vars_from_ecu {
    $ob->lookclear();
    $ob->purge_rx();

    print STDERR "retrieving config structure from ecu\n";

    # get config from avr
    # read one byte at a time, '\n' indicates end of variable=value

    comm_send('mcd'); 

    my $offset = 0;
    my $buf = "";
    my $more_vars = 1;
    my $last_var = "water_pump_channel";
    while ($more_vars == 1) {
	while ((my $data = srl_wait(1)) ne "\n") {
	    $buf .= $data;
	}
	chop $buf; # kill the \r

	my ($name, $value) = split /=/, $buf;
#	print "name=$name, value=$value, offset=$offset\n";

	$mcd_offset{$name} = $offset;
	$memory_map[$offset] = hex($value);
	
	if ($name eq $last_var) {
	    $more_vars = 0;
	}
	$offset++;
	$buf = "";
    }
}

sub get_var_offset_from_ecu {
    my $varname = shift;
    my ($table, $name) = split /\./, $varname;

    if (defined($mcd_offset{$name})) {
	return $mcd_offset{$name};
    } else {
	print STDERR "Warning: variable '$name' not present in firmware\n";
	return -1;
    }
}

sub readmem {
    my $address = shift;
    return $memory_map[$address];
    # we're assuming that noone changed the memory in the ecu
}

sub writemem {
    my ($address, $data) = @_;

    if ($address >= 0) {
	$memory_map[$address] = $data;
	# TODO: write to ecu

	# in case tables are to be written, take care of special relationship between upper/lower bits
	my $a = sprintf("%02x", $address);
	my $d = sprintf("%02x", $data);
	my $str = "mttg${a}c${d}";
    comm_send($str);
	print "$str\n";
    } else {
	print STDERR "Writing cancelled, invalid address\n";
    }
}


sub init_widgets {

    foreach my $name (keys %widgets) { # visit all widgets
	# use handler to distinguish normal control from "runtime" display elements
	if (!defined($widgets{$name}->handler)) {
	    print STDERR "'handler' not specified for '$name'\n";
	    next;
	}

	my $handler = $widgets{$name}->handler;
	my $offset = $widgets{$name}->var_offset; # get corresponding memory offset
	my $the_widget = $main_window->get_widget($name);
	my $widget_type = ref $the_widget;

	if ($handler eq "_GENERIC_") {

	    # then lookup the offset in the memorymap
	    if (defined($offset) && $offset != -1) {
		my $value = readmem($offset);
	    
		# bit fiddling: bitmask & shifting of the value such that the spinbutton is properly initialized
		if (defined($widgets{$name}->bit_shift) && defined($widgets{$name}->bit_mask)) {
		    my $bit_shift = $widgets{$name}->bit_shift;
		    my $bit_mask = $widgets{$name}->bit_mask;

		    $value &= $bit_mask;
		    $value >>= $bit_shift;
		}

		if ($widget_type =~ /SpinButton/) {
		    if (defined($widgets{$name}->ul_conv_expr)) {  # convert the value if needed
			$value = parse_expr($value, $widgets{$name}->ul_conv_expr);
			$value =~ s/\./,/;  # , instead of .
		    }
		    $the_widget->set_value($value);

		} elsif ($widget_type =~ /RadioButton/) {
		    # determine the state of the radiobutton
		    my $state = 0;
		    if (defined($widgets{$name}->bit_val)) {
			$state = $widgets{$name}->bit_val;
		    }

		    $value ^= !$state;
		    $the_widget->set_active($value);
		}

	    } else {
		if ($widget_type =~ /(SpinButton|RadioButton)/) {
		    print STDERR "can't initialize widget '$name', unknown offset\n";
		}
	    }

	} elsif ($handler eq "_RUNTIME_") {
	    $runtime_widgets{$name} = 1; # make a hash of the elements
	}
    }
}

sub update_runtime_widgets {
    my @buf = split //, shift;

    foreach my $name (keys %runtime_widgets) { # visit all runtime widgets

	my $offset = $widgets{$name}->offset; # get corresponding memory offset
	my $the_widget = $main_window->get_widget($name);
	my $widget_type = ref $the_widget;

	if (defined($offset) && $offset != -1) {
	    my $value = ord($buf[$offset]);
	    my $old_value = $value;

	    # bit fiddling: bitmask & shifting of the value such that the spinbutton is properly initialized
	    if (defined($widgets{$name}->bit_shift) && defined($widgets{$name}->bit_mask)) {
		my $bit_shift = $widgets{$name}->bit_shift;
		my $bit_mask = $widgets{$name}->bit_mask;

		$value &= $bit_mask;
		$value >>= $bit_shift;
	    }

	    if (defined($widgets{$name}->ul_conv_expr)) {  # convert the value if needed
		$value = parse_expr($value, $widgets{$name}->ul_conv_expr);
	    }

	    if ($widget_type =~ /ProgressBar/) {
		if ($value > 1) { $value = 1; }
		Gtk2::Gdk::Threads->enter;
		$the_widget->set_fraction($value);
		Gtk2::Gdk::Threads->leave;
#		print "progressbar ($name): $value\n";
	    } elsif ($widget_type =~ /Label/) {
#		$the_widget->set_text($value);
		Gtk2::Gdk::Threads->enter;
		$the_widget->set_label($value);
		Gtk2::Gdk::Threads->leave;
#		print "label ($name): $value\n";
	    }
	}
    }
}

sub parse_expr {
    my ($val, $expr) = @_;

    my $ArithEnv = new Math::Expression;
    my $t1 = $ArithEnv->Parse("x := $val");
    my $t2 = $ArithEnv->Parse($expr);
    $ArithEnv->Eval($t1);
    my $res = $ArithEnv->EvalToScalar($t2);
    
    return $res;
}



















sub leave {
    comm_send('mbye');
    srl_wait(3);
#    sleep(1);
#    $ob->purge_rx();
    undef $ob if defined $ob;
#    exit;
    Gtk2->main_quit;
}

#######################
sub comm_send
{
    my $str = shift ;
    $count_out = $ob->write($str);
    warn "write failed\n" unless ($count_out);
    warn "write incomplete\n" if ($count_out != length($str));
    return $count_out;
}

#######################
sub comm_recv
{
    my $b = shift;
    ($count_in, $string_in) = $ob->read($b);
    warn "read unsuccessful\n" unless ($count_in == $b);
    return ($count_in, $string_in);
}

#################################################
# what's the reply???
sub srl_wait
{
	my $realcharcnt = shift;
	if (defined($realcharcnt)) {
	    $charcnt = $realcharcnt;
	} else {
	    $charcnt = 1;
	}
# TODO: if no reply comes within 2 sec, we should start saying 'v' -s or sg.
# finally report the problem (or let the user ctrl-c)
	# $ob->read($in_);
	$ob->read_const_time($tmout);
	my ($cnt, $in_) = comm_recv($charcnt);
	#sysread (SRL, $in_, 1, 0) or die "cannot readback from SRL $!";
	
	if(defined $in_){
		#print "got back $cnt:$in_ :".ord(substr($in_, 0, 1));
		#print ' '.ord(substr($in_, 1, 1)) if(length($in_) > 1);
		#print "\n";
	} else {
		print "nothing\n";
	}
	
	return $in_ if defined($realcharcnt) && ($realcharcnt > 0);
	return undef if $in_ eq "\r";	# success 
	return 1;					# error
}
