// IfsTelnet.bcpl -- top level command interpreter and useful subroutines
// Copyright Xerox Corporation 1979, 1980, 1982
// Last modified April 9, 1982  5:14 PM by Taft

get "IfsRs.decl"
get "IfsTelnet.decl"
get "CmdScan.decl"
get "IfsFiles.decl"

external
[
// outgoing procedures
TelnetTopLevel
GetKeywordOrCR; Plural; TelnetAborting; TelnetCommandLoop; TelnetCommandQuit
TelnetSubcommandPrompt; SelectKeyword; CreateKeywordIntegerMap; TelnetConfirm

// incoming procedures
InitTelnetJob; CleanupTelnetJob
InitCmd; GetKeyword; GetPhrase; TerminatingChar; Confirm
CreateKeywordTable; DestroyKeywordTable; InsertKeyword
EnableCatch; DisableCatch; EndCatch
DefaultArgs; ReturnFrom; IFSError; CallWithArgVec
Puts; Endofs; Closes; Resets; Errors; Wss; Ws

// incoming statics
CtxRunning; keys; dsp
atSubPrompt; bangSubPrompt; pluralString; singularString
]

manifest ecReturnFromReturned = 103


//---------------------------------------------------------------------------
let TelnetTopLevel(ctx) be
//---------------------------------------------------------------------------
// Top-level procedure for telnet server context.
[
InitTelnetJob(ctx)

   [ //repeat
   TelnetCommandLoop(ctx>>TCtx.topKT,
    (ctx>>TCtx.userInfo>>UserInfo.capabilities ne 0? "*n!", "*n@"))
   if ctx>>TCtx.kill break
   if ctx>>TCtx.controlC then
      [
      Resets(dsp)
      Resets(keys)
      ctx>>TCtx.controlC = false
      Ws("↑C")
      ]
   ] repeat

CleanupTelnetJob(ctx)
]

//---------------------------------------------------------------------------
and GetKeywordOrCR(cs, kt) = valof
//---------------------------------------------------------------------------
// Returns pointer to keyword table entry, or zero if just a carriage
//  return is typed in.
[
let entry = GetKeyword(cs, kt, true)
if entry eq 0 then
   [
   Resets(cs)
   unless GetPhrase(cs) eq 0 & TerminatingChar(cs) eq $*n do
      Errors(cs, ecKeyNotFound)
   ]
resultis entry
]

//---------------------------------------------------------------------------
and TelnetCommandLoop(kt, prompt, returnOnCR, arg, numChars, numPhrases,
    FixedProc; numargs na) be
//---------------------------------------------------------------------------
// Accepts a keyword table kt whose entries are command procedures.
// Repeatedly inputs a command keyword and calls Proc(cs, arg), where
//  Proc is the corresponding command procedure.  If returnOnCR is true,
//  returns if just a carriage return is typed in response to the prompt.
// If FixedProc is supplied, then instead of calling Proc(cs, arg) it
//  calls FixedProc(cs, entry, arg).
[
DefaultArgs(lv na, -2, false, 0, maxCmdChars, maxCmdPhrases, 0)
   [ //repeat
   let cs = InitCmd(numChars, numPhrases)
   if cs ne 0 then
      [
      Wss(cs, prompt)
      let entry = GetKeywordOrCR(cs, kt)
      if entry ne 0 then
         test FixedProc eq 0
            ifso (entry!0)(cs, arg)
            ifnot FixedProc(cs, entry, arg)
      Closes(cs)
      if entry eq 0 & returnOnCR break
      ]
   ] repeatuntil TelnetAborting()
]

//---------------------------------------------------------------------------
and TelnetCommandQuit(cs, nil) be
//---------------------------------------------------------------------------
// This command procedure quits out of the innermost TelnetCommandLoop
[
Closes(cs)
ReturnFrom(TelnetCommandLoop)
IFSError(ecReturnFromReturned)
]

//---------------------------------------------------------------------------
and TelnetSubcommandPrompt() =
//---------------------------------------------------------------------------
 CtxRunning>>TCtx.userInfo>>UserInfo.capabilities ne 0?
  bangSubPrompt, atSubPrompt;

//---------------------------------------------------------------------------
and Plural(number) = number eq 1? singularString, pluralString; 
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
and TelnetAborting() = CtxRunning>>TCtx.aborting ne 0
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
and SelectKeyword(cs, key1, nil, nil, nil, nil, nil, nil, nil, nil, nil,
    nil, nil, nil, nil, nil; numargs na) = valof
//---------------------------------------------------------------------------
// Takes a list of up to 15 keywords, calls GetKeyword with that list,
// and returns the index (1-15) of the one matching the word typed in.
// Keyword arguments may be omitted by supplying zero in some argument
// positions; this permits keywords to be included conditionally.
[
Puts(cs, $*s)
let kt = nil
if EnableCatch(cs) then [ DestroyKeywordTable(kt); EndCatch(cs) ]
kt = CallWithArgVec(CreateKeywordIntegerMap, lv key1, na-1)
let which = GetKeyword(cs, kt)!0
DestroyKeywordTable(kt)
DisableCatch(cs)
resultis which
]

//---------------------------------------------------------------------------
and CreateKeywordIntegerMap(key1, nil, nil, nil, nil, nil, nil, nil, nil,
    nil, nil, nil, nil, nil, nil; numargs na) = valof
//---------------------------------------------------------------------------
// Takes a list of up to 15 keywords and returns a keyword table
// containing those keywords, each of whose entries is the index (1-15)
// of the corresponding keyword in the argument list.
// Keyword arguments may be omitted by supplying zero in some argument
// positions; this permits keywords to be included conditionally.
[
let kt = CreateKeywordTable(na)
for i = 1 to na do
   [
   let keyword = (lv key1)!(i-1)
   if keyword ne 0 then InsertKeyword(kt, keyword)!0 = i
   ]
resultis kt
]

//---------------------------------------------------------------------------
and TelnetConfirm(string; numargs na) = valof
//---------------------------------------------------------------------------
// Requests an isolated confirmation, i.e., one not in the context of
// an existing command line.
[
let result = false
let ccs = InitCmd(50, 1)
if ccs ne 0 then
   [
   result = Confirm(ccs, na gr 0 & string)
   Closes(ccs)
   ]
resultis result
]