#!/bin/bash -
# deer
# Antonio Maschio - 2006-2008 <tbin@libero.it>
# 
# This is deer, the Dartmouth Environment Elementary Renderer, a (very naive)
# attempt to implement the original Dartmouth College Time-sharing 
# System and the BASIC language as conceived by Kemeny & Kurz in 1964,
# in its 1966 version (why 1966? I was born in 1966).
#
# This file, in particular, implements the original DCTS Operating System;
# it is in bash, version 3 or higher.
#
# See the file "deer manual.txt" for explanation about its usage.
# 
# 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
# It's GPL! Enjoy!
#
# (Written with vim, tab stop = 8)
#
# Note:
# The DCTS operating system was born as DTSS (Dartmouth Time-Sharing System)
# but their creators were forced to change it in DCTS (Dartmouth College
# Time-sharing System) because of some kind of property of the acronym by 
# another society. 
# deer emulates the 1964 version of the OS, so I guess this should be called 
# DTSS; but, in virtue of the many commands I implemented, taken from further
# versions of the same OS, and since I'm now too used in calling it DCTS, 
# in the rest of this file and into the documentation I'll call it in such way.
# (And in doing so, I'm sure I won't wake up the sleeping dog.)
#
umask 077
TRUE=1
FALSE=0

# USER VARIABLES
# you can change the following, adapting them to your OS and its features
deered="vim"	# put here your favorite editor
deerless="less" # put here your favorite viewer
INTERVAL=180	# put here your autologoff length in seconds
DELAY=1000	# put here your delay in listing
noTiming=$FALSE	# put here TRUE if you want to disable the timing feature
basic="dib" 	# put here your favorite BASIC interpreter

# the following is meaningful only if dib is set as the BASIC interpreter
# In case of other BASICs, set here all the options you need for it
EF=""		# If set to "-x", enable extended BASIC features

# CONSTANTS (don't change)
RELEASE="1.0.alpha"
YEAR=0
MONTH=1
DAY=2
HOUR=3
MINUTE=4
SECOND=5
ASEC=6
# the following var yields the file length constant for the DCTS files;
# I guess it was 6 for the 1964 system and 8 for later versions (in the '80)
# of the DCTS system (maybe more); I stick around the original version
FILLEN=6


# SYSTEM STRINGS 
# the main Header
LS="\nUSER NO. %6s    PROBLEM NAME: %${FILLEN}s   %02s %s. %4s    TIME: %02s:%02s\n\n"
# the DCTS had various operating machines, one being the D1 system
# as reported in the logging sessions found in www.dtss.org;
# my nickname is tonibin, and so I think that T1 (Tonibin 1) is a
# perfect identifier for the machine emulated by deer
SYSTEMCOMPUTER="T1"
# the following is a random value
USERC=$(echo $((RANDOM/1000)))  # the (fake and random) number of DTCS users
# output strings
SYSTM1="SYSTEM--"		# to be used into HELLO (cfr. 1964 manual)
SYSTM2="ENTER SYSTEM NAME--"	# to be used into SYSTEM (cfr. www.dtss.org)
USERN="USER NUMBER--"
NEWOLD="NEW OR OLD--"
NEWPN="NEW PROBLEM NAME--"
OLDPN="OLD PROBLEM NAME--"
APPFN="APPENDING FILE NAME--"
RENPN="RENAME PROBLEM NAME--"
CATNM="CATALOG NAME--"

# system flags
isLogged=$FALSE   	# has the user typed HELLO and given his User Number?
isHello=$FALSE		# has the user typed SYSTEM out of the HELLO sequence?
isReady=$TRUE		# is the prompt "READY" to be written?
isProgram=$TRUE		# is the user typing a program or a BUILD file?
isSaved=$FALSE		# has the user saved current file?
isExitingAllowed=$FALSE # has the user been warned that program is not saved?

# system Variables 
Command=""		# the main command
Topic1=""		# the topics (name mutuated from the EXPLAIN command)
Topic2=""
Topic3=""
WD="$HOME/.deer/"	# the system dir under which all user data is stored
CurrDir=""		# the user current directory path
CurrWhatDir=""		# the path written out by WHAT
BaseDir=""		# the user home (initially equal to CurrDir)
FileName=""		# the Problem Name
CurrPath=""		# the complete path to the program file
TempFile="/tmp/deertmp" # the temporary file under which data is saved
DeadSearch=".dead.file" # if deer is stopped, saves temporary file into CurrDir
UserNumber=""		# the User Number (and user directory name too);
			# files created during user session are put into a 
			# directory whose name is the UserNumber itself 
SystemBasic=""		# the system programming language, chosen by user;
			# at present, this emulator admits BASIC only
RUNS=0			# the number of times the command RUN has been used
NewOld=""   		# the NEW/OLD string printed in the HELLO sequence

# months abbreviations as (I guess) implemented on the DCTS
# verified only for a few months!
Mon=([1]=JAN FEB MAR APR MAY JUNE JULY AUG SEPT OCT NOV DEC)

##################
# DEER FUNCTIONS #
##################

# clean-up
# save temporary data file (if present) to a "standard" file, 
# if an interrupt should prevent a regular process ending
function cleanup {
        if [ -s $TempFile ]; then
		cat $TempFile > ${CurrDir}${DeadSearch}
        fi
        exit 1
}

# forgotten
# print a remark
function forgotten {
	printf "YOU HAVE FORGOTTEN TO TYPE HELLO...\n"
	READY
}


# interactiveProcess
# the engine of the whole script
function interactiveProcess {
	# print Main Header 
	clear
	
	printf "DARTMOUTH COLLEGE TIME SHARING, %s\n" "$SYSTEMCOMPUTER"
	printf "%02s %s. %4s, AT %02s:%02s, %03s USERS.\n\n" \
		$(DE $DAY) $(DE $MONTH)	$(DE $YEAR)\
		$(DE $HOUR) $(DE $MINUTE) $USERC 
		
	# enable extended patterns
	shopt -s extglob

	# timing disabled
	if [ $noTiming -eq $TRUE ]; then
		while read -e line; do
			processLine "$line"
		done
	# timing enabled
	else
		# read a timed out string
		# Undocumented: if the string EOF is typed,
		# the string reading is ended anyway
		line="EOF"
		read -e -t$INTERVAL line
		while [ "$line" != "EOF" ]; do
			processLine "$line"
			line="EOF"
			read -e -t$INTERVAL line
		done
		# when user types EOF or runs out of time, 
		# exit through proper routine
		isExitingAllowed=$TRUE
		BYE
	fi
}


# processLine
# process input line
function processLine() {
	line=$(echo "$1" | tr '[a-z]' '[A-Z]')

	# reset current program line data
	ln=""
	pr=""
	
	# I add a space to line to prevent 'cut' to yield the same
	# field when called
	line="$line "
	
	# thanks to Icarus Sparry <usenet at icarus dot freeuk dot com>
	# for this splitting; he replied to a comp.unix.shell post of mine
	# like sed 's/^ *[0-9][0-9]* *//'
	pr=${line/#*( )+([0-9])*( )/}
	# like sed 's/^ *//'
	ln=${line##*( )}
	# like sed 's/[!0-9].*//'
	ln=${ln%%[!0-9]*}
	
	# BUILD lines (free text form) are captured into the BUILD command, 
	# so the following is valid only for BASIC program lines
	
	# a line beginning with 0 is invalid
	# if found, a message is issued
	if [ -n "$ln" ] && [ $ln -eq 0 ]; then
		printf "NOT A VALID PROGRAM LINE\n"
		return
	fi

	# BASIC line with no body (it's deleted)
	if [ -n "$ln" ] && [ -z "$pr" ]; then
		unset "prog[$ln]"
		isSaved=$FALSE
		return
	fi

	# BASIC line with body (a valid program line)
	if [ -n "$ln" ]; then
		prog[$ln]="$pr"
		isSaved=$FALSE
		return
	fi
	
	# direct command: split constituents
	Command=$(echo "$line"  | cut -d' ' -f1)
	Topic1=$(echo "$line"   | cut -d' ' -f2)
	Topic2=$(echo "$line"   | cut -d' ' -f3)
	Topic3=$(echo "$line"   | cut -d' ' -f4-)
	
	# turn to upper
	Command=$(echo $Command | tr '[:lower:]' '[:upper:]')
	Topic1=$(echo $Topic1   | tr '[:lower:]' '[:upper:]')
	Topic2=$(echo $Topic2   | tr '[:lower:]' '[:upper:]')
	Topic3=$(echo $Topic3   | tr '[:lower:]' '[:upper:]')
	
	# parse and reset
	parseSYS $Command
	Command=""
	Topic1=""
	Topic2=""
	Topic3=""
}

# parseSYS
# process system commands - it's the real parser
function parseSYS() {
	# HELP - this is different from the original HELP command
	if [ "$1" = "HELP" ]; then
		HELP
		return
	# HELLO
	elif [ "$1" = "HELLO" ]; then
		HELLO
		return
	# LIST-- I must catch it here because it shares the
	# first three letters with LIST
	elif [ "${1:0:6}" = "LIST--" ]; then
		n=${1:6}
		if [ -n "$n" ]; then
			LIST $TRUE "$n"
		fi
		return
	fi
	
	# extract three-letter command
	com=${1:0:3}
	
	case $com in
		# ACCOUNT
		ACC )   ACCOUNT ;;	
		# APPEND
		APP )   APPEND $TRUE ${Topic1:0:$FILLEN} ;;
		# BYE
		BYE )	BYE ;;
		# EDIT
		EDI )	EDIT $TRUE ${Topic1:0:$FILLEN} ;;
		# GOODBYE
		GOO )	BYE ;;
		# EXIT (undocumented)
		EXI )   BYE ;;
		# EXPLAIN
		EXP )   HELP ;;
		# WHAT
		WHA ) 	WHAT ;;
		# RUN
		RUN )	RUN ;;
		# SYSTEM 
		SYS )	SYSTEM $TRUE ${Topic1:0:$FILLEN} ;;
		# OLD
		OLD )	OLD $TRUE ${Topic1:0:$FILLEN} ;;
		# NEW
		NEW )	NEW $TRUE ${Topic1:0:$FILLEN} ;;
		# SCRATCH
		SCR )	SCRATCH $TRUE ;;
		# RENAME
		REN )	RENAME $TRUE ${Topic1:0:$FILLEN} ;;
		# REPLACE
		REP )	REPLACE $TRUE ;;
		# SORT
		SOR )   SORT $TRUE ;;
		# LIST : LIST-- has always been intercepted
		LIS ) 	LIST $TRUE "1" ;;
		# LENGTH
		LEN ) 	LENGTH $TRUE ;;
		# CATALOG 
		CAT )	CATALOG $TRUE ;;
		# SAVE
		SAV )	SAVE $TRUE ;;
		# IGNORE
		IGN )	IGNORE $TRUE ;;
		# USERS
		USE )   USERS ;;
		# UNSAVE
		UNS ) 	UNSAVE $TRUE ${Topic1:0:$FILLEN} ;;
		# BUILD 
		BUI )	BUILD  $TRUE ;;
		# PUNCH
		PUN )	PUNCH  $TRUE ${Topic1:0:$FILLEN} ;;
		# EXTEND
		EXT )   EF="$EF -x" ;;
		# CREATE
		CRE )   CREATE $TRUE ${Topic1:0:$FILLEN} ;;
		# ENTER
		ENT )   ENTER $TRUE ${Topic1:0:$FILLEN} ;;
		# HOME
		HOM )   GOHOME $TRUE ;;
		# VIEW
		VIE )   VIEW $TRUE ${Topic1:0:$FILLEN} ;;
		# (void line) 
		"" )	true ;;
		# or else...
		* )	printf "WHAT?\n"
			printf "\n" ;;
	esac
}


# usage
# print usage and exit
function usage {
version
printf "Usage: deer <options>
OPTIONS:
  -h --help              Give this help and exit
  -l --lower-allowed     Disable conversion of input characters to upper case
  -t n                   Set auto shutoff to n seconds (n=0 disable shut-off)
  -v --version           Show version and exit
  -x --extended-features Enable extended BASIC features (pass -x to dib)

Option -x is passed to dib (if you use another interpreter, don't set it in 
deer). Default auto shutoff is set to 180 seconds. Now:
type HELLO to start a login session; or
type HELP in case you don't know how to proceed; or
type BYE to quit.

..It's GPL! Enjoy!
"
exit 0
}


# version
# print version and does not exit (since it's used into usage)
function version {
printf "deer - Dartmouth Environment Elementary Renderer, version %s\n"	"$RELEASE"
printf "written for bash by Antonio Maschio 2006-2008 <tbin at libero dot it>\n\n"
}


# DE
# Date Calculator
# return various date elements depending on argument
function DE() {
	case $1 in
		$YEAR )		echo $(date +"%Y" ) ;;
		$MONTH )    	mnth=$(date +%m )
				# reduce 0X number to X (jan.-sept.)
				mnth2=$(echo ${mnth:1:1})
				mnth1=$(echo ${mnth:0:1})
				if [ "$mnth1" = "0" ]; then
					mnth=$mnth2
				fi
				echo ${Mon[$mnth]} ;;
		$DAY )		echo $(date +"%d" ) ;;
		$HOUR )		echo $(date +"%H" ) ;;
		$MINUTE )	echo $(date +"%M" ) ;;
		# return seconds since 00:00:00 1970-01-01 UTC (The Epoch)
		$SECOND ) 	echo $(date +"%s" ) ;;
		# return actual seconds
		$ASEC )     	echo $(date +"%S" ) ;;
	esac
}


# deltaTime
# given six parameters (starting hour, minutes, seconds and ending hour,
# minutes, seconds) returns actual time difference in hour, minutes, seconds;
# uses gawk
function deltaTime() {
	echo $1 $2 $3 $4 $5 $6 | gawk ' {
		sH=int($1)
		sM=int($2)
		sS=int($3)
		eH=int($4)
		eM=int($5)
		eS=int($6)
		deltaS=eS-sS
		deltaM=eM-sM
		deltaH=eH-sH

		if (deltaS < 0) {
			deltaS += 60
			deltaM--
		}
		
		if (deltaM < 0) {
			deltaM += 60
			deltaH--
		}

		if (deltaH < 0) deltaH += 24

		printf("%.2d:%.2d:%.2d", deltaH, deltaM, deltaS)
	}' 
	return
}


# CRU (Computer Resource Usage)
# extract current CPU Average usage
function CRU() {
	# using iostat, retrieve idle percentage and subtract it from 100 
	# to get the computer job percentage (usage)
	data=$(echo $(iostat))
	data=${data##*' '}
	data=$(( (100 - data) ))
	echo "0.$data"
}

# IO (Input/Output - Memory transfer)
# extract I/O info (actually MB/s traffic) through iostat -d
function IO() {
	# using iostat -d, which reports as last datum 
	# the MB transfer for disk 0
	io_data=$(echo $(iostat -d))
	echo ${io_data##*' '}	
}

# listFrom
# list from the given line number (or from the first if no argument is given)
function listFrom() {

	sline=${1:-1} 	# starting line
	count=0
	line=1		# current line

	while [ $count -lt ${#prog[@]} ]; do
		if [ -n "${prog[$line]}" ]; then
			if [ $line -ge $sline ]; then
				printf "%s %s\n" "$line" "${prog[$line]}"
				let tc=DELAY
				while [ $tc -gt 0 ]; do
					let tc=tc-1
				done
			fi
			count=$(( $count + 1 ))
		fi
		line=$(( $line + 1 ))
	done
}


###########################################
# COMMAND CALLS (not directly accessible) #
###########################################

# READY
# print the classic "READY" prompt; sometimes I see it written in lower 
# letters... I'm faithful to the 1964 BASIC manual and prefer READY.
function READY {
	printf "READY.\n"
	printf "\n"
}


# USERNUMBERC
# user number getter command
# Ask for the user number (used inside HELLO) 
function USERNUMBERC {
	printf "%s" "$USERN"
	read -e UserNumber
	UserNumber=${UserNumber:0:6}
	UserNumber=$(echo $UserNumber | tr '[a-z]' '[A-Z]')
}


# NEWOLDC
# NEW or OLD file command
# Ask for a NEW or an OLD file, then calls NEW or OLD depending on the user 
# answer.  There's no else clause, so if the user didn't write neither
# NEW nor OLD, the file name won't be set
# $1 is the flag passed to NEW and OLD in the first position
# indicating whether to print READY or not
function NEWOLDC() {
	printf "%s" "$NEWOLD"
	read com
	NewOld=$(echo ${com:0:3} | tr '[:lower:]' '[:upper:]')

	# here I don't pass the last parameter to NEW/OLD, 
	# so that FileName is taken
	if [ "$NewOld" = "NEW" ]; then
		NEW $1
	elif [ "$NewOld" = "OLD" ]; then
		OLD $1
	fi
}


###################
# SYSTEM COMMANDS #
###################	  

# RUN
# execute current file (no parameters)
function RUN() {

	# check logging
	if [ $isLogged -eq $FALSE ]; then
		forgotten
		return
	fi

	# check if not in program state
	if [ "$isProgram" = "$FALSE" ]; then
		printf "NO PROGRAM.\n"
		echo
		return
	fi

	# at this point, it's logical to increment the RUNS var
	let RUNS=$RUNS+1

	# print timeline
	printf "$LS" ${UserNumber} ${FileName:-"NOFILE"}\
	$(DE $DAY) $(DE $MONTH) $(DE $YEAR) $(DE $HOUR) $(DE $MINUTE)
	printf "\n\n"

	# record starting time
	SSec=$(DE $SECOND)
	
	# executing 
	if [ "$isSaved" != "$TRUE" ]; then
		# Note: at page 32 of the 1964 manual, the example shows
		# that after typing a SCRATCH and some program lines input, 
		# a direct RUN is performed; 
		# so I assume an implicit SAVE is done here
		if [ -f "${CurrDir}${FileName}" ]; then
			REPLACE	$FALSE
	        else
			SAVE "" $FALSE
		fi	
	fi
	$basic $EF ${CurrDir}${FileName}
	printf "\n\n"

	# record starting time
	ESec=$(DE $SECOND)
	
	# calculate timing
	DSec=$((ESec-SSec))
	DMin=$((DSec/60))
	DSec=$((DSec-DMin*60))
	
	# timeline
	if [ $DMin -gt 0 ]; then
		printf "TIME:%4d MINS.%3d SECS.\n" $DMin $DSec
	else 
		printf "TIME:%4d SECS.\n" $DSec
	fi
	printf "\n\n"
}


# BYE
# exit from deer (called by BYE and GOODBYE)
function BYE {
	if [ "$isExitingAllowed" = "$FALSE" ] && [ "$isSaved" = "$FALSE" ]\
		&& [ "$isLogged" = "$TRUE" ]; then
		printf "\nPROGRAM NOT YET SAVED, USE SAVE OR REPLACE.\n\n"
		isExitingAllowed=$TRUE
		return
	fi	
	printf "%s CRU\n" $(CRU)
	printf "OFF AT %02s:%02s	%02s %s. %04s\n"\
		$(DE $HOUR) $(DE $MINUTE) $(DE $DAY) $(DE $MONTH) $(DE $YEAR)
	# free mem/files
	unset prog
        if [ -s $TempFile ]; then
                rm $TempFile
        fi

	# see you later!
	printf "\n\n"
	exit 0
}


# USERS
# report the (fake) number of currently logged users
function USERS {
	# check logging
	if [ $isLogged -eq $TRUE ]; then
		printf "%03s USERS.\n\n" $(echo $((RANDOM/1000)))
		READY
	else
		forgotten
	fi
}


# WHAT
# print info about user session
function WHAT {
	if [ "$CurrWhatDir" = "" ]; then
		CurrWhatDir="$UserNumber"
	fi
	printf "\nCURRENT FILE  %s\n" ${FileName:-".NONAME."}
	printf "COMPUTER      T1 (deer system)\n"
	printf "USER NUMBER   %s\n" ${UserNumber:-".NOLOGIN."}
	printf "SYSTEM        %s\n" ${SystemBasic:-"\$DATA"}
	printf "SEARCH PATH   :%s\n"  ${CurrWhatDir:-".NOPATH."}
	READY
}


# SYSTEM
# change system language (actually this is a fake operation, since only BASIC 
# is admitted), then call NEWOLDC to choose a new or an old file name 
function SYSTEM() {
	com=""
	# check logging
	if [ $isLogged -eq $TRUE ]; then
		until [ -n "$com" ]; do
			if [ $isHello -eq $TRUE ];then 
				printf "%s" "$SYSTM1"
			else 
				printf "%s" "$SYSTM2"
			fi
			read com
		done	
		SystemBasic=$(echo ${com:0:5} | tr '[:lower:]' '[:upper:]')
		if [ "$SystemBasic" != "BASIC" ]; then
			printf "\nBASIC IS THE ONLY AVAILABLE SYSTEM.\n"
			printf "REVERTING TO BASIC.\n"
			SystemBasic="BASIC"	
		fi
		# this call may not belong to the original DCTS system */
		NEWOLDC $1
	else
		forgotten
	fi
}

# RENAME
# change name of the current file; if name is not given, ask for it
function RENAME() {
	# check logging
	if [ $isLogged -eq $TRUE ]; then
		if [ -n "$2" ]; then
			FileName=$(echo ${2:0:$FILLEN} | \
				tr '[:lower:]' '[:upper:]')
		else
			printf "%s" "$RENPN"
			read com
			FileName=$(echo ${com:0:$FILLEN} | \
				tr '[:lower:]' '[:upper:]')
		fi
		# set saved status
		isSaved=$FALSE
		
		if [ $1 -eq $TRUE ]; then
			READY
		fi
	else 
		forgotten
		return
	fi
}

# NEW
# create new file; if name is not given, ask for it
function NEW() {
	# check logging
	if [ $isLogged -eq $TRUE ]; then
		if [ -n "$2" ]; then
			FileName=$(echo ${2:0:$FILLEN} | \
				tr '[:lower:]' '[:upper:]')
		else
			printf "%s" "$NEWPN"
			read com
			FileName=$(echo ${com:0:$FILLEN} | \
				tr '[:lower:]' '[:upper:]')
		fi
		# set to programming mode by default for NEW
		isProgram="$TRUE"
		# reset program memory
		unset "prog[*]"
		# set saved status
		isSaved=$FALSE
		if [ $1 -eq $TRUE ]; then
			READY
		fi
	else 
		forgotten
	fi
}

# IGNORE
# retrieve saved file, discarding any modification done since last saving
# Since we use OLD, isProgram is reset to the state of the saved file,
# indipendently of the state set before typing IGNORE
function IGNORE() {
	# check logging
	if [ $isLogged -eq $TRUE ];then
		OLD "$TRUE" "${CurrSDir}${FileName}"
		isSaved=$TRUE
	else
		forgotten
	fi
}


# OLD
# ask for a saved file name and assemble the correct destination;
# in the end, load the program into memory
# The "3" option comes from the EDIT command, which needs
# an untouched filename to work
function OLD() { 
	# check loggin
	if [ $isLogged -eq $TRUE ];then
		if [ "$1" = "3" ]; then
			FileN=$2
		elif [ -n "$2" ]; then
			FileN=$(echo ${2:0:$FILLEN} | \
				tr '[:lower:]' '[:upper:]')
			FileName=$FileN
			FileN=${CurrDir}${FileN}
		else
			printf "%s" "$OLDPN"
			read com
			FileN=$(echo ${com:0:$FILLEN} | \
				tr '[:lower:]' '[:upper:]')
			FileName=$FileN
			FileN=${CurrDir}${FileN}
		fi
	
		# check for file existence
		if ! [ -e "${FileN}" ]; then
			printf "NO SUCH PROGRAM--%${FILLEN}s\n" $FileN
			FileName=""
			READY
			return
		fi
	
		# load file in memory
		read line < "${FileN}"
		# get first item of first line
		ln=${line%%[!0-9]*}
		
		# if it's a program, its first character is a number
		if [ -n "$ln" ]; then
			unset "prog[*]"
			while read line; do
				processLine "$line"
			done < "${FileN}" 
			isProgram="$TRUE"
		
		# if it's a build file: load it into memory: 
		else
			cp  "${FileN}" $TempFile
			isProgram="$FALSE"
		fi
		
		# set save status
		isSaved=$TRUE
		
		if [ $1 -eq $TRUE ] || [ $1 -eq 3 ]; then
			READY
		fi

	else
		forgotten
	fi
}


# LIST
# wrapper for the LIST or LIST-- commands
function LIST {
	# check logging
	if [ $isLogged -eq $TRUE ];then
		OnFile="${CurrDir}${FileName}"
		File="$FileName"
		
		# print timeline
		printf "$LS" ${UserNumber} ${File:-"NOFILE"}\
			$(DE $DAY) $(DE $MONTH) $(DE $YEAR)\
			$(DE $HOUR) $(DE $MINUTE)
		
		# the user means: list a program
		if [ "$isProgram" = "$TRUE" ]; then
			# print lines in order
			listFrom "${2:-1}"
		# the user means: list a BUILD file
		else
			while read line; do
				echo "$line"
				let tc=DELAY
				# delay cycle
				while [ $tc -gt 0 ]; do
					let tc=tc-1
				done
			done < $TempFile
		fi
		printf "\n"
		if [ $1 -eq $TRUE ]; then
			READY
		fi
	else
		forgotten
	fi
}


# EDIT 
# edit current or arg file with current editor
function EDIT() {
	# check logging
	if [ $isLogged -eq $TRUE ];then
		if [ -n "$2" ]; then
			com=${2:0:$FILLEN}
			com=$(echo ${com} | tr '[a-z]' '[A-Z]')
			$deered "${CurrDir}${com}"
		# edit current file
		# a BUILD file is edited in the temporary storage area
		# so a REPLACE is needed in order to save the file
		# a PROGRAM is copied back to the temporary storage area,
		# edited, and reloaded, without affecting the original file,
		# so, again, a REPLACE is needed in order to save the file
		else
			if [ $isProgram -eq $FALSE ]; then
				# edit build file
				$deered "${TempFile}"
			else 
				# edit program
				printf "" > $TempFile
				listFrom 1 >> $TempFile
				$deered "${TempFile}"
				# reload file
				OLD "3" "$TempFile"
			fi
		fi
		# set state
		isSaved=$FALSE

		if [ $1 -eq $TRUE ]; then 
			READY
		fi
	else
		forgotten
	fi	
}


# VIEW 
# view current or arg file with current viewer
function VIEW() {
	# check logging - isSaved is retained
	if [ $isLogged -eq $TRUE ];then
		# view arg file
		if [ -n "$2" ]; then
			com=${2:0:$FILLEN}
			com=$(echo ${com} | tr '[a-z]' '[A-Z]')
			$deerless "${CurrDir}${com}"
		# view current file
		else
			if [ $isProgram -eq $FALSE ]; then
				# view build file
				$deerless "${TempFile}"
			else 
				# view program
				$deerless "${CurrDir}${FileName}"
			fi
		fi
		if [ $1 -eq $TRUE ]; then 
			READY
		fi
	else
		forgotten
	fi
}


# SORT 
# sort current or arg file; the file is not saved after sorting, so that
# the user may retrieve the preceding version
function SORT() {
	# check logging
	if [ $isLogged -eq $TRUE ];then
		isSaved=$FALSE
		if [ $isProgram -eq $FALSE ]; then
			# sort build file; a program is always sorted
			sort "${TempFile}" -o "${TempFile}"
		fi
		
		if [ $1 -eq $TRUE ]; then 
			READY
		fi
	else
		forgotten
	fi
	
}

# SCRATCH
# empty current file
function SCRATCH() {
	# check logging
	if [ $isLogged -eq $TRUE ]; then
		if [ -s $TempFile ]; then 
			rm $TempFile
			touch $TempFile
		fi

		# reset program memory
		unset prog
		declare -a prog

		# set save status
		# isProgram remains unchanged
		isSaved=$FALSE

		if [ $1 -eq $TRUE ]; then
			READY
		fi
	else
		forgotten
	fi
}


# ACCOUNT
# give info about account session
# Note: original data were CRU, SECS, I/O, CORE, written on one line.
# I don't know exactly what's the meaning of these data, but I cannot replicate
# them in bash on a UNIX machine. So I changed them to reflect more practical
# needs for the deer user, and they will be reported in two lines, the first
# with headers, the second with values. Data will be:
# - CRU (here Computer Resource Usage): it uses iostat for retrieving idle 
#   percentage of the system, subtracting it from 100 and yielding the global 
#   system resources usage.
# - I/O (not knowing the original meaning of that): it expresses, through 
#   iostat and in terms of MB/s, the amount of exchanged memory.
# - LOGTIME: it expresses the total account session time.
# - RUNS: it expresses the total of RUNs (assuming this has some meaning 
#   for moderns systems)
# SECS and CORE have been 'forgotten', since I have no idea how to 
# parallel them with those on my own UNIX machine.
function ACCOUNT {
	# check logging
	if [ $isLogged -eq $TRUE ]; then
		# record time of this instant and make 
		# ATIME (Account Logging Time)
		NowHour=$(DE $HOUR)
		NowMin=$(DE $MINUTE)
		NowSec=$(DE $ASEC)
		ATIME=$( deltaTime $StartingHour $StartingMin $StartingSec $NowHour $NowMin $NowSec )
		printf "CRU\tI/O\tLOGTIME\t\tRUNS\n"
		printf "%s\t%s\t%s\t%s\n" $(CRU) $(IO) ${ATIME} ${RUNS}
		READY
	else
		forgotten
	fi
}

# HELLO
# setup initial user parameters
# Note: I don't know if the original DCTS 1964 system checked user logging 
# after any command input, but I like thinking so. So in the following, a
# variable isLogged is set to TRUE if logging was successful, in order for
# subsequent commands to check logged state
function HELLO {
	# isHello is checked into SYSTEM, because different questions
	# are asked if inside or outside HELLO
	isHello=$TRUE 
	# reset remaining parameters
	isLogged=$FALSE
	FileName=""
	UserNumber=""

	# print CRU usage
	printf "%s CRU\n\n" $(CRU)
		
	# ask for User Number */
	USERNUMBERC
	CurrDir="${WD}${UserNumber}/"
	BaseDir="$CurrDir"
	CurrWhatDir="${UserNumber}/"

	# if UserNumber is still void, no action is taken, otherwise...
	if [ "${WD}/" != "${Currdir}" ]; then
	
		# set logged state flag
		isLogged=$TRUE
		
		# call SYSTEM, which in turn calls NEW/OLD 
		# pass FALSE because it must not print READY.
		SYSTEM $FALSE
	
		# Make User Directory if not present
		mkdir -p "$CurrDir"
	fi

	# finally...
	isHello=$FALSE
	READY
}


# CATALOG
# list files under current user directory (no parameter) or under 
# another user directory (giving her/his user number);
function CATALOG() {
	# check logging
	if [ $isLogged -eq $TRUE ]; then
		printf "SAVED PROGRAMS, USER NUMBER %6s\n" "$UserNumber"
		printf "%02s %s %4s, AT %02s:%02s\n" $(DE $DAY) $(DE $MONTH)\
			$(DE $YEAR) $(DE $HOUR) $(DE $MINUTE)
		printf "\n"
		ls -p "$CurrDir" | xargs printf "%-16s" && printf "\n"
		if [ $1 -eq $TRUE ]; then
			READY
		fi
	else
		forgotten
	fi
}

# APPEND
# append arg file to the end of current file; if it's a program, 
# lines must be subsequent, or they will be overwritten
function APPEND {
	# check logging
	if [ $isLogged -eq $TRUE ]; then
		if [ "$2" = "" ]; then
			printf "%s" "$OLDPN"
			read filen
			filen=${filen:0:$FILLEN}
			filen=$(echo $filen | tr '[a-z]' '[A-Z]')
		else
			filen="$2"
		fi

            	# it's a program
		if [ "$isProgram" = "$TRUE" ]; then
			# load file in memory
			read line < "${CurrDir}${filen}"
			# get first item of first line
			ln=${line%%[!0-9]*}
			# a program has line numbers
			if [ -n "$ln" ]; then
				while read line; do
					processLine "$line"
				done < "${CurrDir}${filen}"
			else
				# in case a build file is appended to a program
				printf "NOTHING APPENDED.\n"
               		fi
		# it's a build file 
		else
			cat "${CurrDir}${filen}" >> $TempFile
		fi
	
		# set save status
		isSaved=$FALSE
	
		if [ $1 -eq $TRUE ]; then
			READY
		fi
	else
		forgotten
	fi
}

# HELP
# print the HELP screen (not standard)
function HELP {
	printf "HELP ABOUT deer COMMANDS - ARG IS THE OPTIONAL COMMAND ARGUMENT
  ACCOUNT       GIVE SOME ACCOUNTING INFORMATION
  APPEND ARG    ADD ARG FILE TO END OF CURRENT FILE
  BUILD         ALLOW ENTERING FREE TEXT ON CURRENT FILE
  BYE/GOODBYE   TERMINATE CURRENT SESSION
  CATALOG       GIVE INFORMATION ABOUT USER LOCAL SAVED FILES
  CREATE ARG    CREATE NEW CATALOG UNDER CURRENT
  EDIT          EDIT CURRENT SAVED FILE WITH USER EDITOR
  ENTER ARG     CHANGES CURRENT CATALOG TO ARG CATALOG
  EXPLAIN       PRINT THIS HELP SCREEN (IT MAY BE EXPANDED IN FUTURE)
  HELLO         START A SEQUENCE OF QUESTIONS FOR LOGGING IN
  HELP          PRINT THIS HELP SCREEN 
  HOME          SHORTCUT FOR ENTER *MYCAT; RE-ENTERS MAIN CATALOG
  IGNORE        RETRIEVE SAVED FILE LOSING ALL CHANGES MADE
  LENGTH        GIVE LENGTH OF CURRENT FILE
  LIST          LIST CURRENT FILE
  LIST--XXXXX   LIST CURRENT BASIC FILE STARTING FROM LINE XXXXX
  NEW ARG       CREATE NEW EMPTY FILE AND SET IT CURRENT
  OLD ARG       RETRIEVE AN EXISTING ARG FILE AND SET IT CURRENT
  PUNCH ARG     SEND CURRENT OR ARG FILE TO PRINTER
  RENAME ARG    CHANGE NAME OF CURRENT OR ARG FILE
  REPLACE       SAVE CURRENT FILE REPLACING PREVIOUS CONTENTS
  RUN           EXECUTE CURRENT FILE
  SAVE          STORE CURRENT FILE TO DISK IF FILE DOES NOT EXIST
  (HIT RETURN)"
	read

	printf "  SCRATCH       EMPTY CURRENT FILE
  SORT          SORT CONTENTS OF CURRENT FILE
  SYSTEM ARG    CHANGE SYSTEM LANGUAGE (FAKE)
  UNSAVE ARG    DELETE CURRENT OR ARG FILE FROM DISK
  USERS         GIVE NUMBER OF CURRENTLY LOGGED USERS (FAKE)
  VIEW ARG      SHOW CURRENT OR ARG FILE CONTENT WITH USER VIEWER
  WHAT          GIVE INFORMATION ABOUT CURRENT SESSION

REMEMBER: TYPING IS NO SUBSTITUTE FOR THINKING.
"

}


# BUILD
# accept non numbered lines for text files
# useful for writing help files and documents "on the fly"
# it doesn't use parameters; acts on current file only,
# scratching its content and turning it into BUILD file if
# it was a program, before.
function BUILD() {
	ti=0
	# Check logging
	if [ $isLogged -eq $TRUE ]; then
		if [ $isProgram -eq $TRUE ]; then
			SCRATCH $FALSE
			isProgram=$FALSE
		fi
		isSaved=$FALSE

		rm -f $TempFile
		cp "${CurrDir}${FileName}" $TempFile

		
		printf "(PRESS ENTER THREE TIMES TO END)\n\n"
		read line
		while [ "$line" != "" ] || [ $ti -lt 2 ]; do
			line=$(echo $line | tr '[a-z]' '[A-Z]')
			if [ "$line" = "" ]; then
				let ti=ti+1
			fi
			if [ $ti -le 1 ]; then
				echo "$line" >> "$TempFile"
			fi
		      	read line
	        done
	        
		if [ $1 -eq $TRUE ]; then
			READY
		fi
	else
		forgotten
	fi
}

# PUNCH
# send current or arg file to printer
function PUNCH {
	# check logging
	if [ $isLogged -eq $TRUE ]; then
		# set complete file name
		if [ -n "$2" ]; then
			filen="$2"
			filen=${filen:0:$FILLEN}
			filen=$(echo $file | tr '[:lower:]' '[:upper:]')
			OnFile="${CurrDir}${filen}"
		else  
			OnFile="${CurrDir}${FileName}"
		fi
		
		cat $OnFile | lpr
		if [ $1 -eq $TRUE ]; then
			READY
		fi
	else
		forgotten
	fi
}


# CREATE
# create a catalog under current catalog
function CREATE {
	# check logging
	if [ $isLogged -eq $TRUE ]; then
		if [ "$2" = "" ]; then
			printf "%s" "$CATNM"
			read line
		else
			line="$2"
		fi
		line=${line:0:$FILLEN}
		line=$(echo $line | tr '[a-z]' '[A-Z]')
		mkdir -p "${CurrDir}${line}"
		if [ $1 -eq $TRUE ]; then
			READY
		fi
	else
		forgotten
	fi
}

# ENTER
# enter a catalog
function ENTER {
	# check logging
	if [ $isLogged -eq $TRUE ]; then
		if [ "$2" = "" ]; then
			printf "%s" "$CATNM"
			read line
		else
			line="$2"
		fi
		line=${line:0:$FILLEN}
		line=$(echo $line | tr '[a-z]' '[A-Z]')
		if [ "$line" = "*MYCAT" ] || [ "$line" = "MYCAT" ]; then
			CurrDir="$BaseDir"
			CurrWhatDir=$UserNumber
		else
			CurrDir="${CurrDir}${line}/"
			CurrWhatDir="${UserNumber}/${line}/"
		fi
		cd "$CurrDir"
		if [ $1 -eq $TRUE ]; then
			READY
		fi
	else
		forgotten
	fi
}


# HOME
# get back to home catalog
function GOHOME {
	# check logging
	if [ $isLogged -eq $TRUE ]; then
		CurrDir="$BaseDir"
		CurrWhatDir="$UserNumber"
		cd "$CurrDir"
		if [ $1 -eq $TRUE ]; then
			READY
		fi
	else
		forgotten
	fi
}

# LENGTH
# give file length in bytes
function LENGTH() {
	# check logging
	if [ $isLogged -eq $TRUE ]; then
		# set complete file name
		OnFile="${CurrDir}${FileName}"
	
		Len=$(wc -c "$OnFile")
		Len=$(echo $Len | cut -d' ' -f1)
		printf "%s BYTES\n" "$Len"
		if [ $1 -eq $TRUE ]; then
			READY
		fi
	else
		forgotten
	fi
}


# SAVE
# save data permanently on file, provided there's not a previously saved
# file with the same name; in case of overwriting, use REPLACE
function SAVE() {
	# check logging
	if [ $isLogged -eq $TRUE ]; then
		# set complete file name
		OnFile="${CurrDir}${FileName}"
		
		if [ -e $OnFile ]; then
			printf "FILE EXISTING, NOT SAVED.\n"
			printf "USE REPLACE TO OVERWRITE.\n"
			return
		fi

		if [ $isProgram -eq $TRUE ]; then
			# save lines in order (after creating empty file)
			printf "" > $OnFile
			listFrom 1 >> $OnFile
		else
			rm -f $OnFile
			cp $TempFile $OnFile
		fi

		# set saved status
		isSaved=$TRUE

		if [ "$1" -eq "$TRUE" ]; then
			READY
		fi
	else
		forgotten
	fi
}


# UNSAVE
# delete current or arg file from disk
# Note: file is only deleted on disk; current file name 
# is retained for a subsequent SAVE command
function UNSAVE() {
	# check logging
	if [ $isLogged -eq $TRUE ]; then
		if [ -n "$2" ]; then
			filen=${2:0:$FILLEN}
			filen=$(echo $filen | tr '[:lower:]' '[:upper:]')
			# it's a file
			if [ -f "${CurrDir}${filen}" ]; then
				rm -f "${CurrDir}${filen}"
			# it's a directory
			elif [ -d "${CurrDir}${filen}" ]; then
				rm -fr "${CurrDir}${filen}"
			fi
		else
				rm -f "${CurrDir}${FileName}"
		fi
		isSaved=$FALSE
		
		if [ $1 -eq $TRUE ]; then 
			READY
		fi
	else
		forgotten
	fi
}

# REPLACE
# replace the content of current file overwriting previous version; to be 
# used in place of SAVE if file exists.
function REPLACE() {
	# check logging
	if [ $isLogged -eq $TRUE ]; then
		# determine file name and reset it
		OnFile="${CurrDir}${FileName}"
		printf "" > $OnFile

		# replace
		if [ $isProgram -eq $TRUE ]; then
			# save lines in order
			listFrom 1 >> $OnFile
		else
			rm -f $OnFile
			cp $TempFile $OnFile
		fi

		# set saved status and exit
		isSaved=$TRUE
		if [ $1 -eq $TRUE ]; then
			READY
		fi
	else
		forgotten
	fi
}




########
# MAIN # 
########

# trap signals
trap cleanup HUP INT PIPE QUIT TERM EXIT

# record actual starting time
StartingHour=$(DE $HOUR)
StartingMin=$(DE $MINUTE)
StartingSec=$(DE $ASEC)

# parse double dash options
if [ "X$1" = "X--help" ]; then
	usage
elif [ "X$1" = "X--version" ]; then
	version
	exit 0
elif [ "X$1" = "X--extended-features" ]; then
	EF="$EF -x"
fi

# parse single dash options
while getopts ":hvxt:" opt; do
	case $opt in
		h  ) usage ;;
		v  ) version && exit 0 ;;
		t  ) INTERVAL=$OPTARG
		     if [ $INTERVAL -eq 0 ]; then
			     noTiming=$TRUE
		     fi ;;
		x  ) EF="$EF -x" ;;
	esac
done

# create System Directory if not present */
mkdir -p $WD

# start interactive session
# interactiveProcess is charged to close script
interactiveProcess

# end main
