uses
  SysUtils,
  ddumper,
  dfilereader,
  dbitmem_le,
  dadler32,
  ddeflate,
  dzlib,
  dzlib_decompress,
  dzlib_decompress_utils;

var
  OriginalData: Pointer;
  DstArr: array[0 .. 1 shl 16] of Byte;
  Dst: PByte = @DstArr[0];
  DstSize: SizeUInt = Length(DstArr);
  DstEnd: PByte = @DstArr[High(DstArr)];
  TempLength: LongInt = 0;
  TempSize: SizeUInt;
  I: LongInt;

procedure Error(const Msg: PAnsiChar);
begin
  Writeln(stderr, Msg);
  Halt(1);
end;

procedure MoveSelfRepeated(Src, Dst, DstEnd: Pointer);
begin
  // "Dst - Src" is "how much data do we have now"
  // "DstEnd - Dst" is "how much data do we need to copy"
  while Dst - Src < DstEnd - Dst do begin
    Move(Src^, Dst^, Dst - Src);
    Inc(Dst, Dst - Src);
  end;
  // Copy rest
  Move(Src^, Dst^, DstEnd - Dst);
end;

function DumpZLibDeflateInfo(Data, DataEnd: Pointer): Pointer;
var
  BlockIndex: LongInt;
  Header: Byte;
  BFINAL: Boolean;
  BTYPE: Byte;
  Len, NLen: UInt16;
  Value: UInt16;
  FixedLiteralTree: THuffmanTree;
  FixedDistanceTree: THuffmanTree;
  DynamicLiteralTree: THuffmanTree;
  DynamicDistanceTree: THuffmanTree;
  LiteralTree: PHuffmanTree;  // points to fixed or dynamic
  DistanceTree: PHuffmanTree; // points to fixed or dynamic
  Sym: LongInt;
  BitOffset: LongInt;
  CurLen: LongInt;
  Dist: LongInt;
  Offs: LongInt;
  I: LongInt;
begin
  DeflateBuildFixedTrees(FixedLiteralTree, FixedDistanceTree);
  BlockIndex := 0;
  BitOffset := 0;
  while True do begin
    Writeln('BLOCK #', BlockIndex);
    if Data >= DataEnd then
      Error('File corrupted: not enough bytes in compressed data');
    //Header := ReadBitsLE(Data, BitOffset, 3, Data, BitOffset) + 0;
    //BFINAL := Header and 1 <> 0;
    //BTYPE := (Header shr 1) and 3;
    BFINAL := ReadBitLE(Data, BitOffset, Data, BitOffset) <> 0;
    BTYPE := ReadBitsLE(Data, BitOffset, 2, Data, BitOffset) + 0;
    Writeln('  BFINAL: ', BFINAL);
    Writeln('  BTYPE:  ', BTYPE);
    //Inc(Data);
    if BTYPE = 0 then begin // no compression
      // TODO DataEnd
      if BitOffset > 0 then
        Inc(Data);
      BitOffset := 0;
      Len := SwapEndian(UInt16(Data^));
      NLen := SwapEndian(UInt16(Data^));
      if Len <> ((not NLen) and $ffff) then
        Error('  Error: Len and NLen are not matched');
      Move(Data^, Dst^, Len);
      Inc(Dst, Len);
      Inc(Data, SizeOf(Len) + SizeOf(NLen) + Len);
    end else if BTYPE = 3 then begin // reserved
      Error('  Error: BTYPE=11 is reserved');
    end else begin
      if BTYPE = 2 then begin // dynamic Huffman codes
        Writeln('  Dynamic Huffman codes');
        DeflateReadTrees(Data, BitOffset, DataEnd, DynamicLiteralTree, DynamicDistanceTree);
        LiteralTree := @DynamicLiteralTree;
        DistanceTree := @DynamicDistanceTree;
      end else begin // fixed Huffman codes
        Writeln('  Fixed Huffman codes');
        LiteralTree := @FixedLiteralTree;
        DistanceTree := @FixedDistanceTree;
      end;
      while True do begin
        Sym := DeflateDecodeSymbol(Data, BitOffset, DataEnd, LiteralTree);
        if Sym < 256 then begin // literal byte
          if Sym < 0 then
            Error('Error: bad symbol');
          Write('  Literal: ', Sym);
          Write(' h', HexStr(Sym, 2));
          Write(' b', BinStr(Sym, 8));
          if (32 <= Sym) and (Sym < 128) then
            Write(' (', AnsiChar(Sym), ')');
          Writeln;
          Dst^ := Sym;
          Inc(Dst);
          continue;
        end else if Sym = 256 then begin // end of block
          Writeln('  End of block');
          break;
        end;

        Dec(Sym, 257);
        if Sym >= 29 then begin
          Error('Error: corrupted data');
        end;

        CurLen := DeflateReadLength(Data, BitOffset, DataEnd, Sym);
        Dist := DeflateDecodeSymbol(Data, BitOffset, DataEnd, DistanceTree);
        if Dist < 0 then begin
          Error('Data corrupted: bad distance');
        end else if Dist >= 30 then
          Error('Data corrupted: Dist is too large');
        Offs := DeflateReadDistance(Data, BitOffset, DataEnd, Dist);
        Writeln('  Sym: ', Sym, ' ', CurLen, ' ', Dist, ' ', Offs);
        MoveSelfRepeated(Dst - Offs, Dst, Dst + CurLen);
        for I := 0 to CurLen - 1 do begin
          Write('           ', PByte(Dst)[I]);
          Write(' h', HexStr(PByte(Dst)[I], 2));
          Write(' b', BinStr(PByte(Dst)[I], 8));
          if (32 <= PByte(Dst)[I]) and (PByte(Dst)[I] < 128) then
            Write(' (', AnsiChar(PByte(Dst)[I]), ')');
          Writeln;
        end;
        Inc(Dst, CurLen);

        // TODO

        //Error('Underconstruction: read compressed data');
      end;
    end;
    if BFINAL then begin
      if BitOffset <> 0 then
        Inc(Data);
      Writeln('BLOCKS END');
      Writeln('Remaining bytes: ', DataEnd - Data);
      Exit(Data);
    end;
  end;
  Exit(Data);
end;

const
  FWS: AnsiString = 'FWS'#19;
procedure DumpZLibInfo(Data, DataEnd: Pointer);
var
  Header: TZlibHeader;
  OriginalAdler, DstAdler: Cardinal;
begin
  Writeln('Dumping ', DataEnd - Data);
  if not ZlibHeader(Data, DataEnd, Header, Data) then
    Error(Header.ErrorMsg);
  Writeln('Compression method: ', Header.CompressionMethod);
  Writeln('Compression info:   ', Header.CompressionInfo);
  Writeln('FCHECK:             ', Header.FCHECK);
  Writeln('FDICT:              ', Header.FDICT);
  Writeln('FLEVEL:             ', Header.FLEVEL);

  if Header.FDICT then
    Error('DICT is unsupported');
  if Header.CompressionMethod = ZLIB_CM_DEFLATE then begin
    Data := DumpZLibDeflateInfo(Data, DataEnd);
    Writeln('Uncompressed size: ', Dst - @DstArr[0]);
    OriginalAdler := SwapEndian(Cardinal(Pointer(Data)^));
    //DstAdler := 1;
    DstAdler := Adler32(@DstArr[0], Dst);
    //DstAdler := Adler32(@FWS[1], @FWS[1] + 4);
    //DstAdler := UpdateAdler32(DstAdler, OriginalData, OriginalData + 4);
    //DstAdler := UpdateAdler32(DstAdler, @DstArr[0], Dst);
    if DstAdler <> OriginalAdler then begin
      Error(PAnsiChar('Error: adler-32 hash does not match: got ' + HexStr(DstAdler, 8) + ', expected ' + HexStr(OriginalAdler, 8)));
    end else
      Writeln('Adler-32: ', HexStr(OriginalAdler, 8));
  end else
    Error('Error: unsupproted compression method');
end;

var
  FileName: AnsiString;
  Data: Pointer;
  Size: SizeUInt;
  F: file of byte;

begin
  //FileName := 'shapes.swf';
  FileName := 'worm0_01.swf';
  //FileName := 'test.txt.zz';
  if not ReadFileContent(FileName, 0, Data, Size) then begin
    Writeln(stderr, 'Cannot read ', FileName);
    Halt(1);
  end;
  OriginalData := Data;
  if (PByte(Data)^ = Ord('C')) and (PByte(Data + 1)^ = Ord('W')) and (PByte(Data + 2)^ = Ord('S')) then begin
    Inc(Data, 8);
    Dec(Size, 8);
  end;
  Writeln('Expected size: ', UInt32(Pointer(OriginalData + 4)^));
  DumpZLibInfo(Data, Data + Size);

  if ZLibDecompressAll(Data, Size, @DstArr[0], Length(DstArr), TempSize) then begin
    Dump(@DstArr[0], TempSize);
  end else
    Writeln('ZLibDecompressAll failed');

  {Assign(F, 'dump.swf');
  Rewrite(F);
  BlockWrite(F, FWS[1], 4);
  BlockWrite(F, (Data + 4)^, 4);
  BlockWrite(F, Pointer(@DstArr[0])^, Dst - @DstArr[0]);
  Close(F);}

  FreeMem(OriginalData);
end.
