
(********************************************************************)
(*                                                                  *)
(*  inflatefile.s7i   Uncompress data from a file with Inflate      *)
(*  Copyright (C) 2008, 2017  Thomas Mertes                         *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime Library is distributed in the hope that it    *)
(*  will be useful, but WITHOUT ANY WARRANTY; without even the      *)
(*  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *)
(*  PURPOSE.  See the GNU Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


include "bitdata.s7i";
include "bytedata.s7i";
include "inflate.s7i";


const proc: getNonCompressedBlock (inout file: inFile, inout string: uncompressed) is func
  local
    var integer: length is 0;
    var integer: nlength is 0;
  begin
    length := getUInt16Le(inFile);
    nlength := getUInt16Le(inFile);
    uncompressed &:= gets(inFile, length);
  end func;


const func integer: getLiteralOrLength (inout file: inFile, inout integer: bitPos) is func
  result
    var integer: literalOrLength is 0;
  begin
    literalOrLength := reverseBits[7][getBitsLsb(inFile, bitPos, 7)];
    if literalOrLength <= 2#0010111 then
      literalOrLength +:= 256;
    else
      literalOrLength <<:= 1;
      literalOrLength +:= getBitLsb(inFile, bitPos);
      if literalOrLength <= 2#10111111 then
        literalOrLength -:= 2#00110000;
      elsif literalOrLength <= 2#11000111 then
        literalOrLength +:= 280 - 2#11000000;
      else
        literalOrLength <<:= 1;
        literalOrLength +:= getBitLsb(inFile, bitPos);
        literalOrLength -:= 2#110010000 - 144;
      end if;
    end if;
  end func;


const func integer: getDistance (inout file: inFile, inout integer: bitPos) is
  return reverseBits[5][getBitsLsb(inFile, bitPos, 5)];


const func integer: decodeLength (inout file: inFile, inout integer: bitPos,
    in integer: code) is func
  result
    var integer: length is 0;
  begin
    if code <= 264 then
      length := code - 254;
    elsif code <= 268 then
      length := 11 + ((code - 265) << 1) + getBitLsb(inFile, bitPos);
    elsif code <= 272 then
      length := 19 + ((code - 269) << 2) + getBitsLsb(inFile, bitPos, 2);
    elsif code <= 276 then
      length := 35 + ((code - 273) << 3) + getBitsLsb(inFile, bitPos, 3);
    elsif code <= 280 then
      length := 67 + ((code - 277) << 4) + getBitsLsb(inFile, bitPos, 4);
    elsif code <= 284 then
      length := 131 + ((code - 281) << 5) + getBitsLsb(inFile, bitPos, 5);
    elsif code = 285 then
      length := 258;
    else
      raise RANGE_ERROR;
    end if;
  end func;


const func integer: decodeDistance (inout file: inFile, inout integer: bitPos,
    in integer: code) is func
  result
    var integer: distance is 0;
  begin
    case code of
      when {0, 1, 2, 3}:
        distance := succ(code);
      when {4, 5}:
        distance := 5 + ((code - 4) << 1) + getBitLsb(inFile, bitPos);
      when {6, 7}:
        distance := 9 + ((code - 6) << 2) + getBitsLsb(inFile, bitPos, 2);
      when {8, 9}:
        distance := 17 + ((code - 8) << 3) + getBitsLsb(inFile, bitPos, 3);
      when {10, 11}:
        distance := 33 + ((code - 10) << 4) + getBitsLsb(inFile, bitPos, 4);
      when {12, 13}:
        distance := 65 + ((code - 12) << 5) + getBitsLsb(inFile, bitPos, 5);
      when {14, 15}:
        distance := 129 + ((code - 14) << 6) + getBitsLsb(inFile, bitPos, 6);
      when {16, 17}:
        distance := 257 + ((code - 16) << 7) + getBitsLsb(inFile, bitPos, 7);
      when {18, 19}:
        distance := 513 + ((code - 18) << 8) + getBitsLsb(inFile, bitPos, 8);
      when {20, 21}:
        distance := 1025 + ((code - 20) << 9) + getBitsLsb(inFile, bitPos, 9);
      when {22, 23}:
        distance := 2049 + ((code - 22) << 10) + getBitsLsb(inFile, bitPos, 10);
      when {24, 25}:
        distance := 4097 + ((code - 24) << 11) + getBitsLsb(inFile, bitPos, 11);
      when {26, 27}:
        distance := 8193 + ((code - 26) << 12) + getBitsLsb(inFile, bitPos, 12);
      when {28, 29}:
        distance := 16385 + ((code - 28) << 13) + getBitsLsb(inFile, bitPos, 13);
      otherwise:
        raise RANGE_ERROR;
    end case;
  end func;


const proc: decodeFixedHuffmanCodes (inout file: compressed, inout integer: bitPos,
    inout string: uncompressed) is func
  local
    var integer: literalOrLength is 0;
    var integer: length is 0;
    var integer: distance is 0;
    var integer: number is 0;
    var integer: nextPos is 0;
  begin
    literalOrLength := getLiteralOrLength(compressed, bitPos);
    while literalOrLength <> 256 do
      if literalOrLength < 256 then
        uncompressed &:= chr(literalOrLength);
      else
        length := decodeLength(compressed, bitPos, literalOrLength);
        distance := getDistance(compressed, bitPos);
        distance := decodeDistance(compressed, bitPos, distance);
        if length > distance then
          nextPos := succ(length(uncompressed));
          uncompressed &:= "\0;" mult length;
          for number range nextPos to nextPos + length - 1 do
            uncompressed @:= [number] uncompressed[number - distance];
          end for;
        else # hopefully length(uncompressed) >= distance holds
          uncompressed &:= uncompressed[succ(length(uncompressed)) - distance len length];
        end if;
      end if;
      literalOrLength := getLiteralOrLength(compressed, bitPos);
    end while;
  end func;


const func integer: getHuffmanValue (inout file: inFile, inout integer: bitPos,
    in conversionTableType: conversionTable) is func
  result
    var integer: huffmanValue is 0;
  local
    var integer: bitLength is 1;
    var integer: code is 0;
  begin
    repeat
      code <<:= 1;
      code +:= getBitLsb(inFile, bitPos);
      incr(bitLength);
    until code < conversionTable[bitLength].limit;
    huffmanValue := conversionTable[pred(bitLength)].table[code];
  end func;


const proc: decodeDynamicHuffmanCodes (inout file: compressed, inout integer: bitPos,
    inout string: uncompressed) is func
  local
    const array integer: codeLengthAlphabet is []
        (16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15);
    var integer: hlit is 0;
    var integer: hdist is 0;
    var integer: hclen is 0;
    var array integer: lengthCodeLengths is [0 .. -1] times 0;
    var array integer: combinedCodeLengths is [0 .. -1] times 0;
    var array integer: literalOrLengthCodeLengths is [0 .. -1] times 0;
    var array integer: distanceCodeLengths is [0 .. -1] times 0;
    var integer: number is 0;
    var integer: codeLength is 0;
    var integer: factor is 0;
    var conversionTableType: lengthConversionTable is conversionTableType.value;
    var conversionTableType: literalOrLengthConversionTable is conversionTableType.value;
    var conversionTableType: distanceConversionTable is conversionTableType.value;
    var integer: literalOrLength is 0;
    var integer: length is 0;
    var integer: distance is 0;
    var integer: nextPos is 0;
  begin
    hlit := getBitsLsb(compressed, bitPos, 5);
    hdist := getBitsLsb(compressed, bitPos, 5);
    hclen := getBitsLsb(compressed, bitPos, 4);
    lengthCodeLengths := [0 .. 18] times 0;
    for number range 1 to hclen + 4 do
      lengthCodeLengths[codeLengthAlphabet[number]] := getBitsLsb(compressed, bitPos, 3);
    end for;
    lengthConversionTable := computeConversionTable(lengthCodeLengths);
    number := 1;
    while number <= hlit + 257 + hdist + 1 do
      codeLength := getHuffmanValue(compressed, bitPos, lengthConversionTable);
      if codeLength <= 15 then
        combinedCodeLengths &:= codeLength;
        incr(number);
      elsif codeLength = 16 then
        factor := getBitsLsb(compressed, bitPos, 2) + 3;
        combinedCodeLengths &:= factor times
            combinedCodeLengths[maxIdx(combinedCodeLengths)];
        number +:= factor;
      elsif codeLength = 17 then
        factor := getBitsLsb(compressed, bitPos, 3) + 3;
        combinedCodeLengths &:= factor times 0;
        number +:= factor;
      else # codeLength = 18
        factor := getBitsLsb(compressed, bitPos, 7) + 11;
        combinedCodeLengths &:= factor times 0;
        number +:= factor;
      end if;
    end while;
    literalOrLengthCodeLengths := combinedCodeLengths[.. hlit + 256];
    distanceCodeLengths := combinedCodeLengths[hlit + 257 .. ];
    literalOrLengthConversionTable := computeConversionTable(literalOrLengthCodeLengths);
    distanceConversionTable := computeConversionTable(distanceCodeLengths);
    literalOrLength := getHuffmanValue(compressed, bitPos, literalOrLengthConversionTable);
    while literalOrLength <> 256 do
      if literalOrLength < 256 then
        uncompressed &:= chr(literalOrLength);
      else
        length := decodeLength(compressed, bitPos, literalOrLength);
        distance := getHuffmanValue(compressed, bitPos, distanceConversionTable);
        distance := decodeDistance(compressed, bitPos, distance);
        if length > distance then
          nextPos := succ(length(uncompressed));
          uncompressed &:= "\0;" mult length;
          for number range nextPos to nextPos + length - 1 do
            uncompressed @:= [number] uncompressed[number - distance];
          end for;
        else # hopefully length(uncompressed) >= distance holds
          uncompressed &:= uncompressed[succ(length(uncompressed)) - distance len length];
        end if;
      end if;
      literalOrLength := getHuffmanValue(compressed, bitPos, literalOrLengthConversionTable);
    end while;
  end func;


const proc: processCompressedBlock (inout file: compressed, inout integer: bitPos,
    inout string: uncompressed, inout boolean: bfinal) is func
  local
    var integer: btype is 0;
  begin
    bfinal := odd(getBitLsb(compressed, bitPos));
    btype := getBitsLsb(compressed, bitPos, 2);
    case btype of
      when {0}:
        getNonCompressedBlock(compressed, uncompressed);
        bitPos := 8;  # The least significant bit in the next byte.
      when {1}:
        decodeFixedHuffmanCodes(compressed, bitPos, uncompressed);
      when {2}:
        decodeDynamicHuffmanCodes(compressed, bitPos, uncompressed);
      otherwise:
        raise RANGE_ERROR;
    end case;
  end func;


(**
 *  Decompress a file that was compressed with DEFLATE.
 *  DEFLATE is a compression algorithm that uses a combination of
 *  the LZ77 algorithm and Huffman coding.
 *  @return the uncompressed string.
 *  @exception RANGE_ERROR If ''compressed'' is not in DEFLATE format.
 *)
const func string: inflate (inout file: compressed) is func
  result
    var string: uncompressed is "";
  local
    var integer: bitPos is 0;
    var boolean: bfinal is FALSE;
  begin
    compressed.bufferChar := getc(compressed);
    repeat
      processCompressedBlock(compressed, bitPos, uncompressed, bfinal);
    until bfinal;
  end func;
