;======================================
; ls_main (list directory, main module)
;======================================
;must use "own" because this program
;allocates memory manually
program ls_main own

include library
include prosys
include c64
include 1:diskutils.e

con bufsize=254
con dirlen=32

;dirent "structure" offsets (32 bytes)
con dtype=0
con dtrack=1
con dsector=2
con dname=3      ;byte[16]
con dsstrack=19
con dsssector=20
con drecsize=21
con dreserved=22
con dyear=23
con dmonth=24
con dday=25
con dhour=26
con dminute=27
con dsize=28     ;word
con dnext=30     ;word (unused)

data word types[]="DEL","SEQ","PRG","USR","REL","CBM","DIR"

word bufptr   ;pointer
word firstdir ;pointer
word nextdir  ;pointer
word index    ;pointer
word type     ;pointer to byte[]
word dircount
word blockcount
word element
word pattern
word err
word i
word j

byte dev
byte drive
byte parttype
byte lfn
byte column
byte row
byte first    ;boolean: first dir entry
byte sortname ;boolean: sort by name
byte sortdate ;boolean: sort by date
byte buffer[bufsize]
byte cmdbuf[64]
byte dirname[254]
byte diskname[17]
byte month[3]
byte day[3]
byte year[3]
byte hour[3]
byte minute[3]

;--------------------------------------
; Display usage message.
;--------------------------------------
proc usage

begin

output"#cUsage:#c#s [device] [[path:]pattern] [-n] [-t]",carg[0]
output"#c-n: sort by name"
output"#c-t: sort by date"
output"#c(only one sort option allowed)"
output"#cdevice is 0:, 1:, or a device number"
output"#c(default is 0:, number must be 8 to 30)#c"

end

;--------------------------------------
; Parse command-line arguments.
; return: true if arguments are valid,
;         false otherwise
;--------------------------------------
func byte parse
word i
word j
word colon
byte pathfound ;boolean
byte b

begin

dirname[0]='$'
dirname[1]=0
pattern=0
sortname=false
sortdate=false

if ncarg=0
  dev=c64ddv0 ;use 0:
  return true

dev=0
pathfound=false
for i=1 to ncarg ;0 is program name
  if (carg[i])@< = '-'
    ;args are folded to upper-case!
    if cmpstr(carg[i],"=","-N")
      sortname=true
    else if cmpstr(carg[i],"=","-T")
      sortdate=true
    else
      return false
  else
    if cmpstr(carg[i],"=","0:")
      if dev>0 ;dev already specified
        return false
      dev=c64ddv0
    else if cmpstr(carg[i],"=","1:")
      if dev>0 ;dev already specified
        return false
      dev=c64ddv1
    else
      b=strval(carg[i],#j) ;j must be word
      if (carg[i]+b)@< ;not numeric?
        ;assume path or pattern
        if pathfound ;already specified
          return false
        colon=0
        for j=0 to lenstr(carg[i])-1
          if (carg[i]+j)@<=':'
            if colon>0 ;more than one
              return false
            else
              colon=j
        if colon>0
          for j=0 to colon-1
            dirname[j+1]=(carg[i]+j)@<
          ;we control the wildcards!
          dirname[colon+1]=':'
          dirname[colon+2]='*'
          dirname[colon+3]=0
          pattern=carg[i]+colon+1
        else
          pattern=carg[i]
        pathfound=true
      else
        if dev>0 ;already specified
          return false
        if j<8 or j>30
          return false
        else
          dev=j

if sortname and sortdate ;one only
  return false

if dev=0      ;no dev argument given?
  dev=c64ddv0 ;use 0:

return true

end

;--------------------------------------
; Convert byte to 0-padded string.
; (assumes byte not greater than 99)
; pass: byte to convert
; pass: pointer to converted string
;--------------------------------------
proc bytestr
arg byte b
arg word str ;pointer to byte[]

begin

if b < 10
  m[str]='0'
  m[str+1]=b+$30
else
  m[str]=(b/10)+$30
  m[str+1]=(b%10)+$30
m[str+2]=0

end

;--------------------------------------
; Open directory as a file.
; pass: device number 
; return: error code 
;--------------------------------------
func word opendir
arg byte dev
word err

begin

err=0
lfn=getlfn
if lfn=255
  return E_DOS+70
err=sendcmd(dev,"I0")
jsr csetlfs,lfn,dev,lfn
jsr csetnam,lenstr(dirname),dirname:<,dirname:>
jsr copen
if regf and regfc
  err=E_ACCUM+rega
else
  err=readcmd(dev,cmdbuf)
  if err=0
    err=(cmdbuf[0] and $0f)*10
    err=err+(cmdbuf[1] and $0f)
    if err>0
      output"#c#s",cmdbuf
      err=err+E_DOS

return err

end

;--------------------------------------
; Read one sector of directory file
; (call opendir first).
; return: error code (status byte)
;--------------------------------------
func word readdir
word err
byte first
byte eof

begin
bufptr=buffer
err=0
m[cstatus]=0

jsr ctalk,dev
if m[cstatus] > 127
  err=E_STATUS+m[cstatus]
  return err
jsr ctksa,lfn or $60
if m[cstatus] > 127
  err=E_STATUS+m[cstatus]
  jsr cuntlk
  return err

first=true
eof=false
repeat
  jsr cacptr
  if m[cstatus]<>0
    err=E_STATUS+m[cstatus]
    if m[cstatus] and $40 ;eof?
      eof=true
      if first
        break
      else
        first=false
    else
      break
  m[bufptr]=m[$a4] ;cached by ACPTR
  bufptr=bufptr+1
  if bufptr=buffer+254
    ;break loop but don't set err
    eof=true ;end of this sector
until eof

jsr cuntlk
return err

end

;--------------------------------------
; Get dirent address by index.
; pass: index at which to get address
; return: address of dirent
;--------------------------------------
func word getdir
arg word i
word element

begin

element=(index+(i*2))@+
return firstdir+(dirlen*element)

end

;---------------------------------------
; Case-insensitive match of two
; non-wildcard characters.
; pass: name character
; pass: pattern character
;---------------------------------------
func byte cimatch
arg byte nb 
arg byte pb

begin

if nb>='a' and nb<='z'
  nb=nb and $df
if pb>='a' and pb<='z'
  pb=pb and $df
return nb=pb

end

;---------------------------------------
; Match filename against pattern.
; pass: filename, pattern
; return: true on match, else false 
;---------------------------------------
func byte match
arg word name
arg word pattern
byte matched  ;boolean
byte matching ;boolean

begin

if pattern=0
  return true

matched=true
while matched
  if not name@<
    if not pattern@<
      break
    else if pattern@<='*'
      if (pattern+1)@<
        matched=false
      break
    else
      matched=false
      break
  else
    if not pattern@<
      matched=false
      break
    else if pattern@<='*'
      pattern=pattern+1
      if not pattern@<
        break
      else
        matching=true
        while matching
          ;if name@<=pattern@<
          if cimatch(name@<,pattern@<)
            matching=false
            break
          else if not name@<
            matching=false
            matched=false
          name=name+1
    ;else if name@<<>pattern@< and pattern@<<>'?'
    else if not cimatch(name@<,pattern@<) and pattern@<<>'?'
      matched=false
      break
  name=name+1
  pattern=pattern+1

return matched

end

;--------------------------------------
; Display a directory entry.
; pass: pointer to directory entry
;--------------------------------------
proc showdir
arg word nextdir
word i
byte b
byte drive
byte timestamp ;boolean

begin

i=(nextdir+dtype)@< and $0f
if i<7
  type=types[i]
else
  type="???"
output"#c#3w #16s #3s",(nextdir+dsize)@+,nextdir+dname,type

timestamp=false
drive=drives[dev-8]
if drive and $80 ;CMD?
  timestamp=true
if drive=dr_sd2iec or drive=dr_uiec
  b=0
  b=b or (nextdir+dmonth)@<
  b=b or (nextdir+dday)@<
  b=b or (nextdir+dyear)@<
  b=b or (nextdir+dhour)@<
  b=b or (nextdir+dminute)@<
  if b>0
    timestamp=true
if timestamp
  b=(nextdir+dmonth)@<
  bytestr b,month
  b=(nextdir+dday)@<
  bytestr b,day
  b=(nextdir+dyear)@<
  bytestr b,year
  b=(nextdir+dhour)@<
  bytestr b,hour
  b=(nextdir+dminute)@<
  bytestr b,minute
  output" #s/#s/#s #s:#s",month,day,year,hour,minute

end

;--------------------------------------
; Selection sort of dirent indices.
; pass: nothing (uses global vars)
; return: nothing
;--------------------------------------
proc sort
byte temp0
byte temp1
word least
word leastdir
word datefield

begin

i=0
while i < dircount-1
  least=i
  leastdir=getdir(i)
  j=i+1
  while j < dircount
    nextdir=getdir(j)
    if sortname
      if cmpstr(nextdir+dname,"<",leastdir+dname,false,16)
        least=j
        leastdir=getdir(j)
    else if sortdate ;might contain $00
      datefield=dyear
      while datefield <= dminute
        if m[nextdir+datefield] < m[leastdir+datefield]
          least=j
          leastdir=getdir(j)
          break
        else if m[nextdir+datefield] > m[leastdir+datefield]
          break
        datefield=datefield+1 ;equal, check next
    j=j+1
  if least > i
    temp0=m[index+(i*2)]
    temp1=m[index+(i*2)+1]
    m[index+(i*2)]=m[index+(least*2)]
    m[index+(i*2)+1]=m[index+(least*2)+1]
    m[index+(least*2)]=temp0
    m[index+(least*2)+1]=temp1
  i=i+1

end

;======================================
; program mainline
;======================================

begin

if getver:< < $21
  put "PROMAL 2.1 or higher required."
  exit
if getver:> <> 1
  put "This program is for the Commodore 64."
  exit

if not parse ;sets dev variable
  usage
  exit
edres=0 ;force editor reload
firstdir=himem
nextdir=himem
drvquery
drive=drives[dev-8]
if drive=0
  output"#cDevice #w not present!",dev
  exit
;CMD or sd2iec/uIEC drive?
if drive and $80 or (drive=$71 or drive=$72)
  err=getpartinfo(dev,255)
  parttype=partinfo[0]
err=opendir(dev)
if err<>0
  output"#cError $#04h opening directory!",err
  jsr cclose,lfn
  exit

column=curcol
row=curline
output"#creading directory..."
err=readdir ;header

if drive=dr_1581
  i=2
else if drive=dr_cmdhd or drive=dr_cmdfd
  if parttype=part_1541 or parttype=part_1571
    i=142
  else if parttype=part_nat or parttype=part_1581
    i=2
  else
    output"#cunsupported partition type: #w",parttype
    jsr cclose,lfn
    exit
else
  i=142
j=i+15
while buffer[j]=$a0
  buffer[j]=0
  j=j-1
for j=0 to 15
  diskname[j]=buffer[i+j]
diskname[16]=0

dircount=0
blockcount=0
repeat
  err=readdir
  if err<>0 and err<>E_STATUS+$40
    break
  bufptr=buffer
  for i=1 to 8
    if (bufptr+dtype)@< ;not deleted?
      j=15 ;null-terminate filename
      while (bufptr+dname+j)@<=$a0
        m[bufptr+dname+j]=0
        j=j-1
      m[bufptr+dname+16]=0 ;kills rel ss
      ;match returns true if no pattern
      if match(bufptr+dname,pattern)
        for j=0 to 29
          m[nextdir+j]=m[bufptr+j]
        dircount=dircount+1
        blockcount=blockcount+(nextdir+dsize)@+
        nextdir=nextdir+dirlen
    bufptr=bufptr+dirlen
until err<>0 ;until error or eof
jsr cclose,lfn

if err<>0 and err<>E_STATUS+$40  ;eof
  output"#c*** ERROR $#04h ***#c",err
  exit

if dircount=0
  curset column,row ;overwrite message
  output"#cdirectory of device #w (#s)",dev,drvdesc(drive)
  output"#cdisk name: #s",diskname
  output"#cNo files found.#c"
  exit

;prepare dirent indices
index=nextdir
for i=0 to dircount-1
  j=index+(i*2)
  if j=memlim
    output"#c*** OUT OF MEMORY ***#c"
    exit
  else
    m[j]=i:<
    m[j+1]=i:>

if sortname or sortdate
  curset column,row ;overwrite message
  output"#csorting directory entries..."
  sort
curset column,row ;overwrite message
output"#cdirectory of device #w (#s)",dev,drvdesc(drive)
output"#cdisk name: #s",diskname
for i=0 to dircount-1
  showdir(getdir(i))
output"#cfiles: #w, blocks: #w",dircount,blockcount

end
