#! /usr/local/bin/perl -w

use strict;
use Getopt::Std;

my %stat_key2name;
my %stat_name2key;
my %stat_opts;
my %miss;
my @conn;
my @line_name;
my @line;

my $planfilename = "plan.dat";

sub std_name2key {
    my $name = shift;
    $name =~ tr/A-Z/a-z/;
    $name =~ s/\s+/\-/g;
    $name =~ s/(À|Á|Â|Ã|Ä|Å|à|á|â|ã|ä|å)/a/g;
    $name =~ s/(Ç|ç)/c/g;
    $name =~ s/(È|É|Ê|Ë|è|é|ê|ë)/e/g;
    $name =~ s/(Ì|Í|Î|Ï|ì|í|î|ï)/i/g;
    $name =~ s/(Ò|Ó|Ô|Ö|ò|ó|ô|ö)/o/g;
    $name =~ s/(Ù|Ú|Û|Ü|ù|ú|û|ü)/u/g;
    $name =~ s/[^a-z0-9\-]//g;
    return $name;
};

sub name2key {
    my $name = shift;
    my $key;
    if ( defined($stat_name2key{$name}) ) {
	$key = $stat_name2key{$name};
    } else {
	$key = std_name2key($name);
	$stat_name2key{$name} = $key;
	$stat_key2name{$key} = $name;
	$stat_opts{$key} = 0;
    }
    return $key;
};

sub quotestring {
    my $str = shift;
    $str =~ s/\\/\\\\/g;
    $str =~ s/\"/\\\"/g;
    $str =~ s/\'/\\\'/g;
    $str =~ s/\007/\\a/g;
    $str =~ s/\010/\\b/g;
    $str =~ s/\011/\\t/g;
    $str =~ s/\012/\\n/g;
    $str =~ s/\013/\\v/g;
    $str =~ s/\014/\\f/g;
    $str =~ s/\015/\\r/g;
    $str = "\"$str\"";
    return $str;
};

sub unquotestring {
    my $str = shift;
    my $rstr = "";
    while ( $str ne "" ) {
	my $char = substr $str, 0, 1;
	$str = substr $str, 1;
	if ( $char eq "\"" ) {
	} elsif ( $char eq "\\" ) {
	    my $char2 = substr $str, 0, 1;
	    $str = substr $str, 1;
	    if ( $char2 eq "a" ) {
		$rstr .= "\007";
	    } elsif ( $char2 eq "b" ) {
		$rstr .= "\010";
	    } elsif ( $char2 eq "t" ) {
		$rstr .= "\011";
	    } elsif ( $char2 eq "n" ) {
		$rstr .= "\012";
	    } elsif ( $char2 eq "v" ) {
		$rstr .= "\013";
	    } elsif ( $char2 eq "f" ) {
		$rstr .= "\014";
	    } elsif ( $char2 eq "r" ) {
		$rstr .= "\015";
	    } else {
		$rstr .= $char2;
	    }
	} else {
	    $rstr .= $char;
	}
    }
    return $rstr;
};

sub readplan {
    open F, "<", $planfilename or die "Can't open $planfilename file";
    my $mission = undef;
    my $connection = 0;
    my $metroline = 0;
  LINE:
    while ( my $line = <F> ) {
	# Remove comments.
	die "Bad preparsing"
	    unless $line =~ s/^((?:[^\;\"]|\"(?:[^\"\\]|\\.)*\")*)(?:\;.*)?$/$1/;
	next LINE if $line =~ /^\s*$/;
	if ( defined($mission) ) {
	    if ( $line =~ /^\s*\)\s*\)\s*$/ ) {
		$mission = undef;
	    } elsif ( $line =~ /^\s*(\S*)\s*$/ ) {
		my $station = $1;
		push @{$miss{$mission}}, $station;
	    } else {
		die "Bad parsing in mission $mission"
	    }
	} elsif ( $connection ) {
	    if ( $line =~ /^\s*\)\s*\)\s*$/ ) {
		$connection = 0;
	    } elsif ( $line =~ /^\s*(\S*)\s*$/ ) {
		my $station = $1;
		push @{$conn[$#conn]}, $station;
	    } else {
		die "Bad parsing in connection"
	    }
	} elsif ( $metroline ) {
	    if ( $line =~ /^\s*\)\s*\)\s*$/ ) {
		$metroline = 0;
	    } elsif ( $line =~ /^\s*(\S*)\s*$/ ) {
		my $miss = $1;
		push @{$line[$#line]}, $miss;
	    } else {
		die "Bad parsing in line"
	    }
	} else {
	    if ( $line =~ /^\s*\(\s*station\s+(\S+)\s+(\"(?:[^\"\\]|\\.)*\")((?:\s+\'\S+)*)\s*\)\s*$/ ) {
		my $key = $1;
		my $name = unquotestring($2);
		my $opts = $3;
		$stat_key2name{$key} = $name;
		$stat_name2key{$name} = $key;
		$stat_opts{$key} = 0;
		$stat_opts{$key} |= 1 if $opts =~ /extra-muros/;
		$stat_opts{$key} |= 2 if $opts =~ /restricted-time/;
		$stat_opts{$key} |= 4 if $opts =~ /rer-only/;
	    } elsif ( $line =~ /^\s*\(\s*mission\s+(\S+)\s+\'\(\s*$/ ) {
		$mission = $1;
		$miss{$mission} = [];
	    } elsif ( $line =~ /^\s*\(\s*connection\s+\'\(\s*$/ ) {
		$connection = 1;
		push @conn, [];
	    } elsif ( $line =~ /^\s*\(\s*line\s+(\"(?:[^\"\\]|\\.)*\")\s+\'\(\s*$/ ) {
		my $name = unquotestring($1);
		$metroline = 1;
		push @line, [];
		push @line_name, $name;
	    } else {
		die "Bad parsing";
	    }
	}
    }
    close F;
};

sub writeplan {
    print ";; This file is automatically reformated.  Edit with care.\n";
    print ";; Stations:\n";
    foreach my $key (sort (keys %stat_key2name)) {
	my $opts = "";
	$opts .= " 'extra-muros" if $stat_opts{$key} & 1;
	$opts .= " 'restricted-time" if $stat_opts{$key} & 2;
	$opts .= " 'rer-only" if $stat_opts{$key} & 4;
	printf "(station %s %s%s)\n", $key, quotestring($stat_key2name{$key}), $opts;
    }
    print ";; Missions:\n";
    foreach my $mission (sort (keys %miss)) {
	printf "(mission %s '(\n", $mission;
	foreach my $stat (@{$miss{$mission}}) {
	    printf "  %s\n", $stat;
	}
	printf "  ))\n";
    }
    print ";; Connections:\n";
    foreach my $connection (@conn) {
	printf "(connection '(\n";
	foreach my $stat (@{$connection}) {
	    printf "  %s\n", $stat;
	}
	printf "  ))\n";
    }
    print ";; Lines:\n";
    for (my $i=0 ; $i<scalar(@line) ; $i++) {
	printf "(line %s '(\n", quotestring($line_name[$i]);
	foreach my $mission (@{$line[$i]}) {
	    printf "  %s\n", $mission;
	}
	printf "  ))\n";
    }
};


sub writeplancfile {
    my $fullcfile = shift;
    my %stat_key2num;
    my @stat_num2key;
    my %miss_key2num;
    my @miss_num2key;
    my %miss_line;
    my %direc;
    my %miss_direc;
    my @conn_direc;
    print "/* This file is automatically generated.  Do not edit! */\n\n";
    print "/* Stations: */\n\n";
    foreach my $key (sort (keys %stat_key2name)) {
	push @stat_num2key, $key;
	$stat_key2num{$key} = $#stat_num2key;
    }
    printf "#define NBSTATIONS %d\n\n", scalar(keys %stat_key2name);
    print "extern " unless $fullcfile;
    printf "struct station_s {\n  const char *name;\n"
	. "  char opts;\n} stations[NBSTATIONS]";
    if ( $fullcfile ) {
	printf " = {\n";
	for (my $i=0 ; $i<scalar(@stat_num2key) ; $i++) {
	    my $key = $stat_num2key[$i];
	    printf "  { %s, %d },\n",
	    quotestring($stat_key2name{$key}), $stat_opts{$key};
	}
	print "};\n\n";
    } else {
	printf ";\n\n";
    }
    print "struct stadin_s {\n  int sta;\n  int din;\n};\n\n";
    print "/* Missions: */\n\n";
    foreach my $mission (sort (keys %miss)) {
	push @miss_num2key, $mission;
	$miss_key2num{$mission} = $#miss_num2key;
    }
    my $maxmisssize = 0;
    foreach my $mission (keys %miss) {
	$maxmisssize = scalar @{$miss{$mission}}
	if $maxmisssize < scalar @{$miss{$mission}};
    }
    for (my $i=0 ; $i<scalar(@line) ; $i++ ) {
	foreach my $mission (@{$line[$i]}) {
	    $miss_line{$mission} = $i;
	}
    }
    foreach my $mission (keys %miss) {
	$miss_direc{$mission} = [];
	for (my $j=0 ; $j<scalar(@{$miss{$mission}}) ; $j++) {
	    my $stat = ${$miss{$mission}}[$j];
	    push @{$direc{$stat}}, [$mission, $j];
	    push @{$miss_direc{$mission}}, $#{$direc{$stat}};
	}
    }
    printf "#define MAXMISSSIZE %d\n\n", $maxmisssize;
    printf "#define NBMISSIONS %d\n\n", scalar(keys %miss);
    if ( $fullcfile ) {
	for (my $i=0 ; $i<scalar(@miss_num2key) ; $i++) {
	    my $key = $miss_num2key[$i];
	    printf "static struct stadin_s miss%s[%d] = { ", $i, scalar @{$miss{$key}};
	    for (my $j=0 ; $j<scalar(@{$miss{$key}}) ; $j++) {
		my $stat = ${$miss{$key}}[$j];
		printf "{ %d, %d }, ", $stat_key2num{$stat}, ${$miss_direc{$key}}[$j];
	    }
	    printf "};\n";
	}
	print "\n";
    }
    print "extern " unless $fullcfile;
    print "struct mission_s {\n  int misssize;\n"
	. "  struct stadin_s *miss;\n  int line;\n} missions[NBMISSIONS]";
    if ( $fullcfile ) {
	print " = {\n";
	for (my $i=0 ; $i<scalar(@miss_num2key) ; $i++) {
	    my $key = $miss_num2key[$i];
	    printf "  { %d, ", scalar @{$miss{$key}};
	    printf "miss%s, ", $i;
	    printf "%d ", $miss_line{$key};
	    printf "},\n";
	}
	print "};\n\n";
    } else {
	print ";\n\n";
    };
    print "/* Connections: */\n\n";
    my $maxconnsize = 0;
    foreach my $connection (@conn) {
	$maxconnsize = scalar @{$connection}
	if $maxconnsize < scalar @{$connection};
    }
    for (my $i=0 ; $i<scalar(@conn) ; $i++) {
	$conn_direc[$i] = [];
	for (my $j=0 ; $j<scalar(@{$conn[$i]}) ; $j++) {
	    my $stat = ${$conn[$i]}[$j];
	    push @{$direc{$stat}}, [$i, $j];
	    push @{$conn_direc[$i]}, $#{$direc{$stat}};
	}
    }
    printf "#define MAXCONNSIZE %d\n\n", $maxconnsize;
    printf "#define NBCONNECTIONS %d\n\n", scalar(@conn);
    if ( $fullcfile ) {
	for (my $i=0 ; $i<scalar(@conn) ; $i++) {
	    printf "static struct stadin_s conn%d[%d] = { ", $i, scalar @{$conn[$i]};
	    for (my $j=0 ; $j<scalar(@{$conn[$i]}) ; $j++) {
		my $stat = ${$conn[$i]}[$j];
		printf "{ %d, %d }, ", $stat_key2num{$stat}, ${$conn_direc[$i]}[$j];
	    }
	    printf "};\n";
	}
	print "\n";
    }
    print "extern " unless $fullcfile;
    print "struct connection_s {\n  int connsize;\n"
	. "  struct stadin_s *conn;\n} connections[NBCONNECTIONS]";
    if ( $fullcfile ) {
	print " = {\n";
	foreach (my $i=0 ; $i<scalar(@conn) ; $i++) {
	    printf "  { %d, ", scalar @{$conn[$i]};
	    printf "conn%d ", $i;
	    printf "},\n";
	}
	print "};\n\n";
    } else {
	print ";\n\n";
    }
    print "/* Directions: */\n\n";
    my $maxdirsize = 0;
    foreach my $station (keys %direc) {
	$maxdirsize = scalar @{$direc{$station}}
	if $maxdirsize < scalar @{$direc{$station}};
    }
    printf "#define MAXDIRSIZE %d\n\n", $maxdirsize;
    print "struct onedir_s {\n  int moc;\n  int ind;\n};\n\n";
    if ( $fullcfile ) {
	for (my $i=0 ; $i<scalar(@stat_num2key) ; $i++ ) {
	    my $key = $stat_num2key[$i];
	    unless ( defined($direc{$key}) ) {
		next;
	    }
	    printf "static struct onedir_s dir%d[%d] = { ",
	    $i, scalar @{$direc{$key}};
	    foreach my $dir (@{$direc{$key}}) {
		if ( defined($miss_key2num{${$dir}[0]}) ) {
		    printf "{ %d, %d }, ",
		    $miss_key2num{${$dir}[0]}, ${$dir}[1];
		} else {
		    printf "{ %d, %d }, ",
		    ${$dir}[0]+scalar(@miss_num2key), ${$dir}[1];
		}
	    }
	    printf "};\n";
	}
	print "\n";
    }
    print "extern " unless $fullcfile;
    print "struct direction_s {\n  int dirsize;\n"
	. "  struct onedir_s *dir;\n}"
	. " directions[NBSTATIONS]";
    if ( $fullcfile ) {
	print " = {\n";
	for (my $i=0 ; $i<scalar(@stat_num2key) ; $i++ ) {
	    my $key = $stat_num2key[$i];
	    unless ( defined($direc{$key}) ) {
		printf "  { 0, 0 }, /* VOID??? */\n";
		next;
	    }
	    printf "  { %d, ", scalar @{$direc{$key}};
	    printf "dir%d ", $i;
	    printf "},\n";
	}
	print "};\n\n";
    } else {
	printf ";\n\n";
    }
    print "/* Lines: */\n\n";
    printf "#define NBLINES %d\n\n", scalar(@line);
    print "extern " unless $fullcfile;
    print "const char *lines[NBLINES]";
    if ( $fullcfile ) {
	print " = {\n";
	for (my $i=0 ; $i<scalar(@line) ; $i++ ) {
	    printf "  %s,\n", quotestring($line_name[$i]);
	}
	print "};\n\n";
    } else {
	printf ";\n\n";
    }
    print "/* That's all, folks! */\n";
};


my %opts;

getopts("m:rco:d:CH", \%opts);

if ( defined($opts{d}) ) {
    $planfilename = $opts{d};
}

readplan;

if ( defined($opts{C}) ) {
    writeplancfile 1;
    exit 0;
} elsif ( defined($opts{H}) ) {
    writeplancfile 0;
    exit 0;
}

if ( defined($opts{m}) ) {
    my $mission = $opts{m};
    print STDERR "Enter stations for mission $mission:\n";
    $miss{$mission} = [];
    while ( <> ) {
	chop;
	my $name = $_;
	my $station = name2key($name);
	push @{$miss{$mission}}, $station;
    }
    if ( defined($opts{r}) ) {
	my $mission_rev = "${mission}-rev";
	$miss{$mission_rev} = [];
	@{$miss{$mission_rev}} = reverse @{$miss{$mission}};
    }
} elsif ( defined($opts{c}) ) {
    print STDERR "Enter stations for new connection:\n";
    push @conn, [];
    while ( <> ) {
	chop;
	my $name = $_;
	my $station = name2key($name);
	push @{$conn[$#conn]}, $station;
    }
} else {
    print STDERR "Enter stations:\n";
    while ( <> ) {
	chop;
	my $name = $_;
	my $station = name2key($name);
	$stat_opts{$station} = $opts{o} if defined($opts{o});
    }
}

writeplan;
