{$MODE OBJFPC}
unit dlzw_decompressor;

interface

uses
  dtypes,
  dbitstream;

type
  TRead = function (var Buffer; Count: LongInt): LongInt of object;

function LZWDecompress(ReadFunc: TRead; var P: Pointer; var S: LongInt): Boolean;

implementation

function LZWDecompress(ReadFunc: TRead; var P: Pointer; var S: LongInt): Boolean;
var
  BitReader: TBitReader;
  Table: array of AnsiString;
  Bits: LongInt;
  OldCode, Code: TUint32;
  R: AnsiString;
  Part: AnsiString;
  function GetString(Sym: LongInt): AnsiString;
  var
    I: LongInt;
  begin
    if Sym < 256 then begin
      Result := AnsiChar(Sym);
    end else begin
      Result := Table[Sym - 256];
    end;
  end;
  procedure AddString(const S: AnsiString);
  var
    I: LongInt;
  begin
    SetLength(Table, Length(Table) + 1);
    Table[High(Table)] := S;
    if (High(Table) + 256) > (1 shl Bits) - 2 then
      Inc(Bits);
    //Write('AddToTable: ', High(Table) + 256, ' ');
    //for I := 1 to High(S) do
    //  Write('#', Ord(S[I]));
    //Write(' Bits=', Bits);
    //Writeln;
  end;
  function NextSym(var Sym: TUint32): Boolean;
  begin
    Result := BitReader.ReadUint(Bits, Sym);
  end;
  procedure Store(const S: AnsiString);
  begin
    R := R + S;
  end;
  function LookFor(const S: AnsiString): Boolean;
  var
    I: LongInt;
  begin
    if Length(S) = 1 then
      Exit(True); // unused case?
    for I := 0 to High(Table) do
      if Table[I] = S then
        Exit(True);
    Exit(False);
  end;
  function IsInsideTable(Code: LongInt): Boolean;
  begin
    Result := Code < Length(Table) + 256;
  end;
begin
  BitReader.Init(ReadFunc);
  SetLength(Table, 2);
  Bits := 9;
  R := '';
  OldCode := 0; // FIXME is it correct?
  //if not NextSym(Code) then
  //  Exit(False);
  //Store(GetString(Code));
  //OldCode := Code;
  //Writeln(OldCode);
  while NextSym(Code) do begin
    if Code = 257 then begin
      //Writeln('End of information');
      Break;
    end;
    if Code = 256 {Clear} then begin
      //Writeln('Clear');
      SetLength(Table, 2);
      if not NextSym(Code) then
        Exit(False);
      if Code = 257 then
        Break;
      Store(GetString(Code));
    end else if IsInsideTable(Code) then begin
      //Writeln('From table Code=', Code, ' OldCode=', OldCode);
      Store(GetString(Code));
      AddString(GetString(OldCode) + GetString(Code)[1]);
    end else begin
      //Writeln('OldCode=', OldCode, ' Code=', Code);
      Part := GetString(OldCode) + GetString(OldCode)[1];
      Store(Part);
      AddString(Part);
    end;
    OldCode := Code;
  end;
  S := Length(R);
  GetMem(P, S);
  Move(R[1], P^, S);
  Result := True;
  BitReader.Done;
end;

end.
