/* X3F_IO.C - Library for accessing X3F Files.
 * 
 * Copyright (c) 2008 - Roland Karlsson (roland@proxel.se)
 * BSD-style - see readme.txt
 * 
 */

#include "x3f_io.h"

#include <string.h>
#include <stdlib.h>
#include <math.h>


/* --------------------------------------------------------------------- */
/* Reading and writing - assuming little endian in the file              */
/* --------------------------------------------------------------------- */

static int x3f_get_2(FILE *f)
{
  /* Little endian file */
  return (getc(f)<<0) + (getc(f)<<8); 
}

static int x3f_get_4(FILE *f)
{
  /* Little endian file */
  return (getc(f)<<0) + (getc(f)<<8) + (getc(f)<<16) + (getc(f)<<24); 
}

static void x3f_set_2(FILE *f, uint16_t v)
{
  /* Little endian file */
  putc((v>>0)&0xff, f);
  putc((v>>8)&0xff, f);
}

static void x3f_set_4(FILE *f, uint32_t v)
{
  /* Little endian file */
  putc((v>>0)&0xff, f);
  putc((v>>8)&0xff, f);
  putc((v>>16)&0xff, f);
  putc((v>>24)&0xff, f);
}

#define FREE(P) do { free(P); (P) = NULL; } while (0)

#define GET2(_v) do {(_v) = x3f_get_2(I->input.file);} while (0)
#define GET4(_v) do {(_v) = x3f_get_4(I->input.file);} while (0)
#define GETN(_v,_s) fread(_v,1,_s,I->input.file)

#define PUT2(_v) x3f_set_2(I->output.file, _v)
#define PUT4(_v) x3f_set_4(I->output.file, _v)
#define PUTN(_v,_s) fwrite(_v,1,_s,I->output.file)

#define WRITE_TABLE(_T, _PUTX) \
do { \
  int _i; \
  if ((_T).size == 0) break; \
  for (_i = 0; _i < (_T).size; _i++) \
    _PUTX((_T).element[_i]); \
} while (0)

#define READ_TABLE(_T, _GETX, _NUM) \
do { \
  int _i; \
  (_T).size = (_NUM); \
  (_T).element = (void *)realloc((_T).element, \
                                 (_NUM)*sizeof((_T).element[0])); \
  for (_i = 0; _i < (_T).size; _i++) \
    _GETX((_T).element[_i]); \
} while (0)


/* --------------------------------------------------------------------- */
/* Allocating Huffman help data                                          */
/* --------------------------------------------------------------------- */

static void cleanup_huffman(x3f_huffman_t **HUFP)
{
  x3f_huffman_t *HUF = *HUFP;

  if (HUF == NULL) return;

  FREE(HUF->mapping.element);
  FREE(HUF->table.element);
  FREE(HUF->tree);
  FREE(HUF->row_offsets.element);
  FREE(HUF->rgb8.element);
  FREE(HUF->rgb16.element);
  FREE(HUF->x3rgb16.element);
  FREE(HUF);

  *HUFP = NULL;
}

static x3f_huffman_t *new_huffman(x3f_huffman_t **HUFP)
{
  x3f_huffman_t *HUF = (x3f_huffman_t *)calloc(1, sizeof(x3f_huffman_t));

  cleanup_huffman(HUFP);

  HUF->raw_bits = 0;
  HUF->table.size = 0;

  /* Set all not read data block pointers to NULL */
  HUF->mapping.size = 0;
  HUF->mapping.element = NULL;
  HUF->table.element = NULL;
  HUF->tree = NULL;
  HUF->row_offsets.size = 0;
  HUF->row_offsets.element = NULL;
  HUF->rgb8.size = 0;
  HUF->rgb8.element = NULL;
  HUF->rgb16.size = 0;
  HUF->rgb16.element = NULL;
  HUF->x3rgb16.size = 0;
  HUF->x3rgb16.element = NULL;

  *HUFP = HUF;

  return HUF;
}


/* --------------------------------------------------------------------- */
/* Creating a new x3f structure from file                                */
/* --------------------------------------------------------------------- */

static x3f_t *x3f_new()
{
  x3f_t *x3f = (x3f_t *)calloc(1, sizeof(x3f_t));

  x3f->header.identifier = X3F_FOVb;

  return x3f;
}

/* extern */ x3f_t *x3f_new_from_file(FILE *infile)
{
  x3f_t *x3f = x3f_new();
  x3f_info_t *I = NULL;
  x3f_header_t *H = NULL;
  x3f_directory_section_t *DS = NULL;
  int i, d;

  I = &x3f->info;
  I->error = NULL;
  I->input.file = infile;
  I->output.file = NULL;

  if (infile == NULL) {
    I->error = "No infile";
    return x3f;
  }

  /* Read file header */
  H = &x3f->header;
  fseek(infile, 0, SEEK_SET);
  GET4(H->identifier);
  GET4(H->version);
  GETN(H->unique_identifier, SIZE_UNIQUE_IDENTIFIER);
  GET4(H->mark_bits);
  GET4(H->columns);
  GET4(H->rows);
  GET4(H->rotation);
  if (H->version > X3F_VERSION_2_0) {
    GETN(H->white_balance, SIZE_WHITE_BALANCE);
    GETN(H->extended_types, NUM_EXT_DATA);
    for (i=0; i<NUM_EXT_DATA; i++)
      GET4(H->extended_data[i]);
  }

  /* Go to the beginning of the directory */
  fseek(infile, -4, SEEK_END);
  fseek(infile, x3f_get_4(infile), SEEK_SET);

  /* Read the directory header */
  DS = &x3f->directory_section;
  GET4(DS->identifier);
  GET4(DS->version);
  GET4(DS->num_directory_entries);

  if (DS->num_directory_entries > 0) {
    size_t size = DS->num_directory_entries * sizeof(x3f_directory_entry_t);
    DS->directory_entry = (x3f_directory_entry_t *)calloc(1, size);
  }

  /* Traverse the directory */
  for (d=0; d<DS->num_directory_entries; d++) { 
    x3f_directory_entry_t *DE = &DS->directory_entry[d];
    x3f_directory_entry_header_t *DEH = &DE->header;
    uint32_t save_dir_pos;

    /* Read the directory entry info */
    GET4(DE->input.offset);
    GET4(DE->input.size);

    DE->output.offset = 0;
    DE->output.size = 0;

    GET4(DE->type);

    /* Save current pos and go to the entry */
    save_dir_pos = ftell(infile);
    fseek(infile, DE->input.offset, SEEK_SET);

    /* Read the type independent part of the entry header */
    DEH = &DE->header;
    GET4(DEH->identifier);
    GET4(DEH->version);

    /* NOTE - the tests below could be made on DE->type instead */

    if (DEH->identifier == X3F_SECp) {
      x3f_property_list_t *PL = &DEH->data_subsection.property_list;

      /* Read the property part of the header */
      GET4(PL->num_properties);
      GET4(PL->character_format);
      GET4(PL->reserved);
      GET4(PL->total_length);

      /* Set all not read data block pointers to NULL */
      PL->entry = NULL;
    }

    if (DEH->identifier == X3F_SECi) {
      x3f_image_data_t *ID = &DEH->data_subsection.image_data;

      /* Read the image part of the header */
      GET4(ID->type);
      GET4(ID->format);
      ID->type_format = (ID->type << 16) + (ID->format);
      GET4(ID->columns);
      GET4(ID->rows);
      GET4(ID->row_stride);

      /* Set all not read data block pointers to NULL */
      ID->huffman = NULL;
      ID->data_type = DATA_EMPTY;
      ID->data = NULL;
    }

    if (DEH->identifier == X3F_SECc) {
      x3f_camf_t *CAMF = &DEH->data_subsection.camf;

      /* Read the CAMF part of the header */
      /* TODO: how do the camf area look */

      /* Set all not read data block pointers to NULL */
      CAMF->data = NULL;
    }

    /* Reset the file pointer back to the directory */
    fseek(infile, save_dir_pos, SEEK_SET);
  }

  return x3f;
}


/* --------------------------------------------------------------------- */
/* Pretty print the x3f structure                                        */
/* --------------------------------------------------------------------- */

static char x3f_id_buf[5] = {0,0,0,0,0};

static char *x3f_id(uint32_t id)
{
  x3f_id_buf[0] = (id>>0) & 0xff; 
  x3f_id_buf[1] = (id>>8) & 0xff; 
  x3f_id_buf[2] = (id>>16) & 0xff; 
  x3f_id_buf[3] = (id>>24) & 0xff; 

  return x3f_id_buf;
}

/* extern */ void x3f_print(x3f_t *x3f)
{
  int d;
  x3f_info_t *I = NULL;
  x3f_header_t *H = NULL;
  x3f_directory_section_t *DS = NULL;

  if (x3f == NULL) {
    printf("Null x3f\n");
    return;
  }

  I = &x3f->info;
  printf("info.\n");
  printf("  error = %s\n", I->error);
  printf("  input.\n");
  printf("    file = %p\n", I->input.file);
  printf("  output.\n");
  printf("    file = %p\n", I->output.file);

  H = &x3f->header;
  printf("header.\n");
  printf("  identifier        = %08x (%s)\n", H->identifier, x3f_id(H->identifier));
  printf("  version           = %08x\n", H->version);
  printf("  unique_identifier = %02x...\n", H->unique_identifier[0]);
  printf("  mark_bits         = %08x\n", H->mark_bits);
  printf("  columns           = %08x (%d)\n", H->columns, H->columns);
  printf("  rows              = %08x (%d)\n", H->rows, H->rows);
  printf("  rotation          = %08x (%d)\n", H->rotation, H->rotation);
  if (x3f->header.version > X3F_VERSION_2_0) {
    printf("  white_balance     = %s\n", H->white_balance);
    printf("  extended_types    = %02x%02x%02x%02x...\n",
           H->extended_types[0],
           H->extended_types[1],
           H->extended_types[2],
           H->extended_types[3]);
    printf("  extended_data     = %08x...\n", H->extended_data[0]);
  }

  DS = &x3f->directory_section;
  printf("directory_section.\n");
  printf("  identifier            = %08x (%s)\n", DS->identifier, x3f_id(DS->identifier));
  printf("  version               = %08x\n", DS->version);
  printf("  num_directory_entries = %08x\n", DS->num_directory_entries);
  printf("  directory_entry       = %p\n", DS->directory_entry);

  for (d=0; d<DS->num_directory_entries; d++) { 
    x3f_directory_entry_t *DE = &DS->directory_entry[d];
    x3f_directory_entry_header_t *DEH = &DE->header;

    printf("  directory_entry.\n");
    printf("    input.\n");
    printf("      offset = %08x\n", DE->input.offset);
    printf("      size   = %08x\n", DE->input.size);
    printf("    output.\n");
    printf("      offset = %08x\n", DE->output.offset);
    printf("      size   = %08x\n", DE->output.size);
    printf("    type     = %08x (%s)\n", DE->type, x3f_id(DE->type));
    DEH = &DE->header;
    printf("    header.\n");
    printf("      identifier = %08x (%s)\n", DEH->identifier, x3f_id(DEH->identifier));
    printf("      version    = %08x\n", DEH->version);

    /* NOTE - the tests below could be made on DE->type instead */

    if (DEH->identifier == X3F_SECp) {
      x3f_property_list_t *PL = &DEH->data_subsection.property_list;
      printf("      data_subsection.property_list.\n");
      printf("        num_properties   = %08x\n", PL->num_properties);
      printf("        character_format = %08x\n", PL->character_format);
      printf("        reserved         = %08x\n", PL->reserved);
      printf("        total_length     = %08x\n", PL->total_length);
      printf("        entry            = %p\n", PL->entry);
    }

    if (DEH->identifier == X3F_SECi) {
      x3f_image_data_t *ID = &DEH->data_subsection.image_data;
      x3f_huffman_t *HUF = ID->huffman;

      printf("      data_subsection.image_data.\n");
      printf("        type        = %08x\n", ID->type);
      printf("        format      = %08x\n", ID->format);
      printf("        type_format = %08x\n", ID->type_format);
      printf("        columns     = %08x (%d)\n",
             ID->columns, ID->columns);
      printf("        rows        = %08x (%d)\n",
             ID->rows, ID->rows);
      printf("        row_stride  = %08x (%d)\n",
             ID->row_stride, ID->row_stride);
      if (HUF == NULL) {
        printf("        huffman     = %p\n", HUF);
      } else {
        printf("        huffman->\n");
        printf("          raw_bits    = %x\n", HUF->raw_bits);
        printf("          mapping     = %x %p\n",
               HUF->mapping.size, HUF->mapping.element);
        printf("          table       = %x %p\n",
               HUF->table.size, HUF->table.element);
        printf("          tree        = %p\n", HUF->tree);
        printf("          row_offsets = %x %p\n",
               HUF->row_offsets.size, HUF->row_offsets.element);
        printf("          rgb8        = %x %p\n",
               HUF->rgb8.size, HUF->rgb8.element);
        printf("          rgb16       = %x %p\n",
               HUF->rgb16.size, HUF->rgb16.element);
        printf("          x3rgb16     = %x %p\n",
               HUF->x3rgb16.size, HUF->x3rgb16.element);
      }

      printf("        data type   = %d\n", ID->data_type);
      printf("        data        = %p\n", ID->data);
    }

    if (DEH->identifier == X3F_SECc) {
      x3f_camf_t *CAMF = &DEH->data_subsection.camf;
      printf("      data_subsection.camf.\n");
      printf("        data = %p\n", CAMF->data);
    }
  }
}


/* --------------------------------------------------------------------- */
/* Write the data to file                                                */
/* --------------------------------------------------------------------- */

static void write_or_copy_data_block(void *data,
                                     x3f_info_t *I,
                                     x3f_directory_entry_t *DE,
                                     uint32_t footer)
{
  uint32_t o_off = ftell(I->output.file);
  uint32_t i_off = DE->input.offset + o_off - DE->output.offset;
  uint32_t size = DE->input.size + DE->input.offset - i_off - footer;

  if (data == NULL) {
#define CHUNKS 1024
    uint8_t buffer[CHUNKS];
    size_t rest = size%CHUNKS;
    size_t num = size/CHUNKS;

    fseek(I->input.file, i_off, SEEK_SET);

    if (rest > 0) {
      GETN(buffer, rest);
      PUTN(buffer, rest);
    }

    while (num--) {
      GETN(buffer, CHUNKS);
      PUTN(buffer, CHUNKS);
    }

  } else {
    PUTN(data, size);
  }
}

static uint32_t row_offsets_size(x3f_huffman_t *HUF)
{
  return HUF->row_offsets.size * sizeof(HUF->row_offsets.element[0]);
}

/* extern */ void x3f_write_to_file(x3f_t *x3f, FILE *outfile)
{
  x3f_info_t *I = NULL;
  x3f_header_t *H = NULL;
  x3f_directory_section_t *DS = NULL;
  int i, d;
  uint32_t directory_start;

  if (x3f == NULL)
    return;

  I = &x3f->info;
  I->error = NULL;
  I->output.file = outfile;
  
  if (outfile == NULL) {
    I->error = "No outfile";
    return;
  }

  /* Write file header */
  H = &x3f->header;
  fseek(outfile, 0, SEEK_SET);
  PUT4(H->identifier);
  PUT4(H->version);
  PUTN(H->unique_identifier, SIZE_UNIQUE_IDENTIFIER);
  PUT4(H->mark_bits);
  PUT4(H->columns);
  PUT4(H->rows);
  PUT4(H->rotation);
  if (H->version > X3F_VERSION_2_0) {
    PUTN(H->white_balance, SIZE_WHITE_BALANCE);
    PUTN(H->extended_types, NUM_EXT_DATA);
    for (i=0; i<NUM_EXT_DATA; i++)
      PUT4(H->extended_data[i]);
  }

  DS = &x3f->directory_section;

  /* Traverse the directory */
  for (d=0; d<DS->num_directory_entries; d++) { 
    x3f_directory_entry_t *DE = &DS->directory_entry[d];
    x3f_directory_entry_header_t *DEH = &DE->header;

    /* Save the offset */
    DE->output.offset = ftell(outfile);

    /* Write the type independent part of the entry header */
    DEH = &DE->header;
    PUT4(DEH->identifier);
    PUT4(DEH->version);

    /* NOTE - the tests below could be made on DE->type instead */

    if (DEH->identifier == X3F_SECp) {
      x3f_property_list_t *PL = &DEH->data_subsection.property_list;

      /* Write the property part of the header */
      PUT4(PL->num_properties);
      PUT4(PL->character_format);
      PUT4(PL->reserved);
      PUT4(PL->total_length);

      /* Write the data */
      write_or_copy_data_block(PL->entry, I, DE, 0);
    }

    if (DEH->identifier == X3F_SECi) {
      x3f_image_data_t *ID = &DEH->data_subsection.image_data;
      x3f_huffman_t *HUF = ID->huffman;

      /* Write the image part of the header */
      PUT4(ID->type);
      PUT4(ID->format);
      PUT4(ID->columns);
      PUT4(ID->rows);
      PUT4(ID->row_stride);

      if (HUF != NULL) {
        /* Write all valid huffman data */
        WRITE_TABLE(HUF->mapping, PUT2);
        WRITE_TABLE(HUF->table, PUT4);

        /* Write the image data */
        write_or_copy_data_block(ID->data, I, DE, row_offsets_size(HUF));

        /* Write row offset data if valid */
        WRITE_TABLE(HUF->row_offsets, PUT4);
      } else {
        /* Write the image data */
        write_or_copy_data_block(ID->data, I, DE, 0);
      }
    }

    if (DEH->identifier == X3F_SECc) {
      x3f_camf_t *CAMF = &DEH->data_subsection.camf;

      /* TODO: the header ? */

      /* Write all CAMF data */
      write_or_copy_data_block(CAMF->data, I, DE, 0);
    }

    /* Save the output size */
    DE->output.size = ftell(outfile) - DE->output.offset;
  }

  /* Write the directory header */
  directory_start = ftell(outfile);
  PUT4(DS->identifier);
  PUT4(DS->version);
  PUT4(DS->num_directory_entries);

  /* Write the directory entries */
  for (d=0; d<DS->num_directory_entries; d++) { 
    x3f_directory_entry_t *DE = &DS->directory_entry[d];

    PUT4(DE->output.offset);
    PUT4(DE->output.size);
    PUT4(DE->type);
  }

  /* Write the directory pointer */
  PUT4(directory_start);
}


/* --------------------------------------------------------------------- */
/* Clean up an x3f structure                                             */
/* --------------------------------------------------------------------- */

/* extern */ void x3f_delete(x3f_t *x3f)
{
  x3f_directory_section_t *DS;
  int d;

  if (x3f == NULL)
    return;

  DS = &x3f->directory_section;

  for (d=0; d<DS->num_directory_entries; d++) { 
    x3f_directory_entry_t *DE = &DS->directory_entry[d];
    x3f_directory_entry_header_t *DEH = &DE->header;

    if (DEH->identifier == X3F_SECp) {
      x3f_property_list_t *PL = &DEH->data_subsection.property_list;

      FREE(PL->entry);
    }

    if (DEH->identifier == X3F_SECi) {
      x3f_image_data_t *ID = &DEH->data_subsection.image_data;

      cleanup_huffman(&ID->huffman);

      FREE(ID->data);
    }

    if (DEH->identifier == X3F_SECc) {
      x3f_camf_t *CAMF = &DEH->data_subsection.camf;

      FREE(CAMF->data);
    }
  }

  FREE(DS->directory_entry);
  FREE(x3f);
}


/* --------------------------------------------------------------------- */
/* Getting a reference to a directory entry                              */
/* --------------------------------------------------------------------- */

/* TODO: all those only get the first instance */

static x3f_directory_entry_t *x3f_get(x3f_t *x3f,
                                      uint32_t type,
                                      uint32_t image_type)
{
  x3f_directory_section_t *DS;
  int d;

  if (x3f == NULL) return NULL;

  DS = &x3f->directory_section;

  for (d=0; d<DS->num_directory_entries; d++) { 
    x3f_directory_entry_t *DE = &DS->directory_entry[d];
    x3f_directory_entry_header_t *DEH = &DE->header;
    
    if (DEH->identifier == type) {
      switch (DEH->identifier) {
      case X3F_SECi:
        {
          x3f_image_data_t *ID = &DEH->data_subsection.image_data;

          if (ID->type_format == image_type)
            return DE;
        }
        break;
      default:
        return DE;
      }
    }
  }

  return NULL;
}

/* extern */ x3f_directory_entry_t *x3f_get_raw(x3f_t *x3f)
{
  x3f_directory_entry_t *d = x3f_get(x3f, X3F_SECi, X3F_IMAGE_RAW_HUFFMAN_10BIT);

  if (d == NULL)
    return x3f_get(x3f, X3F_SECi, X3F_IMAGE_RAW_DP1);
  else
    return d;
}

/* extern */ x3f_directory_entry_t *x3f_get_thumb(x3f_t *x3f)
{
  return x3f_get(x3f, X3F_SECi, X3F_IMAGE_THUMB);
}

/* extern */ x3f_directory_entry_t *x3f_get_thumb_huffman(x3f_t *x3f)
{
  return x3f_get(x3f, X3F_SECi, X3F_IMAGE_THUMB_HUFFMAN);
}

/* extern */ x3f_directory_entry_t *x3f_get_thumb_jpeg(x3f_t *x3f)
{
  return x3f_get(x3f, X3F_SECi, X3F_IMAGE_THUMB_JPEG);
}

/* extern */ x3f_directory_entry_t *x3f_get_camf(x3f_t *x3f)
{
  return x3f_get(x3f, X3F_SECc, 0);
}

/* extern */ x3f_directory_entry_t *x3f_get_prop(x3f_t *x3f)
{
  return x3f_get(x3f, X3F_SECp, 0);
}

/* For some obscure reason, the bit numbering is weird. It is
   generally some kind of "big endian" style - e.g. the bit 7 is the
   first in a byte and bit 31 first in a 4 byte int. For patterns in
   the huffman pattern table, bit 27 is the first bit and bit 26 the
   next one. */

#define PATTERN_BIT_POS(_len, _bit) ((_len) - (_bit) - 1)
#define MEMORY_BIT_POS(_bit) PATTERN_BIT_POS(8, _bit)


/* --------------------------------------------------------------------- */
/* Huffman Decode                                                        */
/* --------------------------------------------------------------------- */

#define HUF_TREE_MAX_LENGTH 27
#define HUF_TREE_MAX_NODES(_leaves) ((HUF_TREE_MAX_LENGTH+1)*(_leaves))
#define HUF_TREE_GET_LENGTH(_v) (((_v)>>27)&0x1f)
#define HUF_TREE_GET_CODE(_v) ((_v)&0x07ffffff)

/* Make the huffman tree */

#ifdef DBG_PRNT
static char *display_code(int length, uint32_t code, char *buffer)
{
  int i;

  for (i=0; i<length; i++) {
    int pos = PATTERN_BIT_POS(length, i);
    buffer[i] = ((code>>pos)&1) == 0 ? '0' : '1';
  }

  buffer[i] = 0;

  return buffer;
}
#endif

static x3f_hufftree_t *new_node(x3f_hufftree_t *tree, uint32_t *free_node)
{
  x3f_hufftree_t *t = &tree[*free_node];

  t->branch[0] = NULL;
  t->branch[1] = NULL;
  t->leaf = 0;

  *free_node = *free_node + 1;

  return t;
}

static void add_code_to_tree(x3f_hufftree_t *tree,
                             uint32_t *free_node,
                             int length, uint32_t code, uint32_t value)
{
  int i;

  x3f_hufftree_t *t = tree;

  for (i=0; i<length; i++) {
    int pos = PATTERN_BIT_POS(length, i);
    int bit = (code>>pos)&1;
    x3f_hufftree_t *t_next = t->branch[bit];

    if (t_next == NULL)
      t_next = t->branch[bit] = new_node(tree, free_node);

    t = t_next;
  }

  t->leaf = value;
}

static void new_huffman_tree(x3f_directory_entry_t *DE, uint32_t bits)
{
  x3f_directory_entry_header_t *DEH = &DE->header;
  x3f_image_data_t *ID = &DEH->data_subsection.image_data;
  x3f_huffman_t *HUF = ID->huffman;
  int leaves = 1<<bits;
  
  int i;
  
  HUF->tree = (x3f_hufftree_t *)
    calloc(1, HUF_TREE_MAX_NODES(leaves)*sizeof(x3f_hufftree_t));

  HUF->free_node = 0;

  new_node(HUF->tree, &HUF->free_node);

  for (i=0; i<HUF->table.size; i++) {
    uint32_t element = HUF->table.element[i];

    if (element != 0) {
      uint32_t length = HUF_TREE_GET_LENGTH(element);
      uint32_t code = HUF_TREE_GET_CODE(element); 
      uint32_t value;

      /* If we have a valid mapping table - then the value from the
         mapping table shall be used. Otherwise we use the current
         index in the table as value. */
      if (HUF->table.size == HUF->mapping.size)
        value = HUF->mapping.element[i];
      else
        value = i;

      add_code_to_tree(HUF->tree, &HUF->free_node, length, code, value);

#ifdef DBG_PRNT
      {
        char buffer[100];

        printf("H %5d : %5x : %5d : %02x %08x (%08x) free=%d (%s)\n",
               i, i, value, length, code, element, HUF->free_node,
               display_code(length, code, buffer));
      }
#endif
    }
  }
}


#ifdef DBG_PRNT
static void print_huffman_tree(x3f_hufftree_t *t, int length, uint32_t code)
{
  char buffer[100];

  printf("%*s (%s,%s) %x (%s)\n",
         length, length < 1 ? "-" : (code&1) ? "1" : "0",
         t->branch[0]==NULL ? "-" : "0",
         t->branch[1]==NULL ? "-" : "1",
         t->leaf,
         display_code(length, code, buffer));

  code = code << 1;
  if (t->branch[0]) print_huffman_tree(t->branch[0], length+1, code+0);
  if (t->branch[1]) print_huffman_tree(t->branch[1], length+1, code+1);
}
#endif

/* Decode one line using the huffman tree */


static void huffman_decode_row(x3f_info_t *I,
                               x3f_directory_entry_t *DE,
                               int bits,
                               int bytes,
                               int row)
{
  x3f_directory_entry_header_t *DEH = &DE->header;
  x3f_image_data_t *ID = &DEH->data_subsection.image_data;
  x3f_huffman_t *HUF = ID->huffman;

  uint8_t byte = 0;             /* to avoid compiler warnings */
  uint32_t byte_offset = HUF->row_offsets.element[row];
  uint32_t bit_offset = 0;

  uint16_t c[3] = {0,0,0}, c_fix[3];

  int col;

  for (col = 0; col < ID->columns; col++) {
    int color;

    for (color = 0; color < 3; color++) {
      x3f_hufftree_t *t = HUF->tree;

      while (t->branch[0] != NULL) {
        uint8_t bit;

        if (bit_offset == 0)
          byte =
            ((uint8_t *)(ID->data))[byte_offset];

        bit = (byte>>MEMORY_BIT_POS(bit_offset))&1;

        bit_offset++;

        if (bit_offset == 8) {
          bit_offset = 0;
          byte_offset++;
        }

        t = t->branch[bit];
      }

      c[color] += t->leaf;

      switch (ID->type_format) {
      case X3F_IMAGE_RAW_HUFFMAN_10BIT:
        c_fix[color] = (int16_t)c[color] > 0 ? c[color] : 0;

        HUF->x3rgb16.element[3*(row*ID->columns + col) + color] = c_fix[color]; 
        break;
      case X3F_IMAGE_THUMB_HUFFMAN:
        c_fix[color] = (int8_t)c[color] > 0 ? c[color] : 0;

        HUF->rgb8.element[3*(row*ID->columns + col) + color] = c_fix[color]; 
        break;
      default:
        fprintf(stderr, "Unknown huffman image type\n");
      }
    }
  }
}

/* Decode use the huffman tree */

static void huffman_decode(x3f_info_t *I,
                           x3f_directory_entry_t *DE,
                           int bits,
                           int bytes)
{
  x3f_directory_entry_header_t *DEH = &DE->header;
  x3f_image_data_t *ID = &DEH->data_subsection.image_data;

  int row;

  for (row = 0; row < ID->rows; row++)
    huffman_decode_row(I, DE, bits, bytes, row);
}

/* --------------------------------------------------------------------- */
/* Loading the data in a directory entry                                 */
/* --------------------------------------------------------------------- */

/* First you set the offset to where to start reading the data ... */

static void read_data_set_offset(x3f_info_t *I,
                                 x3f_directory_entry_t *DE,
                                 uint32_t header_size)
{
  uint32_t i_off = DE->input.offset + header_size;

  fseek(I->input.file, i_off, SEEK_SET);
}

/* ... then you read the data, block for block */

static void read_data_block(void **data,
                            x3f_info_t *I,
                            x3f_directory_entry_t *DE,
                            uint32_t footer)
{
  uint32_t size =
    DE->input.size + DE->input.offset - ftell(I->input.file) - footer;

  *data = (void *)malloc(size);

  GETN(*data, size);
}

static void x3f_load_image_all(x3f_info_t *I, x3f_directory_entry_t *DE)
{
  x3f_directory_entry_header_t *DEH = &DE->header;
  x3f_image_data_t *ID = &DEH->data_subsection.image_data;

  ID->data_type = DATA_ALL;
  read_data_block(&ID->data, I, DE, 0);
}

static void x3f_load_property_list(x3f_info_t *I, x3f_directory_entry_t *DE)
{
  //x3f_directory_entry_header_t *DEH = &DE->header;
  //x3f_property_list_t *PL = &DEH->data_subsection.property_list;

  //read_data_set_offset(I, DE, 0 /* TODO - shal be header size */);

  //read_data_block(&PL->data, I, DE, 0);

  //printf("TODO: loading SECp not fully implemented yet\n");

  printf("TODO: loading SECp not implemented yet\n");
}

static void x3f_load_huffman_compressed(x3f_info_t *I,
                                        x3f_directory_entry_t *DE,
                                        int bits,
                                        int bytes,
                                        int use_map_table)
{
  x3f_directory_entry_header_t *DEH = &DE->header;
  x3f_image_data_t *ID = &DEH->data_subsection.image_data;
  x3f_huffman_t *HUF = ID->huffman;
  int table_size = 1<<bits;
  int row_offsets_size = ID->rows * sizeof(HUF->row_offsets.element[0]);

  READ_TABLE(HUF->table, GET4, table_size); 

  ID->data_type = DATA_HUFFMAN;
  read_data_block(&ID->data, I, DE, row_offsets_size);

  READ_TABLE(HUF->row_offsets, GET4, ID->rows);

  printf("Make huffman tree ...\n");

  new_huffman_tree(DE, bits);

  printf("... DONE\n");

#ifdef DBG_PRNT
  print_huffman_tree(HUF->tree, 0, 0);
#endif

  huffman_decode(I, DE, bits, bytes);
}

static void x3f_load_huffman_plain(x3f_info_t *I,
                                   x3f_directory_entry_t *DE,
                                   int bits,
                                   int bytes,
                                   int use_map_table,
                                   int row_stride)
{
  x3f_directory_entry_header_t *DEH = &DE->header;
  x3f_image_data_t *ID = &DEH->data_subsection.image_data;
  //x3f_huffman_t *HUF = ID->huffman;

  ID->data_type = DATA_HUFFMAN;
  read_data_block(&ID->data, I, DE, 0);

  printf("TODO: loading huffman plain not fully implemented yet\n");
}

static void x3f_load_huffman(x3f_info_t *I,
                             x3f_directory_entry_t *DE,
                             int bits,
                             int bytes,
                             int use_map_table,
                             int row_stride)
{
  x3f_directory_entry_header_t *DEH = &DE->header;
  x3f_image_data_t *ID = &DEH->data_subsection.image_data;
  x3f_huffman_t *HUF = new_huffman(&ID->huffman);

  HUF->raw_bits = bits;
  
  if (use_map_table) {
    int table_size = 1<<bits;

    READ_TABLE(HUF->mapping, GET2, table_size); 
  }

  switch (ID->type_format) {
  case X3F_IMAGE_RAW_HUFFMAN_10BIT:
    HUF->x3rgb16.size = ID->columns * ID->rows * 3;
    HUF->x3rgb16.element =
      (uint16_t *)malloc(sizeof(uint16_t)*HUF->x3rgb16.size);
    break;
  case X3F_IMAGE_THUMB_HUFFMAN:
    HUF->rgb8.size = ID->columns * ID->rows * 3;
    HUF->rgb8.element =
      (uint8_t *)malloc(sizeof(uint8_t)*HUF->rgb8.size);
    break;
  default:
    fprintf(stderr, "Unknown huffman image type\n");
  }

  if (row_stride == 0)
    return x3f_load_huffman_compressed(I, DE, bits, bytes, use_map_table);
  else
    return x3f_load_huffman_plain(I, DE, bits, bytes, use_map_table, row_stride);
}

static void x3f_load_pixmap(x3f_info_t *I, x3f_directory_entry_t *DE)
{
  x3f_load_image_all(I, DE);
}

static void x3f_load_jpeg(x3f_info_t *I, x3f_directory_entry_t *DE)
{
  x3f_load_image_all(I, DE);
}

static void x3f_load_image_data(x3f_info_t *I, x3f_directory_entry_t *DE)
{
  x3f_directory_entry_header_t *DEH = &DE->header;
  x3f_image_data_t *ID = &DEH->data_subsection.image_data;

  read_data_set_offset(I, DE, X3F_IMAGE_HEADER_SIZE);
  
  switch (ID->type_format) {
  case X3F_IMAGE_RAW_DP1:
    fprintf(stderr, "TODO: DP1 not implemented\n");
    break;
  case X3F_IMAGE_RAW_HUFFMAN_10BIT:
    x3f_load_huffman(I, DE, 10, 2, 1, ID->row_stride);
    break;
  case X3F_IMAGE_THUMB:
    x3f_load_pixmap(I, DE);
    break;
  case X3F_IMAGE_THUMB_HUFFMAN:
    x3f_load_huffman(I, DE, 8, 1, 0, ID->row_stride);
    break;
  case X3F_IMAGE_THUMB_JPEG:
    x3f_load_jpeg(I, DE);
    break;
  default:
    fprintf(stderr, "Unknown image type\n");
  }
}

static void x3f_load_camf(x3f_info_t *I, x3f_directory_entry_t *DE)
{
  x3f_directory_entry_header_t *DEH = &DE->header;
  x3f_camf_t *CAMF = &DEH->data_subsection.camf;

  read_data_set_offset(I, DE, 0 /* TODO - shal be header size */);

  read_data_block(&CAMF->data, I, DE, 0);

  printf("TODO: loading camf not fully implemented yet\n");
}

/* extern */ void x3f_load_data(x3f_t *x3f, x3f_directory_entry_t *DE)
{
  x3f_info_t *I = &x3f->info;

  if (DE == NULL)
    return;

  switch (DE->header.identifier) {
  case X3F_SECp:
    x3f_load_property_list(I, DE);
    break;
  case X3F_SECi:
    x3f_load_image_data(I, DE);
    break;
  case X3F_SECc:
    x3f_load_camf(I, DE);
    break;
  default:
    fprintf(stderr, "Unknown directory entry type\n");
  }
}

/* extern */ void x3f_load_image_block(x3f_t *x3f, x3f_directory_entry_t *DE)
{
  x3f_info_t *I = &x3f->info;

  if (DE == NULL)
    return;

  switch (DE->header.identifier) {
  case X3F_SECi:
    read_data_set_offset(I, DE, X3F_IMAGE_HEADER_SIZE);
    x3f_load_image_all(I, DE);
    break;
  default:
    fprintf(stderr, "Unknown image directory entry type\n");
  }
}

/* Converts the data in place */

static void gamma_convert_data(int rows,
                               int columns,
                               uint16_t *data,
                               double gamma)
{
  uint16_t max = 16384;
  int row, col, color, i;
  double *gammatab = NULL;      /* too speed up gamma lookup */

  for (row = 0; row < rows; row++)
    for (col = 0; col < columns; col++)
      for (color = 0; color < 3; color++) {
        uint16_t val = data[3 * (columns * row + col) + color];

        if (val > max) max = val;
      }

  printf("max = %d\n", max);
  printf("gamma = %e\n", gamma);

  gammatab = (double *)malloc((max+1)*sizeof(double)); 

  for (i = 0; i <= max; i++)
    gammatab[i] = 65535.0 * pow((double)i/max, 1/gamma);

  for (row = 0; row < rows; row++)
    for (col = 0; col < columns; col++)
      for (color = 0; color < 3; color++) {
        uint16_t *valp = &data[3 * (columns * row + col) + color];

        *valp = gammatab[*valp];
      }

  free(gammatab);
}

/* extern */ void x3f_dump_raw_data(x3f_t *x3f,
                                    char *outfilename)
{
  x3f_directory_entry_t *DE = x3f_get_raw(x3f);

  if (DE != NULL) {
    x3f_directory_entry_header_t *DEH = &DE->header;
    x3f_image_data_t *ID = &DEH->data_subsection.image_data;
    void *data = ID->data;

    if (data != NULL) {
      FILE *f_out = fopen(outfilename, "wb");

      if (f_out != NULL) {
        fwrite(data, 1, DE->input.size, f_out);
        fclose(f_out);
      }
    }
  }
}

/* extern */ void x3f_dump_ppm_data_from_SDX(x3f_t *x3f,
                                             char *outfilename,
                                             double gamma)
{
  x3f_directory_entry_t *DE =
    x3f_get(x3f, X3F_SECi, X3F_IMAGE_RAW_HUFFMAN_10BIT);

  if (DE != NULL) {
    x3f_directory_entry_header_t *DEH = &DE->header;
    x3f_image_data_t *ID = &DEH->data_subsection.image_data;
    x3f_huffman_t *HUF = ID->huffman;

    if (HUF != NULL) {
      FILE *f_out = fopen(outfilename, "wb");

      if (f_out != NULL) {
        uint16_t *data = HUF->x3rgb16.element;
        int row;

        fprintf(f_out, "P3\n%d %d\n65535\n", ID->columns, ID->rows);

        if (gamma > 0.0)
          gamma_convert_data(ID->rows, ID->columns, data, gamma);

        for (row=0; row < ID->rows; row++) {
          int col;

          for (col=0; col < ID->columns; col++) {
            int color;
      
            for (color=0; color < 3; color++)
              fprintf(f_out, "%d ", data[3 * (ID->columns * row + col) + color]);
            fprintf(f_out, "\n");
          }
        }

        fclose(f_out);
      }
    }
  }
}

#include "tiff.h"

/* Routines for writing little endian tiff data */

static void write_16(FILE *f_out, uint16_t val)
{
  fputc((val>>0) & 0xff, f_out);
  fputc((val>>8) & 0xff, f_out);
}

static void write_32(FILE *f_out, uint32_t val)
{
  fputc((val>> 0) & 0xff, f_out);
  fputc((val>> 8) & 0xff, f_out);
  fputc((val>>16) & 0xff, f_out);
  fputc((val>>24) & 0xff, f_out);
}

static void write_ra(FILE *f_out, uint32_t val1, uint32_t val2)
{
  write_32(f_out, val1);
  write_32(f_out, val2);
}

static void write_entry_16(FILE *f_out, uint16_t tag, uint16_t val)
{
  write_16(f_out, tag);
  write_16(f_out, TIFF_SHORT);
  write_32(f_out, 1);
  write_16(f_out, val);
  write_16(f_out, 0);
}

static void write_entry_32(FILE *f_out, uint16_t tag, uint32_t val)
{
  write_16(f_out, tag);
  write_16(f_out, TIFF_LONG);
  write_32(f_out, 1);
  write_32(f_out, val);
}

static void write_entry_of(FILE *f_out, uint16_t tag, uint16_t type, uint32_t num, uint32_t offset)
{
  write_16(f_out, tag);
  write_16(f_out, type);
  write_32(f_out, num);
  write_32(f_out, offset);
}

static void write_array_32(FILE *f_out, uint32_t num, uint32_t *vals)
{
  uint32_t i;

  for (i=0; i<num; i++)
    write_32(f_out, vals[i]);
}

/* extern */ void x3f_dump_tiff_data_from_SDX(x3f_t *x3f,
                                              char *outfilename,
                                              double gamma)
{
  x3f_directory_entry_t *DE =
    x3f_get(x3f, X3F_SECi, X3F_IMAGE_RAW_HUFFMAN_10BIT);

  if (DE != NULL) {
    x3f_directory_entry_header_t *DEH = &DE->header;
    x3f_image_data_t *ID = &DEH->data_subsection.image_data;
    x3f_huffman_t *HUF = ID->huffman;

    if (HUF != NULL) {
      FILE *f_out = fopen(outfilename, "wb");

      if (f_out != NULL) {
        uint16_t *data = HUF->x3rgb16.element;
        int row;
        int bits = 16;
        int planes = 3;
        int rowsperstrip = 32;
        double rps = rowsperstrip;
        int strips = (int)floor((ID->rows + rps - 1)/rps);
        int strip = 0;

        uint32_t ifd_offset;
        uint32_t ifd_offset_offset;
        uint32_t resolution_offset;
        uint32_t bitspersample_offset;
        uint32_t offsets_offset;
        uint32_t bytes_offset;

        uint32_t *stripoffsets = (uint32_t *)malloc(sizeof(uint32_t)*strips);
        uint32_t *stripbytes = (uint32_t *)malloc(sizeof(uint32_t)*strips);

        /* Scale and gamma code the image */

        if (gamma > 0.0)
          gamma_convert_data(ID->rows, ID->columns, data, gamma);

        /* Write initial TIFF file header II-format, i.e. little endian */

        write_16(f_out, 0x4949); /* II */
        write_16(f_out, 42);     /* A carefully choosen number */

        /* (Placeholder for) offset of the first (and only) IFD */

        ifd_offset_offset = ftell(f_out);
        write_32(f_out, 0);

        /* Write resolution */

        resolution_offset = ftell(f_out);
        write_ra(f_out, 72, 1);

        /* Write bits per sample */

        bitspersample_offset = ftell(f_out);
        write_16(f_out, bits);
        write_16(f_out, bits);
        write_16(f_out, bits);

        /* Write image */

        for (strip=0; strip < strips; strip++) {
          stripoffsets[strip] = ftell(f_out);

          for (row=strip*rowsperstrip;
               row < ID->rows && row < (strip + 1)*rowsperstrip;
               row++) {
            int col;

            for (col=0; col < ID->columns; col++) {
              int color;
      
              for (color=0; color < 3; color++)
                write_16(f_out, data[3 * (ID->columns * row + col) + color]);
            }
          }

          stripbytes[strip] = ftell(f_out) - stripoffsets[strip];
        }

        /* Write offset/size arrays */

        offsets_offset = ftell(f_out);
        write_array_32(f_out, strips, stripoffsets);

        bytes_offset = ftell(f_out);
        write_array_32(f_out, strips, stripbytes);

        /* The first (and only) IFD */

        ifd_offset = ftell(f_out);
        write_16(f_out, 12);      /* Number of directory entries */

        /* Required directory entries */
        write_entry_32(f_out, TIFFTAG_IMAGEWIDTH, (uint32)ID->columns);
        write_entry_32(f_out, TIFFTAG_IMAGELENGTH, (uint32)ID->rows);
        write_entry_of(f_out, TIFFTAG_BITSPERSAMPLE, TIFF_SHORT, 3, bitspersample_offset);
        write_entry_16(f_out, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
        write_entry_16(f_out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
        write_entry_of(f_out, TIFFTAG_STRIPOFFSETS, TIFF_LONG, strips, offsets_offset);
        write_entry_16(f_out, TIFFTAG_SAMPLESPERPIXEL, (uint16)planes);
        write_entry_32(f_out, TIFFTAG_ROWSPERSTRIP, rowsperstrip);
        write_entry_of(f_out, TIFFTAG_STRIPBYTECOUNTS, TIFF_LONG, strips, bytes_offset);
        write_entry_of(f_out, TIFFTAG_XRESOLUTION, TIFF_RATIONAL, 1, resolution_offset);
        write_entry_of(f_out, TIFFTAG_YRESOLUTION, TIFF_RATIONAL, 1, resolution_offset);
        write_entry_16(f_out, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);
        
        /* Offset of the next IFD = 0 => no more IFDs */

        write_32(f_out, 0);

        /* Offset of the first (and only) IFD */

        fseek(f_out, ifd_offset_offset, SEEK_SET);
        write_32(f_out, ifd_offset);

        free(stripoffsets);
        free(stripbytes);

        fclose(f_out);
      }
    }
  }
}

/* extern */ void x3f_dump_jpeg(x3f_t *x3f, char *outfilename)
{
  x3f_directory_entry_t *DE = x3f_get_thumb_jpeg(x3f);

  if (DE != NULL) {
    x3f_directory_entry_header_t *DEH = &DE->header;
    x3f_image_data_t *ID = &DEH->data_subsection.image_data;
    void *data = ID->data;

    if (data != NULL) {
      FILE *f_out = fopen(outfilename, "wb");

      if (f_out != NULL) {
        fwrite(data, 1, DE->input.size, f_out);
        fclose(f_out);
      }
    }
  }
}

/* --------------------------------------------------------------------- */
/* The End                                                               */
/* --------------------------------------------------------------------- */
