/* TODO: add support for StripOffsets and StripByteCounts */
mem_map_t * scan_mem_map(ctiff_t * ctif) {
  assert( NULL != ctif);
  static mem_map_t memmap;
  memmap.count = 0;
  memmap.base_p = NULL;
  memmap.max_entries = 2048;
  memmap.base_p = malloc (sizeof(mem_map_entry_t) * memmap.max_entries);
  if (NULL == memmap.base_p) {
	  perror ("could not allocate mem for memmap, abort");
  }
  /* size of tiff file in bytes */
  memmap.max_len = ctif->streamlen;
  /* header */
  add_mem_entry( &memmap, 0, 4, mt_constant);
  /* IFD0 Offset */
  add_mem_entry( &memmap, 4, 4, mt_offset_to_ifd0);
  /* IFDO */
  uint32 ifd = get_ifd0_pos( ctif );
  uint16 count = get_ifd0_count( ctif);

  add_mem_entry( &memmap, ifd, 2, mt_ifd); /* count of tags in ifd */
  int ifdbase=2+ifd;
  /* iterate through IFD0 entries */
  uint16 tagidx;
  ifd_entry_t stripoffset_entry; 

  for (tagidx = 0; tagidx< count; tagidx++) {
	  add_mem_entry( &memmap, ifdbase+(tagidx*12), 8, mt_ifd); /* tagid, field type, count */
	  ifd_entry_t ifd_entry = TIFFGetRawTagIFDListEntry( ctif, tagidx );
	  uint32 tag = TIFFGetRawTagListEntry( ctif, tagidx);
	  TIFFDataType datatype = TIFFGetRawTagType( ctif, tag);
	  int datasize;
	  switch (datatype) {
		  case TIFF_ASCII: datasize = 1; break;
		  case TIFF_LONG: datasize =  4; break;
		  case TIFF_SHORT: datasize =  2; break;
		  case TIFF_BYTE: datasize =  1; break;
		  case TIFF_UNDEFINED: datasize =  1; break;
		  case TIFF_RATIONAL: datasize =  8; break;
		  case TIFF_SSHORT: datasize =  2; break;
		  case TIFF_SBYTE: datasize =  1; break;
		  case TIFF_SLONG: datasize =  4; break;
		  case TIFF_SRATIONAL: datasize =  8; break;
		  case TIFF_FLOAT: datasize =  4; break;
		  case TIFF_DOUBLE: datasize =  8; break;
                  default: fprintf(stderr, "unknown datatype %i, possible a program error", datatype); exit(EXIT_FAILURE);
	  }
          uint32 offset = ifd_entry.data32offset;
          uint16 count = ifd_entry.count;

          if (tag == TIFFTAG_STRIPOFFSETS) {
                  if (ifd_entry.value_or_offset == is_offset) {
                  add_mem_entry( &memmap, ifdbase+(tagidx*12)+8,4,mt_ifd_offset_to_standardized_value ); 
                  add_mem_entry( &memmap, offset, ((uint32) count)*datasize, mt_ifd_offset_to_stripoffsets );
                  } else if (ifd_entry.value_or_offset==is_value) {
                        add_mem_entry( &memmap, ifdbase+(tagidx*12)+8, 4,mt_ifd_embedded_standardized_value );
                  }
                  stripoffset_entry=ifd_entry;
          } else if (ifd_entry.value_or_offset==is_offset) { /* offset */
		  if (tag < 32768) { /* standard tag */
			  add_mem_entry( &memmap, ifdbase+(tagidx*12)+8, 4,mt_ifd_offset_to_standardized_value ); 
			  add_mem_entry( &memmap, offset, ((uint32) count)*datasize, mt_standardized_value );
		  } else if (tag < 65000) { /* registered tag */
			  add_mem_entry( &memmap, ifdbase+(tagidx*12)+8, 4,mt_ifd_offset_to_registered_value ); 
			  add_mem_entry( &memmap, offset, ((uint32) count)*datasize, mt_registered_value );
		  } else { /* private tag */
			  add_mem_entry( &memmap, ifdbase+(tagidx*12)+8, 4,mt_ifd_offset_to_private_value ); 
			  add_mem_entry( &memmap, offset, ((uint32) count)*datasize, mt_private_value );
		  }
	  } else if (ifd_entry.value_or_offset==is_value) { /* embedded value */
		  if (tag < 32768) { /* standard tag */
			  add_mem_entry( &memmap, ifdbase+(tagidx*12)+8, 4,mt_ifd_embedded_standardized_value ); 
		  } else if (tag < 65000) { /* registered tag */
			  add_mem_entry( &memmap, ifdbase+(tagidx*12)+8, 4,mt_ifd_embedded_registered_value ); 
		  } else { /* private tag */
			  add_mem_entry( &memmap, ifdbase+(tagidx*12)+8, 4,mt_ifd_embedded_private_value ); 
		  }

	  }
  }
 
  /* check next IFD mark */
  // uint32 offset = get_ifd0_pos(ctif );
  // uint32 IFDn = get_next_ifd_pos( ctif, offset );
  // printf("IFD: offset=%i, IFD0=%i IFDn=%i ifd+count=%i\n", offset, ifd, IFDn, ifdbase+12*count);
  add_mem_entry( &memmap, ifdbase+12*count, 4, mt_offset_to_ifd);

  /* handle stripoffset data */
  //printf("ifd_count=%i, stripoffset_count=%i\n", stripoffset_entry.count, stripoffset_count);
  uint32 stripoffset_values[stripoffset_entry.count];
  switch (stripoffset_entry.datatype) {
    case TIFF_LONG: {
                      /*  value */
                      if (stripoffset_entry.value_or_offset == is_value) {
                        for (uint32 i=0; i< stripoffset_entry.count; i++) {
                          stripoffset_values[i] = stripoffset_entry.data32;
                        }
                      }
                      /*  offset */
                      if (stripoffset_entry.value_or_offset == is_offset) {
                        offset_t offset;
                        ret_t ret = read_offsetdata(ctif, stripoffset_entry.data32offset, stripoffset_entry.count, stripoffset_entry.datatype, &offset, &ret);
                        if (ret.returncode != is_valid) {
                          fprintf(stderr, "error reading offset data, stripoffset_entry.count has size %u, resulting address %zu, but only offset to %lu is possible\n", stripoffset_entry.count, ((uint64) stripoffset_entry.count*sizeof(uint32)), (uint64) 0xffffffff);
                          exit(EXIT_FAILURE);
                        }
                        uint32 * p = offset.data32p;
                        if ((uint64) stripoffset_entry.count*sizeof(uint32) > 0xffffffff) {
                          fprintf(stderr, "stripoffset_entry.count has size %u, resulting address %zu, but only offset to %lu is possible\n", stripoffset_entry.count, ((uint64) stripoffset_entry.count*sizeof(uint32)), (uint64) 0xffffffff);
                          exit(EXIT_FAILURE);
                        }
                        for (uint32 i=0; i< stripoffset_entry.count; i++) {
                          uint32 pval = *p;
                          if (is_byteswapped(ctif)) {
                            TIFFSwabLong(&pval);
                          }
                          stripoffset_values[i]=pval;
                          p++;
                        }
                      }
                      break;
                    }
    case TIFF_SHORT: {
                       /*  value */
                       if (stripoffset_entry.value_or_offset == is_value) {
                         for (uint32 i=0; i< stripoffset_entry.count; i++) {
                           stripoffset_values[i]= stripoffset_entry.data16[i];
                         }
                       }
                       /*  offset */
                       if (stripoffset_entry.value_or_offset == is_offset) {
                         offset_t offset;
                         ret_t ret = read_offsetdata(ctif, stripoffset_entry.data32offset, stripoffset_entry.count, stripoffset_entry.datatype, &offset, &ret);
                         if (ret.returncode != is_valid) {
                           fprintf(stderr, "error reading offset data, stripoffset_entry.count has size %u, resulting address %zu, but only offset to %lu is possible\n", stripoffset_entry.count, ((uint64) stripoffset_entry.count*sizeof(uint32)), (uint64) 0xffffffff);
                           exit(EXIT_FAILURE);
                         }
                         uint16 * p = offset.data16p;
                         if ((uint64) stripoffset_entry.count*sizeof(uint16) > 0xffffffff) {
                           fprintf(stderr, "stripoffset_entry.count has size %u, resulting address %zu, but only offset to %lu is possible\n", stripoffset_entry.count, ((uint64) stripoffset_entry.count*sizeof(uint16)), (uint64) 0xffffffff);
                           exit(EXIT_FAILURE);
                         }
                         for (uint32 i=0; i< count; i++) {
                           uint16 pval = *p;
                           if (is_byteswapped(ctif)) {
                             TIFFSwabShort(&pval);
                           }
                           stripoffset_values[i]=pval;
                           p++;
                         }
                       }
                       break;
                     }
    default: {
               perror("stripoffset_entry.datatype has an unexpected value, possible a program error\n");
               exit(EXIT_FAILURE);
             }
  }
  //printf("count=%i\n", stripoffset_entry.count);
/*TODO:
  for (int i=0; i< stripoffset_entry.count; i++) {
    uint32 rawstriplen = TIFFRawStripSize(ctif->tif, i);
    //printf("OFFSET: p[%i]=%u len=%i\n", i,stripoffset_values[i], rawstriplen);
    add_mem_entry( &memmap, stripoffset_values[i], rawstriplen, mt_stripoffset_value);
  }
*/
  /* sort entries by offset */
  qsort(memmap.base_p, memmap.count, sizeof( mem_map_entry_t), compare_memmap);
  /*
  printf("memmap before HOLE detection\n");
  print_mem_map( &memmap );
  printf("----------------------------\n");
  */

  /* add all unused areas */
  uint32 memmap_orig_count = memmap.count;
  for (uint32 j=1; j< memmap_orig_count; j++) {
    mem_map_entry_t * prev=memmap.base_p+j-1;
    mem_map_entry_t * act =memmap.base_p+j;
    uint32 estimated_offset = (prev->offset + prev->count);
    if (estimated_offset < act->offset) { /*  found a hole */
      printf("HOLE FOUND at %u\n", estimated_offset);
      
      printf("\tprev->offset=%u prev->count=%u estimated=%u\n", prev->offset, prev->count, estimated_offset);
      printf("\tact->offset=%u act->count=%u\n", act->offset, act->count);
      
      add_mem_entry( &memmap, estimated_offset, (act->offset -estimated_offset), mt_unused);
    }
  }
  /* sort entries by offset again */
  qsort(memmap.base_p, memmap.count, sizeof( mem_map_entry_t), compare_memmap);
  /*  add unused area at end */
  mem_map_entry_t * last = memmap.base_p + memmap.count-1;
  uint32 estimated_offset = (last->offset + last->count);
  if (memmap.max_len > estimated_offset) {
      printf("HOLE (at end) FOUND at %u\n", estimated_offset);
      add_mem_entry( &memmap, estimated_offset, (memmap.max_len -estimated_offset), mt_unused);
  }
  /* sort entries by offset again */
  qsort(memmap.base_p, memmap.count, sizeof( mem_map_entry_t), compare_memmap);
  return &memmap;
}
ret_t check_tagorder(ctiff_t * ctif) {
  tif_rules("tags are in ascending order");
  thandle_t client = TIFFClientdata(ctif->tif);
  TIFFReadWriteProc readproc = TIFFGetReadProc(ctif->tif);
  TIFFSeekProc seekproc = TIFFGetSeekProc(ctif->tif);
  if (! seekproc) {
    perror ("could not get TIFFGetSeekProc");
  }
  if (! readproc) {
    perror ("could not get TIFFGetReadProc");
  }

  uint32 offset = get_ifd0_pos(ctif);
  int count = get_ifd0_count(ctif);

  /* read count of tags (2 Bytes) */
  int i;
  /* replace i/o operatrions with in-memory-operations */
  uint8 * ifdentries = NULL;
  ifdentries = malloc ( sizeof(uint8) * 12 * count);
  /*
     if (read(fd, ifdentries, 12 * count) != 12*count) {
     perror ("TIFF Header read error5");
     exit(EXIT_FAILURE);
     }
     */
  seekproc(client, offset+2, SEEK_SET);

  if ( readproc( client, ifdentries, 12 * count) != 12*count ) {
    perror ("TIFF Header read error5");
    exit( EXIT_FAILURE );
  }

  uint8 * e = ifdentries;
  uint16 lasttag = 0;
  for (i = 0; i<count; i++) {
    uint8 lo = *e;
    e++;
    uint8 hi = *e;
    uint16 tag = (hi << 8) + lo;
    e++;
    if (is_byteswapped(ctif))
      TIFFSwabShort(&tag);
    if (i>0 && lasttag >= tag) {
      // printf("tag idx=%i, tag=%u (0x%04x) (0x%02x) (0x%02x)\n", i, tag, tag, hi, lo);
      free( ifdentries );
      // FIXME: tif_fails?
      char array[TIFFAILSTRLEN];
      snprintf(array, sizeof(array), "Invalid TIFF directory; tags are not sorted in ascending order, previous tag:%u (%s) , actual tag:%u (%s) at pos %i of %i\n", lasttag,  TIFFTagName(lasttag),  tag,  TIFFTagName(tag), i, count);
      return tif_fails(array);
    }
    lasttag = tag;
    e+=10;
  }
  /* loop each tag until end or given tag found */
  free( ifdentries );
  ret_t res;
  res.returnmsg=NULL;
  res.returncode=0;
  return res;
}