IMPLEMENTATION MODULE HuffChan;

(*
 This module shows how to redefine standard IO file functions. It provides
 functions for reading and writing packed files opened in Raw mode.
*)

IMPORT IOChan, IOLink, ChanConsts, IOConsts, SYSTEM, Strings;
FROM Storage IMPORT ALLOCATE, DEALLOCATE;

CONST
  rbldFrq = 512;	(* means: every 512 bytes rebuild table *)

TYPE
  charTap  = POINTER TO ARRAY [0..MAX(INTEGER)-1] OF CHAR;
  smbTp = POINTER TO smbT;

  smbT = RECORD			(* Huffman's tree *)
    ch			: CHAR;
    n			: CARDINAL; (* frequncy of char ch *)
    left,right,next	: smbTp;
  END;

  tblT = RECORD		(* bit sequence for code *)
    vl		: CARDINAL;	(* bit sequence *)
    cnt		: INTEGER;	(* it length *)
  END;

  lclDataT = RECORD	(* channel's local data *)
    tRoot 	: smbTp;
    htbl	: ARRAY [0..255] OF tblT;     (* code -> bit sequence table *)
    ftbl  	: ARRAY [0..255] OF CARDINAL; (* frequncey table *)
    wBf,rb1,rb2	: CARDINAL;
    wbc,rbc,smc	: INTEGER;
    chid	: IOChan.ChanId;
  END;
  lclDataTp = POINTER TO lclDataT;
  charp     = POINTER TO CHAR;

VAR
  did	: IOLink.DeviceId;
  ldt	: lclDataTp;


PROCEDURE Shf(a:CARDINAL; b : INTEGER) : CARDINAL; (* shl a,b (or shr) *)
BEGIN
  RETURN SYSTEM.CAST(CARDINAL,SYSTEM.SHIFT(SYSTEM.CAST(BITSET,a),b));
END Shf;

PROCEDURE wrDword(a:CARDINAL);	(* write 4 bytes to file *)
BEGIN
  IOChan.RawWrite(ldt^.chid,SYSTEM.ADR(a),4);
END wrDword;

PROCEDURE rdDword() : CARDINAL;  (* read 4 bytes from file *)
VAR
  a,z : CARDINAL;
BEGIN
  a:=0;
  IOChan.RawRead(ldt^.chid,SYSTEM.ADR(a),4,z);
  RETURN a;
END rdDword;

PROCEDURE wrSmb(ch : CHAR);	(* write bit sequence for code ch *)
VAR
  v,h : CARDINAL;
  b,c : INTEGER;
BEGIN
  WITH ldt^ DO
    v:=htbl[ORD(ch)].vl;
    c:=htbl[ORD(ch)].cnt;
    IF c+wbc<=32 THEN
      wBf:=Shf(wBf,c);
      wBf:=wBf+v;
      wbc:=wbc+c;
      IF wbc=32 THEN
	wrDword(wBf);
	wBf:=0; wbc:=0;
      END;
      RETURN;
    END;
    b:=c+wbc-32;
    h:=Shf(v,-b);
    wBf:=Shf(wBf,32-wbc)+h;
    wrDword(wBf);
    wBf:=v-Shf(h,b);
    wbc:=b;
  END;
END wrSmb;

PROCEDURE flush();	(* write data in buffer *)
BEGIN
  WITH ldt^ DO
    wBf:=Shf(wBf,32-wbc);
    wrDword(wBf);
  END;
END flush;

PROCEDURE getSym() : CHAR; (* find code for first bit sequence in buffer *)
VAR
  t,i : CARDINAL;
  b   : INTEGER;
BEGIN
  WITH ldt^ DO
    IF rbc<=32 THEN
      rb2:=rdDword();
      t:=Shf(rb2,-rbc);
      IF rbc=32 THEN t:=0; END;
      rb1:=rb1+t;
      rb2:=Shf(rb2,32-rbc);
      IF rbc=0 THEN rb2:=0; END;
      rbc:=rbc+32;
    END;
    FOR i:=0 TO 255 DO
      t:=Shf(rb1,htbl[i].cnt-32);
      IF t=htbl[i].vl THEN
	rb1:=Shf(rb1,htbl[i].cnt);
	b:=32-htbl[i].cnt;
	t:=Shf(rb2,-b);
	rb1:=rb1+t;
	rb2:=Shf(rb2,32-b);
	rbc:=rbc+b-32;
	RETURN CHR(i);
      END;
    END;
  END;
END getSym;

PROCEDURE Insert(s : smbTp); (* insert new character in Huffman's tree *)
VAR
  cr : smbTp;
BEGIN
  WITH ldt^ DO
    IF tRoot=NIL THEN
      cr:=tRoot;
      tRoot:=s;
      s^.next:=cr;
      RETURN;
    ELSIF tRoot^.n<=s^.n THEN
      cr:=tRoot;
      tRoot:=s;
      s^.next:=cr;
      RETURN;
    END;
    cr:=tRoot;
    WHILE (cr^.next<>NIL) & (cr^.next^.n>s^.n) DO
      cr:=cr^.next;
    END;
    s^.next:=cr^.next;
    cr^.next:=s;
  END;
END Insert;

PROCEDURE BuildTree(); (* build Huffman's tree *)
VAR
  cr,ocr,ncr : smbTp;
BEGIN
  WITH ldt^ DO
    LOOP
      ocr:=NIL; cr:=tRoot;
      WHILE cr^.next^.next<>NIL  DO
	ocr:=cr; cr:=cr^.next;
      END;
      NEW(ncr);
      ncr^.n:=cr^.n+cr^.next^.n;
      ncr^.left:=cr;
      ncr^.right:=cr^.next;
      IF ocr<>NIL THEN
	ocr^.next:=NIL;
	Insert(ncr);
      ELSE
	tRoot:=NIL;
	Insert(ncr);
	EXIT;
      END;
    END;
  END;
END BuildTree;

PROCEDURE BuildTable(cr: smbTp; vl,n: CARDINAL); (* build table: code -> bit sequence *)
BEGIN
  WITH ldt^ DO
    IF cr^.left=NIL THEN
      htbl[ORD(cr^.ch)].vl:=vl;
      htbl[ORD(cr^.ch)].cnt:=n;
      DISPOSE(cr);
      RETURN;
    END;
    vl:=vl*2;
    BuildTable(cr^.left,vl,n+1);
    BuildTable(cr^.right,vl+1,n+1);
    DISPOSE(cr);
  END;
END BuildTable;

PROCEDURE clcTab(); (* build code/bitseq. table from frequency table *)
VAR
  i : CARDINAL;
  s : smbTp;
BEGIN
  WITH ldt^ DO
    tRoot:=NIL;
    FOR i:=0 TO 255 DO
      NEW(s);
      s^.ch:=CHR(i);
      s^.n:=ftbl[i];
      s^.left:=NIL; s^.right:=NIL; s^.next:=NIL;
      Insert(s);
    END;
    BuildTree();
    BuildTable(tRoot,0,0);
  END;
END clcTab;

PROCEDURE iniHuf();
VAR
  i : CARDINAL;
BEGIN
  WITH ldt^ DO
    FOR i:=0 TO 255 DO
      ftbl[i]:=1;
    END;
    wBf:=0; wbc:=0; rb1:=0; rb2:=0; rbc:=0;
    smc:=0;
    clcTab();
  END;
END iniHuf;


PROCEDURE RawWrite(x: IOLink.DeviceTablePtr; buf: SYSTEM.ADDRESS;
		len: CARDINAL);
VAR
  i	: CARDINAL;
  ch	: CHAR;
  cht	: charTap;
BEGIN
  IF len = 0 THEN RETURN; END;
  ldt:=SYSTEM.CAST(lclDataTp,x^.cd);
  cht:=SYSTEM.CAST(charTap,buf);
  WITH ldt^ DO
    FOR i:=0 TO len-1 DO
      ch:=cht^[i];
      wrSmb(ch);
      IF ch = 377C THEN wrSmb(ch); END;
      ftbl[ORD(ch)]:=ftbl[ORD(ch)]+1; smc:=smc+1;
      IF smc=rbldFrq THEN
	clcTab();
	smc:=0;
      END;
    END;
  END;
  x^.result:=IOChan.ReadResult(ldt^.chid);
END RawWrite;

PROCEDURE RawRead(x: IOLink.DeviceTablePtr; buf: SYSTEM.ADDRESS;
		blen: CARDINAL; VAR len: CARDINAL);
VAR
  i	: CARDINAL;
  cht	: charTap;
  ch	: CHAR;
BEGIN
  ldt:=SYSTEM.CAST(lclDataTp,x^.cd);
  cht:=SYSTEM.CAST(charTap,buf);
  IF (blen=0) OR (x^.result<>IOConsts.allRight) THEN len:=0; RETURN; END;
  WITH ldt^ DO
    FOR i:=0 TO blen-1 DO
      ch:=getSym();
      IF ch = 377C THEN
	ch:=getSym();
	IF ch = 0C THEN
	  x^.result:=IOConsts.endOfInput;
	  len:=i; cht^[i]:=0C;
	  RETURN;
	END;
      END;
      cht^[i]:=ch;
      ftbl[ORD(ch)]:=ftbl[ORD(ch)]+1; smc:=smc+1;
      IF smc=rbldFrq THEN
	clcTab();
	smc:=0;
      END;
    END;
    len:=blen;
  END;
END RawRead;

PROCEDURE CreateAlias(VAR cid: ChanId; io: ChanId; VAR res: OpenResults);
VAR
  x	: IOLink.DeviceTablePtr;
BEGIN
  IOLink.MakeChan(did,cid);
  IF cid = IOChan.InvalidChan() THEN
    res:=ChanConsts.outOfChans
  ELSE
    NEW(ldt);
    IF ldt=NIL THEN
      IOLink.UnMakeChan(did,cid);
      res:=ChanConsts.outOfChans;
      RETURN;
    END;
    x:=IOLink.DeviceTablePtrValue(cid,did,IOChan.notAvailable,"");
    ldt^.chid:=io;
    x^.cd:=ldt;
    x^.doRawWrite:=RawWrite;
    x^.doRawRead:=RawRead;
    res:=ChanConsts.opened;
    iniHuf();
    x^.result:=IOConsts.allRight;
  END;
END CreateAlias;

PROCEDURE DeleteAlias(VAR cid: ChanId);
VAR
  x	: IOLink.DeviceTablePtr;
BEGIN
  x:=IOLink.DeviceTablePtrValue(cid,did,IOChan.notAvailable,"");
  ldt:=x^.cd;
  IF ldt^.rbc=0 THEN
    wrSmb(377C);
    wrSmb(0C);
    flush();
  END;
  DISPOSE(ldt);
  IOLink.UnMakeChan(did,cid);
END DeleteAlias;

BEGIN
  IOLink.AllocateDeviceId(did);
END HuffChan.
