// IfsScavDEdit.bcpl -- a simple disk editor
// Copyright Xerox Corporation 1979
// Last modified December 22, 1979  3:14 PM by Boggs

get "AltoFileSys.d"
get "Streams.d"
get "Disks.d"
get "Tfs.d"

external
[
// outgoing procedures
DiskEditor

// incoming procedures
OpenDisk; CloseDisk; VirtualDiskDA
InitializeDiskCBZ; GetDiskCb; DoDiskCommand
Allocate; Free; Resets; Puts; Gets; Closes
Ws; PutTemplate; EraseBits; CharWidth
ShowDisplayStream; CreateDisplayStream
ScavConfirm; UnsnarfBuffer
PrintDiskError; SysErr

// incoming statics
dsp; keys; sysZone; sysFont; pass
]

static
[
editDisk; label; data; number; address
numberTyped; pageOpen; cellOpen; pageDirty
]

//----------------------------------------------------------------------------
let DiskEditor(buffPtr, numBuffs) be
//----------------------------------------------------------------------------
[
pass = 0
editDisk = OpenDisk("*NWhat disk would you like to edit? ", 0, true, false)
if editDisk eq 0 then
   [
   for i = 0 to numBuffs-1 do UnsnarfBuffer(buffPtr+i*256)
   return
   ]

// replace the normal tiny display with a giant display
let normalDsp = dsp>>ST.par1
ShowDisplayStream(normalDsp, DSdelete)
dsp>>ST.par1 = CreateDisplayStream(800/((sysFont!-2) & -2),
 buffPtr, 256*numBuffs)
ShowDisplayStream(dsp>>ST.par1, DSalone)

let lenData = 1 lshift editDisk>>DSK.lnPageSize
label = Allocate(sysZone, 10)
data = Allocate(sysZone, lenData)
pageOpen = false
address, number = 0, 0
Puts(dsp, $*N)

switchon GetNumber() into
   [
   case $/:
      [
      address = number
      ExaminePage(not numberTyped)
      endcase
      ]
   case $*L:
      [
      test numberTyped
         ifso Oop()
         ifnot
            [
            address = address +1
            ExaminePage(true)
            ]
      endcase
      ]
   case $↑:
      [
      test numberTyped
         ifso Oop()
         ifnot
            [
            address = address -1
            ExaminePage(true)
            ]
      endcase
      ]
   case $\:
      [
      test numberTyped % not pageOpen
         ifso Oop()
         ifnot
            [
            address = VirtualDiskDA(editDisk, lv label>>DL.previous)
            ExaminePage(true)
            ]
      endcase
      ]

// DiskEditor (cont'd)

   case $L: case $l:
      [
      test numberTyped % not pageOpen
         ifso Oop()
         ifnot EditPage(label, 10)
      endcase
      ]
   case $D: case $d:
      [
      test numberTyped % not pageOpen
         ifso Oop()
         ifnot EditPage(data, lenData)
      endcase
      ]
   case $Q: case $q:
      [
      if ScavConfirm("*NQuit Disk editor") break
      endcase
      ]
   case $*N:
      [
      Puts(dsp, $*N)
      pageOpen = false
      endcase
      ]
   case $*177: [ Ws(" XXX"); endcase ]
   default: [ Oop(); endcase ]
   ] repeat

Free(sysZone, label)
Free(sysZone, data)
CloseDisk(editDisk)

Closes(dsp>>ST.par1)
dsp>>ST.par1 = normalDsp
ShowDisplayStream(dsp>>ST.par1, DSalone)
Puts(dsp, $*N)
for i = 0 to numBuffs-1 do UnsnarfBuffer(buffPtr+i*256)
]

//----------------------------------------------------------------------------
and GetNumber() = valof  //returns terminating character
//----------------------------------------------------------------------------
[
manifest maxStringChars = 7  //6 octal digits + minus sign
let string = vec maxStringChars  //1 char per word
let count = 0  //chars in string
let char = nil
   [
   char = Gets(keys)
   switchon char into
      [
      case $0 to $7:
      case $-:
         [
         if (char eq $-) ? (count eq 0), (count ls maxStringChars) then
            [
            Puts(dsp, char)
            count = count +1
            string!count = char
            ]
         endcase
         ]
      case $*001: case $*010:  //↑A, BS
         [
         if count ne 0 then
            [
            EraseBits(dsp>>ST.par1, -CharWidth(dsp>>ST.par1, string!count))
            count = count -1
            ]
         endcase
         ]
      case $*027:  //↑W
         [
         for i = count to 1 by -1 do
            EraseBits(dsp>>ST.par1, -CharWidth(dsp>>ST.par1, string!i))
         count = 0
         endcase
         ]
      case $*177: count = 0  //falls through
      default: break
      ]
   ] repeat
numberTyped = count ne 0
if numberTyped then
   [
   number = 0
   let minus = false
   if string!1 eq $- then minus = true
   for i = minus? 2,1 to count do
      number = number lshift 3 + (string!i & 7)
   if minus then number = -number
   ]
resultis char
]

//----------------------------------------------------------------------------
and Oop() be [ Ws(" ?"); Resets(keys) ]
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and ExaminePage(displayAddress) be
//----------------------------------------------------------------------------
[
if displayAddress then PutTemplate(dsp, "*N$UO", address)
Ws("/*T")
if DETransferPage(DCreadLD) then
   [
   PutTemplate(dsp, "FID $EUO;$UO, page number $UO, num chars $UO ",
    lv label>>DL.fileId, label!2, label>>DL.pageNumber, label>>DL.numChars)
   let previous = VirtualDiskDA(editDisk, lv label>>DL.previous)
   let next = VirtualDiskDA(editDisk, lv label>>DL.next)
   PutTemplate(dsp, "previous vda $UO, next vda $UO", previous, next)
   number = next
   pageOpen = true
   ]
]

//----------------------------------------------------------------------------
and DETransferPage(action) = valof
//----------------------------------------------------------------------------
[
if address eq eofDA then
   [
   Oop()
   pageOpen = false
   resultis false
   ]
let cbz = Allocate(sysZone, CBzoneLength)
InitializeDiskCBZ(editDisk, cbz, 0, CBzoneLength,
 TransferRetry, lv TransferError)
TransferRetry:
let cb = GetDiskCb(editDisk, cbz)
cb>>CB.AddrL = label
DoDiskCommand(editDisk, cb, data, address, lv label>>DL.fileId,
 label>>DL.pageNumber, action)
while @cbz>>CBZ.queueHead ne 0 do GetDiskCb(editDisk, cbz)
Free(sysZone, cbz)
pageOpen = action eq DCreadLD
resultis true
]

//----------------------------------------------------------------------------
and TransferError(nil, cb, errorCode) be
//----------------------------------------------------------------------------
   test errorCode eq ecUnRecovDiskError
      ifso PrintDiskError(cb)
      ifnot SysErr(0, errorCode, cb)

//----------------------------------------------------------------------------
and EditPage(buffer, length) be
//----------------------------------------------------------------------------
[
PutTemplate(dsp, "*NEdit $S*N", buffer eq label? "label","data")
let diskAddress = address; address = 0
let diskNumber = number; number = 0
pageDirty = false
cellOpen = false
switchon GetNumber() into
   [
   case $/:
      [
      address = number
      ExamineCell(buffer, length, not numberTyped)
      endcase
      ]
   case $*N:
      [
      DepositCell(buffer)
      Puts(dsp, $*N)
      endcase
      ]
   case $*L:
      [
      if DepositCell(buffer) then
         [
         address = address +1
         ExamineCell(buffer, length, true)
         ]
      endcase
      ]
   case $↑:
      [
      if DepositCell(buffer) then
         [
         address = address -1
         ExamineCell(buffer, length, true)
         ]
      endcase
      ]
   case $Q: case $q:
      [
      if ScavConfirm("*NQuit page editor") break
      endcase
      ]
   case $*177: [ Ws(" XXX"); endcase ]
   default: [ Oop(); endcase ]
   ] repeat

number = diskNumber
address = diskAddress
if pageDirty then
   if ScavConfirm("*NRewrite page?") then
      DETransferPage(DCwriteLD)
Puts(dsp, $*N)
]

//----------------------------------------------------------------------------
and ExamineCell(buffer, length, printAddress) = valof
//----------------------------------------------------------------------------
[
if printAddress then PutTemplate(dsp, "*N$UO", address)
if buffer eq label then
   Ws(selecton address into
      [
      case 0: " = SN1          "
      case 1: " = SN2          "
      case 2: " = VN           "
      case 3: " = PackID       "
      case 4: " = numChars     "
      case 5: " = PN           "
      case 6: " = Prev cyl     "
      case 7: " = Prev hd, sec "
      case 8: " = Next cyl     "
      case 9: " = Next hd, sec "
      default: ""
      ])

Ws("/*T")
if address ge length % address ls 0 then
   [
   Oop()
   cellOpen = false
   resultis false
   ]

PutTemplate(dsp, "$6UO*T", buffer!address)
if buffer eq data then  //also display as characters
   [
   let left = buffer!address rshift 8
   let right = buffer!address & 377b
   PutTemplate(dsp, "$3UO $3UO*T", left, right)
   PrintChar(left)
   PrintChar(right)
   ]

number = buffer!address
cellOpen = true
resultis true
]

//----------------------------------------------------------------------------
and PrintChar(char) be
//----------------------------------------------------------------------------
[
if char ls 40b then PutTemplate(dsp, "↑$C", char+100b)
if char ge 40b & char ls 176b then Puts(dsp, char)
]

//----------------------------------------------------------------------------
and DepositCell(buffer) = valof
//----------------------------------------------------------------------------
//Does nothing if no number was typed
[
if numberTyped test cellOpen
   ifso
      [
      buffer!address = number
      pageDirty = true
      ]
   ifnot
      [
      Oop()
      resultis false
      ]
cellOpen = false
resultis true
]