#!/usr/bin/env perl
#
# sm2dot
#
# Converts a state machine description file to a file readable by dot.  dot is
# a program which creates printable directed graphs from a directed graph
# description.
#

use strict;

my ($filename);

if ($#ARGV >= 0)
{
    $filename = $ARGV[0];
}
else
{
    $filename = "&STDIN";
}

if (!open(FILE, "<$filename"))
{
    print STDERR "\nERROR: unable to open $filename\n\n";
    exit(1);
}

my (@sm, $sm);

@sm = <FILE>;
$sm = "\n" . join("", @sm) . "\n";

my($re_keyword, $re_str, $re_params, $re);
$re_keyword = "Graph|State|Event|State_Change|Change_State|" .
  "Action|Invoke_Action";
$re_str = "\\s*\\[[^\]]*\\]\\s*";
$re_params = "((${re_str})?,)*(($re_str))";
$re = "(^|\\n)\\s*($re_keyword)\\s*\\(?$re_params\\)?";

my($keyword, @params_tmp, $graph_name, $graph_params, 
   %nodes, @node_names, @edges, $cur_node, $tmp);

$graph_name = "My Graph";
$graph_params = "";

while (($tmp, $keyword, @params_tmp) = ($sm =~ /$re/))
{
    my ($i, @params);

    for ($i = 1; $i <= $#params_tmp; $i += 2)
    {
	if (defined($params_tmp[$i]))
	{
	    $params_tmp[$i] =~ s/^\s*\[\s*//;
	    $params_tmp[$i] =~ s/\s*\]\s*//;
	    $params_tmp[$i] =~ s/\s+/ /g;
	    $params[$#params+1] = $params_tmp[$i];
	}
    }

    if ($keyword eq "State_Change" || $keyword eq "Change_State" 
	|| $keyword eq "Invoke_Action")
    {
	my($attrs);
	$edges[$#edges+1] = join("\0", $cur_node, $params[0], 
				 defined($params[1]) ? $params[1] : "");
    }
    elsif ($keyword eq "Event")
    {
	# this isn't being used at the moment...
	# $cur_event = $params[0];
    }
    elsif ($keyword eq "State" || $keyword eq "Action")
    {
	$cur_node = $params[0];

	my($opt_attrs, @attrs);
	$opt_attrs = defined($params[1]) ? $params[1] : "";

	if (!defined($nodes{$cur_node}))
	{
	    # print STDERR "OPT_ATTRS = \"$opt_attrs\"\n";
	    if (!($opt_attrs =~ /shape\s*=/))
	    {
		$attrs[$#attrs+1] = ($keyword eq "State") 
		    ? "shape=ellipse" : "shape=box";
		# print STDERR "SETTING SHAPE = \"$attrs[$#attrs]\"\n";
	    }
	    if ($opt_attrs ne "")
            {
		$attrs[$#attrs+1] = $opt_attrs;
	    }

	    $nodes{$cur_node} = join(",", @attrs);
	    $node_names[$#node_names+1] = $cur_node;
	}
	else
	{
	    print STDERR "Warning state $cur_node defined multiple times\n";
	}
    }
    elsif ($keyword eq "Graph")
    {
	$graph_name = $params[0] if defined($params[0]);
	$graph_params = $params[1] if defined($params[1]);
    }

    # print STDERR "DECL - $keyword", join("|","", @params, ""),"\n";

    $sm = $';
}

close(FILE);


my($edge, $from_node, $to_node, $error);

$error = 0;
foreach $edge (@edges)
{
    ($from_node, $to_node) = split("\0", $edge);
    if (!defined($nodes{$from_node}))
    {
	print STDERR "Unable to find from node \"$from_node\"\n";
	$error = 1;
    }
    if (!defined($nodes{$to_node}))
    {
	print STDERR "Unable to find to node \"$to_node\"\n";
	$error = 1;
    }
}

exit(1) if ($error != 0);

print "digraph \"$graph_name\" {\n";
print "graph [$graph_params];\n" if $graph_params ne "";

my($node,$attrs);

foreach $node (@node_names)
{
    print "\"$node\"";
    print (($nodes{$node} ne "") ? " [$nodes{$node}];\n" : ";\n");
}


my($edge, $from_node, $to_node, $attrs);

foreach $edge (@edges)
{
    ($from_node, $to_node, $attrs) = split("\0", $edge);
    print "\"$from_node\" -> \"$to_node\"";
    print (($attrs ne "") ? " [$attrs];\n" : ";\n");
}

print "}\n";

exit(0);
