#!/usr/pkg/bin/perl -w
#
# FNET/ICB CLIENT 1.4	11/10/97
# John M Vinopal        banshee@resort.com
#
# Copyright (C) 1996, 1997, The Resort, All Rights Reserved.
# Permission is granted to copy and modify this program for
# non-commercial purposes, so long as this copyright notice is
# preserved.  This software is distributed without warranty.

use FNET;
use CMDLINE;
use Getopt::Std;
use vars qw($opt_g $opt_N $opt_n $opt_s $opt_w $opt_h);
require 5.000;
use strict;
#use diagnostics;

my $version = "Perl Fnet Client 1.4";
my $LINESIZE = 78;
my $SCREENSIZE = 23;

# Machine Specifics.
my	($os, $EOL);
eval '$os = `uname`';
$os = 'Unix' if $os;  # if you just need generic OS
$os = 'Mac' if defined( $MacPerl::Version && $MacPerl::Version);
$os = 'Dos' unless $os;

if ($os eq "Mac") {
	$EOL = "\n";
} else {
	# XXX - no idea if this is ok for DOS.
	$EOL = "\r\n";
}

# Default Server Variables.
my	($hostname, $portnumber);
$hostname = "klinzhai.evolve.com";
$portnumber = 7326;

# Default Session Variables.
# MacPerl doesn't have getlogin().
my	($name, $nick, $group, $cmd, $pass) = ('', '', "1", "login", '');
if (not $name = eval("getlogin()")) {
	# Generate random user name.
	$name = "user".substr(rand(), 2, 5);
}
$nick = $name;

my	(@privnames, @privlist, @openlist, %hushlist) = () x 4;
my	($cmdchar, $pagemode, $away, $alertmode);
$cmdchar = "/";		# Character denoting a command.
$pagemode = 0;		# Paging Mode: On at line 0.
$away = '';			# Away from terminal string off.
$alertmode = 0;		# Alert mode: beep on output off.

# Process command line options.
getopts("hwg:N:n:s:");
if ($opt_g) { $group = $opt_g; }
if ($opt_N) { $name = $opt_N; $nick = $name; }
if ($opt_n) { $nick = $opt_n; }
if ($opt_s) { $hostname = $opt_s; }
if ($opt_w) { $opt_w = ''; $cmd = "w"; }
if ($opt_h) {
	$opt_h = '';
	print "Usage: $0 [-n nickname] [-g groupname] [-s server] [-w]$EOL";
	exit;
}

# Connect to the server.
print "Awaiting connection to '$hostname'$EOL";
my $FNET = new FNET($hostname,$portnumber, $name, $nick, $group, $cmd, $pass);
if (not $FNET) {
	quit();
}

# Set up select to watch the fds.
my ($rin, $win, $ein, $rout, $wout, $eout);
$rin = $win = $ein = '';
vec($rin,fileno(STDIN),1) = 1;
vec($rin,fileno($FNET->{'SOCK'}),1) = 1;
$ein = $rin | $win;

$SIG{'INT'} = 'doKill';
RESET:		# Return to here following ^z
$SIG{'TSTP'} = 'doSuspend';
system('stty raw -echo');

# Loop forever, processing STDIN and the Socket.
LOOP: while (1) {

	# Block forever waiting for I/O.
	select($rout=$rin, $wout=$win, $eout=$ein, undef);

	# Process user input.
	if (vec($rout, fileno(STDIN), 1) == 1) {
		my $msg = CMDLINE::cmdline($EOL, $os, \&tabfunction);
		# Ignore null messages.
		goto LOOP if ($msg eq '');
		print "$EOL";	# terminate string printed by CMDLINE.

		# Process the new user input as command or message.
		if ($msg =~ s/^$cmdchar//) {
			# Send a command.
			processCmd($msg);
		} else {
			# Send a normal message.
			push(@openlist, "--> $msg");
			$FNET->csendopen($msg);
		}
	}

	# Process incoming data on the socket.
	if (vec($rout, fileno($FNET->{'SOCK'}), 1) == 1) {
		processSock($FNET);
	}
}
quit();
exit;


#
# processCmd: User sending a command, $cmdchar already clipped.
# Format: cmd arbitary set of args
#         --- --------------------
sub processCmd
{
	my ($cmd, $arg) = split(' ', $_[0], 2);

	# Process some commands locally, send the others.
	if ($cmd eq "q" or $cmd eq "quit") {
		quit();
	} elsif ($cmd eq "clear") {
		print "$EOL" x $SCREENSIZE;

	# Hush silences a nickname.  Also a server command.
	} elsif ($cmd eq "hush") {
		if (defined($arg)) {
			$hushlist{$arg} = 1;
		} else {
			print "[=CHush=] Nicks:", keys(%hushlist), "$EOL";
		}
	} elsif ($cmd eq "nohush") {
		delete($hushlist{$arg});

	} elsif ($cmd eq "t" or $cmd eq "time") {
		# XXX - better format?
		print localtime()."$EOL";

	# Alert adds a beep to all output.
	} elsif ($cmd eq "alert") {
		if ($alertmode eq 0) {
			$alertmode = 1;
			print "[=CStatus=] Alert turned on.$EOL";
		} else {
			print "[=CError=] Alert already on.$EOL";
		}
	} elsif ($cmd eq "noalert") {
		if ($alertmode ne 0) {
			$alertmode = 0;
			print "[=CStatus=] Alert turned off.$EOL";
		} else {
			print "[=CError=] Alert already off.$EOL";
		}

	# Away replies with a set string to all personal messages.
	} elsif ($cmd eq "away") {
		if (defined($arg)) {
			my @time = localtime();
			$away = "[=Away\@$time[2]:$time[1]=] ".$arg;
			print "[=CStatus=] Away message set to '$away'.$EOL";
		} else {
			if (not $away) {
				print "[=CStatus=] Away was not set.$EOL";
			} else {
				$away = '';
				print "[=CStatus=] Away unset.$EOL";
			}
		}

	# Print a list of commands and help.
	} elsif ($cmd eq "?" or $cmd eq "help") {
		&printHelp;

	# Change the command prefix from "/".
	} elsif ($cmd eq "command") {
		if (defined($arg)) {
			$arg =~ /^(.)/;
			print "Command prefix changed from '$cmdchar' to '$1'.$EOL";
			$cmdchar = quotemeta($1);
		} else {
			print "Command prefix is '$cmdchar'.$EOL";
		}

	# Toggle paging mode.
	} elsif ($cmd eq "page") {
		if ($pagemode == -1) {
			$pagemode = 0;
			print "[=CStatus=] Hold-page turned on.$EOL";
		} else {
			print "[=CError=] Hold-page already on.$EOL";
		}
	} elsif ($cmd eq "nopage") {
		if ($pagemode != -1) {
			$pagemode = -1;
			print "[=CStatus=] Hold-page turned off.$EOL";
		} else {
			print "[=CError=] Hold-page already off.$EOL";
		}

	# Review the personal message buffer.
	} elsif ($cmd eq "personal") {
		my ($i) = 0;
		if (not $arg) { $arg = 10; }
		if ($arg > $#privlist) { $i = 0; }
		else { $i = $#privlist - $arg + 1; }
		print "--START--$EOL";
		while ($i <= $#privlist) {
			print "$privlist[$i]$EOL";
			$i++;
		}
		print "--END--$EOL";
		
	# Review the general message buffer.
	} elsif ($cmd eq "display") {
		my ($i) = 0;
		if (not $arg) { $arg = 10; }
		if ($arg > $#openlist) { $i = 0; }		# Start at element 0.
		else { $i = $#openlist - $arg + 1; }
		print "--START--$EOL";
		while ($i <= $#openlist) {
			print "$openlist[$i]$EOL";
			$i++;
		}
		print "--END--$EOL";
		
	} elsif ($cmd eq "flirt") {
		# XXX - need to read a flirt file.
		$FNET->sendcmd("m", $arg." [=Flirt!=]");
	} elsif ($cmd eq "btth") {
		$FNET->sendcmd("m", $arg." [=Whack!=] boot to the head!");
	} elsif ($cmd eq "hug") {
		$FNET->sendcmd("m", $arg." [=HUG!=] you've been hugged! :)");

	# Server processed commands.
	} else {
		if ($cmd eq "who") {
			$cmd = "w";
		}

		# Private Message to another user.
		# XXX - how about /m banshee,gristle message?
		elsif ($cmd eq "m") {
			return unless ($arg);
			my ($name, $msg) = split(' ', $arg, 2);
			return unless ($name);
			# Don't delete and readd if already at the tail.
			unless (@privnames && $privnames[$#privnames] eq $name) {
				@privnames = grep { $_ ne "$name" } @privnames;
				push(@privnames, $name);
			}
			# XXX - 50 names max hardcoded.
			(splice(@privnames, 0, 10)) if ($#privnames == 50);
			push(@openlist, "-{to $name}-> $msg") if ($msg);
			push(@privlist, "-{to $name}-> $msg") if ($msg);
		}

		$FNET->sendcmd($cmd, $arg);
	}
}

#
# The core of interactive fnet.  Takes a server packet
# and formats it into the common fnet output.
sub	processSock
{
	my	($FNET) = @_;
	my	($packettype, @msgarray) = $FNET->readit();

	if (not $packettype) {
		print "Failed on socket read.$EOL";
		# XXX - we might want to quit here since we've read
		# corrupted material and the socket is in an uncertain
		# state.
		return;
	}

	# Process the packet data based on the $packettype.
	if ($packettype eq $M_LOGINOK) {
		print "[=Ack=] login ok!$EOL";
	} elsif ($packettype eq $M_OPEN) {
		return if exists($hushlist{$msgarray[0]});
		print "\007" if ($alertmode);
		formatmessage("<$msgarray[0]>", $msgarray[1], 0);
	} elsif ($packettype eq $M_PERSONAL) {
		return if exists($hushlist{$msgarray[0]});
		print "\007" if ($alertmode);
		$FNET->sendcmd("m", $msgarray[0]." ".$away) if ($away);
		formatmessage("<*$msgarray[0]*>", $msgarray[1], 1);
	} elsif ($packettype eq $M_STATUS) {
		print "[=$msgarray[0]=] $msgarray[1]$EOL";
	} elsif ($packettype eq $M_ERROR) {
		print "[=Error=] $msgarray[0]$EOL";
	} elsif ($packettype eq $M_IMPORTANT) {
		print "[=Hey!=]\007 $msgarray[1]$EOL";
	} elsif ($packettype eq $M_EXIT) {
		print "Server sent exit.$EOL";
		quit();
	} elsif ($packettype eq $M_CMDOUT) {
		formatcommandout(@msgarray);
	} elsif ($packettype eq $M_PROTO) {
		print "Connected to the $msgarray[1] ICB server ($msgarray[2])$EOL";
	} elsif ($packettype eq $M_BEEP) {
		print "[=Beep!=]\007 $msgarray[0] sent you a beep!$EOL";

	# XXX - does anyone actually use these packets?
	} elsif ($packettype eq $M_PING) {
		print "WARN: ping packet$EOL";
	} elsif ($packettype eq $M_PONG) {
		print "WARN: pong$EOL";
	} elsif ($packettype eq $M_OOPEN) {
		print "WARN: own open$EOL";
	} elsif ($packettype eq $M_OPERSONAL) {
		print "WARN: own personal$EOL";
	} else {
		print "WARN: unknown packet type '$packettype'$EOL";
	}

	# XXX - for page mode.
	if ($pagemode != -1) {
		$pagemode++;
		if ($pagemode == $SCREENSIZE) {
			print "--More--";
			# XXX - macs return nulls on read.
			until (defined(getc())) {;}
			print "\010" x 8;
			print "\040" x 8;
			print "\010" x 8;
			$pagemode = 0;
		}
	}
}

#
# Format open and private messages, breaking to fit on the screen.
sub	formatmessage
{
	my	($from, $message, $save) = @_;
	my	$slop = 15;		# maximum free space on rhs of screen.
	my	$linesize = $LINESIZE - length($from) - $slop;

	while ($message =~ /(.{1,$linesize})(\S{0,$slop})\s*/go) {
		print "$from $1$2$EOL";
		push(@openlist, "$from $1$2");
		push(@privlist, "$from $1$2") if ($save == 1);
	}
	# XXX - hardcoded consts.
	(splice(@openlist, 0, 100)) if ($#openlist > 500);
	(splice(@privlist, 0, 100)) if ($#privlist > 500);
}

#
# Format the command output from the server.
#
sub	formatcommandout
{
	my	($cmd, @args) = @_;

	# Command Output.
	if ($cmd eq "co") {
		print @args;
		$pagemode-- if ($pagemode > 0);		# Don't paginate console.
	}
	# Who header.
	elsif ($cmd eq "wh\000") {
		print "   Nickname     Idle  Resp  Sign-On  Account";
	}
	# Who line.
	elsif ($cmd eq "wl") {
		print formatwholine(@args);
	} else {
		print "WARN: command -$cmd-: @args";
	}
	print "$EOL";
}

# Take who array and format into the output format.
# [mod][name][idle][relay?][time][login][host][registered]
sub	formatwholine
{
	my	(@args) = @_;
	my	($idle, $relay);

	# Moderator
	if ($args[0] eq "m") {
		$args[0] = "*";
	}

	# Idle time in seconds.
	if ($args[2] < 60) {
		$idle = "-";
	} else {
		$args[2] /= 60;
		$idle = sprintf("%.0dm", $args[2]);
	}

	# Relay time?
	if ($args[3] < 60) {
		$relay = "-";
	} else {
		$args[3] /= 60;
		$relay = sprintf("%.0dm", $args[3]);
	}

	# Translate time into english string.
	my @time = localtime($args[4]);
	$args[4] = "am";
	if ($time[1] < 10) {
		$time[1] = "0".$time[1];
	}
	if ($time[2] > 12) {
		$time[2] -= 12;
		$args[4] = "pm";
	} elsif ($time[2] < 1) {
		$time[2] = 12;
	}
		
	my $str = sprintf("  %s%-12s  %3s   %3s  %2s:%2s%s  %s@%s  %s",
		$args[0], $args[1],
		$idle, $relay,
		$time[2], $time[1], $args[4],
		$args[5], $args[6], $args[7]);

	return $str;
}

# Caught ^Z; reset tty modes and suspend.
sub doSuspend
{
	system('stty -raw echo');
	$SIG{'TSTP'} = 'DEFAULT';
	kill 'TSTP', $$;
	goto RESET;
}

# Caught ^C; reset tty modes and exit.
sub doKill
{
	system('stty -raw echo');
	$SIG{'INT'} = 'DEFAULT';
	kill 'INT', $$;
}

# Reset tty modes and exit.
sub quit
{
	system('stty -raw echo');
	print "Bye!$EOL";
	exit;
}

# Cycle through the @privnames array so long as tab is pressed.
sub tabfunction
{
	my	$index = $#privnames;
	my	($ch, $linelen, $line) = ('', 0, '');

	return('', 0, '') if ($index == -1);

	do {
		if ($linelen != 0) {
			print "\010" x $linelen;
			print "\040" x $linelen;
			print "\010" x $linelen;
			$line = ''; $linelen = 0;
			$index--;
			($index = $#privnames) if ($index < 0);
		}
		$line = "/m $privnames[$index] ";
		$linelen = length($line);
		print $line;
		until (defined($ch = getc())) {;}
	} while (ord($ch) == 9);

	return($ch, $linelen, $line);
}

# Print some help information.
sub printHelp
{
		print "$version Command Help:$EOL" ,
		 "----------Command-------------------" ,
		 "----------Examples------------------$EOL" ,
		 "type to send a message to your group." ,
		 "\thi everyone!$EOL" ,
		 "/w . to see whos in your group.$EOL" ,
		 "also: /w <.|group|\@user>"  ,
		 "\t\t/w \@banshee  or /w 1$EOL"  ,
		 "/g changes your group."  ,
		 "\t\t\t/g group2 or /g \@umlaut$EOL"  ,
		 "/m sends a private message." ,
		 "\t\t/m izzy hi mom!  I'm in jail!$EOL" ,
		 "/beep sends a beep." ,
		 "\t\t\t/beep sl'lee$EOL" ,
		 "/nobeep prevents incoming beeps." ,
		 "\t/nobeep on or /nobeep off$EOL" ,
		 "/hush ignores a user." ,
		 "\t\t\t/hush andrew17$EOL" ,
		 "Try also /time, /m server help, " ,
		 "/display, /personal, /command$EOL" ,
		 "------------------------------------" ,
		 "------------------------------------$EOL";
}
