#!/usr/local/bin/perl

#
# Copyright (c) 1992 The Ohio State University.
# All rights reserved.
#
# Redistribution and use in source and binary forms are permitted
# provided that: (1) source distributions retain this entire copyright
# notice and comment, and (2) distributions including binaries display
# the following acknowledgement:  ``This product includes software
# developed by The Ohio State University and its contributors''
# in the documentation or other materials provided with the distribution
# and in all advertising materials mentioning features or use of this
# software. Neither the name of the University nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#
# Author:  Thomas A. Fine
# Email:   fine@cis.ohio-state.edu
# Org:     The Ohio State University Department of Computer and Information Sci.
# Thanks:  Steve Romig, Mowgli Assor, Frank Adelstein, J Greely
#

#
# This needs to point to where you install it
#
#require './cusses-lib';
#require '/n/monster/0/fine/perl/cusses-lib';
require '/usr/unsup/lib/W3/cusses-lib';

#
# The default page to use if none is specified.
#
#this will set it to run on the current directory if nothing else is given.
$default_html=`pwd`; chop($default_html);
#$default_html="/usr/unsup/lib/W3/default.html";

if ($#ARGV > -1) {
  if ($ARGV[0] eq "-pedantic") {
    $pedantic=1;
    shift(ARGV);
  }
  elsif ($ARGV[0] eq "-quiet") {
    $quiet=1;
    shift(ARGV);
  }
  elsif ($ARGV[0] eq "-mouse") {
    if ($term eq "xterm" || $term eq "xterms") {
      print "xterm mouse enabled (experimental).\n";
      $mouse=1;
      shift(ARGV);
    }
    else {
      print "mouse option ignored - wrong terminal type.\n";
    }
  }
}

if ($#ARGV > -1) {
  $home_html=$ARGV[0];
}
elsif (defined($ENV{'WWW_HOME'})) {
  $home_html=$ENV{'WWW_HOME'};
}
else {
  $home_html=$default_html;
}

if (-d $home_html) {
  $dir=`pwd`; chop $dir;
  chdir($home_html);
  $home_html=`pwd`;
  chop $home_html;
  chdir($dir);
}
elsif (-f $home_html) {
  $dir=`pwd`; chop $dir;
  $basn=$home_html; $basn =~ s|^.*/||;
  $hdir=$home_html; $hdir =~ s|/[^/]*$||;
  chdir($hdir);
  $home_html=`pwd`;
  chop $home_html;
  $home_html .= "/$basn";
  chdir($dir);
}

&setup;

&browse;

#
# END OF MAIN
#

sub tokenize {
  local($len,$pos,$i,$type,$bname);
  local(@dirlist);

  @token=();

  $bname=$_[0];
  $bname =~ s|^.*/||;
  if ($bname eq "") { $bname="/"; }

  if (-d $_[0]) {
    push(@token,"<TITLE>");
    push(@token,"$bname");
    push(@token,"</TITLE>");
    push(@token,"\n");
    push(@token,"<H3>");
    push(@token,"$_[0]");
    push(@token,"</H3>");
    push(@token,"\n");
    push(@token,"<DIR>");
    push(@token,"<LI>");
    push(@token,"<A NAME=up HREF=..>");
    push(@token,"Go up");
    push(@token,"</A>");
    open(LS,"/bin/ls $_[0]|");
    $i=0;
    while(<LS>) {
      chop;
      #if (/^[.]/) { next; }   #skip all dot files
      if (-d "$_[0]/$_") { $type="/"; }
      else { $type=""; }
      ++$i;
      push(@token,"<LI>");
      push(@token,"<A NAME=$i HREF=$_>");
      push(@token,"$_$type");
      push(@token,"</A>");
    }
    close(LS);
    push(@token,"</DIR>");
  }

  elsif ($_[0] !~ /[.]html$/i) {
    push(@token,"<TITLE>");
    push(@token,"$bname");
    push(@token,"</TITLE>");
    push(@token,"\n");
    push(@token,"<LISTING>");
    open(HTML,$_[0]);
    while (<HTML>) {
      push(@token,$_);
    }
    close(HTML);
    push(@token,"</LISTING>");
  }

  else {
    open(HTML,$_[0]);
    while (<HTML>) {
      if (/^$/) {
	push(@token,$_);
	next;
      }

      $pos=0;
      $len=length;
      while (($i=index($_,"<",$pos)) >= 0) {

	#grab previous chunk of text
	if ($pos != $i) {
	  push(@token,substr($_,$pos,$i-$pos));
	  $token[$#token] =~ s/&lt;/</g;
	  $token[$#token] =~ s/&gt;/>/g;
	  $token[$#token] =~ s/&amp;/&/g;
	  $pos=$i;
	}

	#grab current token
	while (($i=index($_,">",$pos)) < 0) {
	  s/\n/ /;
	  $in=<HTML>;
	  if ($in eq "") {
	    print "Shoot! No end of token on current line! ($#token tokens read)\n";
	    exit(1);
	  }
	  $_ .= $in;
	  $len=length;
	}
	push(@token,substr($_,$pos,$i-$pos+1));
	$token[$#token] =~ s/&lt;/</g;
	$token[$#token] =~ s/&gt;/>/g;
	$token[$#token] =~ s/&amp;/&/g;
	$pos=$i+1;
      }

      #this way for throwing out newlines after tags
      #if ($pos < $len-1) {
      #this way for keeping newlines after tags
      if ($pos < $len) {
	push(@token,substr($_,$pos,$len-$pos));
	$token[$#token] =~ s/&lt;/</g;
	$token[$#token] =~ s/&gt;/>/g;
	$token[$#token] =~ s/&amp;/&/g;
      }
    }
    close(HTML);
  }
}

sub token2image {
  local($_);

  $numlines=0; $numbuttons=0;
  @lines=();
  @hrefdata=(); @buttname=(); @buttline=(); @startbutt=(); @endbutt=();
  $currbutt= -1;
  $top=0;
  $cursrow=$rows-1; $curscol=0;
  $isindex=0;
  $state="browsing";

  if ($#token == -1) {
    @token=@savetoken;
  }
  else {
    @savetoken=@token;
  }

  while ($#token >= 0) {
    $_=shift(@token);

    #temporary hack - I should enforce the document structure
    if (($_ eq "<DOCUMENT>") || ($_ eq "</DOCUMENT>")) { next; }
    if (($_ eq "<HEADER>") || ($_ eq "</HEADER>")) { next; }
    if (($_ eq "<HEAD>") || ($_ eq "</HEAD>")) { next; }
    if (($_ eq "<BODY>") || ($_ eq "</BODY>")) { next; }

    if (&istext($_)) {
      &dotext($_);
    }
    else {
      &callroutine($_);
    }
  }
  &forceeol;
}

sub callroutine {
  local($tag)=$_[0];
  local($type,$args);
  local($function);

  $type=$tag; $type =~ s/^<//; $type =~ s/[^A-Za-z].*$//;
  $type =~ tr/a-z/A-Z/;
  $args=$tag; $args =~ s/^<[a-zA-Z]+//; $args =~ s/>$//;

  $function= "do$type";
  &$function($args);
}

sub istext {
  ($_[0] !~ /^<.*>$/);
}

sub istag {
  ($_[0] =~ /^<[A-Z]+.*>$/);
}

#expect types [ pattern ... ]
#types=0 for text, 1 for tag, or 2 for both
sub expect {
  local($_);
  local($type)=shift(@_);
  local(@patray)=(@_);

  if ($#token == -1) { return("<EOF>"); }
  $_=shift(@token);
  if ($type == 0 && &istag($_)){
    if (! $quiet) {
      print "Expected text, found tag ($_).  $#token tokens from the end, output line $numlines.\n";
    }
    if ($pedantic) {
      exit(1);
    }
  }
  if ($type == 1 && &istext($_)) {
    #allow blank lines to be skipped when a tag is expected
    if ($_ eq "\n") {
      return(&expect($type,@_));
    }
    if (! $quiet) {
      print "Expected tag, found text ($_).  $#token tokens from the end, output line $numlines.\n";
    }
    if ($pedantic) {
      exit(1);
    }
  }

  if (&istext($_)) { return($_); }
  #if no patterns are given to match the tag, any pattern is ok
  if ($#_ == -1) { return($_); }

  while ($pat=shift(@patray)) {
    if (/$pat/i) { return($_); }
  }

  #if we're here, we've failed to match a tag
  if (! $quiet) {
    print "Unexpected tag found ($_).  $#token tokens from the end, output line $numlines.\n";
  }
  if ($pedantic) {
    exit(1);
  }
  else {
    return(&expect($type,@_));
  }
}

sub forceeol {
  #force end of line on current line first
  if (defined($lines[$numlines])) {
    if ($lines[$numlines] =~ /\n/) {
      #print "Warning! numlines was out of sync generating line $numlines\n";
      ++$numlines;
    }
    else {
      $lines[$numlines] .= "\n";
      ++$numlines;
    }
  }
  if ($wrapmargin) { $lines[$numlines] .= " " x $wrapmargin; }
}

sub blankline {
  if (defined($lines[$numlines])) {
    if ($lines[$numlines] =~ /\n/) {
      #print "Warning! numlines was out of sync generating line $numlines\n";
      ++$numlines;
    }
    else {
      $lines[$numlines] .= "\n";
      ++$numlines;
    }
  }
  push(@lines,"\n");
  ++$numlines;
  if ($wrapmargin) { $lines[$numlines] .= " " x $wrapmargin; }
}

#
# Tag handlers
#

#
# If it exists in the @lines array, then it is considered formatted
# and can't be touched.
#
# As routines get stuff, they create formatted text, which had better be
# correct when any given LOWEST LEVEL routine gets done with it.  No
# routine should go back and muck with what anything else has already
# formatted.
#
# The only thing added after the fact is the highlight strings for the
# buttons.  when the HP routine is written, this may have to change
# (the problem is counting the formatting characters in line length).
#

#
# I make some assumptions about what can happen where that may not
# be correct.
#
# Mainly, I assume that nothing can happen inside of anything else,
# except for anchors.
#
# I probably don't expect anchors in all the places I should expect them.
#
# I also assume that no html tag will ever be split across lines.
#

sub dotext {
  local($new,$oldlength,$i,$c);

  $new=$_[0];

  if ($paragraph) {
    &blankline;
    #I used to have it indent paragraphs.  I didn't care for it.
    #$lines[$numlines] = "  ";
    $paragraph=0;
  }

  #blank lines are stored only for use in LISTING, XMP, and PLAINTEXT
  #if ($new eq "\n") {
  #  return;
  #}

  $new =~ s/\n/ /;
  $new =~ s/\t/ /g;

  #oldlength insures we don't screw with whats already there
  $oldlength=length($lines[$numlines]);
  $lines[$numlines] .= $new;

  while (length($lines[$numlines]) > $maxwidth) {
    #loop ends on oldlength-1 so that we can check end of old string for space
    #(this sort of violates my rules about modifying already-formatted text)
    for ($i=$maxwidth; $i>=$oldlength-1; --$i) {
      $c=substr($lines[$numlines],$i,1);
      if ($c eq " " || $c eq "\t") {
	$lines[$numlines+1] = " " x $wrapmargin;
	$lines[$numlines+1] .= substr($lines[$numlines],$i+1);
	$lines[$numlines]=substr($lines[$numlines],0,$i);
	$lines[$numlines] .= "\n";
	++$numlines;
	last;
      }
      elsif ($i<$maxwidth && ($c eq "," || $c eq "-")) {
	$lines[$numlines+1] = " " x $wrapmargin;
	$lines[$numlines+1] .= substr($lines[$numlines],$i+1);
	$lines[$numlines]=substr($lines[$numlines],0,$i+1);
        $lines[$numlines] .= "\n";
        ++$numlines;
	last;
      }
    }
    #if split fails, just split at the end of the line
    if ($i < $oldlength) {
      $lines[$numlines+1] = " " x $wrapmargin;
      $lines[$numlines+1] .= substr($lines[$numlines],$maxwidth);
      $lines[$numlines]=substr($lines[$numlines],0,$maxwidth);
      $lines[$numlines] .= "\n";
      ++$numlines;
    }
    $oldlength=0;
  }
  if ($lines[$numlines] =~ /\n/) {
    ++$numlines;
    $lines[$numlines] .= " " x $wrapmargin;
  }
}

sub doTITLE {
  local($tit)="";
  local($_,$indent,$ret);

  #expect only text, or the ending tag
  while (($_=&expect(2,"^<\/TITLE>$")) !~ /^<\/TITLE>$/i) {
    s/\n/ /;
    $tit .= $_;
  }
  $currenttitle=$tit;
  $tit = join(" ",split(/\007*/,$tit));
  $indent=($columns-length($tit))/2;
  $ret = " " x $indent . $tit;
  $ret .= " " x $indent;
  $ret .= "\n";
  $ret="$b_stand_c$ret$e_stand_c";
  push(@lines,$ret);
  ++$numlines;
  &blankline;
}

sub doA {
  local($stuff)="";
  local($_,$indent,$buttontext);

  $hrefdata[$numbuttons]="";
  if ($_[0] =~ /[ \t][Hh][Rr][Ee][Ff]=/) {
    $hrefdata[$numbuttons]=$_[0];
    $hrefdata[$numbuttons] =~ s|^.*[Hh][Rr][Ee][Ff]=||;
    $hrefdata[$numbuttons] =~ s|[ ]+.*$||;
  }

  $buttname[$numbuttons]="";
  if ($_[0] =~ /[ \t][Nn][Aa][Mm][Ee]=/) {
    $buttname[$numbuttons]=$_[0];
    $buttname[$numbuttons] =~ s|^.*[Nn][Aa][Mm][Ee]=||;
    $buttname[$numbuttons] =~ s|[ ]+.*$||;
  }

  #expect only text, or the ending tag
  while (($_=&expect(2,"^<\/A>$")) !~ /^<\/A>$/i) {
    s/\n/ /;
    s/\t/ /g;
    $buttontext .= $_;
  }
  #we handle line wrapping here rather than use dotext, because we
  #don't want buttons split up over lines
  if ($paragraph) {
    &blankline;
    #I used to have it indent paragraphs.  I didn't care for it.
    #$lines[$numlines] = "  ";
    $paragraph=0;
  }
  if (length($buttontext) > $columns) {
    print "SHOOT! button longer than line ($columns characters)\n";
    exit(1);
  }
  if (length("$lines[$numlines]$buttontext") >= $columns) {
    $lines[$numlines] .= "\n";
    ++$numlines;
    $lines[$numlines] .= " " x $wrapmargin;
  }
  $startbutt[$numbuttons]=length($lines[$numlines]);
  $lines[$numlines] .= $buttontext;
  $endbutt[$numbuttons]=length($lines[$numlines]);
  $buttline[$numbuttons]=$numlines;
  ++$numbuttons;
}

sub doNEXTID {
}

sub doISINDEX {
  $isindex=1;
}

sub doPLAINTEXT {
  while ($_=&expect(2)) {
    $lines[$numlines] .= $_;
    if (/\n/) { ++$numlines; }
  }
}

sub doFIXED {
  #set things so that doA won't do any bizzare formatting
  #NOTE: tokens could still get bumped to lines of their own
  $paragraph=0;
  $wrapmargin=0;
  &blankline;

  while (($_=&expect(2,"^<A .*>$","^</FIXED>$")) !~ /^<\/FIXED>$/i) {
    if (/^<A .*>$/i) {
      &callroutine($_);
    }
    else {
      $lines[$numlines] .= $_;
      if (/\n/) { ++$numlines; }
    }
  }
  &blankline;
}

sub doPRE {
  #set things so that doA won't do any bizzare formatting
  #NOTE: tokens could still get bumped to lines of their own
  $paragraph=0;
  $wrapmargin=0;
  &blankline;

  while (($_=&expect(2,"^<A .*>$","^</PRE>$")) !~ /^<\/PRE>$/i) {
    if (/^<A .*>$/i) {
      &callroutine($_);
    }
    else {
      $lines[$numlines] .= $_;
      if (/\n/) { ++$numlines; }
    }
  }
  &blankline;
}

sub doLISTING {
  &blankline;
  while (($_=&expect(2,"^<\/LISTING>$")) !~ /^<\/LISTING>$/i) {
    $lines[$numlines] .= $_;
    if (/\n/) { ++$numlines; }
  }
  &blankline;
}

sub doXMP {
  &blankline;
  while (($_=&expect(2,"^<\/XMP>$")) !~ /^<\/XMP>$/i) {
    $lines[$numlines] .= $_;
    if (/\n/) { ++$numlines; }
  }
  &blankline;
}

sub doP {
  $paragraph=1;
}

sub doH {
  local($head)="";
  local($_,$indent);

  #expect only text, or the ending tag
  while (($_=&expect(2,"^<\/H$_[0]>$")) !~ /^<\/H$_[0]>$/i) {
    s/\n/ /;
    $head .= $_;
  }

  if ($_[0] == 1) {
    &blankline;

    $head = join(" ",split(/\007*/,$head));
    $indent=($columns-length($head))/2;
    $head = " " x $indent . $head;
    $head = "$b_bold_c$head$e_all_c\n";
    push(@lines,$head);
    ++$numlines;

    &blankline;
  }
  elsif ($_[0] == 2) {
    &blankline;
    $head = join(" ",split(/\007*/,$head));
    $head = "$b_bold_c$head$e_all_c\n";
    push(@lines,$head);
    ++$numlines;
    &blankline;
  }
  else {
    &blankline;
    $head = "$b_bold_c$head$e_all_c\n";
    push(@lines,$head);
    ++$numlines;
    &blankline;
  }
}

sub doADDRESS {
  local($_);

  &blankline;
  $wrapmargin=50;
  &dotext(" " x 50);
  while (($_=&expect(2,"^<A .*>$","^<\/ADDRESS>$")) !~ /^<\/ADDRESS>$/i) {
    if (/^<EOF>$/) {
      last;
    }
    elsif (/^<A .*>$/i) {
      &callroutine($_);
    }
    else { #text
      s/\n//;
      &dotext($_);
    }
  }
  $wrapmargin=0;
  &forceeol;
}

sub doHP {
}

sub doDL {
  local($_);

  $wrapmargin=0;
  &blankline;
  $_=&expect(1,"^<\/DL>$","^<DT>$");
  while ($_ !~ /<\/DL>$/i) {

    $wrapmargin=0;
    while (($_=&expect(2,"^<A .*>$","^<DD>$","^</DL>$")) !~ /^<DD>$/i) {
      if (/^<\/DL>$/i) {
	$wrapmargin=0;
	&blankline;
	return;
      }
      if (/^<A .*>$/i) {
	&callroutine($_);
      }
      else { #text
	s/\n//;
	&dotext($_);
      }
    }

    if (length($lines[$numlines]) > 19) {
      $lines[$numlines] .= "\n";
      ++$numlines;
      $lines[$numlines] = " " x 21;
    }
    else {
      $lines[$numlines]=sprintf("%-20s ",$lines[$numlines]);;
    }

    $wrapmargin=21;
    while ($_=&expect(2,"^<A .*>$","^<DT>$","^<\/DL>$")) {
      if (/^<DT>$/i || /^<\/DL>$/i) {
	#it was just too cumbersome to stick in the while statement...
	last;
      }
      elsif (/^<A .*>$/i) {
	&callroutine($_);
      }
      else { #text
	s/\n//;
	&dotext($_);
      }
    }

    $wrapmargin=0;
    &blankline;
  }
}

sub doOL {
  local($_);
  local($itemnum)=0;

  $wrapmargin=5;
  &blankline;
  while (($_=&expect(2,"^<A .*>$","^<LI>$","^<\/OL>$")) !~ /^<\/OL>$/i) {
    if (/^<LI>$/i) {
      ++$itemnum;
      &blankline;
      $lines[$numlines] = sprintf(" %3d ",$itemnum);
    }
    elsif (/^<A .*>$/i) {
      &callroutine($_);
    }
    else {
      &dotext($_);
    }
  }
  $wrapmargin=0;
  &blankline;
}

sub doUL {
  local($_);
  local($itemnum)=0;

  $wrapmargin=5;
  &blankline;
  while (($_=&expect(2,"^<A .*>$","^<LI>$","^<\/UL>$")) !~ /^<\/UL>$/i) {
    if (/^<LI>$/i) {
      ++$itemnum;
      &blankline;
      $lines[$numlines] = sprintf("   * ",$itemnum);
    }
    elsif (/^<A .*>$/i) {
      &callroutine($_);
    }
    else {
      &dotext($_);
    }
  }
  $wrapmargin=0;
  &blankline;
}

sub doMENU {
  local($_);
  local($itemnum)=0;

  $wrapmargin=5;
  &blankline;
  while (($_=&expect(2,"^<A .*>$","^<LI>$","^<\/MENU>$")) !~ /^<\/MENU>$/i) {
    if (/^<LI>$/i) {
      ++$itemnum;
      &forceeol;
      $lines[$numlines] .= sprintf(" %3d ",$itemnum);
    }
    elsif (/^<A .*>$/i) {
      &callroutine($_);
    }
    else {
      &dotext($_);
    }
  }
  $wrapmargin=0;
  &blankline;
}

sub doDIR {
  local($_);
  local($itemnum,$linelen,$itemlen,$fill,$fieldwidth);

  $wrapmargin=4;
  $fieldwidth=19;
  #numbers derived to work evenly on 80 column screen
  #other width screens might waste some right-hand space

  &blankline;
  while (($_=&expect(2,"^<A .*>$","^<LI>$","^<\/DIR>$")) !~ /^<\/DIR>$/i) {
    if (/^<LI>$/i) {
      $linelen=length($lines[$numlines])-$wrapmargin;
      if ($linelen > $columns-$wrapmargin-$fieldwidth) {
	&forceeol;
      }
      elsif ($itemnum) {
	$fill= $fieldwidth-($linelen % $fieldwidth);
	$lines[$numlines] .= " " x $fill;
      }
      ++$itemnum;
    }
    elsif (/^<A .*>$/i) {
      &callroutine($_);
    }
    else {
      &dotext($_);
    }
  }
  $wrapmargin=0;
  &blankline;
}

sub do {
  if ($_[0] =~ /^<!DOCTYPE /i) {
    return;
  }
  elsif ($_[0] =~ /^\//) {
    if (! $quiet) {
      print "Unmatched ending tag found ($_).  $#token tokens from the end, output line $numlines.\n";
    }
    if ($pedantic) {
      exit(1);
    }
  }
  else {
    if (! $quiet) {
      print "Bizzare tag found ($_).  $#token tokens from the end, output line $numlines.\n";
    }
    if ($pedantic) {
      exit(1);
    }
  }
}

#
# browser routines
#

sub setup {
  &cbreak;

  $SIG{'CONT'}=sig_cont;

  if ($mouse) {
    print "[?1000h";
  }

  %browsemap=(
    "q",	"leave",
    "",	"drawscreen",
    "",	"resize",
    "j",	"downline",
    "\n",	"downline",
    "k",	"upline",
    "",	"downhalf",
    "",	"uphalf",
    "",	"downscreen",
    " ",	"downscreen",
    "",	"downscreen",
    "",	"upscreen",
    "b",	"upscreen",
    "v",	"upscreen",
    "",	"savekey",
    "\t",	"dobuttons",
    "",	"backbuttons",
    "",	"prevhist",
    "",	"nexthist",
    "h",	"history",
    "g",	"goto",
    "i",	"gotoindex",
    "M",	"mark",
    "m",	"marked",
    "H",	"gotohome",
    "0",	"gotohome",
    "",	"status",
    "?",	"browserhelp",
    "UNDEF",	"beep",
  );

  if ($mouse) {
    $browsemap{"["}="savekey";
    $browsemap{"[M"}="xhitbutton";
  }

  %buttonsmap=(
    "q",	"leave",
    "",	"drawscreen",
    "",	"drawscreen",
    "\t",	"nextbutton",
    "",	"prevbutton",
    "",	"hitbutton",
    "\n",	"hitbutton",
    "?",	"browserhelp",
    "UNDEF",	"leavebuttons",
  );

  %selectormap=(
    "",	"drawselect",
    "",	"drawselect",
    "\t",	"downselect",
    "j",	"downselect",
    "",	"upselect",
    "k",	"upselect",
    "",	"selectit",
    "\n",	"selectit",
    "c",	"cancelselect",
    "?",	"selectorhelp",
    "UNDEF",	"beep",
  );

  if ($mouse) {
    $selectormap{"["}="savekey";
    $selectormap{"[M"}="xselectit";
  }

}

sub browserhelp {
  &clearscreen;
  print <<EEE;

  Page up		^B	or	<ESC>V	or	 b
  Page down		^F	or	^V	or	<SPC>
  Half page up		^U
  Half page down	^D
  Line up		k
  Line down		j	or	<CR>
  Previous doc		^P
  Next doc		^N
  Redraw		^L
  Resize		^R
  Select next button	<TAB>
  Select prev button	<BS>
  Hit current button (only when button is highlighted)
			<CR>	or	<ESC>
  Select from history	h
  Mark document		M
  Select marked doc	m
  Goto href		g
  Home			H	or	0 (zero)
  Quit			q

  press any key to continue
EEE
  getc;
  &drawscreen;
}

sub leave {
  &reset;
  if ($mouse) {
    print "[?1000l";
  }
  &clearscreen;
  exit(0);
}

sub browse {
  local($c);

  $histloc=0;
  $maxwidth=$columns-1;
  $state="browsing";
  $wrapmargin=0;
  &gotohref($home_html);

  while (1) {
    $c=getc;
    if ($state eq "browsing") {
      &handle_keystroke($c,%browsemap);
    }
    elsif ($state eq "buttons") {
      &handle_keystroke($c,%buttonsmap);
    }
    else {
      print STDERR "ACK! unknown state.\n";
    }
  }
}

sub handle_keystroke {
  local($c)=shift(@_);
  local(%keymap)=@_;
  local($function);

  $c = $saved_keys . $c;

  if (defined($keymap{$c})) {
    $function=$keymap{$c};
    &$function($c);
  }
  else {
    $function=$keymap{"UNDEF"};
    &$function($c);
  }

  if ($savethiskey) {
    $saved_keys = $c;
    $savethiskey=0;
  }
  else {
    $saved_keys="";
  }
}

sub savekey {
  $savethiskey=1;
}

sub status {
  local ($info,$perc);

  $perc=(100*($top+$rows-1)/$#lines);
  $perc=int($perc+0.5);
  if ($perc >= 100) { $perc=100; }
  &mvcurs(0,$rows-1);
  &cleartoeol;
  $info=sprintf("//%s%s (%d%%)",$currentsite,$currentfile,$perc);
  printf("%-${maxwidth}.${maxwidth}s",$info);
  &mvcurs($curscol,$cursrow);
}

sub dobuttons {
  if (&firstbutton >= 0) {
    $state="buttons";
    &showline($buttline[$currbutt]);
  }
  else {
    &beep;
  }
}

sub backbuttons {
  if (&lastbutton >= 0) {
    $state="buttons";
    &showline($buttline[$currbutt]);
  }
  else {
    &beep;
  }
}

sub leavebuttons {
  local($lastbuttline);

  $lastbuttline=$buttline[$currbutt];
  $currbutt= -1;
  &showline($lastbuttline);
  $state="browsing";

  #make use in browser mode of whatever key was hit
  &handle_keystroke($c,%browsemap);
}

sub firstbutton {
  local($i);

  for ($i=0; $i<=$#buttline && $buttline[$i]<=$top+($rows-2); ++$i) {
    if ($hrefdata[$i] eq "") { next; }

    if ($buttline[$i] >= $top) {
      $currbutt=$i;
      last;
    }
  }
  return($currbutt);
}

sub lastbutton {
  local($i);

  for ($i=$#buttline; $i>=0 && $buttline[$i]>=$top; --$i) {
    if ($hrefdata[$i] eq "") { next; }

    if ($buttline[$i] <= $top+($rows-2)) {
      $currbutt=$i;
      last;
    }
  }
  return($currbutt);
}

sub nextbutton {
  local($lastbuttline);

  $lastbuttline=$buttline[$currbutt];
  do {
    ++$currbutt;
    if ($currbutt > $#buttline || $buttline[$currbutt] > $top+$rows-2) {
      $currbutt=&firstbutton;
    }
  } while($hrefdata[$currbutt] eq "");

  if ($lastbuttline != $buttline[$currbutt]) { &showline($lastbuttline); }
  &showline($buttline[$currbutt]);
}

sub prevbutton {
  local($lastbuttline);

  $lastbuttline=$buttline[$currbutt];
  do {
    --$currbutt;
    if ($currbutt < 0 || $buttline[$currbutt] < $top) {
      $currbutt=&lastbutton;
    }
  } while($hrefdata[$currbutt] eq "");

  if ($lastbuttline != $buttline[$currbutt]) { &showline($lastbuttline); }
  &showline($buttline[$currbutt]);
}

sub gotohome {
  &gotohist(0);
}

sub gotoindex {
  local($tmp);

  if (! $isindex) {
    &beep;
    &showmess("Document is not an index");
    return;
  }
  &clearscreen;
  print "Please enter the keywords:\n";
  print "(BUG!  There is an $columns character limit on this entry)\n";
  $tmp=&lineedit($maxwidth);
  $tmp =~ s/ /+/;
  if ($tmp eq "") {
    &drawscreen;
  }
  elsif (&gotohref($tmp) == -1) {
    &drawscreen;
  }
}

sub goto {
  local($tmp);

  &clearscreen;
  print "Please enter the WWW tag to go to:\n";
  print "(BUG!  There is an $columns character limit on this entry)\n";
  $tmp=&lineedit($maxwidth);
  if ($tmp eq "") {
    &drawscreen;
  }
  elsif (&gotohref($tmp) == -1) {
    &drawscreen;
  }
}

sub gotohref {
  local($arg,$type,$data,$dir,$site,$file,$toanchor);
  $arg=$_[0];
  #print "<$arg>\n";

  if ($arg =~ /"/) {
    $arg =~ s/"//g;
  }

  if ($arg =~ /#/) {
    $toanchor = $arg;
    $toanchor =~ s/^.*#//;
    $arg =~ s/#.*$//;
    if ($arg eq "") {
      if ($toanchor ne "") { &findanchor($toanchor); }
      &drawscreen;
      return;
    }
  }

  if($arg =~ /:.\//) {
    $type=$arg;
    $type =~ s|:.*$||;

    $data=$arg;
    $data =~ s|^[a-z]+:./||;
    $data =~ s|[ ]*.*>$||;
  }
  else {
    $type="file";

    $data=$arg;
    $data =~ s|[ ]*.*>$||;
  }

  if ($type eq "file") {
    $site=$currentsite;
    $file=$data;
    if ($file !~ /^\//) {
      $file=$currentdir . $file;
    }
  }
  elsif ($type eq "http") {
    $file=$data;
    $file =~ s|^[^/]*||;

    $site=$data;
    $site =~ s|^//||;
    $site =~ s|/.*$||;
    if ($site eq "") {
      $site=$currentsite;
    }
  }
  elsif ($type eq "gopher") {
    &beep;
    &showmess("Gopher isn't supported yet");
    return(-1);
  }
  elsif ($type eq "wais") {
    &beep;
    &showmess("Wais isn't supported yet");
    return(-1);
  }
  elsif ($type eq "prospero") {
    &beep;
    &showmess("Prospero isn't supported yet");
    return(-1);
  }
  elsif ($type eq "telnet") {
    &beep;
    &showmess("Telnet isn't supported yet");
    return(-1);
  }
  elsif ($type eq "news") {
    &beep;
    &showmess("News isn't supported yet");
    return(-1);
  }
  else {
    &beep;
    &showmess("I don't recognize that address type ($type)");
    return(-1);
  }

  #strip all dot-dots out of path
  #won't deal with dotfiles correctly
  while ($file =~ /\/[^\/.][^\/]*\/[.][.]/) {
    $file =~ s|/[^/.][^/]*/[.][.]||;
  }
  while ($file =~ /^\/+[.][.]/) {
    $file =~ s|[.][.]||;
  }
  #strip all double slashes
  while ($file =~ /\/\//) {
    $file =~ s|//|/|;
  }
  if ($file eq "") { $file="/"; }

  $dir=$file;
  $dir =~ s|/[^/]+$||;
  $dir .= "/";

  if ($site eq "") {
    if (-d $file) {
      $dir=$file;
      $dir .= "/";
    }
    if (-r $file) {
      &tokenize($file);
      &token2image;
      $currentfile=$file;
      $currentdir=$dir;
      $currentsite=$site;
      &addtohist;
      if ($toanchor ne "") { &findanchor($toanchor); }
      &drawscreen;
    }
    else {
      &beep;
      &showmess("Can't access file $file");
      return(-1);
    }
  }
  else {
    if (&http($site,$file)) {
      &tokenize("/tmp/w3browser.$$.html");
      &token2image;
      $currentfile=$file;
      $currentdir=$dir;
      $currentsite=$site;
      &addtohist;
      if ($toanchor ne "") { &findanchor($toanchor); }
      &drawscreen;
      #unlink("/tmp/w3browser.$$.html");
    }
    else {
      &beep;
      &showmess("Site isn't responding");
      return(-1);
    }
  }
}

sub http {
  local($site,$port,$file)=($_[0],$_[0],$_[1]);
  local($sockaddr,$there,$response,$tries) = ("Snc4x8");

  if ($site =~ /:[0-9]+$/) {
    $site =~ s/:[0-9]+$//;
    $port =~ s/^.*://;
  }
  else {
    $port=80;
  }

  #ping first (if possible) to avoid delays
  if (-x "/usr/etc/ping") {
    if (system("/usr/etc/ping $site 2 > /dev/null")) {
      return(0);
    }
  }

  $there = pack($sockaddr,2,$port,&getaddress($site));
  if (!socket(S,2,1,6)) { return(0); }
  if (!connect(S,$there)) { return(0); }
  select(S);$|=1;
  select(STDOUT);
  print S "GET $file\r\n";
  open(WWW,">/tmp/w3browser.$$.html");
  while(<S>) {
    print WWW;
  }
  close(S);
  close(WWW);
  return(1);
}

sub hitbutton {
  if ($hrefdata[$currbutt] eq "") {
    &beep;
    &showmess("Button has no address");
  }
  else {
    &gotohref($hrefdata[$currbutt]);
  }
}

sub xhitbutton {
  #handle X mouse button cursor position information
  local($x,$y,$i,$found,$lastbuttline);

  if ($currbutt > -1) {
    $lastbuttline=$buttline[$currbutt];
  }

  getc;
  $x=getc;
  $y=getc;
  $x=ord($x)-33;
  $y=ord($y)-33;
  $y += $top;

  $found=-1;
  for ($i=0; $i<=$numbuttons; ++$i) {
    if ($buttline[$i] == $y) {
      if ($x >= $startbutt[$i] && $x < $endbutt[$i]) {
	$found=$i;
	last;
      }
    }
  }
  if ($found == -1) {
    #for xterms use a control bar on the bottom
    if ($mouse) {
      if ($y == $top+$rows-1) {
	if ($x < 20) { &prevhist; }
	elsif ($x >= 60) { &nexthist; }
	elsif ($x >=20 && $x < 40) { &upscreen; }
	elsif ($x >=40 && $x < 60) { &downscreen; }
	else { &beep; }
      }
      else {
	&beep;
      }
    }
    else {
      &beep;
    }
    return;
  }

  if ($currbutt > -1 && $lastbuttline != $buttline[$currbutt]) {
    &showline($lastbuttline);
  }
  $currbutt=$found;
  &showline($buttline[$currbutt]);

  if ($hrefdata[$currbutt] eq "") {
    &beep;
    &showmess("Button has no address");
  }
  else {
    &gotohref($hrefdata[$currbutt]);
  }
}

sub findanchor {
  local($i);

  for ($i=0; $i<=$numbuttons; ++$i) {
    if ($buttname[$i] eq $_[0]) {
      $top=$buttline[$i]-1;
      if ($top < 0) { $top=0; }
      return;
    }
  }
}

sub mark {
  if (! defined($beenmarked{$currentfile,$currentsite})) {
    push(@markfile,$currentfile);
    push(@marksite,$currentsite);
    push(@marktitle,$currenttitle);
    $beenmarked{$currentfile,$currentsite}=1;
  }
}

sub marked {
  local($i);

  if ($#marktitle == -1) {
    &beep;
    &showmess("There aren't any marked documents");
    return;
  }
  if (($i=&selector(@marktitle)) >= 0) {
    $currentfile=$markfile[$i];
    $currentsite=$marksite[$i];
    $currenttitle=$marktitle[$i];
    if ($marksite[$i] eq "") {
      &gotohref("file://$currentfile");
    }
    else {
      &gotohref("http://$currentsite$currentfile");
    }
  }
  else {
    &drawscreen;
  }
}

sub addtohist {
  if (! defined($beento{$currentfile,$currentsite})) {
    push(@histfile,$currentfile);
    push(@histsite,$currentsite);
    push(@histtitle,$currenttitle);
    $histloc=$#histfile;
    $beento{$currentfile,$currentsite}=1;
  }
}

sub history {
  local($i);

  if (($i=&selector(@histtitle)) >= 0) {
    &gotohist($i);
  }
  else {
    &drawscreen;
  }
}

sub prevhist {
  if ($histloc == 0) { &beep; }
  else {
    --$histloc;
    if ($histfile[$histloc] ne "") {
      &gotohist($histloc);
    }
    else {
      &beep;
    }
  }
}

sub nexthist {
  if ($histloc == $#histfile) { &beep; }
  else {
    ++$histloc;
    if ($histfile[$histloc] ne "") {
      &gotohist($histloc);
    }
    else {
      &beep;
    }
  }
}

sub gotohist {
  $currentfile=$histfile[$_[0]];
  $currentsite=$histsite[$_[0]];
  $currenttitle=$histtitle[$_[0]];
  if ($histsite[$_[0]] eq "") {
    &gotohref("file://$currentfile");
  }
  else {
    &gotohref("http://$currentsite$currentfile");
  }
}

sub downline {
  if ($top+$rows-2 >= $numlines-1) { &beep; }
  else {
    ++$top;
    &scrollup(&makeline($top+$rows-2));
    &mvcurs($curscol,$cursrow);
  }
}

sub upline {
  if ($top == 0) { &beep; }
  else {
    --$top;
    &scrolldown(&makeline($top));
    &mvcurs($curscol,$cursrow);
  }
}

sub upscreen {
  if ($top == 0) { &beep; }
  else {
    $top -= $rows-2;
    if ($top < 0) { $top=0; }
    &drawscreen;
    &mvcurs($curscol,$cursrow);
  }
}

sub downscreen {
  if ($top+$rows-2 >= $numlines-1) { &beep; }
  else {
    $top += $rows-2;
    if ($top+$rows-2 > $numlines-1) { $top=$numlines-1-($rows-2); }
    &drawscreen;
    &mvcurs($curscol,$cursrow);
  }
}

sub uphalf {
  local($off)=int(($rows-2)/2);

  if ($top == 0) { &beep; }
  else {
    $top -= $off;
    if ($top < 0) { $top=0; }
    &drawscreen;
    &mvcurs($curscol,$cursrow);
  }
}

sub downhalf {
  local($off)=int(($rows-2)/2);

  if ($top+$rows-2 >= $numlines-1) { &beep; }
  else {
    $top += $off;
    if ($top+$rows-2 > $numlines-1) { $top=$numlines-1-($rows-2); }
    &drawscreen;
    &mvcurs($curscol,$cursrow);
  }
}

#
# selector mode routines
#

sub selector {
  local($c);

  @selectray=@_;
  $numselects=$#selectray+1;

  $selecttop=0;
  $selectrow=0;

  &drawselect;
  &selectorhelp;

  $selecting=1;
  while ($selecting) {
    $c=getc;
    &handle_keystroke($c,%selectormap);
  }

  return($selectrow+$selecttop);
}

sub drawselect {
  local($sln)=0;

  &clearscreen;
  while ($sln < $rows-1 && $sln+$selecttop < $numselects) {
    &mvcurs(0,$sln);
    if ($sln == $selectrow) {
      printf ("$b_stand_c%-${maxwidth}.${maxwidth}s$e_stand_c",$selectray[$sln+$selecttop]);
    }
    else {
      printf ("%-${maxwidth}.${maxwidth}s",$selectray[$sln+$selecttop]);
    }
    ++$sln;
  }
  &mvcurs($curscol,$cursrow);
}

sub downselect {
  if ($selecttop+$selectrow == $#selectray) { &beep; }
  else {
    &mvcurs(0,$selectrow);
    &cleartoeol;
    printf ("%-${maxwidth}.${maxwidth}s",$selectray[$selectrow+$selecttop]);

    if ($selectrow == $rows-2) {
      ++$selecttop;
      &scrollup(sprintf("$b_stand_c%-${maxwidth}.${maxwidth}s$e_stand_c",
		        $selectray[$selecttop+$selectrow]));
    }
    else {
      ++$selectrow;
      &mvcurs(0,$selectrow);
      &cleartoeol;
      printf ("$b_stand_c%-${maxwidth}.${maxwidth}s$e_stand_c",$selectray[$selectrow+$selecttop]);
    }
    &mvcurs($curscol,$cursrow);
  }
}

sub upselect {
  if ($selecttop+$selectrow == 0) { &beep; }
  else {
    &mvcurs(0,$selectrow);
    &cleartoeol;
    printf ("%-${maxwidth}.${maxwidth}s",$selectray[$selectrow+$selecttop]);

    if ($selectrow == 0) {
      --$selecttop;
      &scrolldown(sprintf("$b_stand_c%-${maxwidth}.${maxwidth}s$e_stand_c",
		          $selectray[$selecttop]));
    }
    else {
      --$selectrow;
      &mvcurs(0,$selectrow);
      &cleartoeol;
      printf ("$b_stand_c%-${maxwidth}.${maxwidth}s$e_stand_c",$selectray[$selectrow+$selecttop]);
    }
    &mvcurs($curscol,$cursrow);
  }
}

sub xselectit {
  local($x,$y);

  getc;
  $x=getc;
  $y=getc;
  $x=ord($x)-33;
  $y=ord($y)-33;
  $y += $selecttop;

  if ($y >= $numselects) { &beep; }
  else {
    &mvcurs(0,$selectrow);
    &cleartoeol;
    printf ("%-${maxwidth}.${maxwidth}s",$selectray[$selectrow+$selecttop]);

    $selectrow=$y-$selecttop;

    &mvcurs(0,$selectrow);
    &cleartoeol;
    printf ("$b_stand_c%-${maxwidth}.${maxwidth}s$e_stand_c",$selectray[$selectrow+$selecttop]);
  }
  &mvcurs($curscol,$cursrow);
  $selecting=0;
}

sub selectit {
  $selecting=0;
}

sub cancelselect {
  $selecting=0;
  $selecttop= -1;
  $selectrow= 0;
}

sub selectorhelp {
  &mvcurs($curscol,$cursrow);
  &cleartoeol;
  print " RETURN to select,   c to cancel";
  &mvcurs($curscol,$cursrow);
}

#
# general use routines
#

sub resize {
  local($tmptop)=$top;

  &getsize;
  $maxwidth=$columns-1;
  &token2image;
  $top=$tmptop;
  &drawscreen;
}

sub drawscreen {
  local($sln)=0;

  &clearscreen;
  while ($sln < $rows-1 && $sln+$top < $numlines) {
    &showline($sln+$top);
    ++$sln;
  }
  &mvcurs($curscol,$cursrow);
}

sub showline {
  &mvcurs(0,$_[0]-$top);
  print &makeline($_[0]);
  &mvcurs($curscol,$cursrow);
}

sub makeline {
  local($curline,$cln)=($lines[$_[0]],$_[0]);
  local($j);

  for ($j=$#buttline; $j>=0 && $buttline[$j]>$cln; --$j) {
    #use this loop to move quickly to any buttons that matter
  }
  for (; $j>=0 && $buttline[$j]==$cln; --$j) {
    if ($hrefdata[$j] eq "") { next; }

    if ($j == $currbutt) {
      substr($curline,$endbutt[$j]) = $e_stand_c . substr($curline,$endbutt[$j]);
      substr($curline,$startbutt[$j]) = $b_stand_c . substr($curline,$startbutt[$j]);
    }
    else {
      substr($curline,$endbutt[$j]) = $e_under_c . substr($curline,$endbutt[$j]);
      substr($curline,$startbutt[$j]) = $b_under_c . substr($curline,$startbutt[$j]);
    }
  }
  return($curline);
}

sub sig_cont {
  &drawscreen;
}

sub getaddress {
  local($host) = @_;
  local(@ary);
  @ary = gethostbyname($host);
  return(unpack("C4",$ary[4]));
}
