#!/usr/local/bin/perl

$version = "classgrid 2.3";

# classgrid, v2.3, by Dan Wallach <dwallach@cs.rice.edu>
#  -brief option by Steve Smoot <smoot@cs.berkeley.edu>
#
# using PostScript code from Brent Chapman <brent@greatcircle.com>
# and HTML extensions from Matthew Forrest <mforrest@scs.ryerson.ca>


# v2.3,  9 Feb    1999 -- fixed off-by-one hour bug in table generation
#                         for early-morning hours
#
# v2.2, 18 August 1998 -- fancier HTML table output from Matthew Forrest
#
# v2.1, 13 Jan    1996 -- fixed HTML3.0 for MS Internet Explorer stupidity
#                         hopefully fixed a bug that trips Perl 5.001m
#			  checked into CVS
#			  added -help flag
#
# v2.0,  9 Oct    1995 -- added HTML3.0 tables, Perl5.x now required
#                         INPUT FORMAT CHANGED!
#
# v1.3, 25 Sept   1993 -- fixed a bug in full ASCII mode
# v1.2, 29 Jan    1992 -- added -brief, other small changes
# v1.1, 16 August 1991 -- added Postscript support
# v1.0, 15 August 1991 -- initial version

# this program takes your schedule of classes, in basically the same
# format as a typical computer printout, and generates one of those
# pretty grids which are so useful...
#
# Documentation in the accompanying README.html file or here:
# http://www.cs.rice.edu/~dwallach/classgrid/
#
# Perl-related info: this program doesn't adhere to any particular style
# or method, but rather is just sort of a mishmash of different Perl tricks
# to get the job done.  It's ugly, it's a hack, but it works pretty well.
# The multi-dimensional arrays used to do really ugly tricks with eval,
# but everything pretty much uses Perl5 recursive data structures.  Yeah!
#

$[ = 1;

@possible_hours = (
	   "12a", "12:30a",
	   "1a",  "1:30a",
	   "2a",  "2:30a",
	   "3a",  "3:30a",
	   "4a",  "4:30a",
	   "5a",  "5:30a",
	   "6a",  "6:30a",
	   "7a",  "7:30a",
	   "8a",  "8:30a",
	   "9a",  "9:30a",
	  "10a", "10:30a",
	  "11a", "11:30a",
	  "12p", "12:30p",
	   "1p",  "1:30p",
	   "2p",  "2:30p",
	   "3p",  "3:30p",
	   "4p",  "4:30p",
	   "5p",  "5:30p",
	   "6p",  "6:30p",
	   "7p",  "7:30p",
	   "8p",  "8:30p",
	   "9p",  "9:30p",
	   "10p",  "10:30p",
	   "11p",  "11:30p");

@ps_hour_map = (         # converting to military time
	   "0",  "0.5",
	   "1",  "1.5",
	   "2",  "2.5",
	   "3",  "3.5",
	   "4",  "4.5",
	   "5",  "5.5",
	   "6",  "6.5",
	   "7",  "7.5",
	   "8",  "8.5",
	   "9",  "9.5",
	  "10", "10.5",
	  "11", "11.5",
	  "12", "12.5",
	  "13", "13.5",
	  "14", "14.5",
	  "15", "15.5",
	  "16", "16.5",
	  "17", "17.5",
	  "18", "18.5",
	  "19", "19.5",
	  "20", "20.5",
	  "21", "21.5",
	  "22", "22.5",
	  "23", "23.5");

$earliest = $#possible_hours + 1;
$latest = $[ - 1;

@possible_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday" );
@possible_days_short = ("M", "T", "W", "Th", "F");

#  @class_types_short = ( "Lec", "Dis", "Lab" , "Sem", "Meet");

%class_types = (
	"Lec", "Lecture",
	"Sem", "Seminar",
	"Dis", "Discussion",
	"Lab", "Laboratory",
	"Meet", "Meeting");

$entry_width = 11;  # plus margins
$hour_width = 5;    # plus margins
$table_width = ($entry_width + 3)*(@possible_days) + $hour_width + 2;

#
# why isn't this in Perl???  Seems Larry isn't into float->int conversions
#
sub ceil {
    local($float) = @_;

    if (int($float) == $float || $float < 0) {
	return (int($float));
    }

    return(int($float) + 1);
}

sub store_grid {
    local($day_index, $start_index, $end_index, $class, $type, $hall) = @_;

    $earliest = $start_index if $start_index < $earliest;
    $latest = $end_index - 1 if $end_index - 1 > $latest;

    $day{$possible_days[$day_index]}[$start_index] =
	"$class|$class_types{$type}|$hall";
    for(($start_index+1) .. ($end_index-1)) {
	$day{$possible_days[$day_index]}[$_] =
	    "v" x $entry_width . "|" .
	    "v" x $entry_width . "|" .
	    "v" x $entry_width;
    }
    
    $class = "<a href=\"$dbase{$class,'url'}\">$class</a>" if $dbase{$class,'url'};
    
    $span = $end_index - $start_index;
#############################################################
## added bgcolor="" to make room bookings darker than rest of table
#############################################################
    $day_html{$possible_days[$day_index]}[$start_index] =
	"<td bgcolor=\"909090\" rowspan=$span valign=top>$class<br>$class_types{$type}<br>$hall</td>\n";
    for(($start_index+1) .. ($end_index-1)) {
        $day_html{$possible_days[$day_index]}[$_] = " ";
    }
}

#
# create hash table indices into the possible hours array
#
for($[ .. $#possible_hours) {
    $possible_hours{$possible_hours[$_]} = $_;
}

#
# and again for indices into the possible_days arrays
#
for($[ .. $#possible_days) {
    $possible_days{$possible_days[$_]} = $_;
    $possible_days_short{$possible_days_short[$_]} = $_;
}

# default is full text
$full = 1;

#
# okay, finally, the main loop
#

foreach(@ARGV) {
    if (/-ps/) {
	$generate_postscript = 1;
	next;
    }

    if (/-brief/) {
	$full = 0;
	next;
    }
    
    if(/-html/) {
        $generate_html = 1;
	next;
    }

    if(/-help/ || /^-./) {
	printf "$version by Dan Wallach <dwallach\@cs.rice.edu>\n";
	printf "For documentation, go here:\n";
	printf "    http://www.cs.rice.edu/~dwallach/classgrid/\n";
	exit 1;
    }

    if(/^-$/) {
	open(MYFILE, "<&STDIN") || die "Can't dup STDIN: $!";
    } else {
	open(MYFILE, "$_") || die "Can't open $_: $!";
    }

    mainloop: while(<MYFILE>) {

	chop;
	s/#.*$//;
	next if /^$/;

	if(/^class\s+/) {
	    s/^class\s+//;
	    ($class, $title, $teacher, $url) = split(/\|/);
	    push(@classes, ($class));
	    $dbase{$class, "title"} = $title;
	    $dbase{$class, "teacher"} = $teacher;
	    $dbase{$class, "url"} = $url;
	    next mainloop;
	}

	if(/^title\s+/) {
	    s/^title\s+//;
	    push(@ps_output,  "($_) Title\n");
	    $PageTitle = $_;
	    next mainloop;
	}
	
	if(/^gr[ae]y\s+/) {
	    s/^gr.y\s+//;

	    push (@ps_output,  "$_  setgray\n");
	    next mainloop;
	}

	if(/^#/) { 
           # discard comments!
	   next mainloop;
        }
	
	if(/^meeting\s+/) {
	    s/^meeting\s+//;

	    ($class, $type, $days, $hours, $hall, $teacher) = split(/\|/);
	    if (!($class_types{$type})) {$class_types{$type}=$type;} # Hack to do new types
	    $dbase{$class,$type} = sprintf("%12s: %7s  %-12s %s",
					   $class_types{$type},
					   $days, $hours, $hall);
	    
	    #
	    # now, find the hourly indices
	    #
	    
	    ($start, $end) = split(/\s*-\s*/, $hours);
	    
	    $start_index = $possible_hours{$start} || die "Malformed class hours: $start";
	    $end_index = $possible_hours{$end} || die "Malformed class hours: $end";
	    
	    die "Bad class hours: starts after it ends ($start - $end)"
		if $start_index >= $end_index;

	    #
	    # now, find which days we have the class on
	    #
	    
	    @day_array = split(/\s+/, $days);
	    
	    foreach (@day_array) {
		$day_index = $possible_days_short{$_} || die "Malformed class day: \`$_\'";
		&store_grid($day_index, $start_index, $end_index,
			    $class, $type, $hall);
		
		push(@ps_output, "[ ($class) ($class_types{$type}) ($hall) ] $ps_hour_map[$start_index] $ps_hour_map[$end_index] $possible_days[$day_index] Box\n");
	    }
	} else {
	    die "Unknown command in input, line $.\nFor documentation, go here:\n    http://www.cs.rice.edu/~dwallach/classgrid/";
	}
    }
    close(MYFILE);
}

if($generate_postscript) {
    #
    # generate PS output...
    #

##    $BoxesPerPage = &ceil(($latest - $earliest)/2.0);
    $EarliestHour = int($ps_hour_map[$earliest]);
    $LatestHour = &ceil($ps_hour_map[$latest+1]);
    $BoxesPerPage = $LatestHour - $EarliestHour;

    print <<EOSTUFF;
%!

% This PostScript generated by $version
%    by Dan Wallach <dwallach\@cs.rice.edu>
%    http://www.cs.rice.edu/~dwallach/classgrid/
%
% This PS Prologue was written by Brent Chapman <brent\@telebit.com>

/BoxesPerPage $BoxesPerPage def
/EarliestHour $EarliestHour def
/LatestHour $LatestHour def

EOSTUFF

    #
    # snarf the whole prologue
    #
    while(<DATA>) { print; }   

    #
    # send out all the filled boxes
    #
    print @ps_output;		

    #
    # put the class titles at the bottom
    #

    print <<EOSTUFF;

%
% now, the class titles...
%
0 setgray		% full black text
/Helvetica findfont 14 scalefont setfont  % 14 point Helvetica

EOSTUFF
    $linenumber = 1;
    $spacing = 1.5 / @classes;
    foreach (@classes) {
	print "BaseX BaseY $spacing inch $linenumber mul sub moveto\n";
    	print "($_: $dbase{$_,'title'}";
	print ", $dbase{$_,'teacher'}"     if $dbase{$_,'teacher'};
    	print ") show\n";
	$linenumber++;
    }

    print "\nshowpage\n";
}

elsif ($generate_html) {
    $date = `date`;
    chop $date;
    $x = (defined($PageTitle))?$PageTitle:"Schedule";
###############################################################
## added bgcolor=?? page is white, table is light grey, meetings are dark grey
###############################################################
    print <<EOF;
<html><head>
<title>$x</title>
</head>
<!-- Generated by $version, $date -->
<body bgcolor="FFFFFF">
<h1>$x</h1>

<table bgcolor="E0E0E0" border=2 width="100%" cellspacing=2 cellpadding=2>
<tr>
<th></th>
<th width="19%">Monday</th>
<th width="19%">Tuesday</th>
<th width="19%">Wednesday</th>
<th width="19%">Thursday</th>
<th width="19%">Friday</th>
</tr>

EOF

## now gives FREE designation for any time room not booked
## fixed the ugly HTML tables by doing this - previously cell 
## looked awful
##
## Modified By Matthew Forrest <mforrest@scs.ryerson.ca>
## on Aug. 19, 1998

    foreach $day (@possible_days) {
	$start_hour=0;
	$end_hour=0;
	$span=0;
	$my_span=0;
	foreach $blank_hour_index ($earliest .. $latest) {
#
#	    if($day_html{$day}[$blank_hour_index] eq "" and $day_html{$day}[$blank_hour_index+1] eq "") {
#		$day_html{$day}[$blank_hour_index] = "<td rowspan=2><!-- blah --></td>";
#		$day_html{$day}[$blank_hour_index+1] = " ";
#	    } else { if($day_html{$day}[$blank_hour_index] eq "") {
#		$day_html{$day}[$blank_hour_index] = "<td><!-- ONE --></td>";
#	    }}
#       
	    if($day_html{$day}[$blank_hour_index] eq "") {
		if($start_hour eq 0) {
		    $start_hour=$blank_hour_index;
		}
		$span=$span+1;
		$end_hour=$blank_hour_index;
# debug		$day_html{$day}[$blank_hour_index] = "<td valign=top> day $day span $span blank_hour_index $blank_hour_index start_hour $start_hour</td>";
		    
	    }else { if($start_hour ne 0) {
		$day_html{$day}[$start_hour] =
		    "<td rowspan=$span valign=top>FREE</td>\n";
# debug		    "<td rowspan=$span valign=top>FREE day $day span $span blank_hour_index $blank_hour_index start_hour $start_hour end_hour $end_hour</td>\n";
		for(($start_hour+1) .. ($end_hour)) {
		    $day_html{$day}[$_] = "<!-- nothing line: $_ span: $span start: $start_hour end: $end --> ";
		}
		$start_hour=0;
		$end_hour=0;
		$span=0;
	    }}

	    
	}
	if($span > 0) {
#	    $end_hour=$blank_hour_index;
	    $day_html{$day}[$start_hour] =
		"<td rowspan=$span valign=top>FREE</td>\n";
# debug		"<td rowspan=$span valign=top>FREE day $day span $span blank_hour_index $blank_hour_index start_hour $start_hour end_hour $end_hour</td>\n";
	    for(($start_hour+1) .. ($end_hour)) {
		$day_html{$day}[$_] = "<!-- nothing line: $_ span: $span start: $start_hour end: $end --> ";
	    }
	    $start_hour=0;
	    $end_hour=0;
	    $span=0;
	    
	}
    }

    foreach $hour_index ($earliest .. $latest) { 
        $hour = $possible_hours[$hour_index];
	$hour =~ s/^.*:30.*$//;	# nuke any half-hour slots
	$hour =~ s/[ap]$//;     # nuke trailing a's and p's

	#
	# mumble grumble: MS Internet Explorer doesn't do the correct
	# thing if we put multiple <br>'s in a table element.  Instead,
	# I use "blank.gif" -- a one-pixel transparent image -- to
	# force the desired spacing.  Yuck.
	#
	if($day_html{$day}[$blank_hour] eq "") {	printf "<tr>\n<th valign=top align=right>$hour<img src=\"blank.gif\" width=1 height=32 align=top></th>\n";}
	
	foreach $day (@possible_days) {
	    if($day_html{$day}[$hour_index] eq "") {
	        printf "<td></td>\n";
	    } else {
	        printf $day_html{$day}[$hour_index];
	    }
	}
	printf "</tr>\n";
    }
    
    printf "</table>\n<p>";
    foreach (@classes) {
	print "<a href=\"$dbase{$_,'url'}\">" if $dbase{$_,'url'};
    	print "$_: $dbase{$_,'title'}";
	print "</a>" if $dbase{$_,'url'};
	print ", $dbase{$_,'teacher'}"     if $dbase{$_,'teacher'};
	printf "\n<p>\n";
    }
    printf <<EOF;
<hr>
Generated by <a href="http://www.cs.rice.edu/~dwallach/classgrid/">$version</a>, $date
</body>
</html>
EOF


} else {
    if ($full) {
	print "$PageTitle\n\n" if defined($PageTitle);
	
	foreach (@classes) {
	    print "$_: $dbase{$_,'title'}";
	    print ", $dbase{$_,'teacher'}" if $dbase{$_,'teacher'};
	    print "\n";
	    
	    foreach $ctype (sort keys %class_types) {
		print $dbase{$_, $ctype}, "\n" if $dbase{$_, $ctype};
	    }
	    
	    print "\n";
	}
    }

    print "-" x $table_width, "\n";
    print " " x $hour_width, " ";
    foreach(@possible_days) {
        printf "| %${entry_width}s ", $_;
    }
    print "\n";
    print "-" x $table_width, "\n";
    
    foreach $hour_index ($earliest .. $latest) {
        $_  = $hour = $possible_hours[$hour_index];
	s/[ap]$//;
    
        print "-" x $table_width, "\n" if ($full);
        printf "%-${hour_width}s ", $_;
    
        foreach $day (@possible_days) {
	    $day_index = $possible_days{$day};
    	    $entries = $day{$possible_days[$day_index]}[$hour_index];
	    @entries = split(/\|/, $entries);
	    for($[..$[+2) {
		$entry[$day_index * 3 + $_] = $entries[$_];
	    }
        }

	if ($full) {
	    for ($[..$[+2) {
		print  " " x ($hour_width + 1) if $_ != $[;
		foreach $day (@possible_days) {
		    $day_index = $possible_days{$day};
		    printf "| %${entry_width}.${entry_width}s ",
		    $entry[$day_index * 3 + $_];
		}
		print "\n";
	    }
	}
	else {
		foreach $day (@possible_days) {
		    $day_index = $possible_days{$day};
		    printf "| %${entry_width}.${entry_width}s ",
		    $entry[$day_index * 3 + 1];
		}
		print "|\n";
	}

    }
    print "-" x $table_width, "\n";
}
    


__END__

%  BoxesPerPage -- how many total hours, from start to finish we're in class
%  EarliestHour -- 24hr. time of earliest class
%  LatestHour -- 24hr. time of latest class

/inch { 72 mul } def

/BaseX .75 inch def
/BaseY 1.9 inch def

/TextSize 12 def                    % 12 point text for most stuff

/BoxX 1.45 inch def
/BoxY 8 BoxesPerPage div inch def

.1 setlinewidth

0 1 5 {
			% n
    BoxX mul 		% x_offset
    BaseX add		% x
    BaseY moveto	% -
    0 BoxY BoxesPerPage mul 	% 0 y_offset
    rlineto		% -
    stroke		% -

} for

0 1 BoxesPerPage {
			% n
    BoxY mul		% y_offset
    BaseY add		% y
    BaseX exch moveto	% -
    BoxX 5 mul 0	% x_offset 0
    rlineto		% -
    stroke		% -

} for

/TextFont /Helvetica findfont TextSize scalefont def
/TitleFont /Helvetica-Bold findfont 32 scalefont def

TextFont setfont

/daynum 0 def

[
    (Monday)
    (Tuesday)
    (Wednesday)
    (Thursday)
    (Friday)
] {

    daynum BoxX mul BaseX add .1 inch add	% (str) x
    BaseY BoxY BoxesPerPage mul add .1 inch add		% (str) x y
    moveto					% (str)
    show					% -
    daynum 1 add /daynum exch def

} forall

/str 3 string def

EarliestHour 1 LatestHour {
    dup				% n n
    EarliestHour sub		% n n'
    BoxesPerPage exch sub	% n n"
    BoxY mul BaseY add		% n y
    4 sub			% n y'
    BaseX .25 inch sub		% n y' x
    exch moveto			% n
    dup 12 gt			% n bool
    {			
				% n
	12 sub			% n'
    } if
    str cvs show		% -
} for

/Box {	% [ (message) ... ] start_time stop_time day
    
    BoxX mul BaseX add 0 moveto		% [ (msg) ... ] start_time stop_time
    EarliestHour sub BoxesPerPage exch sub			% [ (msg) ... ] start_time stop_ofs
    BoxY mul BaseY add 0 exch rmoveto	% [ (msg) ... ] start_time
    currentpoint newpath moveto		% [ (msg) ... ] start_time
    BoxX 0 rlineto			% [ (msg) ... ] start_time
    EarliestHour sub BoxesPerPage exch sub			% [ (msg) ... ] start_ofs
    BoxY mul BaseY add 			% [ (msg) ... ] y
    currentpoint exch pop		% [ (msg) ... ] y y'
    sub 0 exch rlineto			% [ (msg) ... ]
    BoxX neg 0 rlineto			% [ (msg) ... ] x y
    currentpoint			% [ (msg) ... ] x y
    closepath 				% [ (msg) ... ] x y
    gsave fill grestore 		% [ (msg) ... ] x y
    /gray currentgray def		% [ (msg) ... ] x y
    0 setgray stroke 			% [ (msg) ... ] x y
    moveto				% [ (msg) ... ]
    .05 inch -.05 inch TextSize sub rmoveto	% [ (msg) ... ]
    {
	currentpoint			% (msg) x y
	/Y exch def /X exch def		% (msg)
	show				% -
	X Y moveto 0 TextSize neg rmoveto	% -
    } forall
    gray setgray
	
} def

/Title {			% (str)
    TitleFont setfont		% (str)
    /gray currentgray def	% (str)
    0 setgray 			% (str)
    BaseX BoxesPerPage BoxY mul BaseY add	% (str) x y
    .3 inch add moveto		% (str)
    show			% -
    gray setgray
    TextFont setfont
} def


/am { } def
/pm { 12 add } def 

/Monday 	0 def
/Tuesday	1 def
/Wednesday	2 def
/Thursday	3 def
/Friday		4 def

.95 setgray

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%									      %
% 	And, that's it for the preamble, now the user data.                   %
%									      %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

