// zfp::codec::zfp_base::header
class header : public zfp::array::header {
public:
  // serialization: construct header from array
  header(const zfp::array& a) :
    zfp::array::header(a),
    bit_rate(a.rate())
  {
    std::string error;

    // set up zfp stream and field for generating header
    bitstream* stream = stream_open(buffer, sizeof(buffer));
    zfp_stream* zfp = zfp_stream_open(stream);
    bit_rate = zfp_stream_set_rate(zfp, bit_rate, type, dimensionality(), zfp_true);
    if (zfp_stream_mode(zfp) > ZFP_MODE_SHORT_MAX)
      error = "zfp serialization supports only short headers";
    else {
      // set up field
      zfp_field* field = 0;
      switch (dimensionality()) {
        case 1:
          field = zfp_field_1d(0, type, nx);
          break;
        case 2:
          field = zfp_field_2d(0, type, nx, ny);
          break;
        case 3:
          field = zfp_field_3d(0, type, nx, ny, nz);
          break;
        case 4:
          field = zfp_field_4d(0, type, nx, ny, nz, nw);
          break;
        default:
          error = "zfp serialization supports only 1D, 2D, 3D, and 4D arrays";
          break;
      }

      if (field) {
        // write header to buffer
        size_t bits = zfp_write_header(zfp, field, ZFP_HEADER_FULL);
        if (bits != bit_size)
          error = "zfp header length does not match expected length";
        zfp_stream_flush(zfp);
        zfp_field_free(field);
      }
    }

    zfp_stream_close(zfp);
    stream_close(stream);

    if (!error.empty())
      throw zfp::exception(error);
  }

  // deserialization: construct header from memory buffer of optional size
  header(const void* data, size_t bytes = 0) :
    bit_rate(0)
  {
    std::string error;

    // ensure byte size matches
    if (bytes && bytes != byte_size)
      error = "zfp header length does not match expectations";
    else {
      // copy and parse header
      std::fill(buffer, buffer + word_size, 0);
      std::memcpy(buffer, data, byte_size);
      bitstream* stream = stream_open(buffer, sizeof(buffer));
      zfp_stream* zfp = zfp_stream_open(stream);
      zfp_field field;
      size_t bits = zfp_read_header(zfp, &field, ZFP_HEADER_FULL);
      if (!bits)
        error = "zfp header is corrupt";
      else if (bits != bit_size)
        error = "zfp deserialization supports only short headers";
      else if (zfp_stream_compression_mode(zfp) != zfp_mode_fixed_rate)
        error = "zfp deserialization supports only fixed-rate mode";
      else {
        // success; initialize fields
        type = field.type;
        nx = field.nx;
        ny = field.ny;
        nz = field.nz;
        nw = field.nw;
        bit_rate = double(zfp->maxbits) / (1u << (2 * dimensionality()));
      }
      zfp_stream_close(zfp);
      stream_close(stream);
    }

    // throw exception upon error
    if (!error.empty())
      throw zfp::exception(error);
  }

  virtual ~header() {}

  // rate in bits per value
  double rate() const { return bit_rate; }

  // header data
  const void* data() const { return buffer; }

  // header byte size
  size_t size_bytes(uint mask = ZFP_DATA_HEADER) const
  {
    size_t size = 0;
    if (mask & ZFP_DATA_META)
      size += sizeof(*this) - byte_size;
    if (mask & ZFP_DATA_HEADER)
      size += byte_size;
    return size;
  }

protected:
  // header size measured in bits, bytes, and 64-bit words
  static const size_t bit_size = ZFP_MAGIC_BITS + ZFP_META_BITS + ZFP_MODE_SHORT_BITS;
  static const size_t byte_size = (bit_size + CHAR_BIT - 1) / CHAR_BIT;
  static const size_t word_size = (byte_size + sizeof(uint64) - 1) / sizeof(uint64);

  using zfp::array::header::type;
  using zfp::array::header::nx;
  using zfp::array::header::ny;
  using zfp::array::header::nz;
  using zfp::array::header::nw;

  double bit_rate;          // array rate in bits per value
  uint64 buffer[word_size]; // header data
};