{**********************************************************************}
{*                     APMTEL Database routines                       *}
{*                  John Butler for A.Ness project                    *}
{*                                                                    *}
{*                    Version 3.2 11 Aug 1988                         *}
{**********************************************************************}

%include "CONSTS.INC"
%include "INC:UTIL.IMP"
%include "FILES.INC"
%include "inc:region.imp"


!File organisation is now an index-sequential file with a couple of linked
!lists running through it.

!A cell here is 1024 bytes. Each cell contains a PAGE or indexing information.

!A PAGE may have 0 or many SUBPAGES.
!In Teletext terms the full page number is pppssss where ppp is the pagenumber
!and ssss is the subpage number. If ssss=0 and the only subpage is the page itself.
!The index cells point at the first or only subpage of the page.
!This has pointers to the other subpages (if any). We'll call it a HEAD Cell

!The first 12 bytes in the file are:
! 0,1: Free cell queue head.
! 2,3: Used cell chain head. This chain links the cell heads
!      in chronologica; order.
! 4,5: Used chain tail. Points to the most recent cell
! 7,8: unused
! 8-11: Timestamp of last write to file.


!The first 10 cells (bar the first 12 bytes) are index cells, each being
!2 bytes, indexed by page.  This allows up to 5114 pages (we can only use 4500)

! Page is a 16-bit channel/page number. Top 4 bits channel, bottom 12 page no.
!The head cells start with BLEN (24) bytes admin information then 1000 bytes for
!the first/only subpage. (the 25th line is not used yet). The first 10 bytes are:
! 0,1: Pointer to next subpage or 0
! 2,3: Forward pointer to next head cell
! 4,5: Reverse pointer to last head cell
! 6,7: Page id
! 8-11: Timestamp (offset must equal tst offset below)

%recordformat admin fm(( %c
   (%half free head, busy head, busy tail) %c
%or %c
   (%half cell ptr, fwd link, rev link) %c
), %half page id, %integer timestamp)

%ownrecord (admin fm) junk
%constinteger blk0admin = 12, blknadmin=12, indexblks=5, pageblks = 15, {testing}
   tst offset = 6, blen=24

%constinteger true=1,false=0

%owninteger file ref=0, file size=0


%integerfn open(%string(255) cache file, %integername file size, %integer mode)
   ! Attempt to open one of the database files.   success = 1, failure = -1
   ! Note use of global FILE REF
   %on %event 0,1,2,3,4,5,6,7,8,9 %start
      %if event_event = 3 %and event_sub = 3 %start
         printstring("Catastrophic user cache error - Event ")
         write(event_event,-1); space; write(event_sub,-1); newline
         %stop
      %finish
      %result= -1
   %finish

!t!printstring("open ".cache file)
   file ref=0
   accessfile(cache file ,mode, file ref,file size)
!t!printstring(" accessed "); write(file ref,-1); space; write(file size,-1)
!t!newline
   %result= 1
%end


%externalroutine open cache(%string(255) cache file)
   ! Repeat a re-open of the database file until it succeeds
   ! Open it in write mode.
!t!printstring("Open Cache ".cache file); newline
   %cycle
   %repeatuntil open(cache file, file size, 1) #-1
%end


%externalroutine close cache
   ! Close the database file for use by other users
!t!printstring("Close cache"); newline
   deaccessfile(file ref)
%end

   
%routine read cell(%integer cell, offset, bytes, %name to)
!t! printstring("ReadCell: "); write(cell, 3);space; phex(offset)
!t! space; write(bytes, 3); space; phex(addr(to))
   read region(file ref, cell*1024+offset, bytes, to)
!t! printstring("="); phex(to); newline
%end

%integerfn read admin(%integer cell, %name admin)
   !A result code of -1 almost certainly means we're reading off end-of-file
!t!   %record (admin fm) %name adm
!t!   printstring("ReadAdm "); write(cell, 3); printstring(":")

   %if cell=0 %start
      %if cell*1024+blk0admin<=file size %then %c
      read region(file ref, cell*1024, blk0admin, admin) %else %result=-1
   %else
      %if cell*1024+blknadmin<=file size %then %c
      read region(file ref, cell*1024, blknadmin, admin) %else %result=-1
   %finish
!t!   adm == record(addr(admin))
!t!   write(adm_free head, 4); write(adm_busy head,4); write(adm_busy tail,4)
!t!   newline
   %result=0
%end

%routine write to cell(%integer cell, offset, bytes, %name from)
!t! printstring("WriteCell: "); write(cell, 3);space; phex(offset)
!t! space; write(bytes, 3); space; phex(addr(from))
!t! printstring("="); phex(from); newline
   write region(file ref, cell*1024+offset, bytes, from)
%end

%integerfn kday(%integer d,m,y)
      !Days since 1/1/1900
      %if m>2 %then m=m-3 %else m=m+9 %and y=y-1
      %result=1461*y//4+(153*m+2)//5+d+58
%end; ! of kday

%routine kdate(%integername d,m,y,%integer k)
   !Convert timestamp k to d,m,y.   k is days since 1/1/1900. 2 digit y
   %integer w
   k=k+693902; ! days since Cleopatras birthday
   w=4*k-1
   y=w//146097
   k=w-146097*y
   d=k//4
   k=(4*d+3)//1461;  d=4*d+3-1461*k;  d=(d+4)//4
   m=(5*d-3)//153;   d=5*d-3-153*m;   d=(d+5)//5
   y=k
   %if m<10 %then m=m+3 %else %start
      m=m-9
      %if y=99 %then y = 0 %else y=y+1
   %finish
%end; ! of kdate

%integerfn timestamp
   %integer dd,mm,yy,hh,nn,magic
   %string(31)t
   t = datetime
   dd=stoi(substring(t,1,2)); mm=stoi(substring(t, 4,5)); yy=stoi(substring(t, 7,8))
   hh=stoi(substring(t,11,12)); nn=stoi(substring(t,14,15))
   magic = ((kday(dd,mm,yy)-31411)*24+hh)*60+nn
   !23-bit integer (minutes since 1/1/86)
   !31411 is the day no for 1/1/86.   
   !Date part of magic won't overflow this century.
   %result = magic
%end

%routine readstamp(%record (admin fm) %name adm, %integername d,m,y, h,n)
   %integer dstamp, tstamp
   tstamp = adm_timestamp; dstamp = tstamp//(24*60)
   kdate(d,m,y, dstamp+31411)
   tstamp = tstamp - dstamp*24*60
   h = tstamp//60
   n = tstamp - h*60
%end

%externalroutine create db file(%string (255) filename, %integer indexblks, pageblks)
   %record (admin fm) b0admin
   %integer i, ch

   %routine write rec
      %integer i
      %for i=0,1,sizeof(b0admin)-1 %cycle; printsymbol(byteinteger(addr(b0admin)+i)); %repeat
      %for i=0,1,1023-sizeof(b0admin) %cycle; printsymbol(0); %repeat
   %end

   ch = outstream
   openoutput(1, filename); selectoutput(1)
   b0admin=0
   b0admin_free head = indexblks
   b0admin_timestamp = timestamp
   write rec
   
   b0admin=0
   %for i=1,1,indexblks-1 %cycle
      write rec
   %repeat
   
   %for i=indexblks,1,pageblks+indexblks-2 %cycle
      b0admin_free head=i+1
      write rec
   %repeat

   b0admin_free head = 0
   write rec

   close output
   selectoutput(ch)
   
%end

%integerfn pageno(%integer page id)
   %integer d,t,h
   d = page id&15; page id = page id>>4
   t = page id&15; page id = page id>>4
   h = page id&15
   %result = (h*10+t)*10+d
%end

%routine analyse db file(%string(255) filename)
   %integer ad, len, freecnt, busycnt, d,m,y, h,n, i,rc
   %record (admin fm) b0admin, bnadmin
   %halfinteger cno
   %halfintegerarray ind(0:512*10-1)
   open cache(filename)

   read region(file ref, 0, 1024*10, byteinteger(addr(ind(0)))) 
   %for i=0,1,blk0admin>>1-1 %cycle
      printstring("**** ")
   %repeat
   %for i=blk0admin>>1,1,511 %cycle
      phex4(ind(i))
      %if i&16=15 %then newline %else space
   %repeat
   newline
   rc=read admin(0, b0admin)
   %if rc=0 %start
   cno = b0admin_free head; freecnt=0
   printstring("Free Head ->"); write(cno, 4); newline
   %while cno#0 %cycle
      rc=read admin(cno, bnadmin)
      cno = bnadmin_free head; freecnt=freecnt+1
      printstring(" -> "); write(cno, 4); newline
   %repeat
   write(freecnt, -1); printstring(" Free cells"); newline

   cno = b0admin_busy head; busycnt=0
   printstring("Busy Tail ->"); write(b0admin_busy tail, 4)
   printstring("   Busy Head ->"); write(cno, 4)
   newline
   %while cno#0 %cycle
      rc=read admin(cno, bnadmin)
      printstring("p"); write(bnadmin_page id>>12,-1)
      printstring("."); write(pageno(bnadmin_page id),-1); printstring(": ")
      write(cno, 3)
      cno = bnadmin_fwd link; busy cnt = busy cnt+1
      
      %while bnadmin_cell ptr#0 %cycle
         printstring(","); write(bnadmin_cell ptr, -1)
         rc=read admin(bnadmin_cell ptr, bnadmin)
      %repeat
      printstring(" -> "); write(cno, -1)
      newline
   %repeat
   write(busycnt, -1); printstring(" Head cells"); newline
   readstamp(b0admin, d,m,y, h,n)
   printstring("File last altered ")
   write(d,-1); printsymbol('/'); write(m,-1); printsymbol('/'); write(y,-1);
   write(h,3); printsymbol(':'); write(n,-1); newline
   %else
     printstring("Problem with file"); newline
   %finish
   close cache
%end

%routine write admin(%integer cell, %name admin)
!t!   %record (admin fm) %name adm
!t!   printstring("Write adm"); write(cell, 3); printstring(":")
   %if cell=0 %start
      write region(file ref, cell*1024, blk0admin, admin)
   %else
      write region(file ref, cell*1024, blknadmin, admin)
   %finish
!t!   adm == record(addr(admin))
!t!   write(adm_free head, 4); write(adm_busy head, 4); write(adm_busy tail, 4)
!t!   newline
%end

%integerfn indexno(%integer page)
   %integer p
   !Page is supplied as a BCD number.
   !Top 4 bits channel, bottom 12 page as 3 nibbles.
   p = page&15; page=page>>4
   p = p + (page&15)*10; page=page>>4
   p = p + (page&15)*100;page=page>>4
   p = p-100 ;!Should now be an integer in the range 0-899
   p = p + ((page&15)-1)*900 ;!Include the channel number
   !5 channels = 4500 pages.
!t! printline("Indexno ".itos(p,-1))
   %result=p
%end

%routine find free cell(%halfname cellno)
   %integer offset,rc
   %halfinteger zero
   %record (admin fm) b0admin, bnadmin
   !Attempt to find a free cell (should be on the free list)
!t! printline("Find free cell")
   rc=read admin(0, b0admin)

   %if rc=0 %start
   %if b0admin_free head#0 %start
      !It is.  Take it off the free queue and give it to the user.
      rc=read admin(b0admin_free head, bnadmin)
      cellno = b0admin_free head; b0admin_free head = bnadmin_free head
   %else
      !Grab the first chain of pages off the busy queue, give the user the
      !first and put the rest on the free queue
      rc=read admin(b0admin_busy head, bnadmin)
      cellno = b0admin_busy head; b0admin_busy head = bnadmin_fwd link
      !Zap the index pointer to this cell head.
      zero=0
      offset = indexno(bnadmin_page id)*2 + blk0admin
      write to cell(offset>>10, offset&1023, 2, zero)
   %finish
   write admin(0, b0admin)
   %finishelse cellno=0
!t! printline("Free cell = ".itos(cellno, -1))
%end

%routine create new cell head(%integer page id, %halfname head)
   %record (admin fm) b0admin, bnadmin
   %integer offset,rc

!t! printstring("Create new cell head "); phex4(page id); newline
   !Find a free cell
   find free cell(head)
   %return %if head=0

   !Locate the busy queue tail pointer.
   rc=read admin(0, b0admin)

   %if b0admin_busy tail=0 %start
      !First cell on queue. Point head at it.
      b0admin_busy head = head
   %else
      !Add the new cell to the end of the busy queue
      rc=read admin(b0admin_busy tail, bnadmin)
      bnadmin_fwd link=head
      write admin(b0admin_busy tail, bnadmin)
   %finish

   !Point the new cell back to the previous cell and forward to NIL
   bnadmin_cell ptr=0; bnadmin_fwd link=0; bnadmin_rev link=b0admin_busy tail
   bnadmin_page id = page id
   write admin(head, bnadmin)
   !Update the busy queue tail pointer
   b0admin_busy tail = head
   b0admin_timestamp=timestamp
   b0admin_page id=page id
   write admin(0, b0admin)
   offset = indexno(page id)*2 + blk0admin
   write to cell(offset>>10, offset&1023, 2, head)
%end

%externalintegerfn record no(%integer page id)
   %halfinteger index
   %integer offset

   offset = indexno(page id)*2 + blk0admin
   read cell(0, offset, 2, index)
!t! printline("Record no(".itos(page id,-1).") = ".itos(index,-1))
   %result = index
%end

!Cache must have been OPENed first.

%routine read data(%integer cell, %name to)
!t! printstring("ReadData:"); write(cell, 3); newline
   read region(file ref, cell*1024+blen, 1000, to)
%end

%externalroutine read cached page(%integer page id, address, %integername subpages)
   %integer ptr, filesize, pg,rc
   %record (admin fm) blknadmin

   !Subpages will be 0 if the page is absent.
!t! printline("Read cached page ".itos(page id,-1))
   pg=record no(page id); ptr=address; subpages=0
   %while pg#0 %cycle
      rc=read admin(pg, blknadmin)
      %if rc=0 %start
        read data(pg, byteinteger(ptr))
        pg = blknadmin_cell ptr; ptr=ptr+1000
        subpages=subpages+1
      %finishelse pg=-1 ;!probably off end of file
   %repeat
!t! printline("Subpages = ".itos(subpages,-1))
%end


%routine move(%integer bytes, %name from, to)
   !Move BYTES bytes from FROM to TO. Pinched from IE.
   !If addr(FROM) < addr(TO) do the move from the top down to allow overlap
   %return %if Bytes = 0 %or  From == To

   %if Addr (To) < Addr (From) %start
      *Subq.l #1, d0
   f loop:
      *move.b (a0)+, (a1)+
      *dbra   d0, f loop
   %else
      *add.l  d0, a0
      *add.l  d0, a1
      *subq.l #1, d0
   b loop:
      *move.b -(a0), -(a1)
      *dbra   d0, b loop
   %finish
%end

%routine write complete cell(%integer cell, %record (admin fm) %name admin,
%integer fromaddr)
   !This is directly equivalent to write admin followed by write data
   !but putting it like this saves filestore read/writes
   !(though at the expense of a block copy)
   %bytearray buff(0:1023)
   move(blen, admin, buff(0))
   move(1000, byteinteger(fromaddr), buff(blen))
   write region(file ref, cell*1024, 1024, buff(0))
%end

%externalroutine write cached page(%integer page id, address, subpages)
    %integer ptr,tt,rc
    %half pg, newpg, headcell
    %record (admin fm) bnadmin
!t!  printline("Write cached page ".itos(page id, -1).", ".itos(subpages,-1)." subpages")
    pg=0; newpg = record no(page id); ptr=0
    create new cell head(page id, newpg) %if newpg=0
    headcell=newpg
    tt=timestamp
    %while newpg#0 %and ptr # subpages*1000 %cycle
       !Overwrite what's there (if it's there) as far as we can
       !Read the admin info
       pg = newpg
       rc=read admin(pg, bnadmin)
       !Change the time stamp
       bnadmin_timestamp=tt %and tt=0 %if tt#0
       write complete cell(pg, bnadmin, address+ptr)
       !overwrite the page
  
       ptr=ptr+1000
       newpg = bnadmin_cell ptr
    %repeat
  
!t!  printline("adding to end") %if ptr # subpages*1000
    %while ptr # subpages * 1000 %cycle ;!We have data left to write
       !Get an unused page (or the oldest used one)
       find free cell(newpg)
       !Chain it onto the subpage list
       rc=read admin(pg, bnadmin)
       bnadmin_cell ptr = newpg
       write admin(pg, bnadmin)
       !Write the new page admin with a null pointer to the next page
       bnadmin_cell ptr = 0
       !For subpage cells we point the forward link to the head cell
       !and the reverse link to the previous subpage cell
       bnadmin_rev link = pg
       bnadmin_fwd link = headcell
       pg = newpg
       write complete cell(pg, bnadmin, address+ptr)
       ptr=ptr+1000
    %repeat
!t!  printline("Cached")
%end
     

%begin
%if cli param="" %then analyse db file("db.dat") %else %c
analyse db file(cli param)
%endofprogram
