/* 'checkit_tiff' is a conformance checker for baseline TIFFs
 *
 * author: Andreas Romeyke, 2015-2022
 * licensed under conditions of libtiff
 * (see http://libtiff.maptools.org/misc.html)
 *
 */

#include <math.h>
#include "check.h"
#include "check_helper.h"
#include <assert.h>
#include <fcntl.h>
#include "ctstring.h"
#include <errno.h>
#include <string.h>
#ifdef HAVE_MMAP
#include <sys/mman.h>
#endif
#include <endian.h>
#include <stdio.h>
#include <unistd.h>


off_t ct_seek(ctiff_t * ctif, off_t pos, int whence) {
#ifdef HAVE_MMAP
  switch (ctif->ioflag) {
    case is_filep:
      assert(ctif->fd >= 0);
      // TODO: add checks if seek will be outside of file!!!
      return lseek(ctif->fd, pos, whence);
      break;

    case  is_memmap:
      assert( ctif->streamp != NULL);
      assert( ctif->actual_streamp != NULL);
      switch (whence) {
        case SEEK_SET:
          ctif->actual_streamp = ctif->streamp + pos;
          break;
        case SEEK_CUR:
          ctif->actual_streamp+=pos;
          break;
        case SEEK_END:
          ctif->actual_streamp = ctif->streamp + ctif->streamlen + pos;
          break;
      }
      return ctif->actual_streamp - ctif->streamp;
      break;
  }
#else
  assert(ctif->fd >= 0);
  // TODO: add checks if seek will be outside of file!!!
  return lseek(ctif->fd, pos, whence);
#endif
  return no_file_found; /* should not occur */
}

static ssize_t ct_read_intern(ctiff_t * ctif, void *buf, size_t count) {
  if (NULL == buf) {
      perror("Buffer buf is a NULL-pointer");
      exit( EXIT_FAILURE );
  }
#ifdef HAVE_MMAP
  switch (ctif->ioflag) {
    case is_filep:
      // TODO: add checks if seek will be outside of file!!!
      assert(ctif->fd >= 0);
      return read(ctif->fd, buf, count);
      break;
    case  is_memmap: {
                       assert( ctif->streamp != NULL);
                       assert( ctif->actual_streamp != NULL);

                       off_t testpos = (ctif->actual_streamp+count) - (ctif->streamp);
                       if ( (testpos > 0) && ((size_t) testpos > ctif->streamlen)) {
                         /*
                          * fprintf(stderr, "read offset outside of file on new pos=%li (filesize=%lu)\n", testpos, ctif->streamlen);
                          * exit(EXIT_FAILURE);
                          */
                         return no_file_found;
                       }
                       if ( testpos < 0 ) {
                         /*
                          * fprintf(stderr, "read offset outside of file on new pos=%li (filesize=%lu)\n", testpos, ctif->streamlen);
                          * exit(EXIT_FAILURE);
                          */
                         return no_file_found;
                       }
                       memcpy(buf, ctif->actual_streamp, count);
                       ctif->actual_streamp+=count;
                       return count;
                       /* break; */
                     }
  }
#else
  // TODO: add checks if seek will be outside of file!!!
  assert(ctif->fd >= 0);
  return read(ctif->fd, buf, count);
#endif
    return no_file_found; /* should not occur */
}

ssize_t ct_read8(ctiff_t * ctif, uint8 *buf, size_t byte_count) {
    return ct_read_intern(ctif, buf, byte_count);
}
static ssize_t ct_read8s(ctiff_t * ctif, int8 *buf, size_t byte_count) {
    return ct_read_intern(ctif, buf, byte_count);
}

ssize_t ct_read16(ctiff_t * ctif, uint16 *buf, size_t word_count) {
    size_t count = word_count * sizeof(uint16);
    ssize_t rdbytes = ct_read_intern(ctif, buf, count);
    for (size_t i = 0; i < word_count; i++) {
        uint16 *tmpbuf = buf + i;
        *tmpbuf = htole16(*tmpbuf);
    }
    return rdbytes;
}
static ssize_t ct_read16s(ctiff_t * ctif, int16 *buf, size_t word_count) {
    size_t count = word_count * sizeof(int16);
    ssize_t rdbytes = ct_read_intern(ctif, buf, count);
    for (size_t i = 0; i < word_count; i++) {
        int16 *tmpbuf = buf + i;
        /* unsure if this works as expected on negative values */
        *tmpbuf = (int16) htole16((uint16) *tmpbuf);
    }
    return rdbytes;
}

ssize_t ct_read32(ctiff_t * ctif, uint32 *buf, size_t quad_count) {
    size_t count = quad_count * sizeof(uint32);
    ssize_t rdbytes = ct_read_intern(ctif, buf, count);
    //printf("buf32: %lu (0x%x) quadcount=%lu count=%lu rdbytes=%lu\n", *buf, *buf, quad_count, count, rdbytes);
    for (size_t i = 0; i < quad_count; i++) {
        uint32 * tmpbuf = buf+i;
        *tmpbuf = htole32( *tmpbuf );
    }
    //printf("buf32': %lu (0x%x)\n", *buf, *buf);
    return rdbytes;
}

static ssize_t ct_read32s(ctiff_t * ctif, int32 *buf, size_t quad_count) {
    size_t count = quad_count * sizeof(int32);
    ssize_t rdbytes = ct_read_intern(ctif, buf, count);
    //printf("buf32: %lu (0x%x) quadcount=%lu count=%lu rdbytes=%lu\n", *buf, *buf, quad_count, count, rdbytes);
    for (size_t i = 0; i < quad_count; i++) {
        int32 * tmpbuf = buf+i;
        /* unsure if this works as expected on negative values */
        *tmpbuf = (int32) htole32((uint32) *tmpbuf );
    }
    //printf("buf32': %lu (0x%x)\n", *buf, *buf);
    return rdbytes;
}

static ssize_t ct_readf(ctiff_t * ctif, float *buf, size_t float_count) {
    size_t count = float_count * sizeof(float);
    ssize_t rdbytes = ct_read_intern(ctif, buf, count);
    return rdbytes;
}

static ssize_t ct_readd(ctiff_t * ctif, double *buf, size_t double_count) {
    size_t count = double_count * sizeof(double);
    ssize_t rdbytes = ct_read_intern(ctif, buf, count);
    return rdbytes;
}

int TIFFGetRawTagListIndex(const cifd_t *ifd, tag_t tag) { /* find n-th entry in IFD for given tag, return -1 if not found */
  return ifd->index_cache_by_tag[tag];
}

#ifdef __TINYC__
float fabsf(float f) {
    return  (f >= 0.0) ? f : -f;
}
#endif

//------------------------------------------------------------------------------
ret_t check_tag_has_fvalue(ctiff_t * ctif, cifd_t *ifd, tag_t tag, float value) {
  GET_EMPTY_RET(ret);
  TIFP_CHECK( ctif, ret);
  float * valp = NULL;
  uint32 found;
  ret =TIFFGetFieldRATIONAL(ctif, ifd, tag, &valp, &found);
  if ((1 == found) && (NULL != valp)) {
    float val = * valp;
    if ( fabsf(val - value) < 0.01 ) {
      ret.returncode=is_valid;
    } else {
      string_t msg = float2str(val);
      ret = set_value_found_ret(&ret, msg ) ;
      ret.returncode = tagerror_value_differs;
    }
  }
  free(valp);
  return ret;
}

//------------------------------------------------------------------------------
ret_t check_tag_has_u16value(ctiff_t * ctif, cifd_t *ifd, tag_t tag, uint16 value) {
  GET_EMPTY_RET(ret);
  TIFP_CHECK( ctif, ret);

  uint16 * valp = NULL;
  uint32 found;
  ret=TIFFGetFieldSHORT(ctif, ifd, tag, &valp, &found);
  if ((1 == found) && (NULL != valp)) {
    uint16 val  = *valp;
    if ( val == value ) {
      ret.returncode=is_valid;
    } else {
      string_t msg = int2str(val);
      ret = set_value_found_ret_with_returncode(&ret, msg, tagerror_value_differs);
    }
  }
  free(valp);
  return ret;
}


//------------------------------------------------------------------------------
ret_t check_tag_has_u32value(ctiff_t * ctif, cifd_t *ifd, tag_t tag, uint32 value) {
  GET_EMPTY_RET(ret);
  TIFP_CHECK( ctif, ret);

  uint32 * valp = NULL;
  uint32 found;
  ret=TIFFGetFieldLONG(ctif, ifd, tag, &valp, &found);
  if ((1 == found) && (NULL != valp)) {
    uint32 val = *valp;
    if ( val == value )  {
      ret.returncode=is_valid;
    } else {
      string_t msg = uint2str(val);
      ret = set_value_found_ret_with_returncode(&ret, msg, tagerror_value_differs);
    }
  }
  free(valp);
  return ret;
}


ret_t parse_header_and_endianess(ctiff_t * ctif) {
  GET_EMPTY_RET(ret);
  TIFP_CHECK( ctif, ret);

   /* seek the image file directory (bytes 4-7) */
  //ct_seek(fd, (off_t) 0, SEEK_SET);
  if ( ct_seek(ctif, 0, SEEK_SET) != 0)  {
    ret = set_value_found_ret(&ret, str("TIFF header ct_seek error to 0"));
    ret.returncode=tiff_seek_error_header;
    return ret;
  }
  uint16 header;
  uint16 magic;
  int endianness;
  if ( ct_read16( ctif, &header, 1) != 2 ) {
    ret = set_value_found_ret(&ret, str("TIFF Header ct_read error to magic byte header (first 2 bytes)"));
    ret.returncode=tiff_read_error_header;
    return ret;
  }
  if (header == 0x4949) { endianness = 0; /* little endian */
  } else if (header == 0x4d4d) { endianness = 1; /*  big endian */
  } else {
    char errmsg[VALUESTRLEN]="";
    snprintf (errmsg, VALUESTRLEN, "TIFF Header error, not Byte Order Bytes for TIFF: 0x%04x", header);
    if (header == 0x4550) {
      strcat(errmsg, ", could be a Microsoft Document Image file (little endian), if header begins with by 0x45 0x50 0x2a 0x00");
    }
    ret=set_value_found_ret(&ret, str(errmsg));
    ret.returncode = tiff_byteorder_error;
    return ret;
  }
  ctif->isbyteswapped = endianness;
  if ( ct_read16( ctif, &magic, 1) != 2 ) {
    ret = set_value_found_ret(&ret, str("TIFF Header ct_read error to magic byte header (second 2 bytes == 42)"));
    ret.returncode=tiff_read_error_header;
    return ret;
  }

  uint16 magic2 = magic;
  if (endianness) {
      magic2 = TIFFSwabShort( magic2 ); /*  big endian */
  }
  ret.returncode = should_not_occur;
  if (magic2 == 42) {
      ret.returncode=is_valid;
} else {
    char errmsg[VALUESTRLEN]="";
    snprintf (errmsg, VALUESTRLEN, "TIFF Header error, not a MAGIC BYTE for TIFF: 0x%04x\n", magic);
    if (magic2==0x002b) { strcat(errmsg, ", but could be a BigTIFF, see http://www.awaresystems.be/imaging/tiff/bigtiff.html");
}
    if (magic2==0x0055) { strcat(errmsg, ", but could be a Panasonic Raw/RW2, see http://libopenraw.freedesktop.org/wiki/Panasonic_RAW/");
}
    if (magic2==0x01bc) { strcat(errmsg, ", but could be a JPEG XR, see http://www.itu.int/rec/T-REC-T.832");
}
    if (magic2==0x314e) { strcat(errmsg, ", but could be a Navy Image FileFormat, see http://www.navsea.navy.mil/nswc/carderock/tecinfsys/cal-std/doc/28002c.pdf");
}
    if (magic2==0x4352) { strcat(errmsg, ", but could be a DNG camera profile, see http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/products/photoshop/pdfs/dng_spec_1.4.0.0.pdf");
}
    if (magic2==0x4f52) { strcat(errmsg, ", but could be an Olympus ORF, see http://libopenraw.freedesktop.org/wiki/Olympus_ORF/");
}
    if (magic2==0x5352) { strcat(errmsg, ", but could be an Olympus ORF, see http://libopenraw.freedesktop.org/wiki/Olympus_ORF/");
}
    ret = set_value_found_ret_with_returncode(&ret, str(errmsg), tiff_byteorder_error);
  }
  assert( ret.returncode != should_not_occur);
  return ret;
}

void empty_cache(cifd_t * ifd) {
  /* init with -1 indicating 'not found' */
  memset(ifd->index_cache_by_tag, -1, sizeof(int)* MAXTAGS);
  memset(ifd->tag_cache_by_index, 0, sizeof(tag_t)* MAXTAGS);
}

ret_t get_IFD_at_pos(ctiff_t * ctif, uint32 offset, cifd_t * resulting_ifd) {
    GET_EMPTY_RET(ret);TIFP_CHECK(ctif, ret);
    if (ct_seek(ctif, offset, SEEK_SET) != offset) {
        char msg[VALUESTRLEN];
        snprintf(msg, VALUESTRLEN, "TIFF Header seek error, seek set to byte %u", offset);
        ret = set_value_found_ret(&ret, str(msg));
        ret.returncode = tiff_seek_error_header;
        return ret;
    }
    uint16 count;
    if (ct_read16(ctif, &count, 1) != 2) {
        char msg[VALUESTRLEN];
        snprintf(msg, VALUESTRLEN, "TIFF Header ct_read error2, reading ifd count (2 bytes) at file pos=%u (%x)",
                 offset, offset);
        ret = set_value_found_ret(&ret, str(msg));
        ret.returncode = tiff_read_error_header;
        return ret;
    }
    if (is_byteswapped(ctif)) {
        count = TIFFSwabShort(count);
    }
    resulting_ifd->tag_count = count;
    resulting_ifd->position = (off_t) offset;
    resulting_ifd->tagorder = unknown_tag_order;
    /* init with -1 indicating 'not found' */
    empty_cache(resulting_ifd);
    switch (resulting_ifd->tagorder) {
        case has_unsorted_tags:
        case has_sorted_tags:
            break;
        case unknown_tag_order: {
            tag_t last_tag = TIFFGetRawTagListEntry(ctif, resulting_ifd, 0);
            resulting_ifd->index_cache_by_tag[last_tag] = 0;
            resulting_ifd->tag_cache_by_index[0] = last_tag;
            int is_unsorted = 0;
            for (uint16 tagidx = 1; tagidx < count; tagidx++) {
                tag_t current_tag = TIFFGetRawTagListEntry(ctif, resulting_ifd, tagidx);

                is_unsorted += (last_tag >= current_tag);
                resulting_ifd->index_cache_by_tag[current_tag] = tagidx;
                resulting_ifd->tag_cache_by_index[tagidx] = current_tag;
                last_tag = current_tag;
            }
            if (is_unsorted > 0) { resulting_ifd->tagorder = has_unsorted_tags; }
        }
    }
    ret.returncode = is_valid;
    return ret;
}


ret_t get_first_IFD(ctiff_t * ctif, uint32 * ifd) {
  GET_EMPTY_RET(ret);
  TIFP_CHECK( ctif, ret);

  int isByteSwapped = ctif->isbyteswapped;
  /* seek the image file directory (bytes 4-7) */
  if (ct_seek(ctif, 4, SEEK_SET) != 4 ) {
    ret = set_value_found_ret(&ret, str("TIFF Header seak error, seek set to byte 4"));
    ret.returncode=tiff_seek_error_header;
    return ret;
  }
  uint32 offset;
  if ( ct_read32( ctif, &offset, 1) != 4 ) {
    ret = set_value_found_ret(&ret, str("TIFF Header ct_read error, reading 4 bytes from 4"));
    ret.returncode=tiff_read_error_header;
    return ret;
  }
  if (isByteSwapped) {
    offset = TIFFSwabLong (offset);
  }
  if (offset <= 7) {
    char msg[VALUESTRLEN];
    snprintf(msg, VALUESTRLEN, "pointer to IFD0 is %u", offset);
    ret=set_value_found_ret(&ret, str(msg));
    ret.returncode=tiff_ifd0_offset_must_be_greater_than_eight;
    return ret;
  }
  ret = get_IFD_at_pos(ctif, offset, ctif->ifd0);
  *ifd = offset;
  return ret;
}

/** reads IDS and try to find index of given tag, returns -1 if not found
 * @param ctif pointer to tiff structure
 * @param ifd pointer to IFD
 * @param tag tag
 * @return index or -1 if not found
 */
static int TIFFFind_Index_of_Tag(ctiff_t * ctif, cifd_t *ifd, tag_t tag) {
    assert(tag >= MINTAGS);
    uint16 count = get_ifd_count(ifd);

    if (ifd->tagorder !=  unknown_tag_order) {
        return ifd->index_cache_by_tag[tag];
    } else {
        for (uint16 tagidx = 0; tagidx < count; tagidx++) {
            if (tag == TIFFGetRawTagListEntry(ctif, ifd, tagidx)) {
                return tagidx;
            };
        };
    }
    return no_index_found;
}


/* scans first IDF and returns the n-th tag, if errorneous it returns a value 0 */
tag_t TIFFGetRawTagListEntry(ctiff_t * ctif, const cifd_t *ifd, int tagidx) {
    tag_t tag;
    if (ifd->tagorder == has_sorted_tags) { // only if sorted, the caches should be used
        //printf("RETURN FROM CACHE: tagidx=%i -> tag=%u\n", tagidx, ifd->tag_cache_by_index[tagidx]);
       return ifd->tag_cache_by_index[tagidx];
    }
    get_ifd0_count(ctif); /* return code ignored, used to read TIFF header */
    /* ct_read count of tags (2 Bytes) */
    off_t adress = (off_t) ifd->position + 2 + tagidx * 12; /* IFD has 2 bytes for count of tags, plus 12 bytes for each tag */
    if (ct_seek(ctif, adress, SEEK_SET) != adress) { /* IFD0 plus 2byte to get IFD-entries, then nth tag */
        //perror ("TIFF Header ct_seek error, seeking adress from ifd0 for tagid");
        //exit( EXIT_FAILURE );
        return 0;
    }
    if (ct_read16(ctif, &tag, 1) != 2) {
        //perror ("TIFF Header ct_read error2, reading tagid from ifd0 (2bytes)");
        // exit( EXIT_FAILURE );
        return 0;
    }
    if (is_byteswapped(ctif)) {
        tag = TIFFSwabShort(tag);
    }
    return tag;
}

/* offset data is pointer to memory of values of type offset_type */
#define OFFSET_MALLOC(ctread, ctif_p, offsetdata, offset_type, count ) {\
  size_t size = (size_t) sizeof(offset_type) * (count);                   \
  /*printf("OFFSET_MALLOC: count=%lu, size=%lu\n", count, size); */                      \
  if((size_t) (ctif_p)->streamlen < size) {\
    char msg[VALUESTRLEN]; \
    snprintf(msg, VALUESTRLEN,  "TIFF Offset ct_read error, try to read from offset count=%zu bytes, but file has size=%lu\n", size, (ctif_p)->streamlen); \
    *ret_p  = set_value_found_ret( ret_p, str(msg)); \
    ret_p->returncode = tiff_seek_error_offset;\
    return * ret_p;\
  }\
  (offsetdata) = NULL; (offsetdata) = malloc (size);\
  if (NULL == (offsetdata)) {\
    fprintf( stderr, "could not allocate memory for offset_t\n");\
    exit (EXIT_FAILURE);\
  }\
  ssize_t result = ctread( ctif_p, (offsetdata), count);                   \
  /*printf("RESULT=%lu\n", result); */                                                                     \
  if (result == 0) {\
    char msg[VALUESTRLEN]; \
    snprintf(msg, VALUESTRLEN,  "TIFF Offset ct_read error, try to read from offset count=%zu bytes, but EOF detected\n", size); \
    *ret_p  = set_value_found_ret( ret_p, str(msg)); \
    ret_p->returncode = tiff_read_error_offset; \
    return *ret_p; \
  }\
  if (\
      (result == no_file_found) ||\
      (result != (ssize_t) size)\
      ) {\
       /*  fprintf(stderr, "TIFF Offset ct_read error, try to read from offset count=%lu bytes\n", sizeof(offset_type) * count); */\
       /*  exit( EXIT_FAILURE ); */\
    char msg[VALUESTRLEN]; \
    snprintf(msg, VALUESTRLEN,  "TIFF Offset ct_read error, try to read from offset count=%zu bytes\n", size); \
    *ret_p  = set_value_found_ret( ret_p, str(msg)); \
    ret_p->returncode = tiff_read_error_offset; \
    return *ret_p; \
  }\
}

static void offset_swabshort(const ctiff_t * ctif, uint16 * address, uint16 count) {
  if (is_byteswapped(ctif)) {
    for (int i=0; i<count; i++, address++) {
      *address = TIFFSwabShort(*address);
    }
  }
}

static void offset_swablong(const ctiff_t * ctif, uint32 * address, uint16 count) {
  if (is_byteswapped(ctif)) {
    for (int i=0; i<count; i++, address++) {
      *address = TIFFSwabLong(*address);
    }
  }
}
/*  get count-data datastream from offset-address */
ret_t read_offsetdata(ctiff_t * ctif, uint32 address, uint32 count, uint16 datatype, offset_t * offset_p, ret_t * ret_p) {
  assert(NULL !=  offset_p);
  assert(NULL != ret_p);
  offset_p->count = count;
  offset_p->datatype = datatype;
  free_value_if_set(ret_p);
  ret_p->returncode = is_valid;
  /* ct_read and seek to IFD address */
  if (ct_seek(ctif, address, SEEK_SET) != (off_t) address) {
    offset_p->count = 0;
    ret_p->returncode = tiff_seek_error_offset;
    return * ret_p;
  }
#ifdef DEBUG
  printf("read_offsetdata(tif, address=%u, count=%u, datatype=%u)\n", address, count, datatype);
#endif
  switch (datatype) {
    case TIFF_BYTE: /* 8-bit unsigned integer */
    case TIFF_UNDEFINED: /* !8-bit untyped data */
      /*
      offset_p->data8p = NULL;
      offset_p->data8p = malloc ( sizeof(uint8) * count);
      if (ct_read(fd, offset_p->data8p,  sizeof(uint8) * count) != sizeof(uint8) *count)
        perror ("TIFF Offset ct_read error");
      */
      OFFSET_MALLOC(ct_read8, ctif, offset_p->data8p, uint8, count)
      break;
    case TIFF_ASCII: /* 8-bit bytes w/ last byte null */
      assert( sizeof(char) == sizeof(uint8));
      OFFSET_MALLOC(ct_read_intern,ctif, offset_p->datacharp, char, count)
      break;
    case TIFF_SBYTE: /* !8-bit signed integer */
      OFFSET_MALLOC(ct_read_intern, ctif, offset_p->datas8p, int8, count)
      break;
    case TIFF_SHORT: /* 16-bit unsigned integer */
      OFFSET_MALLOC(ct_read16, ctif, offset_p->data16p, uint16, count)
      offset_swabshort(ctif, offset_p->data16p, count);
      break;
    case TIFF_SSHORT: /* !16-bit signed integer */
      OFFSET_MALLOC(ct_read16s, ctif, offset_p->datas16p, int16, count)
      offset_swabshort(ctif, (uint16 *) offset_p->datas16p, count);
      break;
    case TIFF_LONG: /* 32-bit unsigned integer */
    case TIFF_IFD: /* %32-bit unsigned integer (offset) */
      OFFSET_MALLOC(ct_read32, ctif, offset_p->data32p, uint32, count)
      offset_swablong(ctif, offset_p->data32p, count);
      break;
    case TIFF_SLONG: /* !32-bit signed integer */
      OFFSET_MALLOC(ct_read32s, ctif, offset_p->datas32p, int32, count)
      offset_swablong(ctif, (uint32 *) offset_p->data32p, count);
      break;
    case TIFF_RATIONAL: /* 64-bit unsigned fraction */
      //printf("TIFF RATIONAL, count=%lu\n", count);
      OFFSET_MALLOC(ct_read32, ctif, offset_p->data32p, uint32, 2*count) /* because numerator + denominator */
      offset_swablong(ctif, offset_p->data32p, 2*count);
      break;
    case TIFF_SRATIONAL: /* !64-bit signed fraction */
      fprintf(stderr, "offsetdata datatype=%i not supported yet", datatype);
      exit(EXIT_FAILURE);
    case TIFF_FLOAT: /* !32-bit IEEE floating point */
      assert( sizeof(float) == 4);
      OFFSET_MALLOC(ct_readf, ctif, offset_p->datafloatp, float, count)
      break;
    case TIFF_DOUBLE: /* !64-bit IEEE floating point */
      assert( sizeof(double) == 8);
      OFFSET_MALLOC(ct_readd, ctif, offset_p->datadoublep, double, count)
      break;
    case TIFF_LONG8: /* BigTIFF 64-bit unsigned integer */
    case TIFF_IFD8: /* BigTIFF 64-bit unsigned integer (offset) */
      assert( sizeof(double) == 8);
      OFFSET_MALLOC(ct_read_intern, ctif, offset_p->data64p, uint64, count)
      break;
    case TIFF_SLONG8: /* BigTIFF 64-bit signed integer */
      assert( sizeof(double) == 8);
      OFFSET_MALLOC(ct_read_intern, ctif, offset_p->datas64p, int64, count)
        break;
    default: /*  should not occur */
      {
        char msg[VALUESTRLEN];
        snprintf(msg, VALUESTRLEN, "offsetdata datatype=%i not supported yet", datatype);
        *ret_p = set_value_found_ret(ret_p, str(msg));
        ret_p->returncode = should_not_occur;
        assert( ret_p->returncode != should_not_occur);
        return *ret_p;
      }
  };
#ifdef DEBUG
  printf ("is valid offset\n");
  printf ("RET=%s\n", get_parser_error_description(ret_p->returncode));
#endif
  return *ret_p;
}

/* scans first IDF and returns the type of the n-th tag */
ifd_entry_t TIFFGetRawTagIFDListEntry( ctiff_t * ctif, const cifd_t *ifd, int tagidx ) {
  uint16 tagcount = get_ifd_count( ifd);
  assert( tagcount > 0);
  int byteswapped = is_byteswapped(ctif);
#ifdef DEBUG
  printf(" count of tags = %i\n", tagcount);
#endif
  ifd_entry_t ifd_entry={0};
  ifd_entry.value_or_offset = is_error;
  off_t adress=(off_t) get_ifd_pos(ifd) +2+tagidx*12; /* IFD has 2 bytes for count of tags, plus 12 bytes for each tag */
  if (ct_seek(ctif, adress, SEEK_SET) !=adress) { /* IFD0 plus 2byte to get IFD-entries, then nth tag */
    //perror ("TIFF Header ct_seek error, seeking adress from ifd0 for tagid");
    return ifd_entry;
  }
  uint16 tagid;
  if ( ct_read16( ctif, &tagid, 1) != 2) {
    //perror ("TIFF Header ct_read error2, reading tagid from ifd0 (2bytes)");
    return ifd_entry;
  }
  if (byteswapped) {
    tagid = TIFFSwabShort(tagid);
  }
  // tag type check
  uint16 tagtype;
  if ( ct_read16( ctif, &tagtype, 1) != 2) {
    //perror ("TIFF Header ct_read error2, reading tagtype from ifd0 (2bytes)");
    return ifd_entry;
  }
  if (byteswapped) {
    tagtype = TIFFSwabShort(tagtype);
  }
  uint32 count;
  if ( ct_read32( ctif, &count, 1) != 4) {
    //perror ("TIFF Header ct_read error4, reading count from ifd0 (4bytes)");
    return ifd_entry;
  }
  if (byteswapped) {
    count = TIFFSwabLong( count);
  }
#ifdef DEBUG
  printf("\ntagid=%0x, tagtype=%0x count=%0x\n\n", tagid, tagtype, count);
#endif

      /*  is value or offset? */
      ifd_entry.count=count;
      ifd_entry.datatype=tagtype;

      uint32 value_or_offset;
      if ( ct_read32( ctif, &value_or_offset, 1) != 4) {
        //perror ("TIFF Header ct_read error4, reading value/offset from ifd0 (4bytes)");
        return ifd_entry;
      }
      if (byteswapped) {
        value_or_offset = TIFFSwabLong( value_or_offset);
      }
      switch( tagtype) {
        case TIFF_BYTE: /* 8-bit unsigned integer */
        case TIFF_ASCII: /* 8-bit bytes w/ last byte null */
        case TIFF_SBYTE: /* !8-bit signed integer */
        case TIFF_UNDEFINED: /* !8-bit untyped data */
          if (count > 4) { /* offset */
            ifd_entry.value_or_offset=is_offset;
            ifd_entry.data32offset=value_or_offset;
          } else { /*  values */
            ifd_entry.value_or_offset=is_value;
            ifd_entry.data8[0] = (uint8) (value_or_offset & 0x000000ff);
            ifd_entry.data8[1] = (uint8) ((value_or_offset >> 8) & 0x000000ff);
            ifd_entry.data8[2] = (uint8) ((value_or_offset >> 16) & 0x000000ff);
            ifd_entry.data8[3] = (uint8) ((value_or_offset >> 24) & 0x000000ff);
#ifdef DEBUG
            printf("data8[0]=%u\n", ifd_entry.data8[0] );
            printf("data8[1]=%u\n", ifd_entry.data8[1] );
            printf("data8[2]=%u\n", ifd_entry.data8[2] );
            printf("data8[3]=%u\n", ifd_entry.data8[3] );
#endif
          }; break;
        case TIFF_SHORT: /* 16-bit unsigned integer */
        case TIFF_SSHORT: /* !16-bit signed integer */
          if (count > 2) { /* offset */
            ifd_entry.value_or_offset=is_offset;
            ifd_entry.data32offset=value_or_offset;
          } else { /*  values */
            ifd_entry.value_or_offset=is_value;
	    // swap back if byteswapped
	    if (byteswapped) {
		    value_or_offset = TIFFSwabLong( value_or_offset);
	    }
            uint16 w0 = (uint16) (value_or_offset & 0x0000ffff);
            uint16 w1 = (uint16) ((value_or_offset >> 16) & 0x0000ffff);;
            if (byteswapped) { /* swap correctly 16bit */
              w0 = TIFFSwabShort( w0 );
              w1 = TIFFSwabShort( w1 );
            }
            ifd_entry.data16[0] = w0;
            ifd_entry.data16[1] = w1;
#ifdef DEBUG
            printf("data16[0]=%u\n", w0);
            printf("data16[1]=%u\n", w1);
#endif
          }; break;
        case TIFF_LONG: /* 32-bit unsigned integer */
        case TIFF_SLONG: /* !32-bit signed integer */
          if (count > 1) { /* offset */
            ifd_entry.value_or_offset=is_offset;
            ifd_entry.data32offset=value_or_offset;
          } else { /*  values */
            ifd_entry.value_or_offset=is_value;
            ifd_entry.data32=value_or_offset;
#ifdef DEBUG
            printf("data32[0]=%u\n", value_or_offset);
#endif
          }; break;
        case TIFF_RATIONAL: /* 64-bit unsigned fraction */
        case TIFF_SRATIONAL: /* !64-bit signed fraction */
        case TIFF_FLOAT: /* !32-bit IEEE floating point */
        case TIFF_DOUBLE: /* !64-bit IEEE floating point */
        case TIFF_IFD: /* %32-bit unsigned integer (offset) */
        case TIFF_LONG8: /* BigTIFF 64-bit unsigned integer */
        case TIFF_SLONG8: /* BigTIFF 64-bit signed integer */
        case TIFF_IFD8: /* BigTIFF 64-bit unsigned integer (offset) */
          ifd_entry.value_or_offset=is_offset;
          ifd_entry.data32offset=value_or_offset;
      }
#ifdef DEBUG
      printf(" tag=%u (0x%04x) tagtype=0x%04x is_offset=%s count=%u value_or_offset=0x%08x\n", tagid, tagid, tagtype, (ifd_entry.value_or_offset==is_offset ? "true" : "false"), count, value_or_offset);
#endif
  return ifd_entry;
}

/*  TODO */
ifd_entry_t TIFFGetRawIFDEntry( ctiff_t * ctif, cifd_t *ifd, tag_t tag) {
  int tagidx = TIFFFind_Index_of_Tag(ctif, ifd, tag);
  ifd_entry_t ifd_entry;
  if (tagidx >= 0) {
    ifd_entry =  TIFFGetRawTagIFDListEntry( ctif, ifd, tagidx );
  } else { /* tag not defined */
	  printf("\ttag %u (%s) was not found, but requested because defined\n", tag, TIFFTagName(tag));
	  ifd_entry.value_or_offset = is_error;
	  ifd_entry.count = 0;
    ifd_entry.data32offset=0;
    ifd_entry.datatype = TIFF_ERROR;
  }
  return ifd_entry;
}
/* scans first IDF and returns the type of the n-th tag */
uint32 TIFFGetRawTagTypeListEntry( ctiff_t * ctif, cifd_t *ifd, int tagidx ) {
  if (tagidx >= 0) {
    ifd_entry_t ifd_entry = TIFFGetRawTagIFDListEntry(ctif, ifd, tagidx);
    return ifd_entry.datatype;
  } else { /* tag not defined */
    fprintf(stderr, "tagidx should be greater equal 0");
    return TIFF_ERROR;
  }
}


/** ct_reads the datatype of given tag on specified TIFF,
 * because FieldType of libtiff does not return the true value (because it maps
 * its own datastructure), we need to use this function instead
 * @param ctif pointer to TIFF structure
 * @param ifd pointer to IFD
 * @param tag tag
 * @return datatype of given tag
 * if tag does not exists the function aborts with an error
 */
TIFFDataType TIFFGetRawTagType(ctiff_t * ctif, cifd_t *ifd, tag_t tag) {
  int tagidx = TIFFFind_Index_of_Tag(ctif, ifd, tag);
  if (tagidx >= 0) {
    TIFFDataType datatype =  TIFFGetRawTagTypeListEntry( ctif, ifd, tagidx );
#ifdef DEBUG
    printf("### datatype=%i \n", datatype);
#endif
    return datatype;
  } else { /* tag not defined */
	  printf("\ttag %u (%s) was not found, but requested because defined\n", tag, TIFFTagName(tag));
	  return TIFF_ERROR;
  }
}

ctiff_t * initialize_ctif(const char * tiff_file, ct_ioflag_t ioflag) {
  ctiff_t * ctif = malloc ( sizeof( ctiff_t) );
  if (NULL == ctif) {
    fprintf( stderr, "could not allocate memory for ctiff_t\n");
    exit (EXIT_FAILURE);
  }
  /* load tiff file */
#ifdef __WIN32__
  int tif = open(tiff_file, O_RDONLY | O_BINARY);
#else
  int tif = open(tiff_file, O_RDONLY);
#endif
  if (no_file_found == tif) {
	  fprintf( stderr, "TIFF file '%s' could not be opened, %s\n", tiff_file, strerror(errno));
	  exit (EXIT_FAILURE);
  };
  ctif->fd = tif;
  ctif->streamlen = (ssize_t) fsize(tif);
  ctif->streamp = NULL;
  ctif->actual_streamp = NULL;
  static cifd_t ifd0 = {
          .tagorder=unknown_tag_order,
          .position=0,
          .tag_count=0,
          .index_cache_by_tag={-1},
          .tag_cache_by_index={0},
  };
  /* empty_cache(&ifd0); */
  ctif->ifd0 = &ifd0;

#ifdef HAVE_MMAP
  switch (ioflag) {
	  case is_filep: {
				 /* streamlen */
				 break;
			 }
	  case  is_memmap: {

				   void * tifmap = mmap( NULL, ctif->streamlen, PROT_READ, MAP_PRIVATE, tif, 0 );
				   if (MAP_FAILED == tifmap) {
					   fprintf( stderr, "TIFF file '%s' could not be mem-mapped, %s\n", tiff_file, strerror(errno));
					   exit (EXIT_FAILURE);
				   };
				   ctif->streamp=tifmap;
				   ctif->actual_streamp=tifmap;
				  break;
			   }
  }
#endif
  ctif->ioflag = ioflag;
  ctif->filename = strdup(tiff_file);
  return ctif;
}

void free_ctif( ctiff_t * ctif) {
	assert( NULL != ctif);
	free(ctif->filename);
	ctif->filename=NULL;
	 switch (ctif->ioflag) {
	  case is_filep: {
				 break;
			 }
	  case is_memmap: {

				  /* TODO */
				  break;
			  }
	 }
	 close(ctif->fd);
	 ctif->fd = no_file_found;
	free (ctif);
	/* ctif = NULL; */
}

uint32 get_ifd_pos(const cifd_t *ifd ) {
	return ifd->position;
}

uint16 get_ifd_count(const cifd_t *ifd) {
	return ifd->tag_count;
}

uint32 get_ifd0_pos(const ctiff_t * ctif) {
    assert( NULL != ctif);
    return (get_ifd_pos(ctif->ifd0));
}

uint16 get_ifd0_count(const ctiff_t * ctif) {
    assert( NULL != ctif);
    return (get_ifd_count(ctif->ifd0));
}

inline int is_byteswapped(const ctiff_t * ctif ) {
	assert( NULL != ctif);
	return ctif->isbyteswapped;
}

uint32 get_next_ifd_pos(ctiff_t * ctif, uint32 actual_pos) {
	assert( NULL != ctif);

	ct_seek(ctif, actual_pos, SEEK_SET);
	uint16 count;
	if ( ct_read16( ctif, &count, 1) != 2 ) {
    /*  FIXME: replace perror/exit with own error handling routine */
		//perror ("TIFF Header ct_read error2, reading count from ifd0 (2bytes)");
		//exit( EXIT_FAILURE );
        return 0;
	}

	if (ctif->isbyteswapped) {
		count = TIFFSwabShort(count);
    }
	ct_seek(ctif, 12 * count, SEEK_CUR); /* each IFD entry is 12 bytes long */
	uint32 offset;
	if ( ct_read32( ctif, &offset, 1) != 4 ) {
    /*  FIXME: replace perror/exit with own error handling routine */
		//perror ("TIFF Header ct_read error3, reading next ifd offset from ifd0 (4bytes)");
		//exit( EXIT_FAILURE );
        return 0;
	}
	if (ctif->isbyteswapped) {
		offset = TIFFSwabLong(offset);
    }
	return offset;
}

ret_t TIFFGetFieldASCII(ctiff_t * ctif, cifd_t *ifd, tag_t tag, char** string_pp, uint32 * countp ) {
  assert( *string_pp == NULL);
  assert( countp != NULL);
  GET_EMPTY_RET(ret)
  int tagidx = TIFFGetRawTagListIndex(ifd, tag);
  if (tagidx >= 0) { /* there exists the tag */
    ifd_entry_t entry = TIFFGetRawTagIFDListEntry( ctif, ifd, tagidx );
    /* assert (entry.datatype == TIFF_ASCII); */
    if (entry.datatype != TIFF_ASCII) {
      ret = set_value_found_ret_formatted_with_returncode(&ret, tagerror_unexpected_type_found, "data of type ASCII expected, but got type %s", TIFFTypeName(entry.datatype));
      return ret;
    }
    *countp = entry.count;
/*
    *(string_pp) = malloc( sizeof(char) * entry.count +1);
    if (NULL == (* string_pp)) {
      ret.returncode=could_not_allocate_memory;
      return ret;
    }
    memset(*string_pp, '\0', entry.count+1);
*/
    *(string_pp) = calloc(entry.count+1, sizeof(char) );
    if (NULL == (* string_pp)) {
      ret.returncode=could_not_allocate_memory;
      return ret;
    }
    if (entry.value_or_offset == is_value) {
      assert (entry.count <= 4);
      for (uint32 i=0; i<entry.count; i++) {
        (*string_pp)[i]=(char) entry.data8[i];
      }
      ret.returncode=is_valid;
      return ret;
    } else if (entry.value_or_offset == is_offset) {
      uint32 data32offset = entry.data32offset;
      offset_t offset;
      ret = read_offsetdata( ctif, data32offset, entry.count, entry.datatype, &offset, &ret);
      if (ret.returncode != is_valid) {
        /*  FIXME: free(offset.datacharp); */
        return ret;
      }
      char const * p = offset.datacharp;
      char * s = *(string_pp);
#ifdef DEBUG
      /* DEBUG: */
      printf("offset.count=%u, offset.datacharp=%p\n", offset.count, offset.datacharp);
      printf("tag=%i entry.count=%u offset.count=%u\n", tag, entry.count, offset.count);
#endif
      for (uint32 i=0; i<entry.count; i++) {
#ifdef DEBUG
        printf("P[%u]=%c\n", i, *p);
#endif
        *(s++) = *(p++);
      }
      //printf("ASCII='%s'\n", *(string_pp));
      free(offset.datacharp);
      ret.returncode=is_valid;
      return ret;
    }
  }
  ret.returncode = tag_does_not_exist;
  return ret;
}

ret_t TIFFGetFieldLONG(ctiff_t * ctif, cifd_t *ifd, tag_t tag, uint32 ** long_pp, uint32 * countp) {
  assert( *long_pp == NULL);
  assert( countp != NULL);
  *countp = 0; /*  init */
  GET_EMPTY_RET(ret)
  int tagidx = TIFFGetRawTagListIndex(ifd, tag);
  if (tagidx >= 0) { /* there exists the tag */
    ifd_entry_t entry = TIFFGetRawTagIFDListEntry( ctif, ifd, tagidx );
    uint32 overflow =  (0xffffffff / (sizeof(uint32)));
    if (entry.count > overflow) {
      ret = set_value_found_ret_formatted_with_returncode(&ret, tagerror_count_results_in_offsets_overflow, "count=%u overflow boundary=%u", entry.count, overflow);
      return ret;
    }

    *countp = entry.count;
    *(long_pp) = malloc( sizeof(uint32) * entry.count);
    if (NULL == (*long_pp)) {
      ret.returncode=could_not_allocate_memory;
      return ret;
    }
    /* assert (entry.datatype == TIFF_LONG); */
    if (entry.datatype != TIFF_LONG) {
      ret = set_value_found_ret_formatted_with_returncode(&ret, tagerror_unexpected_type_found, "data of type LONG expected, but got type %s", TIFFTypeName(entry.datatype));
      return ret;
    }

    if (entry.value_or_offset == is_value) {
      assert (entry.count <= 1);
      //printf("LONG (direct)=%lu\n", entry.data32);
      memcpy((void *) (*long_pp), (void *) &entry.data32, (sizeof(uint32)*entry.count));
      ret.returncode=is_valid;
      return ret;
    } else if (entry.value_or_offset == is_offset) {
      uint32 data32offset = entry.data32offset;
      offset_t offset;
      ret = read_offsetdata( ctif, data32offset, entry.count, entry.datatype, &offset, &ret);
      if (ret.returncode != is_valid) {
        /*  FIXME: free(offset.datacharp); */
        return ret;
      }
      //printf("LONG (offset)=%lu\n", *offset.datacharp);
      memcpy((void *) (*long_pp), (void *) offset.datacharp, (sizeof(uint32)*offset.count));
      free(offset.datacharp);
      ret.returncode=is_valid;
      return ret;
    }
  }
  ret.returncode = tag_does_not_exist;
  return ret;
}

ret_t TIFFGetFieldSHORT(ctiff_t * ctif, cifd_t *ifd, tag_t tag, uint16 ** short_pp, uint32 * countp) {
  assert( *short_pp == NULL);
  assert( countp != NULL);
  *countp = 0; /*  init */
  GET_EMPTY_RET(ret)
  int tagidx = TIFFGetRawTagListIndex(ifd, tag);
  if (tagidx >= 0) { /* there exists the tag */
    ifd_entry_t entry = TIFFGetRawTagIFDListEntry( ctif, ifd, tagidx );
    uint32 overflow =  (0xffffffff / (sizeof(uint16)));
    if (entry.count > overflow) {
      ret = set_value_found_ret_formatted_with_returncode(&ret, tagerror_count_results_in_offsets_overflow, "count=%u overflow boundary=%u", entry.count, overflow);
      return ret;
    }

    *countp = entry.count;
    *(short_pp) = malloc( (size_t) sizeof(uint16) * entry.count);
    if (NULL == *(short_pp)) {
      ret.returncode=could_not_allocate_memory;
      return ret;
    }
    /* assert (entry.datatype == TIFF_SHORT); */
    if (entry.datatype != TIFF_SHORT) {
      ret = set_value_found_ret_formatted_with_returncode(&ret, tagerror_unexpected_type_found, "data of type SHORT expected, but got type %s", TIFFTypeName(entry.datatype));
      return ret;
    }

    if (entry.value_or_offset == is_value) {
      assert (entry.count <= 2);
      memcpy((void *) (*short_pp), (void *) &entry.data16, (sizeof(uint16)*entry.count));
      //printf("SHORT (direct)=%u %u\n", entry.data32, **short_pp);
      ret.returncode=is_valid;
      return ret;
    } else if (entry.value_or_offset == is_offset) {
      uint32 data32offset = entry.data32offset;
      offset_t offset;
      ret = read_offsetdata( ctif, data32offset, entry.count, entry.datatype, &offset, &ret);
      if (ret.returncode != is_valid) {
        /*  FIXME: free(offset.datacharp); */
        return ret;
      }
      //printf("SHORT (offset)=%u\n", *offset.datacharp);
      memcpy((void *) (*short_pp), (void *)offset.datacharp, (sizeof(uint16)*offset.count));
      free( offset.datacharp );
      offset.datacharp=NULL;
      ret.returncode=is_valid;
      return ret;
    }
  }
  ret.returncode = tag_does_not_exist;
  return ret;
}

ret_t TIFFGetFieldRATIONAL(ctiff_t * ctif, cifd_t *ifd, tag_t tag, float ** float_pp, uint32 * countp) {
  assert( *float_pp == NULL);
  assert( countp != NULL);
  *countp = 0; /*  init */
  GET_EMPTY_RET(ret)
  int tagidx = TIFFGetRawTagListIndex(ifd, tag);
  if (tagidx >= 0) { /* there exists the tag */
    ifd_entry_t entry = TIFFGetRawTagIFDListEntry( ctif, ifd, tagidx );
    uint32 overflow =  (0xffffffff / (2*sizeof(uint32)));
    if (entry.count > overflow) {
      ret = set_value_found_ret_formatted_with_returncode(&ret, tagerror_count_results_in_offsets_overflow, "count=%u overflow boundary=%u", entry.count, overflow);
      return ret;
    }
    //printf("entry.count=%i\n", entry.count);
    *countp = entry.count;
    *(float_pp) = malloc( sizeof(float) * (entry.count));
    if (NULL == *(float_pp)) {
      ret.returncode=could_not_allocate_memory;
      return ret;
    }
    /* assert (entry.datatype == TIFF_RATIONAL); */
    if (entry.datatype != TIFF_RATIONAL) {
      ret = set_value_found_ret_formatted_with_returncode(&ret, tagerror_unexpected_type_found, "data of type RATIONAL expected, but got type %s", TIFFTypeName(entry.datatype));
      return ret;
    }

    /*  rational is defined as 2x32bits */
    if (entry.value_or_offset == is_value) {
      ret.returncode = tagerror_expected_offsetdata;
      return ret;
    } else if (entry.value_or_offset == is_offset) {
      uint32 data32offset = entry.data32offset;
      offset_t offset;
      //printf("data32offset=%u count=%i\n", data32offset, entry.count);
      ret = read_offsetdata( ctif, data32offset, entry.count, entry.datatype, &offset, &ret);
      if (ret.returncode != is_valid) {
        /*  FIXME: free(offset.datacharp); */
        return ret;
      }
      /* copy to a float */
      uint32 const * orig_data32p = offset.data32p;
      for (uint32 i = 0; i< entry.count; i++, orig_data32p+=2) {
        uint32 numerator = *(orig_data32p);
        uint32 denominator = *(orig_data32p+1);
        //printf("DEBUG: numerator=%u denumeator=%u\n", numerator, denominator);
        float v;
        if (0 == denominator) {
          v=NAN;
        } else {
          v = (float) numerator / (float) denominator;
        }
        //printf("DEBUG2: *float_pp[%i]=%f (%u / %u)\n", i, v, numerator, denominator);
        (*(float_pp))[i]=v;
      }
      free( offset.data32p );
      offset.data32p=NULL;
      ret.returncode=is_valid;
      return ret;
    }
  }
  ret.returncode = tag_does_not_exist;
  return ret;
}

/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 smarttab expandtab :*/
