#include <iostream>

#define DUMP_DATA     0
#define DUMP_PREVIEWS (DUMP_DATA && 1)
#define DUMP_IMAGES   (DUMP_DATA && 0)

extern "C"
{
#include "json.h"
}

#include "adler_32.h"
#include "crc_32.h"

struct filereader_t
{
  filereader_t(const char *filename)
    : m_filename(filename)
  {
  }

  ~filereader_t()
  {
    if ( m_buf != nullptr )
      delete [] m_buf;
  }

  bool open()
  {
    FILE *fp = fopen(m_filename, "rb");
    if ( fp == nullptr )
      return false;
    fseek(fp, 0, SEEK_END);
    m_buflen = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    m_buf = new uint8_t[m_buflen];
    fread(m_buf, m_buflen, 1, fp);
    fclose(fp);

    reset();

    return true;
  }

  void reset()
  {
    m_ptr = m_buf;
  }

  void skip_crlf()
  {
    m_ptr += 2;
  }

  uint8_t get_u8()
  {
    return *m_ptr++;
  }

  uint16_t get_be16()
  {
    uint16_t v0 = get_u8();
    uint16_t v1 = get_u8();
    return (v0 << 8) | v1;
  }

  uint32_t get_be32()
  {
    uint32_t v0 = get_be16();
    uint32_t v1 = get_be16();
    return (v0 << 16) | v1;
  }

  uint16_t get_le16()
  {
    uint16_t v0 = get_u8();
    uint16_t v1 = get_u8();
    return (v1 << 8) | v0;
  }

  uint32_t get_le32()
  {
    uint32_t v0 = get_le16();
    uint32_t v1 = get_le16();
    return (v1 << 16) | v0;
  }

  uint8_t *get_buf(size_t buflen)
  {
    uint8_t *buf = m_ptr;
    m_ptr += buflen;
    return buf;
  }

  uint8_t *get_ptr()
  {
    return m_ptr;
  }

  size_t get_offset()
  {
    return m_ptr - m_buf;
  }

  size_t get_bytesleft()
  {
    return m_buflen - get_offset();
  }

private:
  const char *m_filename;
  uint8_t *m_buf = nullptr;
  size_t m_buflen;
  uint8_t *m_ptr;
};

struct json_ctx_cpp_t
{
  json_ctx_cpp_t()
  {
    json_ctx_start(&m_ctx, 1);
  }

  ~json_ctx_cpp_t()
  {
    json_ctx_free(&m_ctx);
  }

  /* object */
  size_t object_length(json_t *jso)
  {
    return json_object_length(jso);
  }

  json_t *object_new()
  {
    return json_object_new(&m_ctx);
  }

  int object_del(json_t *jso, const char *key)
  {
    return json_object_del(jso, key);
  }

  int object_add(json_t *jso, const char *key, json_t *jval)
  {
    return json_object_add(jso, key, jval);
  }

  json_t *object_get(json_t *jso, const char *key)
  {
    return json_object_get(jso, key);
  }

  int object_done(json_t *jso)
  {
    return json_object_done(&m_ctx, jso);
  }

  /* array */
  json_t *array_new(size_t len)
  {
    return json_array_new(&m_ctx, len);
  }

  json_t *array_get(json_t *jso, size_t idx)
  {
    return json_array_get(jso, idx);
  }

  int array_set(json_t *jso, size_t idx, json_t *jval)
  {
    return json_array_set(jso, idx, jval);
  }

  /* u8_array */
  json_t *buffer_new(const uint8_t *buf, size_t len)
  {
    json_t *jso = json_u8_array_new(&m_ctx, len);
    json_set_pflags(jso, JSON_PFLAGS_NO_LF);
    memcpy(jso->u8_array, buf, len);
    return jso;
  }

  /* string */
  json_t *string_new(const uint8_t *str)
  {
    return json_string_new(&m_ctx, (const char *) str);
  }

  /* int */
  json_t *int_new(int64_t val)
  {
    return json_int_new(&m_ctx, val);
  }

  /* float */
  json_t *float_new(uint32_t val)
  {
    union {
        uint32_t u32;
        float flt;
    } u;
    u.u32 = val;
    return json_float_new(&m_ctx, u.flt);
  }

  /* u8_2darray */
  json_t *u8_2darray_new(const uint8_t *buf, int16_t width, int16_t height)
  {
    return json_u8_2darray_new(&m_ctx, width, height, buf);
  }

  /* u16_2darray */
  json_t *u16_2darray_new(const uint8_t *buf, int16_t width, int16_t height)
  {
    return json_u16_2darray_new(&m_ctx, width, height, (const uint16_t *) buf);
  }

private:
  json_ctx_t m_ctx;
};

class BufferReader
{
public:
  BufferReader(const uint8_t *buf, size_t buflen)
    : m_ptr(buf),
      m_ptr_end(buf + buflen)
  {
  }

  bool readUInt16LE(uint16_t *p_u16)
  {
    // don't overrun buffer
    if ( (m_ptr + 2) > m_ptr_end )
      return false;

    // read Uint16LE
    *p_u16 = (m_ptr[0] | (m_ptr[1] << 8));
    m_ptr += 2;

    return true;
  }

private:
  const uint8_t *m_ptr;
  const uint8_t *m_ptr_end;
};

class RGBBuffer
{
public:
  RGBBuffer(uint32_t ResolutionX, uint32_t ResolutionY)
    : m_ResolutionX(ResolutionX),
      m_ResolutionY(ResolutionY),
      m_buflen(3 * ResolutionX * ResolutionY)
  {
    m_buf = (uint8_t *) malloc(m_buflen);
    m_ptr = m_buf;
    m_ptr_end = m_ptr + m_buflen;
  }

  ~RGBBuffer()
  {
    free(m_buf);
  }

  uint32_t getResolutionX() const
  {
    return m_ResolutionX;
  }

  uint32_t getResolutionY() const
  {
    return m_ResolutionY;
  }

  size_t get_buflen() const
  {
    return m_buflen;
  }

  uint8_t *get_buf() const
  {
    return m_buf;
  }

  bool writeRGB(uint8_t pixelR, uint8_t pixelG, uint8_t pixelB, uint16_t runLength)
  {
    // don't overrun buffer
    if ( (m_ptr + (3 * runLength)) > m_ptr_end )
      return false;

    // write pixels
    uint8_t *ptr = m_ptr;
    while ( runLength-- )
    {
      ptr[0] = pixelR;
      ptr[1] = pixelG;
      ptr[2] = pixelB;
      ptr += 3;
    }
    m_ptr = ptr;

    return true;
  }

  void decodeRleRGB15(BufferReader &reader)
  {
    uint16_t u16;
    while ( reader.readUInt16LE(&u16) )
    {
      // printf("[0] %04X\n", u16);

      // RRRR RGGG GGfB BBBB (f is the hasRunLength flag)
      // 1111 1111 0000 0000
      // 7654 3210 7654 3210
      bool hasRunLength = ((u16 & 0x20) != 0);
      uint8_t pixelR = (u16 >> 11) << 3;
      uint8_t pixelG = (u16 >>  6) << 3;
      uint8_t pixelB = (u16 >>  0) << 3;

      uint16_t runLength = 1;
      if ( hasRunLength )
      {
        if ( !reader.readUInt16LE(&u16) )
          break;

        // xx11 rrrr rrrr rrrr (r is the run length minus 1, x is wasted bits)
        // 1111 1111 0000 0000
        // 7654 3210 7654 3210
        runLength = (u16 & 0x0FFF) + 1;

        // printf("[1] %04X %d\n", u16, runLength);
      }

      // write `runLength` pixels
      if ( !writeRGB(pixelR, pixelG, pixelB, runLength) )
        break;
    }
  }

private:
  uint32_t m_ResolutionX;
  uint32_t m_ResolutionY;
  size_t m_buflen;
  uint8_t *m_buf;
  uint8_t *m_ptr;
  uint8_t *m_ptr_end;
};

static bool writePPM(const char *fname, uint8_t *buf, size_t buflen, uint32_t ResolutionX, uint32_t ResolutionY, const char *filetype)
{
  // open output file
  FILE *fp = fopen(fname, "wb");
  if ( fp == nullptr )
    return false;

  // write header
  fprintf(fp, "%s\n", filetype);
  fprintf(fp, "%u %u\n", ResolutionX, ResolutionY);
  fprintf(fp, "255\n");

  // write data
  fwrite(buf, buflen, 1, fp);

  // close output file
  fclose(fp);
  return true;
}

static json_t *parse_Image(json_ctx_cpp_t &ctx, filereader_t &file, const char *fname)
{
  json_t *jimage = ctx.object_new();

  ctx.object_add(jimage, "ResolutionX", ctx.int_new(file.get_le32()));
  ctx.object_add(jimage, "ResolutionY", ctx.int_new(file.get_le32()));
  ctx.object_add(jimage, "ImageOffset", ctx.int_new(file.get_le32()));
  ctx.object_add(jimage, "ImageLength", ctx.int_new(file.get_le32()));
  ctx.object_add(jimage, "Unknown1",    ctx.int_new(file.get_le32()));
  ctx.object_add(jimage, "Unknown2",    ctx.int_new(file.get_le32()));
  ctx.object_add(jimage, "Unknown3",    ctx.int_new(file.get_le32()));
  ctx.object_add(jimage, "Unknown4",    ctx.int_new(file.get_le32()));
  uint32_t ImageLength = json_int_val(ctx.object_get(jimage, "ImageLength"));
#if DUMP_DATA
  ctx.object_add(jimage, "data",        ctx.buffer_new(file.get_buf(ImageLength), ImageLength));
#else
  ctx.object_add(jimage, "data",        ctx.int_new(adler_32(file.get_buf(ImageLength), ImageLength)));
#endif
  ctx.object_done(jimage);

  if ( DUMP_PREVIEWS )
  {
    json_t *jdata = ctx.object_get(jimage, "data");
    const uint8_t *data = jdata->u8_array;
    BufferReader reader(data, ImageLength);

    uint32_t ResolutionX = json_int_val(ctx.object_get(jimage, "ResolutionX"));
    uint32_t ResolutionY = json_int_val(ctx.object_get(jimage, "ResolutionY"));
    RGBBuffer rgb(ResolutionX, ResolutionY);
    rgb.decodeRleRGB15(reader);
    uint8_t *dst = rgb.get_buf();
    writePPM(fname, dst, ResolutionX * ResolutionY * 3, ResolutionX, ResolutionY, "P6");
  }

  return jimage;
}

static json_t *parse_printParameters(json_ctx_cpp_t &ctx, filereader_t &file)
{
  json_t *jprintParameters = ctx.object_new();

  ctx.object_add(jprintParameters, "BottomLiftHeight",    ctx.float_new(file.get_le32()));
  ctx.object_add(jprintParameters, "BottomLiftSpeed",     ctx.float_new(file.get_le32()));
  ctx.object_add(jprintParameters, "LiftHeight",          ctx.float_new(file.get_le32()));
  ctx.object_add(jprintParameters, "LiftSpeed",           ctx.float_new(file.get_le32()));
  ctx.object_add(jprintParameters, "RetractSpeed",        ctx.float_new(file.get_le32()));
  ctx.object_add(jprintParameters, "VolumeMl",            ctx.float_new(file.get_le32()));
  ctx.object_add(jprintParameters, "WeightG",             ctx.float_new(file.get_le32()));
  ctx.object_add(jprintParameters, "CostDollars",         ctx.float_new(file.get_le32()));
  ctx.object_add(jprintParameters, "BottomLightOffDelay", ctx.float_new(file.get_le32()));
  ctx.object_add(jprintParameters, "LightOffDelay",       ctx.float_new(file.get_le32()));
#if 1
  ctx.object_add(jprintParameters, "BottomLayerCount",    ctx.int_new  (file.get_le32())); // NOTE Halot Box
#else
  ctx.object_add(jprintParameters, "BottomLayerCount",    ctx.float_new(file.get_le32())); // NOTE CHITUBOX
#endif
  ctx.object_add(jprintParameters, "ExposureTime",        ctx.float_new(file.get_le32()));
  ctx.object_add(jprintParameters, "BottomExposureTime",  ctx.float_new(file.get_le32()));
  ctx.object_add(jprintParameters, "Padding1",            ctx.int_new  (file.get_le32()));
  ctx.object_add(jprintParameters, "Padding2",            ctx.int_new  (file.get_le32()));
  ctx.object_add(jprintParameters, "Padding3",            ctx.int_new  (file.get_le32()));
  ctx.object_add(jprintParameters, "Padding4",            ctx.int_new  (file.get_le32()));
  ctx.object_done(jprintParameters);

  return jprintParameters;
}

static json_t *parse_sliceParameters(json_ctx_cpp_t &ctx, filereader_t &file)
{
  json_t *jsliceParameters = ctx.object_new();

  ctx.object_add(jsliceParameters, "BottomLiftHeight2",        ctx.float_new(file.get_le32()));
  ctx.object_add(jsliceParameters, "BottomLiftSpeed2",         ctx.float_new(file.get_le32()));
  ctx.object_add(jsliceParameters, "LiftHeight2",              ctx.float_new(file.get_le32()));
  ctx.object_add(jsliceParameters, "LiftSpeed2",               ctx.float_new(file.get_le32()));
  ctx.object_add(jsliceParameters, "RetractHeight2",           ctx.float_new(file.get_le32()));
  ctx.object_add(jsliceParameters, "RetractSpeed2",            ctx.float_new(file.get_le32()));
  ctx.object_add(jsliceParameters, "RestTimeAfterLift",        ctx.float_new(file.get_le32()));
  ctx.object_add(jsliceParameters, "PerLayerSettings",         ctx.int_new  (file.get_le32()));
  ctx.object_add(jsliceParameters, "ModifiedTimestampMinutes", ctx.int_new  (file.get_le32())); // NOTE timestamp / 60
  ctx.object_add(jsliceParameters, "AntiAliasLevel",           ctx.int_new  (file.get_le32()));
  ctx.object_add(jsliceParameters, "SoftwareVersion",          ctx.float_new(file.get_le32()));
  ctx.object_add(jsliceParameters, "RestTimeAfterRetract",     ctx.float_new(file.get_le32()));
  ctx.object_add(jsliceParameters, "RestTimerBeforeLift",      ctx.float_new(file.get_le32()));
  ctx.object_add(jsliceParameters, "RestTimeAfterLift2",       ctx.float_new(file.get_le32()));
  ctx.object_add(jsliceParameters, "BottomExposureTime",       ctx.float_new(file.get_le32()));
  ctx.object_add(jsliceParameters, "NormalExposureTime",       ctx.float_new(file.get_le32()));
  ctx.object_add(jsliceParameters, "TransitionLayerCount",     ctx.int_new  (file.get_le32()));
  ctx.object_add(jsliceParameters, "Padding2",                 ctx.int_new  (file.get_le32()));
  ctx.object_add(jsliceParameters, "Padding3",                 ctx.int_new  (file.get_le32()));
  ctx.object_done(jsliceParameters);

  return jsliceParameters;
}

static json_t *parse_LayerDefinition(json_ctx_cpp_t &ctx, filereader_t &file)
{
  json_t *jlayerDef = ctx.object_new();

  ctx.object_add(jlayerDef, "PositionZ",        ctx.float_new(file.get_le32()));
  ctx.object_add(jlayerDef, "ExposureTime",     ctx.float_new(file.get_le32()));
  ctx.object_add(jlayerDef, "LightOffSeconds",  ctx.float_new(file.get_le32()));
  ctx.object_add(jlayerDef, "DataAddress",      ctx.int_new  (file.get_le32()));
  ctx.object_add(jlayerDef, "DataSize",         ctx.int_new  (file.get_le32()));
  ctx.object_add(jlayerDef, "DataType",         ctx.int_new  (file.get_le32()));
  ctx.object_add(jlayerDef, "CentroidDistance", ctx.int_new  (file.get_le32()));
  ctx.object_add(jlayerDef, "totalArea",        ctx.int_new  (file.get_le32()));
  ctx.object_add(jlayerDef, "Unknown3",         ctx.int_new  (file.get_le32()));
  ctx.object_add(jlayerDef, "Unknown4",         ctx.int_new  (file.get_le32()));
  ctx.object_done(jlayerDef);

  return jlayerDef;
}

static void decode_slice(const uint8_t *ptr, uint32_t data_size, uint32_t frameNum)
{
  uint8_t *dst = (uint8_t *) malloc(33177600);
  uint8_t *dst_ptr = dst;

  const uint8_t *ptr_end = ptr + data_size;

  // uint32_t pixelCount = 0;
  while ( ptr < ptr_end )
  {
    uint32_t runLength = 1;
    uint8_t byte0 = *ptr++;
    uint8_t pixel = (byte0 & ~0x80) << 1;
    if ( (byte0 & 0x80) != 0 )
    {
      uint8_t byte1 = *ptr++;
      switch ( byte1 & 0xE0 )
      {
      case 0x00: // 000
      case 0x20: // 001
      case 0x40: // 010
      case 0x60: // 011
        runLength = byte1;
        break;
      case 0x80: // 100
      case 0xA0: // 101
        runLength = byte1 & ~0x80;
        runLength = (runLength << 8) | *ptr++;
        break;
      case 0xC0: // 110
        runLength = byte1 & ~0xC0;
        runLength = (runLength << 8) | *ptr++;
        runLength = (runLength << 8) | *ptr++;
        break;
      case 0xE0: // 111
        runLength = byte1 & ~0xE0;
        runLength = (runLength << 8) | *ptr++;
        runLength = (runLength << 8) | *ptr++;
        runLength = (runLength << 8) | *ptr++;
        break;
      }
    }
    // pixelCount += runLength;
    while ( runLength-- )
      *dst_ptr++ = pixel;
  }
  // printf("pixelCount: %d\n", pixelCount);

  // 7680 x 4320 = 33177600

  if ( DUMP_IMAGES )
  {
    char fname[0x100];
    sprintf(fname, "out/frame%04d.ppm", frameNum);
    writePPM(fname, dst, 33177600, 7680, 4320, "P5");
  }

  free(dst);
}

static json_t *parse_Layer(json_ctx_cpp_t &ctx, filereader_t &file, json_t *jlayerDef)
{
  json_t *jlayer = ctx.object_new();

  size_t real_pos = file.get_offset();
  size_t DataAddress = json_int_val(ctx.object_get(jlayerDef, "DataAddress"));
  if ( real_pos != DataAddress )
    printf("DataAddress %zu expected %zu\n", real_pos, DataAddress);

  ctx.object_add(jlayer, "LiftHeight",           ctx.float_new(file.get_le32()));
  ctx.object_add(jlayer, "LiftSpeed",            ctx.float_new(file.get_le32()));
  ctx.object_add(jlayer, "LiftHeight2",          ctx.float_new(file.get_le32()));
  ctx.object_add(jlayer, "LiftSpeed2",           ctx.float_new(file.get_le32()));
  ctx.object_add(jlayer, "RetractSpeed",         ctx.float_new(file.get_le32()));
  ctx.object_add(jlayer, "RetractHeight2",       ctx.float_new(file.get_le32()));
  ctx.object_add(jlayer, "RetractSpeed2",        ctx.float_new(file.get_le32()));
  ctx.object_add(jlayer, "RestTimeBeforeLift",   ctx.float_new(file.get_le32()));
  ctx.object_add(jlayer, "RestTimeAfterLift",    ctx.float_new(file.get_le32()));
  ctx.object_add(jlayer, "RestTimeAfterRetract", ctx.float_new(file.get_le32()));
  ctx.object_add(jlayer, "LightPWM",             ctx.float_new(file.get_le32()));
  uint32_t DataSize = json_int_val(ctx.object_get(jlayerDef, "DataSize")) - (11 * 4);
#if DUMP_DATA
  ctx.object_add(jlayer, "data",                 ctx.buffer_new(file.get_buf(DataSize), DataSize));
#else
  ctx.object_add(jlayer, "data",                 ctx.int_new(adler_32(file.get_buf(DataSize), DataSize)));
#endif
  ctx.object_done(jlayer);

  return jlayer;
}

static void parse_cxdlpv4(filereader_t &file)
{
  json_ctx_cpp_t ctx;
  json_t *jroot;

  const uint8_t *orig_ptr = file.get_ptr();

  jroot = ctx.object_new();

  ctx.object_add(jroot, "fileMagic",                         ctx.string_new     (file.get_buf(file.get_be32())));
  ctx.object_add(jroot, "versionNumber",                     ctx.int_new        (file.get_be16()));
  ctx.object_add(jroot, "model",                             ctx.string_new     (file.get_buf(file.get_be32())));

  // machine information
  ctx.object_add(jroot, "ResolutionX",                       ctx.int_new        (file.get_le16()));
  ctx.object_add(jroot, "ResolutionY",                       ctx.int_new        (file.get_le16()));
  ctx.object_add(jroot, "BedSizeX",                          ctx.float_new      (file.get_le32()));
  ctx.object_add(jroot, "BedSizeY",                          ctx.float_new      (file.get_le32()));
  ctx.object_add(jroot, "BedSizeZ",                          ctx.float_new      (file.get_le32()));
  ctx.object_add(jroot, "TotalHeightMilimeter",              ctx.float_new      (file.get_le32()));
  ctx.object_add(jroot, "LayerHeightMilimeter",              ctx.float_new      (file.get_le32()));
  ctx.object_add(jroot, "BottomLayersCount",                 ctx.int_new        (file.get_le32()));
  ctx.object_add(jroot, "PreviewLargeOffsetAddress",         ctx.int_new        (file.get_le32()));
  ctx.object_add(jroot, "LayersDefinitionOffsetAddress",     ctx.int_new        (file.get_le32()));
  ctx.object_add(jroot, "LayerCount",                        ctx.int_new        (file.get_le32()));
  ctx.object_add(jroot, "PreviewSmallOffsetAddress",         ctx.int_new        (file.get_le32()));
  ctx.object_add(jroot, "PrintTime",                         ctx.int_new        (file.get_le32()));
  ctx.object_add(jroot, "ProjectorType",                     ctx.int_new        (file.get_le32()));
  ctx.object_add(jroot, "PrintParametersOffsetAddress",      ctx.int_new        (file.get_le32()));
  ctx.object_add(jroot, "PrintParametersSize",               ctx.int_new        (file.get_le32()));
  ctx.object_add(jroot, "AntiAliasLevel",                    ctx.int_new        (file.get_le32()));
  ctx.object_add(jroot, "LightPWM",                          ctx.int_new        (file.get_le16()));
  ctx.object_add(jroot, "BottomLightPWM",                    ctx.int_new        (file.get_le16()));
  ctx.object_add(jroot, "m_EncryptionKey",                   ctx.int_new        (file.get_le32()));
  ctx.object_add(jroot, "SlicerOffset",                      ctx.int_new        (file.get_le32()));
  ctx.object_add(jroot, "SlicerSize",                        ctx.int_new        (file.get_le32()));

  // thumbnail
  json_t *jthumbnail = parse_Image(ctx, file, "out/thumbnail.ppm");
  ctx.object_add(jroot, "Thumbnail", jthumbnail);

  // preview
  json_t *jpreview = parse_Image(ctx, file, "out/preview.ppm");
  ctx.object_add(jroot, "Preview", jpreview);

  // printParameters
  json_t *jprintParameters = parse_printParameters(ctx, file);
  ctx.object_add(jroot, "printParameters", jprintParameters);

  // sliceParameters
  json_t *jsliceParameters = parse_sliceParameters(ctx, file);
  ctx.object_add(jroot, "sliceParameters", jsliceParameters);

  // LayerDefinition
  uint32_t LayerCount = json_int_val(ctx.object_get(jroot, "LayerCount"));
  json_t *jlayerDefs = ctx.array_new(LayerCount);
  for ( uint32_t i = 0; i < LayerCount; i++ )
  {
    json_t *jlayerDef = parse_LayerDefinition(ctx, file);
    ctx.array_set(jlayerDefs, i, jlayerDef);
  }
  ctx.object_add(jroot, "layerDefinitions", jlayerDefs);

  // Layer
  json_t *jlayers = ctx.array_new(LayerCount);
  for ( uint32_t i = 0; i < LayerCount; i++ )
  {
    json_t *jlayerDef = ctx.array_get(jlayerDefs, i);
    json_t *jlayer = parse_Layer(ctx, file, jlayerDef);
    ctx.array_set(jlayers, i, jlayer);

    if ( DUMP_IMAGES )
    {
      uint32_t DataSize = json_int_val(ctx.object_get(jlayerDef, "DataSize")) - (11 * 4);
      json_t *jdata = ctx.object_get(jlayer, "data");
      const uint8_t *data = jdata->u8_array;
      decode_slice(data, DataSize, i);
    }
  }
  ctx.object_add(jroot, "layers", jlayers);

  size_t bytesleft = file.get_bytesleft();
  if ( bytesleft > 4 )
  {
    printf("skipping %zu garbage bytes at end of file\n", bytesleft);
    file.get_buf(bytesleft - 4);
  }

  size_t orig_len = file.get_offset();
  ctx.object_add(jroot, "crc32", ctx.int_new(file.get_be32()));

  /* close root object */
  ctx.object_done(jroot);

  /* dump json */
  json_fputs(stdout, jroot);

  /* calculate crc32 */
  crc_32_type result;
  result.process_bytes(orig_ptr, orig_len);
  uint32_t crc32_calc = result.checksum();
  uint32_t crc32_read = json_int_val(ctx.object_get(jroot, "crc32"));
  if ( crc32_calc != crc32_read )
    printf("crc32 read %08X calc %08X\n", crc32_read, crc32_calc);
}

int main(int argc, char *argv[])
{
  if ( argc < 2 )
  {
    std::cerr << "usage: %s <file.cxdlpv4>" << argv[0] << std::endl;
    return -1;
  }

  /* open input file */
  filereader_t file(argv[1]);
  if ( !file.open() )
  {
    std::cerr << "Could not open input file \"" << argv[1] << "\"" << std::endl;
    return -1;
  }

  parse_cxdlpv4(file);

  size_t bytesleft = file.get_bytesleft();
  if ( bytesleft != 0 )
    printf("bytes left at end of file: %zu\n", bytesleft);

  return 0;
}
