#!/usr/bin/perl

# Copyright 2000-2008 Robert Krawitz <rlk@alum.mit.edu>
#
#   This program is free software; you can redistribute it and/or modify it
#   under the terms of the GNU General Public License as published by the Free
#   Software Foundation; either version 2 of the License, or (at your option)
#   any later version.
#
#   This program is distributed in the hope that it will be useful, but
#   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
#   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
#   for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

use Getopt::Std;
use strict;

getopts('VvO:');

use vars qw($atend
	    $stuff
	    $opt_v
	    $opt_V
	    $opt_O
	    $curoffset
	    $curpos
	    $esc
	    $initial_vertical_position
	    $page_mgmt_unit
	    $horizontal_position
	    $horizontal_unit
	    $vertical_unit
	    $vertical_position
	    $raster_x
	    $raster_y
	    $print_offsets
	    %seqtable
	    @seqkeys
	    %chartable
	    %xchartable
	    %nchartable
	    %rchartable
	    %keylengths
	    $total_length
	    @offsets);

$atend = 0;
%chartable = ();
%xchartable = ();
%nchartable = ();
%rchartable = ();
%keylengths = ();

%seqtable = ( "@", 0,
	      "(R", "REMOTE",
	      "(", "VARIABLE",
	      "U", 1,
	      "\\", 2,
	      "\$", 2,
	      "r", 1,
	      "\031", 1,
	      ".", "SPECIAL",
	      "i", "SPECIAL1",
	      "\000", 2,
	      "\001", 22
	  );

map {
    my ($xchar) = pack("C", $_);
    if ($_ >= 32 && $_ < 127) {
	$chartable{$xchar} = " $xchar";
	$xchartable{$xchar} = " *$xchar";
	$rchartable{$xchar} = $xchar;
    } else {
	$chartable{$xchar} = sprintf("%02x", $_);
	$xchartable{$xchar} = sprintf("*%02x", $_);
	$rchartable{$xchar} = sprintf("%02x ", $_);
    }
    $nchartable{$xchar} = $_;
} (0..255);

@seqkeys = (sort { length $b <=> length $a } keys %seqtable);

map { $keylengths{$_} = length $_ } @seqkeys;

$esc = "\033";

$curpos = 0;
$curoffset = 0;

$page_mgmt_unit = 360;
$horizontal_unit = 180;
$vertical_unit = 360;

$initial_vertical_position = 0;
$vertical_position = 0;
$horizontal_position = 0;
$print_offsets = 0;
$total_length = 0;

sub fill_buffer($) {
    my ($where) = @_;
    return 1 if $total_length - $curoffset >= $where;
    my ($end) = $total_length - $curoffset;
    if ($curpos == 0 && $end == 0) {
	$stuff = <>;		# Need to do this once to "activate" ARGV
	$total_length = length $stuff;
	$end = $total_length - $curoffset;
    }
    my ($old_end) = $end;
    my ($tmp);
    my ($bytes_to_read) = 16384;
    if ($where - $end > $bytes_to_read) {
	$bytes_to_read = $where - $end;
    }
    if ($curoffset >= 16384) {
	substr($stuff, 0, $curoffset) = "";
	$total_length -= $curoffset;
	$curoffset = 0;
    }
    while ($end < $where) {
	my $foo = read ARGV, $tmp, $bytes_to_read;
	$stuff .= $tmp;
	$end += $foo;
	$total_length += $foo;
	if ($old_end == $end) {
	    $atend = 1;
	    return 0;
	} else {
	    $bytes_to_read -= $end - $old_end;
	    $old_end = $end;
	}
    }
    return 1;
}

sub increment_curpos($) {
    my ($curpos_increment) = @_;
    $curpos += $curpos_increment;
    $curoffset += $curpos_increment;
}

# This slows things down tremendously...
sub xdata($;$) {
    my ($start, $end) = @_;
    if (defined $end) {
	return substr($stuff, $start + $curoffset, $end);
    } else {
	return substr($stuff, $start + $curoffset);
    }
}

sub do_remote_command() {
    print "\n";
    printf "%08x  ", $curpos;
    print "1b  (  R ";
    increment_curpos(3);
    fill_buffer(2);
    my $lchar = substr($stuff, $curoffset + 0, 1);
    my $nlchar = $nchartable{$lchar};
    my $hchar = substr($stuff, $curoffset + 1, 1);
    my $nhchar = $nchartable{$hchar};
    printf " %02x %02x ", $nlchar, $nhchar;
    my $skipchars = ($nhchar * 256) + $nlchar;
    increment_curpos(2);
    fill_buffer($skipchars);
    for (my $i = 0; $i < $skipchars; $i++) {
	print $rchartable{substr($stuff, $curoffset + $i, 1)};
    }
    increment_curpos($skipchars);
    while (fill_buffer(2) &&
	   substr($stuff, $curoffset + 0, 2) =~ /[A-Z0-9][A-Z0-9]/) {
	print "\n";
	printf "%08x    ", $curpos;
	my ($cmd) = substr($stuff, $curoffset + 0, 2);
	print $cmd;
	increment_curpos(2);
	fill_buffer(2);
	$lchar = substr($stuff, $curoffset + 0, 1);
	$nlchar = $nchartable{$lchar};
	$hchar = substr($stuff, $curoffset + 1, 1);
	$nhchar = $nchartable{$hchar};
	if ($cmd eq "DF") {
	    $skipchars = 0;
	} else {
	    $skipchars = ($nhchar * 256) + $nlchar;
	}
	printf " %02x %02x ", $nlchar, $nhchar;
	increment_curpos(2);
	fill_buffer($skipchars);
	printf "%*v02x ", " ", substr($stuff, $curoffset, $skipchars);
	increment_curpos($skipchars);
    }
}

sub print_prefix_bytes($$) {
    my ($bytes_to_print) = @_;
    print "\n";
    printf "%08x  ", $curpos;
    print "1b ";
    fill_buffer($bytes_to_print);
    my $char = substr($stuff, $curoffset + 1, 1);
    print "$chartable{$char} ";
    printf "%*v02x ", " ", substr($stuff, $curoffset + 2, $bytes_to_print - 2);
    increment_curpos($bytes_to_print);
}

sub print_output_data($$$$$$) {
    my ($comptype, $bitsperpixel, $dots, $rows, $dot_scale, $color) = @_;
    my $counter;
    my $fchar;
    my $last_row = 0;
    my $first_row = -1;
    my $i;
    my $vstuff;
    $dots *= 8;
    $dots /= $dot_scale;
    my $real_dots = $dots / $bitsperpixel;
    if ($opt_v) {
	print " ($real_dots dots, $rows rows, $bitsperpixel bits";
    }
    my $savedots = $dots;
    if ($comptype == 0) {
	fill_buffer($dots / 8);
	if ($opt_V) {
	    printf "%*v02x ", " ", substr($stuff, $curoffset + 0, $dots / 8);
	}
	increment_curpos($dots / 8);
    } elsif ($comptype == 1) {
	foreach $i (0..$rows-1) {
	    my ($found_something) = 0;
	    $dots = $savedots;
	    my ($tstuff) = "\n    $i    ";
	    while ($dots > 0) {
		fill_buffer(1);
		$counter = ord(substr($stuff, $curoffset + 0, 1));
		increment_curpos(1);
		if ($counter <= 127) {
		    $counter++;
		    fill_buffer($counter);
		    if ($opt_v || $opt_V) {
			my $tmp = sprintf "%*v02x ", " ", substr($stuff, $curoffset + 0, $counter);
			if (!($tmp =~ /^[0 ]+$/)) {
			    $found_something = 1;
			    $last_row = $i;
			    if ($first_row == -1) {
				$first_row = $i;
			    }
			}
			if ($opt_V) {
			    $tstuff .= $tmp;
			}
		    }
		    increment_curpos($counter);
		} else {
		    $counter = 257 - $counter;
		    fill_buffer(1);
		    if ($opt_v || $opt_V) {
			$fchar = sprintf "%v02x ", substr($stuff, $curoffset + 0, 1);
			if ($fchar ne "00 ") {
			    $found_something = 1;
			    $last_row = $i;
			    if ($first_row == -1) {
				$first_row = $i;
			    }
			}
		    }
		    if ($opt_V) {
			map { $tstuff .= $fchar } (0..$counter - 1);
		    }
		    increment_curpos(1);
		}
		$dots -= $counter * 8;
	    }
	    if ($opt_V && $found_something) {
		$vstuff .= $tstuff;
	    }
	}
    } else {
	print "\nUnknown compression type $comptype!\n";
    }
    if ($opt_v) {
	my ($offset) = $offsets[$color];
	my ($first_position) = ($vertical_position / $vertical_unit)
	    + ($first_row + $offset) * $raster_y;
	my ($last_position) = ($vertical_position / $vertical_unit)
	    + ($last_row + $offset) * $raster_y;
	my ($final_position) = ($vertical_position / $vertical_unit)
	    + ($rows + $offset) * $raster_y;
	my ($final_horizontal) = $horizontal_position +
	    ($real_dots * $page_mgmt_unit * $raster_x);
	if ($print_offsets) {
	    printf (" %d,%d+%d %.4f  %d,%d+%d %.4f  %.4f) ",
		    $horizontal_position, $first_row, $offset, $first_position,
		    $final_horizontal, $last_row, $offset, $last_position,
		    $final_position);
	} else {
	    printf (" %d,%d %.4f  %d,%d %.4f  %.4f) ",
		    $horizontal_position, $first_row, $first_position,
		    $final_horizontal, $last_row, $last_position,
		    $final_position);
	}
    }
    if ($opt_V) {
	print " $vstuff";
    }
}

sub do_special_command() {
    fill_buffer(8);
    my $comptype = $nchartable{substr($stuff, $curoffset + 2, 1)};
    my $color = 0;
    my $dots = unpack("v", substr($stuff, $curoffset + 6, 2));
    my $rows = $nchartable{substr($stuff, $curoffset + 5, 1)};
    print_prefix_bytes(8, 2);
    print_output_data($comptype, 1, $dots, $rows, 8, $color);
    fill_buffer(1);
    while (substr($stuff, $curoffset + 0, 1) eq "\r") {
	fill_buffer(1);
	increment_curpos(1);
    }
}

sub do_special1_command() {
    fill_buffer(9);
    my $color = $nchartable{substr($stuff, $curoffset + 2, 1)};
    my $comptype = $nchartable{substr($stuff, $curoffset + 3, 1)};
    my $bitsperpixel = $nchartable{substr($stuff, $curoffset + 4, 1)};
    my $dots = unpack("v", substr($stuff, $curoffset + 5, 2));
    my $rows = unpack("v", substr($stuff, $curoffset + 7, 2));
    print_prefix_bytes(9, 1);
    print_output_data($comptype, $bitsperpixel, $dots, $rows, 1, $color);
    fill_buffer(1);
    while (substr($stuff, $curoffset + 0, 1) eq "\r") {
	fill_buffer(1);
	increment_curpos(1);
    }
}

sub get_long($) {
    my ($string) = @_;
    my ($tmp) = unpack("V", $string);
    if ($tmp >= (1 << 31)) {
	return -(0xffffffff ^ $tmp) - 1;
    } else {
	return $tmp;
    }
}

sub get_short($) {
    my ($string) = @_;
    my ($tmp) = unpack("v", $string);
    if ($tmp >= (1 << 15)) {
	return -(0xffff ^ $tmp) - 1;
    } else {
	return $tmp;
    }
}

sub get_byte($) {
    my ($string) = @_;
    return $nchartable{$string};
}

if ($opt_O) {
    my (@stuff) = split(/,/, $opt_O);
    map {
	my ($key, $val) = split(/=/, $_);
	if ($val) {
	    $print_offsets = 1;
	}
	@offsets[$key] = $val;
    } @stuff;
}

while (! $atend) {
    my $found;
    my $key;
    my $skipchars;
    my $startoff;
    my $kchar;
    my $nkchar;
    my $lchar;
    my $nlchar;
    my $hchar;
    my $nhchar;
    my $i;
    my $char;
    my $nchar;
    my $bytes;
    my ($maxklen) = $keylengths{$seqkeys[0]};
    fill_buffer(1);
    my $cchar = substr($stuff, $curoffset + 0, 1);
    if ($cchar eq "$esc") {
	$found = 0;
	fill_buffer(2 + $maxklen);
	foreach $key (@seqkeys) {
	    my ($klen) = $keylengths{$key};
	    if (substr($stuff, $curoffset + 1, $klen) eq $key) {
		$skipchars = $seqtable{$key};
		if ($skipchars eq "SPECIAL") {
		    do_special_command();
		    $found = 2;
		} elsif ($skipchars eq "SPECIAL1") {
		    do_special1_command();
		    $found = 2;
		} elsif ($skipchars eq "REMOTE") {
		    do_remote_command();
		    $found = 2;
		} else {
		    print "\n";
		    printf "%08x  ", $curpos;
		    print "1b ";
		    $startoff = 0;
		    my $print_stuff = 0;
		    my $print_variable = 0;
		    if ($skipchars eq "VARIABLE") {
			fill_buffer(3);
			$print_variable = 1;
			$kchar = substr($stuff, $curoffset + $klen + 1, 1);
			$nkchar = unpack("C", $kchar);
			$lchar = substr($stuff, $curoffset + $klen + 2, 1);
			$nlchar = unpack("C", $lchar);
			$hchar = substr($stuff, $curoffset + $klen + 3, 1);
			$nhchar = unpack("C", $hchar);
			$skipchars = ($nhchar * 256) + $nlchar;
			$startoff = 3;
			$print_stuff = 1;
		    }
		    my ($blen) = $skipchars + $klen + $startoff;
		    fill_buffer($blen + 1);
		    $char = substr($stuff, $curoffset + 1, 1);
		    print "$chartable{$char} ";
		    if ($blen > 1) {
			$char = substr($stuff, $curoffset + 2, 1);
			print "$chartable{$char} ";
			if ($blen > 2) {
			    if ($print_variable && $char eq "d") {
				printf ("%*v02x ", " ",
					substr($stuff, $curoffset + 3, 2));
			    } else {
				printf ("%*v02x ", " ",
					substr($stuff, $curoffset + 3, $blen - 2));
			    }
			}
		    }
		    if ($print_stuff) {
			my $xchar = substr($stuff, $curoffset + 2, 1);
			if ($xchar eq "c") {
			    my ($top, $bottom);
			    if ($skipchars == 8) {
				$top = get_long(substr($stuff, $curoffset + 5, 4));
				if ($opt_v) {
				    $bottom = get_long(substr($stuff, $curoffset + 9, 4));
				}
			    } else {
				$top = get_short(substr($stuff, $curoffset + 5, 2));
				if ($opt_v) {
				    $bottom = get_short(substr($stuff, $curoffset + 7, 2));
				}
			    }
			    if ($opt_v) {
				printf (" (page format %d %d %.2f %.2f)",
					$top, $bottom, $top / $page_mgmt_unit,
					$bottom / $page_mgmt_unit);
			    }
			    $initial_vertical_position =
				$top * $vertical_unit / $page_mgmt_unit;
			    $vertical_position = $initial_vertical_position;
			} elsif ($xchar eq "S") {
			    if ($opt_v) {
				my ($width, $height);
				if ($skipchars == 8) {
				    $width = get_long(substr($stuff, $curoffset + 5, 4));
				    $height = get_long(substr($stuff, $curoffset + 9, 4));
				} else {
				    $width = get_short(substr($stuff, $curoffset + 5, 2));
				    $height = get_short(substr($stuff, $curoffset + 7, 2));
				}
				printf (" (paper size %d %d %.2f %.2f)",
					$width, $height, $width / $page_mgmt_unit,
					$height / $page_mgmt_unit);
			    }
			} elsif ($xchar eq "C") {
			    if ($opt_v) {
				my ($length);
				if ($skipchars == 4) {
				    $length = get_long(substr($stuff, $curoffset + 5, 4));
				} else {
				    $length = get_short(substr($stuff, $curoffset + 5, 2));
				}
				printf (" (page length %d %.2f)",
					$length, $length / $page_mgmt_unit);
			    }
			} elsif ($xchar eq "D") {
			    my $base = get_short(substr($stuff, $curoffset + 5, 2));
			    my $y = $nchartable{substr($stuff, $curoffset + 7, 1)};
			    my $x = $nchartable{substr($stuff, $curoffset + 8, 1)};
			    $raster_x = $x / $base;
			    $raster_y = $y / $base;
			    if ($opt_v) {
				printf (" (raster base %d, %d x %d)",
					$base, $base / $x, $base / $y);
			    }
			} elsif ($xchar eq "U") {
			    if ($skipchars == 5) {
				my $page_mgmt = $nchartable{substr($stuff, $curoffset + 5, 1)};
				my $vertical = $nchartable{substr($stuff, $curoffset + 6, 1)};
				my $horiz = $nchartable{substr($stuff, $curoffset + 7, 1)};
				my $scale = get_short(substr($stuff, $curoffset + 8, 2));
				$page_mgmt_unit = $scale / $page_mgmt;
				$horizontal_unit = $scale / $horiz;
				$vertical_unit = $scale / $vertical;
				if ($opt_v) {
				    printf (" (units base %d  mgmt %d  vert %d  horiz %d)",
					    $scale, $page_mgmt_unit,
					    $vertical_unit, $horizontal_unit);
				}
			    } else {
				my $page_mgmt = $nchartable{substr($stuff, $curoffset + 5, 1)};
				if ($opt_v) {
				    printf " (units base = %d/3600)", $page_mgmt;
				}
				$page_mgmt_unit = 3600 / $page_mgmt;
				$horizontal_unit = 3600 / $page_mgmt;
				$vertical_unit = 3600 / $page_mgmt;
			    }
			} elsif ($xchar eq "v") {
			    my ($length);
			    if ($skipchars == 4) {
				$length = get_long(substr($stuff, $curoffset + 5, 4));
			    } else {
				$length = get_short(substr($stuff, $curoffset + 5, 2));
			    }
			    $vertical_position += $length;
			    if ($opt_v) {
				printf (" (skip vertical %d at %d %.4f)",
					$length, $vertical_position,
					$vertical_position / $vertical_unit);
			    }
			} elsif ($xchar eq "\$") {
			    if ($skipchars == 4) {
				$horizontal_position =
				    get_long(substr($stuff, $curoffset + 5, 4));
			    } else {
				$horizontal_position =
				    get_short(substr($stuff, $curoffset + 5, 2));
			    }
			    if ($opt_v) {
				printf (" (horizontal position %d %.4f)",
					$horizontal_position,
					$horizontal_position / $horizontal_unit);
			    }
			} elsif ($xchar eq "d") {
			    if ($opt_v) {
				printf " (nop)";
			    }
			}
		    }
		    $found = 1;
		}
		$bytes = $klen + 1 + $skipchars + $startoff;
		last;
	    }
	}
	if (! $found) {
	    print "\n";
	    printf "%08x  ", $curpos;
	    print "1b ";
	    increment_curpos(1);
	} elsif ($found == 1) {
	    increment_curpos($bytes);
	}
    } elsif ($cchar eq "\0" || $cchar eq "\f") {
	printf "\n%08x  ", $curpos;
	print "$chartable{$cchar} ";
	$vertical_position = $initial_vertical_position;
	increment_curpos(1);
    } else {
	print "$xchartable{$cchar} " if ($cchar ne "\021");
	increment_curpos(1);
    }
}

print "\n" if $curpos > 1;

