import 'util/archive_exception.dart';
import 'util/crc32.dart';
import 'util/input_stream.dart';
import 'zlib/inflate.dart';

/**
 * Decompress data with the gzip format decoder.
 */
class GZipDecoder {
  static const int SIGNATURE = 0x8b1f;
  static const int DEFLATE = 8;
  static const int FLAG_TEXT = 0x01;
  static const int FLAG_HCRC = 0x02;
  static const int FLAG_EXTRA = 0x04;
  static const int FLAG_NAME = 0x08;
  static const int FLAG_COMMENT = 0x10;

  List<int> decodeBytes(List<int> data, {bool verify: false}) {
    return decodeBuffer(new InputStream(data), verify: verify);
  }

  void decodeStream(dynamic input, dynamic output) {
    _readHeader(input);
    new Inflate.stream(input, output);
  }

  List<int> decodeBuffer(dynamic input, {bool verify: false}) {
    _readHeader(input);

    // Inflate
    List<int> buffer = new Inflate.buffer(input).getBytes();

    if (verify) {
      int crc = input.readUint32();
      int computedCrc = getCrc32(buffer);
      if (crc != computedCrc) {
        throw new ArchiveException('Invalid CRC checksum');
      }

      int size = input.readUint32();
      if (size != buffer.length) {
        throw new ArchiveException('Size of decompressed file not correct');
      }
    }

    return buffer;
  }

  void _readHeader(dynamic input) {
    // The GZip format has the following structure:
    // Offset   Length   Contents
    // 0      2 bytes  magic header  0x1f, 0x8b (\037 \213)
    // 2      1 byte   compression method
    //                  0: store (copied)
    //                  1: compress
    //                  2: pack
    //                  3: lzh
    //                  4..7: reserved
    //                  8: deflate
    // 3      1 byte   flags
    //                  bit 0 set: file probably ascii text
    //                  bit 1 set: continuation of multi-part gzip file, part number present
    //                  bit 2 set: extra field present
    //                  bit 3 set: original file name present
    //                  bit 4 set: file comment present
    //                  bit 5 set: file is encrypted, encryption header present
    //                  bit 6,7:   reserved
    // 4      4 bytes  file modification time in Unix format
    // 8      1 byte   extra flags (depend on compression method)
    // 9      1 byte   OS type
    // [
    //        2 bytes  optional part number (second part=1)
    // ]?
    // [
    //        2 bytes  optional extra field length (e)
    //       (e)bytes  optional extra field
    // ]?
    // [
    //          bytes  optional original file name, zero terminated
    // ]?
    // [
    //          bytes  optional file comment, zero terminated
    // ]?
    // [
    //       12 bytes  optional encryption header
    // ]?
    //          bytes  compressed data
    //        4 bytes  crc32
    //        4 bytes  uncompressed input size modulo 2^32

    int signature = input.readUint16();
    if (signature != SIGNATURE) {
      throw new ArchiveException('Invalid GZip Signature');
    }

    int compressionMethod = input.readByte();
    if (compressionMethod != DEFLATE) {
      throw new ArchiveException('Invalid GZip Compression Methos');
    }

    int flags = input.readByte();
    /*int fileModTime =*/ input.readUint32();
    /*int extraFlags =*/ input.readByte();
    /*int osType =*/ input.readByte();

    if (flags & FLAG_EXTRA != 0) {
      int t = input.readUint16();
      input.readBytes(t);
    }

    if (flags & FLAG_NAME != 0) {
      input.readString();
    }

    if (flags & FLAG_COMMENT != 0) {
      input.readString();
    }

    // just throw away for now
    if (flags & FLAG_HCRC != 0) {
      input.readUint16();
    }
  }
}
