text: /*
   * This code is part of the tng binary trajectory format.
   *
   * Copyright (c) 2012-2017, The GROMACS development team.
   * Copyright (c) 2020, by the GROMACS development team.
   * TNG was orginally written by Magnus Lundborg, Daniel Spångberg and
   * Rossen Apostolov. The API is implemented mainly by Magnus Lundborg,
   * Daniel Spångberg and Anders Gärdenäs.
   *
   * Please see the AUTHORS file for more information.
   *
   * The TNG library is free software; you can redistribute it and/or
   * modify it under the terms of the Revised BSD License.
   *
   * To help us fund future development, we humbly ask that you cite
   * the research papers on the package.
   *
   * Check out http://www.gromacs.org for more information.
   */
  
  /* These three definitions are required to enforce 64 bit file sizes. */
  /* Force 64 bit variants of file access calls. */
  #define _FILE_OFFSET_BITS 64
  /* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */
  #define _LARGEFILE_SOURCE
  /* Define for large files, on AIX-style hosts. */
  #define _LARGE_FILES
  
  #include "tng/tng_io.h"
  
  #ifdef USE_STD_INTTYPES_H
  #    include <inttypes.h>
  #endif
  
  #include <limits.h>
  #include <stdlib.h>
  #include <string.h>
  #include <time.h>
  #include <math.h>
  #include <zlib.h>
  
  //#include "external/zlib.h"
  #include "tng/md5.h"
  #include "compression/tng_compress.h"
  #include "tng/version.h"
  
  #if defined(_WIN32) || defined(_WIN64)
  #    ifndef fseeko
  #        define fseeko _fseeki64
  #    endif
  #    ifndef ftello
  #        ifdef __MINGW32__
  #            define ftello ftello64
  #        else
  #            define ftello _ftelli64
  #        endif
  #    endif
  #endif
  
  
  #ifndef USE_WINDOWS
  #    if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
  #        define USE_WINDOWS
  #    endif /* win32... */
  #endif     /* not defined USE_WINDOWS */
  
  #ifdef USE_WINDOWS
  #    define TNG_INLINE __inline
  #    define TNG_SNPRINTF _snprintf
  #else
  #    define TNG_INLINE inline
  #    define TNG_SNPRINTF snprintf
  #endif
  
  static TNG_INLINE size_t tng_min_size(const size_t a, const size_t b)
  {
      return (a < b ? a : b);
  }
  
  static TNG_INLINE int64_t tng_min_i64(const int64_t a, const int64_t b)
  {
      return (a < b ? a : b);
  }
  
  static TNG_INLINE int64_t tng_max_i64(const int64_t a, const int64_t b)
  {
      return (a > b ? a : b);
  }
  
  /**
   * @brief This function swaps the byte order of a 32 bit numerical variable
   * to big endian.
   * @param tng_data is a trajectory data container.
   * @param v is a pointer to a 32 bit numerical value (float or integer).
   * @details The function does not only work with integer, but e.g. floats need casting.
   * If the byte order is already big endian no change is needed.
   * @return TNG_SUCCESS (0) if successful, TNG_FAILURE (1) if the current
   * byte order is not recognised.
   */
  static tng_function_status tng_swap_byte_order_big_endian_32(const struct tng_trajectory* tng_data,
                                                               uint32_t*                    v)
  {
      switch (tng_data->endianness_32)
      {
          case TNG_LITTLE_ENDIAN_32:           /* Byte order is reversed. */
              *v = ((*v & 0xFF000000) >> 24) | /* Move first byte to end */
                   ((*v & 0x00FF0000) >> 8) |  /* Move 2nd byte to pos 3 */
                   ((*v & 0x0000FF00) << 8) |  /* Move 3rd byte to pos 2 */
                   ((*v & 0x000000FF) << 24);  /* Move last byte to first */
  
              return (TNG_SUCCESS);
  
          case TNG_BYTE_PAIR_SWAP_32: /* byte pair swap */
              *v = ((*v & 0xFFFF0000) >> 16) | ((*v & 0x0000FFFF) << 16);
  
              return (TNG_SUCCESS);
  
          case TNG_BIG_ENDIAN_32: /* Already correct */ return (TNG_SUCCESS);
  
          default: return (TNG_FAILURE);
      }
  }
  
  /**
   * @brief This function swaps the byte order of a 64 bit numerical variable
   * to big endian.
   * @param tng_data is a trajectory data container.
   * @param v is a pointer to a 64 bit numerical value (double or integer).
   * @details The function does not only work with integer, but e.g. floats need casting.
   * The byte order swapping routine can convert four different byte
   * orders to big endian.
   * If the byte order is already big endian no change is needed.
   * @return TNG_SUCCESS (0) if successful, TNG_FAILURE (1) if the current
   * byte order is not recognised.
   */
  static tng_function_status tng_swap_byte_order_big_endian_64(const struct tng_trajectory* tng_data,
                                                               uint64_t*                    v)
  {
      switch (tng_data->endianness_64)
      {
          case TNG_LITTLE_ENDIAN_64:                     /* Byte order is reversed. */
              *v = ((*v & 0xFF00000000000000LL) >> 56) | /* Move first byte to end */
                   ((*v & 0x00FF000000000000LL) >> 40) | /* Move 2nd byte to pos 7 */
                   ((*v & 0x0000FF0000000000LL) >> 24) | /* Move 3rd byte to pos 6 */
                   ((*v & 0x000000FF00000000LL) >> 8) |  /* Move 4th byte to pos 5 */
                   ((*v & 0x00000000FF000000LL) << 8) |  /* Move 5th byte to pos 4 */
                   ((*v & 0x0000000000FF0000LL) << 24) | /* Move 6th byte to pos 3 */
                   ((*v & 0x000000000000FF00LL) << 40) | /* Move 7th byte to pos 2 */
                   ((*v & 0x00000000000000FFLL) << 56);  /* Move last byte to first */
  
              return (TNG_SUCCESS);
  
          case TNG_QUAD_SWAP_64: /* Byte quad swap */
              *v = ((*v & 0xFFFFFFFF00000000LL) >> 32) | ((*v & 0x00000000FFFFFFFFLL) << 32);
  
              return (TNG_SUCCESS);
  
          case TNG_BYTE_PAIR_SWAP_64: /* Byte pair swap */
              *v = ((*v & 0xFFFF0000FFFF0000LL) >> 16) | ((*v & 0x0000FFFF0000FFFFLL) << 16);
  
              return (TNG_SUCCESS);
  
          case TNG_BYTE_SWAP_64: /* Byte swap */
              *v = ((*v & 0xFF00FF00FF00FF00LL) >> 8) | ((*v & 0x00FF00FF00FF00FFLL) << 8);
  
              return (TNG_SUCCESS);
  
          case TNG_BIG_ENDIAN_64: /* Already correct */ return (TNG_SUCCESS);
  
          default: return (TNG_FAILURE);
      }
  }
  
  /**
   * @brief This function swaps the byte order of a 32 bit numerical variable
   * to little endian.
   * @param tng_data is a trajectory data container.
   * @param v is a pointer to a 32 bit numerical value (float or integer).
   * @details The function does not only work with integer, but e.g. floats need casting.
   * If the byte order is already little endian no change is needed.
   * @return TNG_SUCCESS (0) if successful, TNG_FAILURE (1) if the current
   * byte order is not recognised.
   */
  static tng_function_status tng_swap_byte_order_little_endian_32(const struct tng_trajectory* tng_data,
                                                                  uint32_t*                    v)
  {
      switch (tng_data->endianness_32)
      {
          case TNG_LITTLE_ENDIAN_32: /* Already correct */ return (TNG_SUCCESS);
  
          case TNG_BYTE_PAIR_SWAP_32: /* byte pair swapped big endian to little endian */
              *v = ((*v & 0xFF00FF00) >> 8) | ((*v & 0x00FF00FF) << 8);
  
              return (TNG_SUCCESS);
  
          case TNG_BIG_ENDIAN_32:              /* Byte order is reversed. */
              *v = ((*v & 0xFF000000) >> 24) | /* Move first byte to end */
                   ((*v & 0x00FF0000) >> 8) |  /* Move 2nd byte to pos 3 */
                   ((*v & 0x0000FF00) << 8) |  /* Move 3rd byte to pos 2 */
                   ((*v & 0x000000FF) << 24);  /* Move last byte to first */
  
              return (TNG_SUCCESS);
  
          default: return (TNG_FAILURE);
      }
  }
  
  /**
   * @brief This function swaps the byte order of a 64 bit numerical variable
   * to little endian.
   * @param tng_data is a trajectory data container.
   * @param v is a pointer to a 64 bit numerical value (double or integer).
   * @details The function does not only work with integer, but e.g. floats need casting.
   * The byte order swapping routine can convert four different byte
   * orders to little endian.
   * If the byte order is already little endian no change is needed.
   * @return TNG_SUCCESS (0) if successful, TNG_FAILURE (1) if the current
   * byte order is not recognised.
   */
  static tng_function_status tng_swap_byte_order_little_endian_64(const struct tng_trajectory* tng_data,
                                                                  uint64_t*                    v)
  {
      switch (tng_data->endianness_64)
      {
          case TNG_LITTLE_ENDIAN_64: /* Already correct */ return (TNG_SUCCESS);
  
          case TNG_QUAD_SWAP_64: /* Byte quad swapped big endian to little endian */
              *v = ((*v & 0xFF000000FF000000LL) >> 24) | ((*v & 0x00FF000000FF0000LL) >> 8)
                   | ((*v & 0x0000FF000000FF00LL) << 8) | ((*v & 0x000000FF000000FFLL) << 24);
  
              return (TNG_SUCCESS);
  
          case TNG_BYTE_PAIR_SWAP_64: /* Byte pair swapped big endian to little endian */
              *v = ((*v & 0xFF00FF0000000000LL) >> 40) | ((*v & 0x00FF00FF00000000LL) >> 24)
                   | ((*v & 0x00000000FF00FF00LL) << 24) | ((*v & 0x0000000000FF00FFLL) << 40);
  
              return (TNG_SUCCESS);
  
          case TNG_BYTE_SWAP_64: /* Byte swapped big endian to little endian */
              *v = ((*v & 0xFFFF000000000000LL) >> 48) | ((*v & 0x0000FFFF00000000LL) >> 16)
                   | ((*v & 0x00000000FFFF0000LL) << 16) | ((*v & 0x000000000000FFFFLL) << 48);
  
              return (TNG_SUCCESS);
  
          case TNG_BIG_ENDIAN_64:                        /* Byte order is reversed. */
              *v = ((*v & 0xFF00000000000000LL) >> 56) | /* Move first byte to end */
                   ((*v & 0x00FF000000000000LL) >> 40) | /* Move 2nd byte to pos 7 */
                   ((*v & 0x0000FF0000000000LL) >> 24) | /* Move 3rd byte to pos 6 */
                   ((*v & 0x000000FF00000000LL) >> 8) |  /* Move 4th byte to pos 5 */
                   ((*v & 0x00000000FF000000LL) << 8) |  /* Move 5th byte to pos 4 */
                   ((*v & 0x0000000000FF0000LL) << 24) | /* Move 6th byte to pos 3 */
                   ((*v & 0x000000000000FF00LL) << 40) | /* Move 7th byte to pos 2 */
                   ((*v & 0x00000000000000FFLL) << 56);  /* Move last byte to first */
  
              return (TNG_SUCCESS);
  
          default: return (TNG_FAILURE);
      }
  }
  
  /**
   * @brief Read a NULL terminated string from a file.
   * @param tng_data is a trajectory data container
   * @param str is a pointer to the character string that will
   * contain the read string. *str is reallocated in the function
   * and must be NULL or pointing at already allocated memory.
   * @param hash_mode is an option to decide whether to use the md5 hash or not.
   * @param md5_state is a pointer to the current md5 storage, which will be
   * appended with str if hash_mode == TNG_USE_HASH.
   * @param line_nr is the line number where this function was called, to be
   * able to give more useful error messages.
   */
  static tng_function_status tng_freadstr(const struct tng_trajectory* tng_data,
                                          char**                       str,
                                          const char                   hash_mode,
                                          md5_state_t*                 md5_state,
                                          const int                    line_nr)
  {
      char temp[TNG_MAX_STR_LEN], *temp_alloc;
      int  c, count = 0;
  
      do
      {
          c = fgetc(tng_data->input_file);
  
          if (c == EOF)
          {
              /* Clear file error flag and return -1 if EOF is read.*/
              clearerr(tng_data->input_file);
              return TNG_FAILURE;
          }
          else
          {
              /* Cast c to char */
              temp[count++] = (char)c;
          }
      } while ((temp[count - 1] != '\0') && (count < TNG_MAX_STR_LEN));
  
      temp_alloc = (char*)realloc(*str, count);
      if (!temp_alloc)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, line_nr);
          free(*str);
          *str = 0;
          return TNG_FAILURE;
      }
      *str = temp_alloc;
  
      strncpy(*str, temp, count);
  
      if (hash_mode == TNG_USE_HASH)
      {
          md5_append(md5_state, (md5_byte_t*)*str, count);
      }
  
      return TNG_SUCCESS;
  }
  
  /**
   * @brief Write a NULL terminated string to a file.
   * @param tng_data is a trajectory data container
   * @param str is a pointer to the character string should be written.
   * @param hash_mode is an option to decide whether to use the md5 hash or not.
   * @param md5_state is a pointer to the current md5 storage, which will be
   * appended with str if hash_mode == TNG_USE_HASH.
   * @param line_nr is the line number where this function was called, to be
   * able to give more useful error messages.
   */
  static TNG_INLINE tng_function_status tng_fwritestr(tng_trajectory_t tng_data,
                                                      const char*      str,
                                                      const char       hash_mode,
                                                      md5_state_t*     md5_state,
                                                      const int        line_nr)
  {
      size_t len;
  
      len = tng_min_size(strlen(str) + 1, TNG_MAX_STR_LEN);
  
      if (fwrite(str, len, 1, tng_data->output_file) != 1)
      {
          fprintf(stderr, "TNG library: Could not write block data. %s: %d\n", __FILE__, line_nr);
          return (TNG_CRITICAL);
      }
  
      if (hash_mode == TNG_USE_HASH)
      {
          md5_append(md5_state, (md5_byte_t*)str, len);
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Read a numerical value from file.
   * The byte order will be swapped if need be.
   * @param tng_data is a trajectory data container
   * @param dest is a pointer to where to store the read data.
   * @param len is the length (in bytes) of the numerical data type. Should
   * be 8 for 64 bit, 4 for 32 bit or 1 for a single byte flag.
   * @param hash_mode is an option to decide whether to use the md5 hash or not.
   * @param md5_state is a pointer to the current md5 storage, which will be
   * appended with str if hash_mode == TNG_USE_HASH.
   * @param line_nr is the line number where this function was called, to be
   * able to give more useful error messages.
   */
  static TNG_INLINE tng_function_status tng_file_input_numerical(const struct tng_trajectory* tng_data,
                                                                 void*                        dest,
                                                                 const size_t                 len,
                                                                 const char   hash_mode,
                                                                 md5_state_t* md5_state,
                                                                 const int    line_nr)
  {
      if (fread(dest, len, 1, tng_data->input_file) == 0)
      {
          fprintf(stderr, "TNG library: Cannot read block. %s: %d\n", __FILE__, line_nr);
          return (TNG_CRITICAL);
      }
      if (hash_mode == TNG_USE_HASH)
      {
          md5_append(md5_state, (md5_byte_t*)dest, len);
      }
      switch (len)
      {
          case 8:
              if (tng_data->input_endianness_swap_func_64
                  && tng_data->input_endianness_swap_func_64(tng_data, (uint64_t*)dest) != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n", __FILE__, line_nr);
              }
              break;
          case 4:
              if (tng_data->input_endianness_swap_func_32
                  && tng_data->input_endianness_swap_func_32(tng_data, (uint32_t*)dest) != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n", __FILE__, line_nr);
              }
              break;
          default: break;
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Write a numerical value to file.
   * The byte order will be swapped if need be.
   * @param tng_data is a trajectory data container
   * @param src is a pointer to the data to write.
   * @param len is the length (in bytes) of the numerical data type. Should
   * be 8 for 64 bit, 4 for 32 bit or 1 for a single byte flag.
   * @param hash_mode is an option to decide whether to use the md5 hash or not.
   * @param md5_state is a pointer to the current md5 storage, which will be
   * appended with str if hash_mode == TNG_USE_HASH.
   * @param line_nr is the line number where this function was called, to be
   * able to give more useful error messages.
   */
  static TNG_INLINE tng_function_status tng_file_output_numerical(const struct tng_trajectory* tng_data,
                                                                  const void*                  src,
                                                                  const size_t                 len,
                                                                  const char   hash_mode,
                                                                  md5_state_t* md5_state,
                                                                  const int    line_nr)
  {
      uint32_t temp_i32;
      uint64_t temp_i64;
  
      switch (len)
      {
          case 8:
              temp_i64 = *((uint64_t*)src);
              if (tng_data->output_endianness_swap_func_64
                  && tng_data->output_endianness_swap_func_64(tng_data, &temp_i64) != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n", __FILE__, line_nr);
              }
              if (fwrite(&temp_i64, len, 1, tng_data->output_file) != 1)
              {
                  fprintf(stderr, "TNG library: Could not write data. %s: %d\n", __FILE__, line_nr);
                  return (TNG_CRITICAL);
              }
              if (hash_mode == TNG_USE_HASH)
              {
                  md5_append(md5_state, (md5_byte_t*)&temp_i64, len);
              }
              break;
          case 4:
              temp_i32 = *((uint32_t*)src);
              if (tng_data->output_endianness_swap_func_32
                  && tng_data->output_endianness_swap_func_32(tng_data, &temp_i32) != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n", __FILE__, line_nr);
              }
              if (fwrite(&temp_i32, len, 1, tng_data->output_file) != 1)
              {
                  fprintf(stderr, "TNG library: Could not write data. %s: %d\n", __FILE__, line_nr);
                  return (TNG_CRITICAL);
              }
              if (hash_mode == TNG_USE_HASH)
              {
                  md5_append(md5_state, (md5_byte_t*)&temp_i32, len);
              }
              break;
          default:
              if (fwrite(src, len, 1, tng_data->output_file) != 1)
              {
                  fprintf(stderr, "TNG library: Could not write data. %s: %d\n", __FILE__, line_nr);
                  return (TNG_CRITICAL);
              }
              if (hash_mode == TNG_USE_HASH)
              {
                  md5_append(md5_state, (md5_byte_t*)src, len);
              }
              break;
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Generate the md5 hash of a block.
   * The hash is created based on the actual block contents.
   * @param block is a general block container.
   * @return TNG_SUCCESS (0) if successful.
   */
  static tng_function_status tng_block_md5_hash_generate(const struct tng_gen_block* block)
  {
      md5_state_t md5_state;
  
      md5_init(&md5_state);
      md5_append(&md5_state, (md5_byte_t*)block->block_contents, (int)block->block_contents_size);
      md5_finish(&md5_state, (md5_byte_t*)block->md5_hash);
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief If there is data left in the block read that to append that to the MD5 hash.
   * @param tng_data is a trajectory data container.
   * @param block is the data block that is being read.
   * @param start_pos is the file position where the block started.
   * @param md5_state is the md5 to which the md5 of the remaining block
   * will be appended.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_md5_remaining_append(const struct tng_trajectory* tng_data,
                                                      const struct tng_gen_block*  block,
                                                      const int64_t                start_pos,
                                                      md5_state_t*                 md5_state)
  {
      int64_t curr_file_pos;
      char*   temp_data;
  
      curr_file_pos = ftello(tng_data->input_file);
      if (curr_file_pos < start_pos + block->block_contents_size)
      {
          temp_data = (char*)malloc(start_pos + block->block_contents_size - curr_file_pos);
          if (!temp_data)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          if (fread(temp_data, start_pos + block->block_contents_size - curr_file_pos, 1, tng_data->input_file)
              == 0)
          {
              fprintf(stderr,
                      "TNG library: Cannot read remaining part of block to generate MD5 sum. %s: "
                      "%d\n",
                      __FILE__, __LINE__);
              free(temp_data);
              return (TNG_CRITICAL);
          }
          md5_append(md5_state, (md5_byte_t*)temp_data, start_pos + block->block_contents_size - curr_file_pos);
          free(temp_data);
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Open the input file if it is not already opened.
   * @param tng_data is a trajectory data container.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_input_file_init(struct tng_trajectory* tng_data)
  {
      int64_t file_pos;
  
      if (!tng_data->input_file)
      {
          if (!tng_data->input_file_path)
          {
              fprintf(stderr, "TNG library: No file specified for reading. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          tng_data->input_file = fopen(tng_data->input_file_path, "rb");
          if (!tng_data->input_file)
          {
              fprintf(stderr, "TNG library: Cannot open file %s. %s: %d\n", tng_data->input_file_path,
                      __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      if (!tng_data->input_file_len)
      {
          file_pos = ftello(tng_data->input_file);
          fseeko(tng_data->input_file, 0, SEEK_END);
          tng_data->input_file_len = ftello(tng_data->input_file);
          fseeko(tng_data->input_file, file_pos, SEEK_SET);
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Open the output file if it is not already opened
   * @param tng_data is a trajectory data container.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_output_file_init(struct tng_trajectory* tng_data)
  {
      if (!tng_data->output_file)
      {
          if (!tng_data->output_file_path)
          {
              fprintf(stderr, "TNG library: No file specified for writing. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
  
          tng_data->output_file = fopen(tng_data->output_file_path, "wb+");
  
          if (!tng_data->output_file)
          {
              fprintf(stderr, "TNG library: Cannot open file %s. %s: %d\n",
                      tng_data->output_file_path, __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Setup a file block container.
   * @param block_p a pointer to memory to initialise as a file block container.
   * @details Memory is allocated during initialisation.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  tng_function_status tng_block_init(struct tng_gen_block** block_p)
  {
      tng_gen_block_t block;
  
      *block_p = (struct tng_gen_block*)malloc(sizeof(struct tng_gen_block));
      if (!*block_p)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      block = *block_p;
  
      block->id = -1;
      /* Reset the md5_hash */
      memset(block->md5_hash, '\0', TNG_MD5_HASH_LEN);
      block->name                 = 0;
      block->block_version        = TNG_API_VERSION;
      block->header_contents      = 0;
      block->header_contents_size = 0;
      block->block_contents       = 0;
      block->block_contents_size  = 0;
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Clean up a file block container.
   * @param block_p a pointer to the file block container to destroy.
   * @details All allocated memory in the data structure is freed, as well as
   * block_p itself.
   * @return TNG_SUCCESS (0) if successful.
   */
  tng_function_status tng_block_destroy(struct tng_gen_block** block_p)
  {
      tng_gen_block_t block = *block_p;
  
      if (!*block_p)
      {
          return (TNG_SUCCESS);
      }
  
      /*     fprintf(stderr, "TNG library: Destroying block\n"); */
      if (block->name)
      {
          free(block->name);
          block->name = 0;
      }
      if (block->header_contents)
      {
          free(block->header_contents);
          block->header_contents = 0;
      }
      if (block->block_contents)
      {
          free(block->block_contents);
          block->block_contents = 0;
      }
  
      free(*block_p);
      *block_p = 0;
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Read the header of a data block, regardless of its type
   * @param tng_data is a trajectory data container.
   * @param block is a general block container.
   * @return TNG_SUCCESS (0) if successful, TNG_FAILURE(1) if a minor
   * error has occured (not able to read the header size, thus skipping
   * the block) or TNG_CRITICAL (2) if a major error has occured.
   */
  tng_function_status tng_block_header_read(struct tng_trajectory* tng_data, struct tng_gen_block* block)
  {
      int64_t start_pos;
  
      TNG_ASSERT(block != 0, "TNG library: Trying to read to uninitialized block (NULL pointer).");
  
      if (tng_input_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      start_pos = ftello(tng_data->input_file);
  
      /* First read the header size to be able to read the whole header. */
      if (fread(&block->header_contents_size, sizeof(block->header_contents_size), 1, tng_data->input_file) == 0)
      {
          fprintf(stderr, "TNG library: Cannot read header size. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      if (block->header_contents_size == 0)
      {
          block->id = -1;
          return (TNG_FAILURE);
      }
  
      /* If this was the size of the general info block check the endianness */
      if (ftello(tng_data->input_file) < 9)
      {
          /* File is little endian */
          if (*((const char*)&block->header_contents_size) != 0x00
              && *((const char*)(&block->header_contents_size) + 7) == 0x00)
          {
              /* If the architecture endianness is little endian no byte swap
               * will be needed. Otherwise use the functions to swap to little
               * endian */
              if (tng_data->endianness_32 == TNG_LITTLE_ENDIAN_32)
              {
                  tng_data->input_endianness_swap_func_32 = 0;
              }
              else
              {
                  tng_data->input_endianness_swap_func_32 = &tng_swap_byte_order_little_endian_32;
              }
              if (tng_data->endianness_64 == TNG_LITTLE_ENDIAN_64)
              {
                  tng_data->input_endianness_swap_func_64 = 0;
              }
              else
              {
                  tng_data->input_endianness_swap_func_64 = &tng_swap_byte_order_little_endian_64;
              }
          }
          /* File is big endian */
          else
          {
              /* If the architecture endianness is big endian no byte swap
               * will be needed. Otherwise use the functions to swap to big
               * endian */
              if (tng_data->endianness_32 == TNG_BIG_ENDIAN_32)
              {
                  tng_data->input_endianness_swap_func_32 = 0;
              }
              else
              {
                  tng_data->input_endianness_swap_func_32 = &tng_swap_byte_order_big_endian_32;
              }
              if (tng_data->endianness_64 == TNG_BIG_ENDIAN_64)
              {
                  tng_data->input_endianness_swap_func_64 = 0;
              }
              else
              {
                  tng_data->input_endianness_swap_func_64 = &tng_swap_byte_order_big_endian_64;
              }
          }
      }
  
      if (tng_data->input_endianness_swap_func_64
          && tng_data->input_endianness_swap_func_64(tng_data, (uint64_t*)&block->header_contents_size)
                     != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n", __FILE__, __LINE__);
      }
  
      if (tng_file_input_numerical(tng_data, &block->block_contents_size,
                                   sizeof(block->block_contents_size), TNG_SKIP_HASH, 0, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, &block->id, sizeof(block->id), TNG_SKIP_HASH, 0, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (fread(block->md5_hash, TNG_MD5_HASH_LEN, 1, tng_data->input_file) == 0)
      {
          fprintf(stderr, "TNG library: Cannot read block header. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      tng_freadstr(tng_data, &block->name, TNG_SKIP_HASH, 0, __LINE__);
  
      if (tng_file_input_numerical(tng_data, &block->block_version, sizeof(block->block_version),
                                   TNG_SKIP_HASH, 0, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      fseeko(tng_data->input_file, start_pos + block->header_contents_size, SEEK_SET);
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Write a whole block, both header and contents, regardless of it type
   * @param tng_data is a trajectory data container.
   * @param block is a general block container.
   * @return TNG_SUCCESS (0) if successful, TNG_FAILURE (1) if a minor error
   * has occurred or TNG_CRITICAL (2) if a major error has occured.
   */
  /* Disabled until it is used.*/
  /*
  // static tng_function_status tng_block_verbatim_write(tng_trajectory_t tng_data,
  //                                                     tng_gen_block_t block)
  // {
  //     if(!block->header_contents)
  //     {
  //         fprintf(stderr, "TNG library: No contents to write. %s: %d\n", __FILE__, __LINE__);
  //         return(TNG_FAILURE);
  //     }
  //     if(fwrite(block->header_contents, block->header_contents_size, 1,
  //                 tng_data->output_file) != 1)
  //     {
  //         fprintf(stderr, "TNG library: Could not write all header data. %s: %d\n",
  //                 __FILE__, __LINE__);
  //         return(TNG_CRITICAL);
  //     }
  //
  //     if(!block->block_contents)
  //     {
  //         fprintf(stderr, "TNG library: No block data to write. %s: %d\n",
  //                 __FILE__, __LINE__);
  //         return(TNG_FAILURE);
  //     }
  //     if(fwrite(block->block_contents, block->block_contents_size, 1,
  //                 tng_data->output_file) != 1)
  //     {
  //         fprintf(stderr, "TNG library: Could not write all block data. %s: %d\n",
  //                 __FILE__, __LINE__);
  //         return(TNG_CRITICAL);
  //     }
  //     return(TNG_SUCCESS);
  // }
  */
  
  /**
   * @brief Update the md5 hash of a block already written to the file
   * @param tng_data is a trajectory data container.
   * @param block is the block, of which to update the md5 hash.
   * @param header_start_pos is the file position where the block header starts.
   * @param contents_start_pos is the file position where the block contents
   * start.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_md5_hash_update(const struct tng_trajectory* tng_data,
                                                 struct tng_gen_block*        block,
                                                 const int64_t                header_start_pos,
                                                 const int64_t                contents_start_pos)
  {
      if (block->block_contents)
      {
          free(block->block_contents);
      }
  
      block->block_contents = (char*)malloc(block->block_contents_size);
      if (!block->block_contents)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      fseeko(tng_data->output_file, contents_start_pos, SEEK_SET);
      if (fread(block->block_contents, block->block_contents_size, 1, tng_data->output_file) == 0)
      {
          fprintf(stderr, "TNG library: Cannot read block. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      tng_block_md5_hash_generate(block);
  
      fseeko(tng_data->output_file, header_start_pos + 3 * sizeof(int64_t), SEEK_SET);
      fwrite(block->md5_hash, TNG_MD5_HASH_LEN, 1, tng_data->output_file);
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Update the frame set pointers in the file header (general info block),
   * already written to disk
   * @param tng_data is a trajectory data container.
   * @param hash_mode specifies whether to update the block md5 hash when
   * updating the pointers.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_header_pointers_update(struct tng_trajectory* tng_data, const char hash_mode)
  {
      tng_gen_block_t block;
      FILE*           temp = tng_data->input_file;
      uint64_t        output_file_pos, pos, contents_start_pos;
  
      if (tng_output_file_init(tng_data) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot initialise destination file. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      tng_data->input_file = tng_data->output_file;
  
      tng_block_init(&block);
  
      output_file_pos = ftello(tng_data->output_file);
      fseeko(tng_data->output_file, 0, SEEK_SET);
  
      if (tng_block_header_read(tng_data, block) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot read general info header. %s: %d\n", __FILE__, __LINE__);
          tng_data->input_file = temp;
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      contents_start_pos = ftello(tng_data->output_file);
  
      fseeko(tng_data->output_file, block->block_contents_size - 5 * sizeof(int64_t), SEEK_CUR);
  
      tng_data->input_file = temp;
  
      pos = tng_data->first_trajectory_frame_set_output_file_pos;
  
      if (tng_data->input_endianness_swap_func_64)
      {
          if (tng_data->input_endianness_swap_func_64(tng_data, &pos) != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n", __FILE__, __LINE__);
          }
      }
  
      if (fwrite(&pos, sizeof(int64_t), 1, tng_data->output_file) != 1)
      {
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      pos = tng_data->last_trajectory_frame_set_output_file_pos;
  
      if (tng_data->input_endianness_swap_func_64)
      {
          if (tng_data->input_endianness_swap_func_64(tng_data, &pos) != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n", __FILE__, __LINE__);
          }
      }
  
      if (fwrite(&pos, sizeof(int64_t), 1, tng_data->output_file) != 1)
      {
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      if (hash_mode == TNG_USE_HASH)
      {
          tng_md5_hash_update(tng_data, block, 0, contents_start_pos);
      }
  
      tng_block_destroy(&block);
  
      fseeko(tng_data->output_file, output_file_pos, SEEK_SET);
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Update the frame set pointers in the current frame set block, already
   * written to disk. It also updates the pointers of the blocks pointing to
   * the current frame set block.
   * @param tng_data is a trajectory data container.
   * @param hash_mode specifies whether to update the block md5 hash when
   * updating the pointers.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_frame_set_pointers_update(struct tng_trajectory* tng_data, const char hash_mode)
  {
      tng_gen_block_t            block;
      tng_trajectory_frame_set_t frame_set;
      FILE*                      temp = tng_data->input_file;
      uint64_t                   pos, output_file_pos, contents_start_pos;
  
      if (tng_output_file_init(tng_data) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot initialise destination file. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      tng_block_init(&block);
      output_file_pos = ftello(tng_data->output_file);
  
      tng_data->input_file = tng_data->output_file;
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      pos = tng_data->current_trajectory_frame_set_output_file_pos;
  
      /* Update next frame set */
      if (frame_set->next_frame_set_file_pos > 0)
      {
          fseeko(tng_data->output_file, frame_set->next_frame_set_file_pos, SEEK_SET);
  
          if (tng_block_header_read(tng_data, block) != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot read frame header. %s: %d\n", __FILE__, __LINE__);
              tng_data->input_file = temp;
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
  
          contents_start_pos = ftello(tng_data->output_file);
  
          fseeko(tng_data->output_file,
                 block->block_contents_size - (5 * sizeof(int64_t) + 2 * sizeof(double)), SEEK_CUR);
  
          if (tng_data->input_endianness_swap_func_64)
          {
              if (tng_data->input_endianness_swap_func_64(tng_data, &pos) != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n", __FILE__, __LINE__);
              }
          }
  
          if (fwrite(&pos, sizeof(int64_t), 1, tng_data->output_file) != 1)
          {
              tng_data->input_file = temp;
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
  
          if (hash_mode == TNG_USE_HASH)
          {
              tng_md5_hash_update(tng_data, block, frame_set->next_frame_set_file_pos, contents_start_pos);
          }
      }
      /* Update previous frame set */
      if (frame_set->prev_frame_set_file_pos > 0)
      {
          fseeko(tng_data->output_file, frame_set->prev_frame_set_file_pos, SEEK_SET);
  
          if (tng_block_header_read(tng_data, block) != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot read frame header. %s: %d\n", __FILE__, __LINE__);
              tng_data->input_file = temp;
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
  
          contents_start_pos = ftello(tng_data->output_file);
  
          fseeko(tng_data->output_file,
                 block->block_contents_size - (6 * sizeof(int64_t) + 2 * sizeof(double)), SEEK_CUR);
  
          if (tng_data->input_endianness_swap_func_64)
          {
              if (tng_data->input_endianness_swap_func_64(tng_data, &pos) != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n", __FILE__, __LINE__);
              }
          }
  
          if (fwrite(&pos, sizeof(int64_t), 1, tng_data->output_file) != 1)
          {
              tng_data->input_file = temp;
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
  
          if (hash_mode == TNG_USE_HASH)
          {
              tng_md5_hash_update(tng_data, block, frame_set->prev_frame_set_file_pos, contents_start_pos);
          }
      }
  
      /* Update the frame set one medium stride step after */
      if (frame_set->medium_stride_next_frame_set_file_pos > 0)
      {
          fseeko(tng_data->output_file, frame_set->medium_stride_next_frame_set_file_pos, SEEK_SET);
  
          if (tng_block_header_read(tng_data, block) != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot read frame set header. %s: %d\n", __FILE__, __LINE__);
              tng_data->input_file = temp;
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
  
          contents_start_pos = ftello(tng_data->output_file);
  
          fseeko(tng_data->output_file,
                 block->block_contents_size - (3 * sizeof(int64_t) + 2 * sizeof(double)), SEEK_CUR);
  
          if (tng_data->input_endianness_swap_func_64)
          {
              if (tng_data->input_endianness_swap_func_64(tng_data, &pos) != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n", __FILE__, __LINE__);
              }
          }
  
          if (fwrite(&pos, sizeof(int64_t), 1, tng_data->output_file) != 1)
          {
              tng_data->input_file = temp;
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
  
          if (hash_mode == TNG_USE_HASH)
          {
              tng_md5_hash_update(tng_data, block, frame_set->medium_stride_next_frame_set_file_pos,
                                  contents_start_pos);
          }
      }
      /* Update the frame set one medium stride step before */
      if (frame_set->medium_stride_prev_frame_set_file_pos > 0)
      {
          fseeko(tng_data->output_file, frame_set->medium_stride_prev_frame_set_file_pos, SEEK_SET);
  
          if (tng_block_header_read(tng_data, block) != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot read frame set header. %s: %d\n", __FILE__, __LINE__);
              tng_data->input_file = temp;
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
  
          contents_start_pos = ftello(tng_data->output_file);
  
          fseeko(tng_data->output_file,
                 block->block_contents_size - (4 * sizeof(int64_t) + 2 * sizeof(double)), SEEK_CUR);
  
          if (tng_data->input_endianness_swap_func_64)
          {
              if (tng_data->input_endianness_swap_func_64(tng_data, &pos) != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n", __FILE__, __LINE__);
              }
          }
  
          if (fwrite(&pos, sizeof(int64_t), 1, tng_data->output_file) != 1)
          {
              tng_data->input_file = temp;
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
  
          if (hash_mode == TNG_USE_HASH)
          {
              tng_md5_hash_update(tng_data, block, frame_set->medium_stride_prev_frame_set_file_pos,
                                  contents_start_pos);
          }
      }
  
      /* Update the frame set one long stride step after */
      if (frame_set->long_stride_next_frame_set_file_pos > 0)
      {
          fseeko(tng_data->output_file, frame_set->long_stride_next_frame_set_file_pos, SEEK_SET);
  
          if (tng_block_header_read(tng_data, block) != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot read frame set header. %s: %d\n", __FILE__, __LINE__);
              tng_data->input_file = temp;
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
  
          contents_start_pos = ftello(tng_data->output_file);
  
          fseeko(tng_data->output_file,
                 block->block_contents_size - (1 * sizeof(int64_t) + 2 * sizeof(double)), SEEK_CUR);
  
          if (tng_data->input_endianness_swap_func_64)
          {
              if (tng_data->input_endianness_swap_func_64(tng_data, &pos) != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n", __FILE__, __LINE__);
              }
          }
  
          if (fwrite(&pos, sizeof(int64_t), 1, tng_data->output_file) != 1)
          {
              tng_data->input_file = temp;
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
  
          if (hash_mode == TNG_USE_HASH)
          {
              tng_md5_hash_update(tng_data, block, frame_set->long_stride_next_frame_set_file_pos,
                                  contents_start_pos);
          }
      }
      /* Update the frame set one long stride step before */
      if (frame_set->long_stride_prev_frame_set_file_pos > 0)
      {
          fseeko(tng_data->output_file, frame_set->long_stride_prev_frame_set_file_pos, SEEK_SET);
  
          if (tng_block_header_read(tng_data, block) != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot read frame set header. %s: %d\n", __FILE__, __LINE__);
              tng_data->input_file = temp;
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
  
          contents_start_pos = ftello(tng_data->output_file);
  
          fseeko(tng_data->output_file,
                 block->block_contents_size - (2 * sizeof(int64_t) + 2 * sizeof(double)), SEEK_CUR);
  
          if (tng_data->input_endianness_swap_func_64)
          {
              if (tng_data->input_endianness_swap_func_64(tng_data, &pos) != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n", __FILE__, __LINE__);
              }
          }
  
          if (fwrite(&pos, sizeof(int64_t), 1, tng_data->output_file) != 1)
          {
              tng_data->input_file = temp;
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
  
          if (hash_mode == TNG_USE_HASH)
          {
              tng_md5_hash_update(tng_data, block, frame_set->long_stride_prev_frame_set_file_pos,
                                  contents_start_pos);
          }
      }
  
      fseeko(tng_data->output_file, output_file_pos, SEEK_SET);
  
      tng_data->input_file = temp;
  
      tng_block_destroy(&block);
  
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_reread_frame_set_at_file_pos(struct tng_trajectory* tng_data, const int64_t pos)
  {
      tng_gen_block_t     block;
      tng_function_status stat;
  
      tng_block_init(&block);
  
      fseeko(tng_data->input_file, pos, SEEK_SET);
      if (pos > 0)
      {
          stat = tng_block_header_read(tng_data, block);
          if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
          {
              fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                      pos, __FILE__, __LINE__);
              tng_block_destroy(&block);
              return (TNG_FAILURE);
          }
  
          if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
          {
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
      }
  
      tng_block_destroy(&block);
  
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_file_pos_of_subsequent_trajectory_block_get(struct tng_trajectory* tng_data,
                                                                             int64_t* pos)
  {
      int64_t                    orig_pos, curr_frame_set_pos;
      tng_gen_block_t            block;
      tng_function_status        stat;
      tng_trajectory_frame_set_t frame_set = &tng_data->current_trajectory_frame_set;
  
      orig_pos           = ftello(tng_data->input_file);
      curr_frame_set_pos = tng_data->current_trajectory_frame_set_input_file_pos;
  
      *pos = tng_data->first_trajectory_frame_set_input_file_pos;
  
      if (*pos <= 0)
      {
          return (TNG_SUCCESS);
      }
  
      fseeko(tng_data->input_file, *pos, SEEK_SET);
  
      tng_block_init(&block);
      /* Read block headers first to see that a frame set block is found. */
      stat = tng_block_header_read(tng_data, block);
      if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
      {
          fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n", *pos,
                  __FILE__, __LINE__);
          tng_block_destroy(&block);
          return (TNG_FAILURE);
      }
  
      if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
      {
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      /* Read all frame set blocks (not the blocks between them) */
      while (frame_set->next_frame_set_file_pos > 0)
      {
          fseeko(tng_data->input_file, frame_set->next_frame_set_file_pos, SEEK_SET);
          stat = tng_block_header_read(tng_data, block);
          if (stat == TNG_CRITICAL)
          {
              fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                      *pos, __FILE__, __LINE__);
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
          if (stat != TNG_SUCCESS || block->id != TNG_TRAJECTORY_FRAME_SET)
          {
              return (TNG_FAILURE);
          }
  
          stat = tng_block_read_next(tng_data, block, TNG_SKIP_HASH);
          if (stat != TNG_SUCCESS)
          {
              tng_block_destroy(&block);
              return (stat);
          }
          /* Update *pos if this is the earliest frame set so far (after orig_pos) */
          if (tng_data->current_trajectory_frame_set_input_file_pos < *pos
              && tng_data->current_trajectory_frame_set_input_file_pos > orig_pos)
          {
              *pos = tng_data->current_trajectory_frame_set_input_file_pos;
          }
      }
  
      /* Re-read the frame set that used to be the current one */
      tng_reread_frame_set_at_file_pos(tng_data, curr_frame_set_pos);
  
      fseeko(tng_data->input_file, orig_pos, SEEK_SET);
  
      tng_block_destroy(&block);
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Migrate a whole frame set from one position in the file to another.
   * @param tng_data is a trajectory data container.
   * @param block_start_pos is the starting position in the file of the frame set.
   * @param block_len is the length of the whole frame set (including all data blocks etc).
   * @param new_pos is the new position in the file of the frame set.
   * @param hash_mode is an option to decide whether to generate/update the relevant md5 hashes.
   */
  static tng_function_status tng_frame_set_complete_migrate(struct tng_trajectory* tng_data,
                                                            const int64_t          block_start_pos,
                                                            const int64_t          block_len,
                                                            const int64_t          new_pos,
                                                            const char             hash_mode)
  {
      tng_bool updated = TNG_FALSE;
  
      char* contents;
  
      if (tng_input_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      fseeko(tng_data->input_file, block_start_pos, SEEK_SET);
  
      contents = (char*)malloc(block_len);
      if (!contents)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      if (fread(contents, block_len, 1, tng_data->input_file) == 0)
      {
          fprintf(stderr, "TNG library: Cannot read data from file when migrating data. %s: %d\n",
                  __FILE__, __LINE__);
          free(contents);
          return (TNG_CRITICAL);
      }
      fseeko(tng_data->output_file, new_pos, SEEK_SET);
  
      if (fwrite(contents, block_len, 1, tng_data->output_file) != 1)
      {
          fprintf(stderr, "TNG library: Could not write data to file when migrating data. %s: %d\n",
                  __FILE__, __LINE__);
          free(contents);
          return (TNG_CRITICAL);
      }
  
      tng_data->current_trajectory_frame_set_output_file_pos = new_pos;
      if (tng_data->input_file == tng_data->output_file)
      {
          tng_data->current_trajectory_frame_set_input_file_pos = new_pos;
      }
  
      tng_frame_set_pointers_update(tng_data, hash_mode);
  
      /* Update the general info block if needed */
      if (block_start_pos == tng_data->first_trajectory_frame_set_output_file_pos)
      {
          tng_data->first_trajectory_frame_set_output_file_pos = new_pos;
          updated                                              = TNG_TRUE;
      }
      if (block_start_pos == tng_data->last_trajectory_frame_set_output_file_pos)
      {
          tng_data->last_trajectory_frame_set_output_file_pos = new_pos;
          updated                                             = TNG_TRUE;
      }
      if (updated)
      {
          tng_header_pointers_update(tng_data, hash_mode);
      }
  
      /* Fill the block with NULL to avoid confusion. */
      memset(contents, '\0', block_len);
      fseeko(tng_data->output_file, block_start_pos, SEEK_SET);
  
      /* FIXME: casting block_len to size_t is dangerous */
      fwrite(contents, 1, block_len, tng_data->output_file);
  
      free(contents);
  
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_length_of_current_frame_set_contents_get(struct tng_trajectory* tng_data,
                                                                          int64_t*               len)
  {
      int64_t             orig_pos, pos, curr_frame_set_pos;
      tng_gen_block_t     block;
      tng_function_status stat;
  
      orig_pos           = ftello(tng_data->input_file);
      curr_frame_set_pos = pos = tng_data->current_trajectory_frame_set_input_file_pos;
  
      *len = 0;
  
      fseeko(tng_data->input_file, curr_frame_set_pos, SEEK_SET);
  
      tng_block_init(&block);
      /* Read block headers first to see that a frame set block is found. */
      stat = tng_block_header_read(tng_data, block);
      if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
      {
          fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                  curr_frame_set_pos, __FILE__, __LINE__);
          tng_block_destroy(&block);
          return (TNG_FAILURE);
      }
  
      /* Read the headers of all blocks in the frame set (not the actual contents of them) */
      while (stat == TNG_SUCCESS)
      {
          fseeko(tng_data->input_file, block->block_contents_size, SEEK_CUR);
          *len += block->header_contents_size + block->block_contents_size;
          pos += block->header_contents_size + block->block_contents_size;
          if (pos >= tng_data->input_file_len)
          {
              break;
          }
          stat = tng_block_header_read(tng_data, block);
          if (block->id == TNG_TRAJECTORY_FRAME_SET)
          {
              break;
          }
      }
  
      /* Re-read the frame set that used to be the current one */
      tng_reread_frame_set_at_file_pos(tng_data, curr_frame_set_pos);
  
      fseeko(tng_data->input_file, orig_pos, SEEK_SET);
  
      tng_block_destroy(&block);
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Migrate blocks in the file to make room for new data in a block. This
   * is required e.g. when adding data to a block or extending strings in a
   * block.
   * @param tng_data is a trajectory data container.
   * @param start_pos is the position from which to start moving data, usually
   * the byte after the end of the block to which data was added.
   * @param offset is the number of bytes that were inserted.
   * @param hash_mode is an option to decide whether to generate/update the relevant md5 hashes.
   * @details Trajectory blocks (frame sets and their related blocks) are moved
   * to the end of the file (if needed) in order to make room for non-trajectory
   * data.
   */
  static tng_function_status tng_migrate_data_in_file(struct tng_trajectory* tng_data,
                                                      const int64_t          start_pos,
                                                      const int64_t          offset,
                                                      const char             hash_mode)
  {
      int64_t             traj_start_pos, empty_space, orig_file_pos, frame_set_length;
      tng_gen_block_t     block;
      tng_function_status stat;
  
      if (offset <= 0)
      {
          return (TNG_SUCCESS);
      }
  
      stat = tng_file_pos_of_subsequent_trajectory_block_get(tng_data, &traj_start_pos);
      if (stat != TNG_SUCCESS)
      {
          return (stat);
      }
  
      tng_data->current_trajectory_frame_set_input_file_pos = traj_start_pos;
  
      empty_space = traj_start_pos - (start_pos - 1);
  
      if (empty_space >= offset)
      {
          return (TNG_SUCCESS);
      }
  
      orig_file_pos = ftello(tng_data->input_file);
      tng_block_init(&block);
  
      while (empty_space < offset)
      {
          fseeko(tng_data->input_file, traj_start_pos, SEEK_SET);
          stat = tng_block_header_read(tng_data, block);
          if (stat == TNG_CRITICAL)
          {
              fprintf(stderr, "TNG library: Cannot read block header. %s: %d\n", __FILE__, __LINE__);
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
          if (stat != TNG_SUCCESS || block->id != TNG_TRAJECTORY_FRAME_SET)
          {
              tng_block_destroy(&block);
              return (TNG_FAILURE);
          }
          stat = tng_length_of_current_frame_set_contents_get(tng_data, &frame_set_length);
          if (stat != TNG_SUCCESS)
          {
              tng_block_destroy(&block);
              return (stat);
          }
          stat = tng_frame_set_complete_migrate(tng_data, traj_start_pos, frame_set_length,
                                                tng_data->input_file_len, hash_mode);
          if (stat != TNG_SUCCESS)
          {
              tng_block_destroy(&block);
              return (stat);
          }
  
          empty_space += frame_set_length;
      }
      fseeko(tng_data->input_file, orig_file_pos, SEEK_SET);
      tng_block_destroy(&block);
  
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_block_header_len_calculate(const struct tng_trajectory* tng_data,
                                                            struct tng_gen_block*        block,
                                                            int64_t*                     len)
  {
      int name_len;
      (void)tng_data;
  
      /* If the string is unallocated allocate memory for just string
       * termination */
      if (!block->name)
      {
          block->name = (char*)malloc(1);
          if (!block->name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          block->name[0] = 0;
      }
  
      name_len = tng_min_size(strlen(block->name) + 1, TNG_MAX_STR_LEN);
  
      /* Calculate the size of the header to write */
      *len = sizeof(block->header_contents_size) + sizeof(block->block_contents_size)
             + sizeof(block->id) + sizeof(block->block_version) + TNG_MD5_HASH_LEN + name_len;
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Write the header of a data block, regardless of its type
   * @param tng_data is a trajectory data container.
   * @param block is a general block container.
   * @param hash_mode is an option to decide whether to use the md5 hash or not.
   * If hash_mode == TNG_USE_HASH an md5 hash will be generated and written.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_block_header_write(struct tng_trajectory* tng_data, struct tng_gen_block* block)
  {
      TNG_ASSERT(block != 0, "TNG library: Trying to write uninitialized block (NULL pointer).");
  
      if (tng_output_file_init(tng_data) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot initialise destination file. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      if (tng_block_header_len_calculate(tng_data, block, &block->header_contents_size) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot calculate length of block header. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &block->header_contents_size,
                                    sizeof(block->header_contents_size), TNG_SKIP_HASH, 0, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &block->block_contents_size,
                                    sizeof(block->block_contents_size), TNG_SKIP_HASH, 0, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &block->id, sizeof(block->id), TNG_SKIP_HASH, 0, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (fwrite(block->md5_hash, TNG_MD5_HASH_LEN, 1, tng_data->output_file) != 1)
      {
          fprintf(stderr, "TNG library: Could not write header data. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      if (tng_fwritestr(tng_data, block->name, TNG_SKIP_HASH, 0, __LINE__) == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &block->block_version, sizeof(block->block_version),
                                    TNG_SKIP_HASH, 0, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_general_info_block_len_calculate(struct tng_trajectory* tng_data, int64_t* len)
  {
      size_t first_program_name_len, first_user_name_len;
      size_t first_computer_name_len, first_pgp_signature_len;
      size_t last_program_name_len, last_user_name_len;
      size_t last_computer_name_len, last_pgp_signature_len;
      size_t forcefield_name_len;
  
      /* If the strings are unallocated allocate memory for just string
       * termination */
      if (!tng_data->first_program_name)
      {
          tng_data->first_program_name = (char*)malloc(1);
          if (!tng_data->first_program_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          tng_data->first_program_name[0] = 0;
      }
      if (!tng_data->last_program_name)
      {
          tng_data->last_program_name = (char*)malloc(1);
          if (!tng_data->last_program_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          tng_data->last_program_name[0] = 0;
      }
      if (!tng_data->first_user_name)
      {
          tng_data->first_user_name = (char*)malloc(1);
          if (!tng_data->first_user_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          tng_data->first_user_name[0] = 0;
      }
      if (!tng_data->last_user_name)
      {
          tng_data->last_user_name = (char*)malloc(1);
          if (!tng_data->last_user_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          tng_data->last_user_name[0] = 0;
      }
      if (!tng_data->first_computer_name)
      {
          tng_data->first_computer_name = (char*)malloc(1);
          if (!tng_data->first_computer_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          tng_data->first_computer_name[0] = 0;
      }
      if (!tng_data->last_computer_name)
      {
          tng_data->last_computer_name = (char*)malloc(1);
          if (!tng_data->last_computer_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          tng_data->last_computer_name[0] = 0;
      }
      if (!tng_data->first_pgp_signature)
      {
          tng_data->first_pgp_signature = (char*)malloc(1);
          if (!tng_data->first_pgp_signature)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          tng_data->first_pgp_signature[0] = 0;
      }
      if (!tng_data->last_pgp_signature)
      {
          tng_data->last_pgp_signature = (char*)malloc(1);
          if (!tng_data->last_pgp_signature)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          tng_data->last_pgp_signature[0] = 0;
      }
      if (!tng_data->forcefield_name)
      {
          tng_data->forcefield_name = (char*)malloc(1);
          if (!tng_data->forcefield_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          tng_data->forcefield_name[0] = 0;
      }
  
      first_program_name_len = tng_min_size(strlen(tng_data->first_program_name) + 1, TNG_MAX_STR_LEN);
      last_program_name_len  = tng_min_size(strlen(tng_data->last_program_name) + 1, TNG_MAX_STR_LEN);
      first_user_name_len    = tng_min_size(strlen(tng_data->first_user_name) + 1, TNG_MAX_STR_LEN);
      last_user_name_len     = tng_min_size(strlen(tng_data->last_user_name) + 1, TNG_MAX_STR_LEN);
      first_computer_name_len = tng_min_size(strlen(tng_data->first_computer_name) + 1, TNG_MAX_STR_LEN);
      last_computer_name_len = tng_min_size(strlen(tng_data->last_computer_name) + 1, TNG_MAX_STR_LEN);
      first_pgp_signature_len = tng_min_size(strlen(tng_data->first_pgp_signature) + 1, TNG_MAX_STR_LEN);
      last_pgp_signature_len = tng_min_size(strlen(tng_data->last_pgp_signature) + 1, TNG_MAX_STR_LEN);
      forcefield_name_len    = tng_min_size(strlen(tng_data->forcefield_name) + 1, TNG_MAX_STR_LEN);
  
      *len = sizeof(tng_data->time) + sizeof(tng_data->var_num_atoms_flag)
             + sizeof(tng_data->frame_set_n_frames)
             + sizeof(tng_data->first_trajectory_frame_set_input_file_pos)
             + sizeof(tng_data->last_trajectory_frame_set_input_file_pos)
             + sizeof(tng_data->medium_stride_length) + sizeof(tng_data->long_stride_length)
             + sizeof(tng_data->distance_unit_exponential) + first_program_name_len + last_program_name_len
             + first_user_name_len + last_user_name_len + first_computer_name_len + last_computer_name_len
             + first_pgp_signature_len + last_pgp_signature_len + forcefield_name_len;
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Read a general info block. This is the first block of a TNG file.
   * Populate the fields in tng_data.
   * @param tng_data is a trajectory data container.
   * @param block is a general block container.
   * @param hash_mode is an option to decide whether to use the md5 hash or not.
   * If hash_mode == TNG_USE_HASH the written md5 hash in the file will be
   * compared to the md5 hash of the read contents to ensure valid data.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_general_info_block_read(struct tng_trajectory*      tng_data,
                                                         const struct tng_gen_block* block,
                                                         const char                  hash_mode)
  {
      int64_t     start_pos;
      char        hash[TNG_MD5_HASH_LEN];
      md5_state_t md5_state;
  
      TNG_ASSERT(block != 0,
                 "TNG library: Trying to read data to an uninitialized block (NULL pointer)");
  
      if (tng_input_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      start_pos = ftello(tng_data->input_file);
  
      if (hash_mode == TNG_USE_HASH)
      {
          md5_init(&md5_state);
      }
  
      tng_freadstr(tng_data, &tng_data->first_program_name, hash_mode, &md5_state, __LINE__);
  
      tng_freadstr(tng_data, &tng_data->last_program_name, hash_mode, &md5_state, __LINE__);
  
      tng_freadstr(tng_data, &tng_data->first_user_name, hash_mode, &md5_state, __LINE__);
  
      tng_freadstr(tng_data, &tng_data->last_user_name, hash_mode, &md5_state, __LINE__);
  
      tng_freadstr(tng_data, &tng_data->first_computer_name, hash_mode, &md5_state, __LINE__);
  
      tng_freadstr(tng_data, &tng_data->last_computer_name, hash_mode, &md5_state, __LINE__);
  
      tng_freadstr(tng_data, &tng_data->first_pgp_signature, hash_mode, &md5_state, __LINE__);
  
      tng_freadstr(tng_data, &tng_data->last_pgp_signature, hash_mode, &md5_state, __LINE__);
  
      tng_freadstr(tng_data, &tng_data->forcefield_name, hash_mode, &md5_state, __LINE__);
  
      if (tng_file_input_numerical(tng_data, &tng_data->time, sizeof(tng_data->time), hash_mode,
                                   &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
  
      if (tng_file_input_numerical(tng_data, &tng_data->var_num_atoms_flag,
                                   sizeof(tng_data->var_num_atoms_flag), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, &tng_data->frame_set_n_frames,
                                   sizeof(tng_data->frame_set_n_frames), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, &tng_data->first_trajectory_frame_set_input_file_pos,
                                   sizeof(tng_data->first_trajectory_frame_set_input_file_pos),
                                   hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      tng_data->current_trajectory_frame_set.next_frame_set_file_pos =
              tng_data->first_trajectory_frame_set_input_file_pos;
  
      if (tng_file_input_numerical(tng_data, &tng_data->last_trajectory_frame_set_input_file_pos,
                                   sizeof(tng_data->last_trajectory_frame_set_input_file_pos),
                                   hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, &tng_data->medium_stride_length,
                                   sizeof(tng_data->medium_stride_length), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, &tng_data->long_stride_length,
                                   sizeof(tng_data->long_stride_length), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (block->block_version >= 3)
      {
          if (tng_file_input_numerical(tng_data, &tng_data->distance_unit_exponential,
                                       sizeof(tng_data->distance_unit_exponential), hash_mode,
                                       &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
      }
  
      if (hash_mode == TNG_USE_HASH)
      {
          /* If there is data left in the block that the current version of the library
           * cannot interpret still read that to generate the MD5 hash. */
          tng_md5_remaining_append(tng_data, block, start_pos, &md5_state);
  
          md5_finish(&md5_state, (md5_byte_t*)hash);
          if (strncmp(block->md5_hash, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", TNG_MD5_HASH_LEN) != 0)
          {
              if (strncmp(block->md5_hash, hash, TNG_MD5_HASH_LEN) != 0)
              {
                  fprintf(stderr,
                          "TNG library: General info block contents corrupt. Hashes do not match. "
                          "%s: %d\n",
                          __FILE__, __LINE__);
              }
          }
      }
      else
      {
          /* Seek to the end of the block */
          fseeko(tng_data->input_file, start_pos + block->block_contents_size, SEEK_SET);
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Write a general info block. This is the first block of a TNG file.
   * @param tng_data is a trajectory data container.
   * @param hash_mode is an option to decide whether to use the md5 hash or not.
   * If hash_mode == TNG_USE_HASH an md5 hash will be generated and written.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_general_info_block_write(struct tng_trajectory* tng_data, const char hash_mode)
  {
      int64_t         header_file_pos, curr_file_pos;
      size_t          name_len;
      tng_gen_block_t block;
      md5_state_t     md5_state;
  
      if (tng_output_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      fseeko(tng_data->output_file, 0, SEEK_SET);
  
      tng_block_init(&block);
  
      name_len = strlen("GENERAL INFO");
  
      block->name = (char*)malloc(name_len + 1);
      if (!block->name)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      strcpy(block->name, "GENERAL INFO");
      block->id = TNG_GENERAL_INFO;
  
      if (tng_general_info_block_len_calculate(tng_data, &block->block_contents_size) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot calculate length of general info block. %s: %d\n",
                  __FILE__, __LINE__);
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      header_file_pos = 0;
  
      if (tng_block_header_write(tng_data, block) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot write header of file %s. %s: %d\n",
                  tng_data->output_file_path, __FILE__, __LINE__);
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      if (hash_mode == TNG_USE_HASH)
      {
          md5_init(&md5_state);
      }
  
      if (tng_fwritestr(tng_data, tng_data->first_program_name, hash_mode, &md5_state, __LINE__) == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_fwritestr(tng_data, tng_data->last_program_name, hash_mode, &md5_state, __LINE__) == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_fwritestr(tng_data, tng_data->first_user_name, hash_mode, &md5_state, __LINE__) == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_fwritestr(tng_data, tng_data->last_user_name, hash_mode, &md5_state, __LINE__) == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_fwritestr(tng_data, tng_data->first_computer_name, hash_mode, &md5_state, __LINE__) == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_fwritestr(tng_data, tng_data->last_computer_name, hash_mode, &md5_state, __LINE__) == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_fwritestr(tng_data, tng_data->first_pgp_signature, hash_mode, &md5_state, __LINE__) == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_fwritestr(tng_data, tng_data->last_pgp_signature, hash_mode, &md5_state, __LINE__) == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_fwritestr(tng_data, tng_data->forcefield_name, hash_mode, &md5_state, __LINE__) == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
  
      if (tng_file_output_numerical(tng_data, &tng_data->time, sizeof(tng_data->time), hash_mode,
                                    &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &tng_data->var_num_atoms_flag,
                                    sizeof(tng_data->var_num_atoms_flag), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &tng_data->frame_set_n_frames,
                                    sizeof(tng_data->frame_set_n_frames), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &tng_data->first_trajectory_frame_set_output_file_pos,
                                    sizeof(tng_data->first_trajectory_frame_set_output_file_pos),
                                    hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &tng_data->last_trajectory_frame_set_output_file_pos,
                                    sizeof(tng_data->last_trajectory_frame_set_output_file_pos),
                                    hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &tng_data->medium_stride_length,
                                    sizeof(tng_data->medium_stride_length), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &tng_data->long_stride_length,
                                    sizeof(tng_data->long_stride_length), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &tng_data->distance_unit_exponential,
                                    sizeof(tng_data->distance_unit_exponential), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
      if (hash_mode == TNG_USE_HASH)
      {
          md5_finish(&md5_state, (md5_byte_t*)block->md5_hash);
          curr_file_pos = ftello(tng_data->output_file);
          fseeko(tng_data->output_file, header_file_pos + 3 * sizeof(int64_t), SEEK_SET);
          if (fwrite(block->md5_hash, TNG_MD5_HASH_LEN, 1, tng_data->output_file) != 1)
          {
              fprintf(stderr, "TNG library: Could not write MD5 hash. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          fseeko(tng_data->output_file, curr_file_pos, SEEK_SET);
      }
  
      tng_block_destroy(&block);
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Read the chain data of a molecules block.
   * @param tng_data is a trajectory data container.
   * @param chain is the chain data container.
   * @param hash_mode is an option to decide whether to generate/update the relevant md5 hashes.
   * @param md5_state is a pointer to the current md5 storage, which will be updated appropriately
   * if hash_mode == TNG_USE_HASH.
   * @return TNG_SUCCESS(0) is successful.
   */
  static tng_function_status tng_chain_data_read(const struct tng_trajectory* tng_data,
                                                 struct tng_chain*            chain,
                                                 const char                   hash_mode,
                                                 md5_state_t*                 md5_state)
  {
      if (tng_file_input_numerical(tng_data, &chain->id, sizeof(chain->id), hash_mode, md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      tng_freadstr(tng_data, &chain->name, hash_mode, md5_state, __LINE__);
  
      if (tng_file_input_numerical(tng_data, &chain->n_residues, sizeof(chain->n_residues), hash_mode,
                                   md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Write the chain data of a molecules block.
   * @param tng_data is a trajectory data container.
   * @param chain is the chain data container.
   * @param hash_mode is an option to decide whether to generate/update the relevant md5 hashes.
   * @param md5_state is a pointer to the current md5 storage, which will be updated appropriately
   * if hash_mode == TNG_USE_HASH.
   * @return TNG_SUCCESS(0) is successful.
   */
  static tng_function_status tng_chain_data_write(struct tng_trajectory*  tng_data,
                                                  const struct tng_chain* chain,
                                                  const char              hash_mode,
                                                  md5_state_t*            md5_state)
  {
      if (tng_file_output_numerical(tng_data, &chain->id, sizeof(chain->id), hash_mode, md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_fwritestr(tng_data, chain->name, hash_mode, md5_state, __LINE__) == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &chain->n_residues, sizeof(chain->n_residues),
                                    hash_mode, md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Read the residue data of a molecules block.
   * @param tng_data is a trajectory data container.
   * @param residue is the residue data container.
   * @param hash_mode is an option to decide whether to generate/update the relevant md5 hashes.
   * @param md5_state is a pointer to the current md5 storage, which will be updated appropriately
   * if hash_mode == TNG_USE_HASH.
   * @return TNG_SUCCESS(0) is successful.
   */
  static tng_function_status tng_residue_data_read(const struct tng_trajectory* tng_data,
                                                   struct tng_residue*          residue,
                                                   const char                   hash_mode,
                                                   md5_state_t*                 md5_state)
  {
      if (tng_file_input_numerical(tng_data, &residue->id, sizeof(residue->id), hash_mode, md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      tng_freadstr(tng_data, &residue->name, hash_mode, md5_state, __LINE__);
  
      if (tng_file_input_numerical(tng_data, &residue->n_atoms, sizeof(residue->n_atoms), hash_mode,
                                   md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Write the residue data of a molecules block.
   * @param tng_data is a trajectory data container.
   * @param residue is the residue data container.
   * @param hash_mode is an option to decide whether to generate/update the relevant md5 hashes.
   * @param md5_state is a pointer to the current md5 storage, which will be updated appropriately
   * if hash_mode == TNG_USE_HASH.
   * @return TNG_SUCCESS(0) is successful.
   */
  static tng_function_status tng_residue_data_write(struct tng_trajectory*    tng_data,
                                                    const struct tng_residue* residue,
                                                    const char                hash_mode,
                                                    md5_state_t*              md5_state)
  {
      if (tng_file_output_numerical(tng_data, &residue->id, sizeof(residue->id), hash_mode, md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_fwritestr(tng_data, residue->name, hash_mode, md5_state, __LINE__) == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &residue->n_atoms, sizeof(residue->n_atoms), hash_mode,
                                    md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Read the atom data of a molecules block.
   * @param tng_data is a trajectory data container.
   * @param atom is the atom data container.
   * @param hash_mode is an option to decide whether to generate/update the relevant md5 hashes.
   * @param md5_state is a pointer to the current md5 storage, which will be updated appropriately
   * if hash_mode == TNG_USE_HASH.
   * @return TNG_SUCCESS(0) is successful.
   */
  static tng_function_status tng_atom_data_read(const struct tng_trajectory* tng_data,
                                                struct tng_atom*             atom,
                                                const char                   hash_mode,
                                                md5_state_t*                 md5_state)
  {
      if (tng_file_input_numerical(tng_data, &atom->id, sizeof(atom->id), hash_mode, md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      tng_freadstr(tng_data, &atom->name, hash_mode, md5_state, __LINE__);
  
      tng_freadstr(tng_data, &atom->atom_type, hash_mode, md5_state, __LINE__);
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Write the atom data of a molecules block.
   * @param tng_data is a trajectory data container.
   * @param atom is the atom data container.
   * @param hash_mode is an option to decide whether to generate/update the relevant md5 hashes.
   * @param md5_state is a pointer to the current md5 storage, which will be updated appropriately
   * if hash_mode == TNG_USE_HASH.
   * @return TNG_SUCCESS(0) is successful.
   */
  static tng_function_status tng_atom_data_write(struct tng_trajectory* tng_data,
                                                 const struct tng_atom* atom,
                                                 const char             hash_mode,
                                                 md5_state_t*           md5_state)
  {
      if (tng_file_output_numerical(tng_data, &atom->id, sizeof(atom->id), hash_mode, md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_fwritestr(tng_data, atom->name, hash_mode, md5_state, __LINE__) == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_fwritestr(tng_data, atom->atom_type, hash_mode, md5_state, __LINE__) == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_molecules_block_len_calculate(const struct tng_trajectory* tng_data,
                                                               int64_t*                     len)
  {
      int64_t        i, j;
      tng_molecule_t molecule;
      tng_chain_t    chain;
      tng_residue_t  residue;
      tng_atom_t     atom;
      tng_bond_t     bond;
  
      *len = 0;
  
      for (i = 0; i < tng_data->n_molecules; i++)
      {
          molecule = &tng_data->molecules[i];
          if (!molecule->name)
          {
              molecule->name = (char*)malloc(1);
              if (!molecule->name)
              {
                  fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                  return (TNG_CRITICAL);
              }
              molecule->name[0] = 0;
          }
          *len += tng_min_size(strlen(molecule->name) + 1, TNG_MAX_STR_LEN);
  
          chain = molecule->chains;
          for (j = 0; j < molecule->n_chains; j++)
          {
              *len += sizeof(chain->id);
  
              if (!chain->name)
              {
                  chain->name = (char*)malloc(1);
                  if (!chain->name)
                  {
                      fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                      return (TNG_CRITICAL);
                  }
                  chain->name[0] = 0;
              }
              *len += tng_min_size(strlen(chain->name) + 1, TNG_MAX_STR_LEN);
  
              *len += sizeof(chain->n_residues);
  
              chain++;
          }
  
          residue = molecule->residues;
          for (j = 0; j < molecule->n_residues; j++)
          {
              *len += sizeof(residue->id);
  
              if (!residue->name)
              {
                  residue->name = (char*)malloc(1);
                  if (!residue->name)
                  {
                      fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                      return (TNG_CRITICAL);
                  }
                  residue->name[0] = 0;
              }
              *len += tng_min_size(strlen(residue->name) + 1, TNG_MAX_STR_LEN);
  
              *len += sizeof(residue->n_atoms);
  
              residue++;
          }
  
          atom = molecule->atoms;
          for (j = 0; j < molecule->n_atoms; j++)
          {
              *len += sizeof(atom->id);
              if (!atom->name)
              {
                  atom->name = (char*)malloc(1);
                  if (!atom->name)
                  {
                      fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                      return (TNG_CRITICAL);
                  }
                  atom->name[0] = 0;
              }
              *len += tng_min_size(strlen(atom->name) + 1, TNG_MAX_STR_LEN);
  
              if (!atom->atom_type)
              {
                  atom->atom_type = (char*)malloc(1);
                  if (!atom->atom_type)
                  {
                      fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                      return (TNG_CRITICAL);
                  }
                  atom->atom_type[0] = 0;
              }
              *len += tng_min_size(strlen(atom->atom_type) + 1, TNG_MAX_STR_LEN);
  
              atom++;
          }
  
          for (j = 0; j < molecule->n_bonds; j++)
          {
              *len += sizeof(bond->from_atom_id) + sizeof(bond->to_atom_id);
          }
      }
      *len += sizeof(tng_data->n_molecules)
              + (sizeof(molecule->id) + sizeof(molecule->quaternary_str) + sizeof(molecule->n_chains)
                 + sizeof(molecule->n_residues) + sizeof(molecule->n_atoms) + sizeof(molecule->n_bonds))
                        * tng_data->n_molecules;
  
      if (!tng_data->var_num_atoms_flag)
      {
          *len += tng_data->n_molecules * sizeof(int64_t);
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Read a molecules block. Contains chain, residue and atom data
   * @param tng_data is a trajectory data container.
   * @param block is a general block container.
   * @param hash_mode is an option to decide whether to use the md5 hash or not.
   * If hash_mode == TNG_USE_HASH the written md5 hash in the file will be
   * compared to the md5 hash of the read contents to ensure valid data.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_molecules_block_read(struct tng_trajectory*      tng_data,
                                                      const struct tng_gen_block* block,
                                                      const char                  hash_mode)
  {
      int64_t        start_pos, i, j, k, l;
      tng_molecule_t molecule;
      tng_chain_t    chain;
      tng_residue_t  residue;
      tng_atom_t     atom;
      tng_bond_t     bond;
      char           hash[TNG_MD5_HASH_LEN];
      md5_state_t    md5_state;
  
      if (tng_input_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      start_pos = ftello(tng_data->input_file);
  
      /* FIXME: Does not check if the size of the contents matches the expected
       * size or if the contents can be read. */
  
      if (tng_data->molecules)
      {
          for (i = 0; i < tng_data->n_molecules; i++)
          {
              tng_molecule_destroy(tng_data, &tng_data->molecules[i]);
          }
          free(tng_data->molecules);
          tng_data->molecules   = 0;
          tng_data->n_molecules = 0;
      }
  
      if (hash_mode == TNG_USE_HASH)
      {
          md5_init(&md5_state);
      }
  
      if (tng_file_input_numerical(tng_data, &tng_data->n_molecules, sizeof(tng_data->n_molecules),
                                   hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_data->molecules)
      {
          free(tng_data->molecules);
      }
  
      tng_data->n_particles = 0;
  
      tng_data->molecules =
              (struct tng_molecule*)malloc(tng_data->n_molecules * sizeof(struct tng_molecule));
      if (!tng_data->molecules)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      if (!tng_data->var_num_atoms_flag)
      {
          if (tng_data->molecule_cnt_list)
          {
              free(tng_data->molecule_cnt_list);
          }
          tng_data->molecule_cnt_list = (int64_t*)malloc(sizeof(int64_t) * tng_data->n_molecules);
          if (!tng_data->molecule_cnt_list)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      /* Read each molecule from file */
      for (i = 0; i < tng_data->n_molecules; i++)
      {
          molecule = &tng_data->molecules[i];
  
          molecule->name = 0;
  
          if (tng_file_input_numerical(tng_data, &molecule->id, sizeof(molecule->id), hash_mode,
                                       &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          /*         fprintf(stderr, "TNG library: Read id: %" PRId64 " offset: %d\n", molecule->id, offset);*/
          tng_freadstr(tng_data, &molecule->name, hash_mode, &md5_state, __LINE__);
  
          if (tng_file_input_numerical(tng_data, &molecule->quaternary_str,
                                       sizeof(molecule->quaternary_str), hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          if (!tng_data->var_num_atoms_flag)
          {
              if (tng_file_input_numerical(tng_data, &tng_data->molecule_cnt_list[i], sizeof(int64_t),
                                           hash_mode, &md5_state, __LINE__)
                  == TNG_CRITICAL)
              {
                  return (TNG_CRITICAL);
              }
          }
  
          if (tng_file_input_numerical(tng_data, &molecule->n_chains, sizeof(molecule->n_chains),
                                       hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          if (tng_file_input_numerical(tng_data, &molecule->n_residues, sizeof(molecule->n_residues),
                                       hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          if (tng_file_input_numerical(tng_data, &molecule->n_atoms, sizeof(molecule->n_atoms),
                                       hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          tng_data->n_particles += molecule->n_atoms * tng_data->molecule_cnt_list[i];
  
          if (molecule->n_chains > 0)
          {
              molecule->chains = (struct tng_chain*)malloc(molecule->n_chains * sizeof(struct tng_chain));
              if (!molecule->chains)
              {
                  fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                  return (TNG_CRITICAL);
              }
  
              chain = molecule->chains;
          }
          else
          {
              chain = 0;
          }
  
          if (molecule->n_residues > 0)
          {
              molecule->residues =
                      (struct tng_residue*)malloc(molecule->n_residues * sizeof(struct tng_residue));
              if (!molecule->residues)
              {
                  fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                  if (molecule->chains)
                  {
                      free(molecule->chains);
                      molecule->chains = 0;
                  }
                  return (TNG_CRITICAL);
              }
  
              residue = molecule->residues;
          }
          else
          {
              residue = 0;
          }
  
          molecule->atoms = (struct tng_atom*)malloc(molecule->n_atoms * sizeof(struct tng_atom));
          if (!molecule->atoms)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              if (molecule->chains)
              {
                  free(molecule->chains);
                  molecule->chains = 0;
              }
              if (molecule->residues)
              {
                  free(molecule->residues);
                  molecule->residues = 0;
              }
              return (TNG_CRITICAL);
          }
  
          atom = molecule->atoms;
  
          if (molecule->n_chains > 0)
          {
              /* Read the chains of the molecule */
              for (j = 0; j < molecule->n_chains; j++)
              {
                  chain->molecule = molecule;
  
                  chain->name = 0;
  
                  tng_chain_data_read(tng_data, chain, hash_mode, &md5_state);
  
                  if (j == 0)
                  {
                      chain->residues = molecule->residues;
                      residue         = chain->residues;
                  }
                  else
                  {
                      chain->residues = residue;
                  }
  
                  /* Read the residues of the chain */
                  for (k = 0; k < chain->n_residues; k++)
                  {
                      residue->chain = chain;
  
                      residue->name = 0;
  
                      tng_residue_data_read(tng_data, residue, hash_mode, &md5_state);
  
                      residue->atoms_offset = atom - molecule->atoms;
                      /* Read the atoms of the residue */
                      for (l = 0; l < residue->n_atoms; l++)
                      {
                          atom->residue = residue;
  
                          atom->name      = 0;
                          atom->atom_type = 0;
  
                          tng_atom_data_read(tng_data, atom, hash_mode, &md5_state);
  
                          atom++;
                      }
                      residue++;
                  }
                  chain++;
              }
          }
          else
          {
              if (molecule->n_residues > 0)
              {
                  for (k = 0; k < molecule->n_residues; k++)
                  {
                      residue->chain = 0;
  
                      residue->name = 0;
  
                      tng_residue_data_read(tng_data, residue, hash_mode, &md5_state);
  
                      residue->atoms_offset = atom - molecule->atoms;
                      /* Read the atoms of the residue */
                      for (l = 0; l < residue->n_atoms; l++)
                      {
                          atom->residue = residue;
  
                          tng_atom_data_read(tng_data, atom, hash_mode, &md5_state);
  
                          atom++;
                      }
                      residue++;
                  }
              }
              else
              {
                  for (l = 0; l < molecule->n_atoms; l++)
                  {
                      atom->residue = 0;
  
                      atom->name      = 0;
                      atom->atom_type = 0;
  
                      tng_atom_data_read(tng_data, atom, hash_mode, &md5_state);
  
                      atom++;
                  }
              }
          }
  
          if (tng_file_input_numerical(tng_data, &molecule->n_bonds, sizeof(molecule->n_bonds),
                                       hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          if (molecule->n_bonds > 0)
          {
              tng_data->molecules[i].bonds =
                      (struct tng_bond*)malloc(molecule->n_bonds * sizeof(struct tng_bond));
              if (!molecule->bonds)
              {
                  fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                  if (molecule->chains)
                  {
                      free(molecule->chains);
                      molecule->chains = 0;
                  }
                  if (molecule->residues)
                  {
                      free(molecule->residues);
                      molecule->residues = 0;
                  }
                  if (molecule->atoms)
                  {
                      free(molecule->atoms);
                      molecule->atoms = 0;
                  }
                  return (TNG_CRITICAL);
              }
  
              bond = molecule->bonds;
  
              for (j = 0; j < molecule->n_bonds; j++)
              {
                  if (tng_file_input_numerical(tng_data, &bond->from_atom_id, sizeof(bond->from_atom_id),
                                               hash_mode, &md5_state, __LINE__)
                      == TNG_CRITICAL)
                  {
                      return (TNG_CRITICAL);
                  }
  
                  if (tng_file_input_numerical(tng_data, &bond->to_atom_id, sizeof(bond->to_atom_id),
                                               hash_mode, &md5_state, __LINE__)
                      == TNG_CRITICAL)
                  {
                      return (TNG_CRITICAL);
                  }
  
                  bond++;
              }
          }
          else
          {
              molecule->bonds = 0;
          }
      }
  
      if (hash_mode == TNG_USE_HASH)
      {
          /* If there is data left in the block that the current version of the library
           * cannot interpret still read that to generate the MD5 hash. */
          tng_md5_remaining_append(tng_data, block, start_pos, &md5_state);
  
          md5_finish(&md5_state, (md5_byte_t*)hash);
          if (strncmp(block->md5_hash, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", TNG_MD5_HASH_LEN) != 0)
          {
              if (strncmp(block->md5_hash, hash, TNG_MD5_HASH_LEN) != 0)
              {
                  fprintf(stderr,
                          "TNG library: Molecules block contents corrupt. Hashes do not match. "
                          "%s: %d\n",
                          __FILE__, __LINE__);
              }
          }
      }
  
      else
      {
          /* Seek to the end of the block */
          fseeko(tng_data->input_file, start_pos + block->block_contents_size, SEEK_SET);
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Write a molecules block.
   * @param tng_data is a trajectory data container.
   * @param hash_mode is an option to decide whether to use the md5 hash or not.
   * If hash_mode == TNG_USE_HASH an md5 hash will be generated and written.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_molecules_block_write(struct tng_trajectory* tng_data, const char hash_mode)
  {
      int             name_len;
      int64_t         i, j, k, l, header_file_pos, curr_file_pos;
      tng_molecule_t  molecule;
      tng_chain_t     chain;
      tng_residue_t   residue;
      tng_atom_t      atom;
      tng_bond_t      bond;
      tng_gen_block_t block;
      md5_state_t     md5_state;
  
      if (tng_output_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      tng_block_init(&block);
  
      name_len = (unsigned int)strlen("MOLECULES");
  
      block->name = (char*)malloc(name_len + 1);
      if (!block->name)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      strcpy(block->name, "MOLECULES");
      block->id = TNG_MOLECULES;
  
      if (tng_molecules_block_len_calculate(tng_data, &block->block_contents_size) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot calculate length of molecules block. %s: %d\n",
                  __FILE__, __LINE__);
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      header_file_pos = ftello(tng_data->output_file);
  
      if (tng_block_header_write(tng_data, block) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot write header of file %s. %s: %d\n",
                  tng_data->output_file_path, __FILE__, __LINE__);
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      if (hash_mode == TNG_USE_HASH)
      {
          md5_init(&md5_state);
      }
  
      if (tng_file_output_numerical(tng_data, &tng_data->n_molecules, sizeof(tng_data->n_molecules),
                                    hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      for (i = 0; i < tng_data->n_molecules; i++)
      {
          molecule = &tng_data->molecules[i];
  
          if (tng_file_output_numerical(tng_data, &molecule->id, sizeof(molecule->id), hash_mode,
                                        &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          if (tng_fwritestr(tng_data, molecule->name, hash_mode, &md5_state, __LINE__) == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          if (tng_file_output_numerical(tng_data, &molecule->quaternary_str,
                                        sizeof(molecule->quaternary_str), hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          if (!tng_data->var_num_atoms_flag)
          {
              if (tng_file_output_numerical(tng_data, &tng_data->molecule_cnt_list[i],
                                            sizeof(int64_t), hash_mode, &md5_state, __LINE__)
                  == TNG_CRITICAL)
              {
                  return (TNG_CRITICAL);
              }
          }
  
          if (tng_file_output_numerical(tng_data, &molecule->n_chains, sizeof(molecule->n_chains),
                                        hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          if (tng_file_output_numerical(tng_data, &molecule->n_residues, sizeof(molecule->n_residues),
                                        hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          if (tng_file_output_numerical(tng_data, &molecule->n_atoms, sizeof(molecule->n_atoms),
                                        hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          if (molecule->n_chains > 0)
          {
              chain = molecule->chains;
              for (j = 0; j < molecule->n_chains; j++)
              {
                  tng_chain_data_write(tng_data, chain, hash_mode, &md5_state);
  
                  residue = chain->residues;
                  for (k = 0; k < chain->n_residues; k++)
                  {
                      tng_residue_data_write(tng_data, residue, hash_mode, &md5_state);
  
                      atom = molecule->atoms + residue->atoms_offset;
                      for (l = 0; l < residue->n_atoms; l++)
                      {
                          tng_atom_data_write(tng_data, atom, hash_mode, &md5_state);
  
                          atom++;
                      }
                      residue++;
                  }
                  chain++;
              }
          }
          else
          {
              if (molecule->n_residues > 0)
              {
                  residue = molecule->residues;
                  for (k = 0; k < molecule->n_residues; k++)
                  {
                      tng_residue_data_write(tng_data, residue, hash_mode, &md5_state);
  
                      atom = molecule->atoms + residue->atoms_offset;
                      for (l = 0; l < residue->n_atoms; l++)
                      {
                          tng_atom_data_write(tng_data, atom, hash_mode, &md5_state);
  
                          atom++;
                      }
                      residue++;
                  }
              }
              else
              {
                  atom = molecule->atoms;
                  for (l = 0; l < molecule->n_atoms; l++)
                  {
                      tng_atom_data_write(tng_data, atom, hash_mode, &md5_state);
  
                      atom++;
                  }
              }
          }
  
          if (tng_file_output_numerical(tng_data, &molecule->n_bonds, sizeof(molecule->n_bonds),
                                        hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          bond = molecule->bonds;
          for (j = 0; j < molecule->n_bonds; j++)
          {
              if (tng_file_output_numerical(tng_data, &bond->from_atom_id, sizeof(bond->from_atom_id),
                                            hash_mode, &md5_state, __LINE__)
                  == TNG_CRITICAL)
              {
                  return (TNG_CRITICAL);
              }
  
              if (tng_file_output_numerical(tng_data, &bond->to_atom_id, sizeof(bond->to_atom_id),
                                            hash_mode, &md5_state, __LINE__)
                  == TNG_CRITICAL)
              {
                  return (TNG_CRITICAL);
              }
  
              bond++;
          }
      }
      if (hash_mode == TNG_USE_HASH)
      {
          md5_finish(&md5_state, (md5_byte_t*)block->md5_hash);
          curr_file_pos = ftello(tng_data->output_file);
          fseeko(tng_data->output_file, header_file_pos + 3 * sizeof(int64_t), SEEK_SET);
          if (fwrite(block->md5_hash, TNG_MD5_HASH_LEN, 1, tng_data->output_file) != 1)
          {
              fprintf(stderr, "TNG library: Could not write MD5 hash. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          fseeko(tng_data->output_file, curr_file_pos, SEEK_SET);
      }
  
      tng_block_destroy(&block);
  
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_frame_set_block_len_calculate(const struct tng_trajectory* tng_data,
                                                               int64_t*                     len)
  {
      *len = sizeof(int64_t) * 8;
      *len += sizeof(double) * 2;
  
      if (tng_data->var_num_atoms_flag)
      {
          *len += sizeof(int64_t) * tng_data->n_molecules;
      }
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Read a frame set block. Update tng_data->current_trajectory_frame_set
   * @param tng_data is a trajectory data container.
   * @param block is a general block container.
   * @param hash_mode is an option to decide whether to use the md5 hash or not.
   * If hash_mode == TNG_USE_HASH the written md5 hash in the file will be
   * compared to the md5 hash of the read contents to ensure valid data.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_frame_set_block_read(tng_trajectory_t tng_data,
                                                      tng_gen_block_t  block,
                                                      const char       hash_mode)
  {
      int64_t                    file_pos, start_pos, i, prev_n_particles;
      tng_trajectory_frame_set_t frame_set = &tng_data->current_trajectory_frame_set;
      char                       hash[TNG_MD5_HASH_LEN];
      md5_state_t                md5_state;
  
      if (tng_input_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      start_pos = ftello(tng_data->input_file);
  
      /* FIXME: Does not check if the size of the contents matches the expected
       * size or if the contents can be read. */
  
      file_pos = start_pos - block->header_contents_size;
  
      tng_data->current_trajectory_frame_set_input_file_pos = file_pos;
  
      tng_frame_set_particle_mapping_free(tng_data);
  
      if (hash_mode == TNG_USE_HASH)
      {
          md5_init(&md5_state);
      }
      if (tng_file_input_numerical(tng_data, &frame_set->first_frame, sizeof(frame_set->first_frame),
                                   hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, &frame_set->n_frames, sizeof(frame_set->n_frames),
                                   hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_data->var_num_atoms_flag)
      {
          prev_n_particles       = frame_set->n_particles;
          frame_set->n_particles = 0;
          /* If the list of molecule counts has already been created assume that
           * it is of correct size. */
          if (!frame_set->molecule_cnt_list)
          {
              frame_set->molecule_cnt_list = (int64_t*)malloc(sizeof(int64_t) * tng_data->n_molecules);
  
              if (!frame_set->molecule_cnt_list)
              {
                  fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                  return (TNG_CRITICAL);
              }
          }
          for (i = 0; i < tng_data->n_molecules; i++)
          {
              if (tng_file_input_numerical(tng_data, &frame_set->molecule_cnt_list[i],
                                           sizeof(int64_t), hash_mode, &md5_state, __LINE__)
                  == TNG_CRITICAL)
              {
                  return (TNG_CRITICAL);
              }
  
              frame_set->n_particles += tng_data->molecules[i].n_atoms * frame_set->molecule_cnt_list[i];
          }
          if (prev_n_particles && frame_set->n_particles != prev_n_particles)
          {
              /* FIXME: Particle dependent data memory management */
          }
      }
  
      if (tng_file_input_numerical(tng_data, &frame_set->next_frame_set_file_pos,
                                   sizeof(frame_set->next_frame_set_file_pos), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, &frame_set->prev_frame_set_file_pos,
                                   sizeof(frame_set->prev_frame_set_file_pos), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, &frame_set->medium_stride_next_frame_set_file_pos,
                                   sizeof(frame_set->medium_stride_next_frame_set_file_pos),
                                   hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, &frame_set->medium_stride_prev_frame_set_file_pos,
                                   sizeof(frame_set->medium_stride_prev_frame_set_file_pos),
                                   hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, &frame_set->long_stride_next_frame_set_file_pos,
                                   sizeof(frame_set->long_stride_next_frame_set_file_pos), hash_mode,
                                   &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, &frame_set->long_stride_prev_frame_set_file_pos,
                                   sizeof(frame_set->long_stride_prev_frame_set_file_pos), hash_mode,
                                   &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (block->block_version >= 3)
      {
          if (tng_file_input_numerical(tng_data, &frame_set->first_frame_time,
                                       sizeof(frame_set->first_frame_time), hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          if (tng_file_input_numerical(tng_data, &tng_data->time_per_frame,
                                       sizeof(tng_data->time_per_frame), hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
      }
      else
      {
          frame_set->first_frame_time = -1;
          tng_data->time_per_frame    = -1;
      }
  
      if (hash_mode == TNG_USE_HASH)
      {
          /* If there is data left in the block that the current version of the library
           * cannot interpret still read that to generate the MD5 hash. */
          tng_md5_remaining_append(tng_data, block, start_pos, &md5_state);
  
          md5_finish(&md5_state, (md5_byte_t*)hash);
          if (strncmp(block->md5_hash, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", TNG_MD5_HASH_LEN) != 0)
          {
              if (strncmp(block->md5_hash, hash, TNG_MD5_HASH_LEN) != 0)
              {
                  fprintf(stderr,
                          "TNG library: Frame set block contents corrupt (first frame %" PRId64
                          "). Hashes do not match. "
                          "%s: %d\n",
                          frame_set->first_frame, __FILE__, __LINE__);
              }
          }
      }
      else
      {
          /* Seek to the end of the block */
          fseeko(tng_data->input_file, start_pos + block->block_contents_size, SEEK_SET);
      }
  
      /* If the output file and the input files are the same the number of
       * frames in the file are the same number as has just been read.
       * This is updated here to later on see if there have been new frames
       * added and thereby the frame set needs to be rewritten. */
      if (tng_data->output_file == tng_data->input_file)
      {
          frame_set->n_written_frames = frame_set->n_frames;
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Write tng_data->current_trajectory_frame_set to file
   * @param tng_data is a trajectory data container.
   * @param block is a general block container.
   * @param hash_mode is an option to decide whether to use the md5 hash or not.
   * If hash_mode == TNG_USE_HASH an md5 hash will be generated and written.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_frame_set_block_write(struct tng_trajectory* tng_data,
                                                       struct tng_gen_block*  block,
                                                       const char             hash_mode)
  {
      char*                      temp_name;
      int64_t                    i, header_file_pos, curr_file_pos;
      unsigned int               name_len;
      tng_trajectory_frame_set_t frame_set = &tng_data->current_trajectory_frame_set;
      md5_state_t                md5_state;
  
      if (tng_output_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      name_len = (unsigned int)strlen("TRAJECTORY FRAME SET");
  
      if (!block->name || strlen(block->name) < name_len)
      {
          temp_name = (char*)realloc(block->name, name_len + 1);
          if (!temp_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              free(block->name);
              block->name = 0;
              return (TNG_CRITICAL);
          }
          block->name = temp_name;
      }
      strcpy(block->name, "TRAJECTORY FRAME SET");
      block->id = TNG_TRAJECTORY_FRAME_SET;
  
      if (tng_frame_set_block_len_calculate(tng_data, &block->block_contents_size) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot calculate length of frame set block. %s: %d\n",
                  __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      header_file_pos = ftello(tng_data->output_file);
  
      if (tng_block_header_write(tng_data, block) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot write header of file %s. %s: %d\n",
                  tng_data->output_file_path, __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      if (hash_mode == TNG_USE_HASH)
      {
          md5_init(&md5_state);
      }
      if (tng_file_output_numerical(tng_data, &frame_set->first_frame, sizeof(frame_set->first_frame),
                                    hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &frame_set->n_frames, sizeof(frame_set->n_frames),
                                    hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_data->var_num_atoms_flag)
      {
          for (i = 0; i < tng_data->n_molecules; i++)
          {
              if (tng_file_output_numerical(tng_data, &frame_set->molecule_cnt_list[i],
                                            sizeof(int64_t), hash_mode, &md5_state, __LINE__)
                  == TNG_CRITICAL)
              {
                  return (TNG_CRITICAL);
              }
          }
      }
  
      if (tng_file_output_numerical(tng_data, &frame_set->next_frame_set_file_pos,
                                    sizeof(frame_set->next_frame_set_file_pos), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &frame_set->prev_frame_set_file_pos,
                                    sizeof(frame_set->prev_frame_set_file_pos), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &frame_set->medium_stride_next_frame_set_file_pos,
                                    sizeof(frame_set->medium_stride_next_frame_set_file_pos),
                                    hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &frame_set->medium_stride_prev_frame_set_file_pos,
                                    sizeof(frame_set->medium_stride_prev_frame_set_file_pos),
                                    hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &frame_set->long_stride_next_frame_set_file_pos,
                                    sizeof(frame_set->long_stride_next_frame_set_file_pos), hash_mode,
                                    &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &frame_set->long_stride_prev_frame_set_file_pos,
                                    sizeof(frame_set->long_stride_prev_frame_set_file_pos), hash_mode,
                                    &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &frame_set->first_frame_time,
                                    sizeof(frame_set->first_frame_time), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &tng_data->time_per_frame,
                                    sizeof(tng_data->time_per_frame), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
      if (hash_mode == TNG_USE_HASH)
      {
          md5_finish(&md5_state, (md5_byte_t*)block->md5_hash);
          curr_file_pos = ftello(tng_data->output_file);
          fseeko(tng_data->output_file, header_file_pos + 3 * sizeof(int64_t), SEEK_SET);
          if (fwrite(block->md5_hash, TNG_MD5_HASH_LEN, 1, tng_data->output_file) != 1)
          {
              fprintf(stderr, "TNG library: Could not write MD5 hash. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          fseeko(tng_data->output_file, curr_file_pos, SEEK_SET);
      }
  
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_trajectory_mapping_block_len_calculate(const struct tng_trajectory* tng_data,
                                                                        const int64_t n_particles,
                                                                        int64_t*      len)
  {
      (void)tng_data;
      *len = sizeof(int64_t) * (2 + n_particles);
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Read an atom mappings block (translating between real atom indexes and how
   *  the atom info is written in this frame set).
   * @param tng_data is a trajectory data container.
   * @param block is a general block container.
   * @param hash_mode is an option to decide whether to use the md5 hash or not.
   * If hash_mode == TNG_USE_HASH the written md5 hash in the file will be
   * compared to the md5 hash of the read contents to ensure valid data.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_trajectory_mapping_block_read(struct tng_trajectory*      tng_data,
                                                               const struct tng_gen_block* block,
                                                               const char                  hash_mode)
  {
      int64_t                    start_pos, i;
      tng_trajectory_frame_set_t frame_set = &tng_data->current_trajectory_frame_set;
      tng_particle_mapping_t     mapping, mappings;
      char                       hash[TNG_MD5_HASH_LEN];
      md5_state_t                md5_state;
  
      if (tng_input_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      start_pos = ftello(tng_data->input_file);
  
      /* FIXME: Does not check if the size of the contents matches the expected
       * size or if the contents can be read. */
  
      frame_set->n_mapping_blocks++;
      mappings = (tng_particle_mapping_t)realloc(
              frame_set->mappings, sizeof(struct tng_particle_mapping) * frame_set->n_mapping_blocks);
      if (!mappings)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(frame_set->mappings);
          frame_set->mappings = 0;
          return (TNG_CRITICAL);
      }
      frame_set->mappings = mappings;
      mapping             = &mappings[frame_set->n_mapping_blocks - 1];
  
  
      if (hash_mode == TNG_USE_HASH)
      {
          md5_init(&md5_state);
      }
  
      if (tng_file_input_numerical(tng_data, &mapping->num_first_particle,
                                   sizeof(mapping->num_first_particle), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, &mapping->n_particles, sizeof(mapping->n_particles),
                                   hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      mapping->real_particle_numbers = (int64_t*)malloc(mapping->n_particles * sizeof(int64_t));
      if (!mapping->real_particle_numbers)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      /* If the byte order needs to be swapped the data must be read one value at
       * a time and swapped */
      if (tng_data->input_endianness_swap_func_64)
      {
          for (i = 0; i < mapping->n_particles; i++)
          {
              if (tng_file_input_numerical(tng_data, &mapping->real_particle_numbers[i],
                                           sizeof(int64_t), hash_mode, &md5_state, __LINE__)
                  == TNG_CRITICAL)
              {
                  return (TNG_CRITICAL);
              }
          }
      }
      /* Otherwise the data can be read all at once */
      else
      {
          if (fread(mapping->real_particle_numbers, mapping->n_particles * sizeof(int64_t), 1, tng_data->input_file)
              == 0)
          {
              fprintf(stderr, "TNG library: Cannot read block. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          if (hash_mode == TNG_USE_HASH)
          {
              md5_append(&md5_state, (md5_byte_t*)mapping->real_particle_numbers,
                         mapping->n_particles * sizeof(int64_t));
          }
      }
  
      if (hash_mode == TNG_USE_HASH)
      {
          /* If there is data left in the block that the current version of the library
           * cannot interpret still read that to generate the MD5 hash. */
          tng_md5_remaining_append(tng_data, block, start_pos, &md5_state);
  
          md5_finish(&md5_state, (md5_byte_t*)hash);
          if (strncmp(block->md5_hash, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", TNG_MD5_HASH_LEN) != 0)
          {
              if (strncmp(block->md5_hash, hash, TNG_MD5_HASH_LEN) != 0)
              {
                  fprintf(stderr,
                          "TNG library: Particle mapping block contents corrupt. Hashes do not "
                          "match. "
                          "%s: %d\n",
                          __FILE__, __LINE__);
              }
          }
      }
      else
      {
          /* Seek to the end of the block */
          fseeko(tng_data->input_file, start_pos + block->block_contents_size, SEEK_SET);
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Write the atom mappings of the current trajectory frame set
   * @param tng_data is a trajectory data container.
   * @param block is a general block container.
   * @param mapping_block_nr is the index of the mapping block to write.
   * @param hash_mode is an option to decide whether to use the md5 hash or not.
   * If hash_mode == TNG_USE_HASH an md5 hash will be generated and written.
   * @return TNG_SUCCESS (0) if successful, TNG_FAILURE (1) if a minor error
   * has occurred or TNG_CRITICAL (2) if a major error has occured.
   */
  static tng_function_status tng_trajectory_mapping_block_write(struct tng_trajectory* tng_data,
                                                                struct tng_gen_block*  block,
                                                                const int  mapping_block_nr,
                                                                const char hash_mode)
  {
      int64_t                header_file_pos, curr_file_pos;
      char*                  temp_name;
      int                    i;
      unsigned int           name_len;
      md5_state_t            md5_state;
      tng_particle_mapping_t mapping = &tng_data->current_trajectory_frame_set.mappings[mapping_block_nr];
  
      if (mapping_block_nr >= tng_data->current_trajectory_frame_set.n_mapping_blocks)
      {
          fprintf(stderr, "TNG library: Mapping block index out of bounds. %s: %d\n", __FILE__, __LINE__);
          return (TNG_FAILURE);
      }
  
      if (tng_output_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      name_len = (unsigned int)strlen("PARTICLE MAPPING");
  
      if (!block->name || strlen(block->name) < name_len)
      {
          temp_name = (char*)realloc(block->name, name_len + 1);
          if (!temp_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              free(block->name);
              block->name = 0;
              return (TNG_CRITICAL);
          }
          block->name = temp_name;
      }
      strcpy(block->name, "PARTICLE MAPPING");
      block->id = TNG_PARTICLE_MAPPING;
  
      if (tng_trajectory_mapping_block_len_calculate(tng_data, mapping->n_particles, &block->block_contents_size)
          != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot calculate length of atom mapping block. %s: %d\n",
                  __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      header_file_pos = ftello(tng_data->output_file);
  
      if (tng_block_header_write(tng_data, block) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot write header of file %s. %s: %d\n",
                  tng_data->output_file_path, __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      if (hash_mode == TNG_USE_HASH)
      {
          md5_init(&md5_state);
      }
      if (tng_file_output_numerical(tng_data, &mapping->num_first_particle,
                                    sizeof(mapping->num_first_particle), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &mapping->n_particles, sizeof(mapping->n_particles),
                                    hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_data->output_endianness_swap_func_64)
      {
          for (i = 0; i < mapping->n_particles; i++)
          {
              if (tng_file_output_numerical(tng_data, &mapping->real_particle_numbers[i],
                                            sizeof(int64_t), hash_mode, &md5_state, __LINE__)
                  == TNG_CRITICAL)
              {
                  return (TNG_CRITICAL);
              }
          }
      }
      else
      {
          if (fwrite(mapping->real_particle_numbers, mapping->n_particles * sizeof(int64_t), 1, tng_data->output_file)
              != 1)
          {
              fprintf(stderr, "TNG library: Could not write block data. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          if (hash_mode == TNG_USE_HASH)
          {
              md5_append(&md5_state, (md5_byte_t*)mapping->real_particle_numbers,
                         mapping->n_particles * sizeof(int64_t));
          }
      }
  
      if (hash_mode == TNG_USE_HASH)
      {
          md5_finish(&md5_state, (md5_byte_t*)block->md5_hash);
          curr_file_pos = ftello(tng_data->output_file);
          fseeko(tng_data->output_file, header_file_pos + 3 * sizeof(int64_t), SEEK_SET);
          if (fwrite(block->md5_hash, TNG_MD5_HASH_LEN, 1, tng_data->output_file) != 1)
          {
              fprintf(stderr, "TNG library: Could not write MD5 hash. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          fseeko(tng_data->output_file, curr_file_pos, SEEK_SET);
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Prepare a block for storing particle data
   * @param tng_data is a trajectory data container.
   * @param block_type_flag specifies if this is a trajectory block or a
   * non-trajectory block. (TNG_TRAJECTORY_BLOCK or TNG_NON_TRAJECTORY_BLOCK)
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_particle_data_block_create(struct tng_trajectory* tng_data,
                                                            const char             block_type_flag)
  {
      tng_trajectory_frame_set_t frame_set = &tng_data->current_trajectory_frame_set;
  
      tng_data_t data;
  
      if (block_type_flag == TNG_TRAJECTORY_BLOCK)
      {
          frame_set->n_particle_data_blocks++;
          data = (tng_data_t)realloc(frame_set->tr_particle_data,
                                     sizeof(struct tng_data) * frame_set->n_particle_data_blocks);
          if (!data)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              free(frame_set->tr_particle_data);
              frame_set->tr_particle_data = 0;
              return (TNG_CRITICAL);
          }
          frame_set->tr_particle_data = data;
      }
      else
      {
          tng_data->n_particle_data_blocks++;
          data = (tng_data_t)realloc(tng_data->non_tr_particle_data,
                                     sizeof(struct tng_data) * tng_data->n_particle_data_blocks);
          if (!data)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              free(tng_data->non_tr_particle_data);
              tng_data->non_tr_particle_data = 0;
              return (TNG_CRITICAL);
          }
          tng_data->non_tr_particle_data = data;
      }
  
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_compress(struct tng_trajectory*      tng_data,
                                          const struct tng_gen_block* block,
                                          const int64_t               n_frames,
                                          const int64_t               n_particles,
                                          const char                  type,
                                          char**                      data,
                                          int64_t*                    new_len)
  {
      int     nalgo;
      int     compressed_len;
      int*    alt_algo = 0;
      char*   dest;
      int64_t algo_find_n_frames = -1;
      float   f_precision;
      double  d_precision;
  
      if (block->id != TNG_TRAJ_POSITIONS && block->id != TNG_TRAJ_VELOCITIES)
      {
          fprintf(stderr,
                  "TNG library: Can only compress positions and velocities with the "
                  "TNG method. %s: %d\n",
                  __FILE__, __LINE__);
          return (TNG_FAILURE);
      }
      if (type != TNG_FLOAT_DATA && type != TNG_DOUBLE_DATA)
      {
          fprintf(stderr, "TNG library: Data type not supported. %s: %d\n", __FILE__, __LINE__);
          return (TNG_FAILURE);
      }
  
      if (n_frames <= 0 || n_particles <= 0)
      {
          fprintf(stderr,
                  "TNG library: Missing frames or particles. Cannot compress data "
                  "with the TNG method. %s: %d\n",
                  __FILE__, __LINE__);
          return (TNG_FAILURE);
      }
  
      f_precision = 1 / (float)tng_data->compression_precision;
      d_precision = 1 / tng_data->compression_precision;
  
      if (block->id == TNG_TRAJ_POSITIONS)
      {
          /* If there is only one frame in this frame set and there might be more
           * do not store the algorithm as the compression algorithm, but find
           * the best one without storing it */
          if (n_frames == 1 && tng_data->frame_set_n_frames > 1)
          {
              nalgo    = tng_compress_nalgo();
              alt_algo = (int*)malloc(nalgo * sizeof *tng_data->compress_algo_pos);
  
              /* If we have already determined the initial coding and
               * initial coding parameter do not determine them again. */
              if (tng_data->compress_algo_pos)
              {
                  alt_algo[0] = tng_data->compress_algo_pos[0];
                  alt_algo[1] = tng_data->compress_algo_pos[1];
                  alt_algo[2] = tng_data->compress_algo_pos[2];
                  alt_algo[3] = tng_data->compress_algo_pos[3];
              }
              else
              {
                  alt_algo[0] = -1;
                  alt_algo[1] = -1;
                  alt_algo[2] = -1;
                  alt_algo[3] = -1;
              }
  
              /* If the initial coding and initial coding parameter are -1
               * they will be determined in tng_compress_pos/_float/. */
              if (type == TNG_FLOAT_DATA)
              {
                  dest = tng_compress_pos_float((float*)*data, (int)n_particles, (int)n_frames,
                                                f_precision, 0, alt_algo, &compressed_len);
              }
              else
              {
                  dest = tng_compress_pos((double*)*data, (int)n_particles, (int)n_frames,
                                          d_precision, 0, alt_algo, &compressed_len);
              }
              /* If there had been no algorithm determined before keep the initial coding
               * and initial coding parameter so that they won't have to be determined again. */
              if (!tng_data->compress_algo_pos)
              {
                  nalgo                       = tng_compress_nalgo();
                  tng_data->compress_algo_pos = (int*)malloc(nalgo * sizeof *tng_data->compress_algo_pos);
                  tng_data->compress_algo_pos[0] = alt_algo[0];
                  tng_data->compress_algo_pos[1] = alt_algo[1];
                  tng_data->compress_algo_pos[2] = -1;
                  tng_data->compress_algo_pos[3] = -1;
              }
          }
          else if (!tng_data->compress_algo_pos || tng_data->compress_algo_pos[2] == -1
                   || tng_data->compress_algo_pos[2] == -1)
          {
              if (n_frames > 6)
              {
                  algo_find_n_frames = 5;
              }
              else
              {
                  algo_find_n_frames = n_frames;
              }
  
              /* If the algorithm parameters are -1 they will be determined during the
               * compression. */
              if (!tng_data->compress_algo_pos)
              {
                  nalgo                       = tng_compress_nalgo();
                  tng_data->compress_algo_pos = (int*)malloc(nalgo * sizeof *tng_data->compress_algo_pos);
                  tng_data->compress_algo_pos[0] = -1;
                  tng_data->compress_algo_pos[1] = -1;
                  tng_data->compress_algo_pos[2] = -1;
                  tng_data->compress_algo_pos[3] = -1;
              }
              if (type == TNG_FLOAT_DATA)
              {
                  dest = tng_compress_pos_float((float*)*data, (int)n_particles,
                                                (int)algo_find_n_frames, f_precision, 0,
                                                tng_data->compress_algo_pos, &compressed_len);
  
                  if (algo_find_n_frames < n_frames)
                  {
                      free(dest);
                      dest = tng_compress_pos_float((float*)*data, (int)n_particles, (int)n_frames, f_precision,
                                                    0, tng_data->compress_algo_pos, &compressed_len);
                  }
              }
              else
              {
                  dest = tng_compress_pos((double*)*data, (int)n_particles, (int)algo_find_n_frames,
                                          d_precision, 0, tng_data->compress_algo_pos, &compressed_len);
  
                  if (algo_find_n_frames < n_frames)
                  {
                      free(dest);
                      dest = tng_compress_pos((double*)*data, (int)n_particles, (int)n_frames, d_precision,
                                              0, tng_data->compress_algo_pos, &compressed_len);
                  }
              }
          }
          else
          {
              if (type == TNG_FLOAT_DATA)
              {
                  dest = tng_compress_pos_float((float*)*data, (int)n_particles, (int)n_frames, f_precision,
                                                0, tng_data->compress_algo_pos, &compressed_len);
              }
              else
              {
                  dest = tng_compress_pos((double*)*data, (int)n_particles, (int)n_frames, d_precision,
                                          0, tng_data->compress_algo_pos, &compressed_len);
              }
          }
      }
      else if (block->id == TNG_TRAJ_VELOCITIES)
      {
          /* If there is only one frame in this frame set and there might be more
           * do not store the algorithm as the compression algorithm, but find
           * the best one without storing it */
          if (n_frames == 1 && tng_data->frame_set_n_frames > 1)
          {
              nalgo    = tng_compress_nalgo();
              alt_algo = (int*)malloc(nalgo * sizeof *tng_data->compress_algo_vel);
  
              /* If we have already determined the initial coding and
               * initial coding parameter do not determine them again. */
              if (tng_data->compress_algo_vel)
              {
                  alt_algo[0] = tng_data->compress_algo_vel[0];
                  alt_algo[1] = tng_data->compress_algo_vel[1];
                  alt_algo[2] = tng_data->compress_algo_vel[2];
                  alt_algo[3] = tng_data->compress_algo_vel[3];
              }
              else
              {
                  alt_algo[0] = -1;
                  alt_algo[1] = -1;
                  alt_algo[2] = -1;
                  alt_algo[3] = -1;
              }
  
              /* If the initial coding and initial coding parameter are -1
               * they will be determined in tng_compress_pos/_float/. */
              if (type == TNG_FLOAT_DATA)
              {
                  dest = tng_compress_vel_float((float*)*data, (int)n_particles, (int)n_frames,
                                                f_precision, 0, alt_algo, &compressed_len);
              }
              else
              {
                  dest = tng_compress_vel((double*)*data, (int)n_particles, (int)n_frames,
                                          d_precision, 0, alt_algo, &compressed_len);
              }
              /* If there had been no algorithm determined before keep the initial coding
               * and initial coding parameter so that they won't have to be determined again. */
              if (!tng_data->compress_algo_vel)
              {
                  nalgo                       = tng_compress_nalgo();
                  tng_data->compress_algo_vel = (int*)malloc(nalgo * sizeof *tng_data->compress_algo_vel);
                  tng_data->compress_algo_vel[0] = alt_algo[0];
                  tng_data->compress_algo_vel[1] = alt_algo[1];
                  tng_data->compress_algo_vel[2] = -1;
                  tng_data->compress_algo_vel[3] = -1;
              }
          }
          else if (!tng_data->compress_algo_vel || tng_data->compress_algo_vel[2] == -1
                   || tng_data->compress_algo_vel[2] == -1)
          {
              if (n_frames > 6)
              {
                  algo_find_n_frames = 5;
              }
              else
              {
                  algo_find_n_frames = n_frames;
              }
  
              /* If the algorithm parameters are -1 they will be determined during the
               * compression. */
              if (!tng_data->compress_algo_vel)
              {
                  nalgo                       = tng_compress_nalgo();
                  tng_data->compress_algo_vel = (int*)malloc(nalgo * sizeof *tng_data->compress_algo_vel);
                  tng_data->compress_algo_vel[0] = -1;
                  tng_data->compress_algo_vel[1] = -1;
                  tng_data->compress_algo_vel[2] = -1;
                  tng_data->compress_algo_vel[3] = -1;
              }
              if (type == TNG_FLOAT_DATA)
              {
                  dest = tng_compress_vel_float((float*)*data, (int)n_particles,
                                                (int)algo_find_n_frames, f_precision, 0,
                                                tng_data->compress_algo_vel, &compressed_len);
                  if (algo_find_n_frames < n_frames)
                  {
                      free(dest);
                      dest = tng_compress_vel_float((float*)*data, (int)n_particles, (int)n_frames, f_precision,
                                                    0, tng_data->compress_algo_vel, &compressed_len);
                  }
              }
              else
              {
                  dest = tng_compress_vel((double*)*data, (int)n_particles, (int)algo_find_n_frames,
                                          d_precision, 0, tng_data->compress_algo_vel, &compressed_len);
                  if (algo_find_n_frames < n_frames)
                  {
                      free(dest);
                      dest = tng_compress_vel((double*)*data, (int)n_particles, (int)n_frames, d_precision,
                                              0, tng_data->compress_algo_vel, &compressed_len);
                  }
              }
          }
          else
          {
              if (type == TNG_FLOAT_DATA)
              {
                  dest = tng_compress_vel_float((float*)*data, (int)n_particles, (int)n_frames, f_precision,
                                                0, tng_data->compress_algo_vel, &compressed_len);
              }
              else
              {
                  dest = tng_compress_vel((double*)*data, (int)n_particles, (int)n_frames, d_precision,
                                          0, tng_data->compress_algo_vel, &compressed_len);
              }
          }
      }
      else
      {
          fprintf(stderr,
                  "TNG library: Can only compress positions and velocities using TNG-MF1 "
                  "algorithms.\n");
          return (TNG_FAILURE);
      }
  
      if (alt_algo)
      {
          free(alt_algo);
      }
  
      free(*data);
  
      *data = dest;
  
      *new_len = compressed_len;
  
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_uncompress(const struct tng_trajectory* tng_data,
                                            const struct tng_gen_block*  block,
                                            const char                   type,
                                            char**                       data,
                                            const int64_t                uncompressed_len)
  {
      double* d_dest = 0;
      float*  f_dest = 0;
      int     result;
      (void)tng_data;
  
      TNG_ASSERT(uncompressed_len,
                 "TNG library: The full length of the uncompressed data must be > 0.");
  
      if (block->id != TNG_TRAJ_POSITIONS && block->id != TNG_TRAJ_VELOCITIES)
      {
          fprintf(stderr,
                  "TNG library: Can only uncompress positions and velocities with the"
                  "TNG method.\n");
          return (TNG_FAILURE);
      }
      if (type != TNG_FLOAT_DATA && type != TNG_DOUBLE_DATA)
      {
          fprintf(stderr, "TNG library: Data type not supported.\n");
          return (TNG_FAILURE);
      }
  
      if (type == TNG_FLOAT_DATA)
      {
          f_dest = (float*)malloc(uncompressed_len);
          if (!f_dest)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          result = tng_compress_uncompress_float(*data, f_dest);
  
          free(*data);
  
          *data = (char*)f_dest;
      }
      else
      {
          d_dest = (double*)malloc(uncompressed_len);
          if (!d_dest)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          result = tng_compress_uncompress(*data, d_dest);
  
          free(*data);
  
          *data = (char*)d_dest;
      }
  
      if (result == 1)
      {
          fprintf(stderr, "TNG library: Cannot uncompress TNG compressed block.\n");
          return (TNG_FAILURE);
      }
  
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_gzip_compress(const struct tng_trajectory* tng_data,
                                               char**                       data,
                                               const int64_t                len,
                                               int64_t*                     new_len)
  {
      Bytef* dest;
      uLongf stat, max_len;
      (void)tng_data;
  
      max_len = compressBound(len);
      dest    = (Bytef*)malloc(max_len);
      if (!dest)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      stat = compress(dest, &max_len, (Bytef*)*data, len);
      if (stat != (unsigned long)Z_OK)
      {
          free(dest);
          if (stat == (unsigned long)Z_MEM_ERROR)
          {
              fprintf(stderr, "TNG library: Not enough memory. ");
          }
          else if (stat == (unsigned long)Z_BUF_ERROR)
          {
              fprintf(stderr, "TNG library: Destination buffer too small. ");
          }
          fprintf(stderr, "TNG library: Error gzipping data. %s: %d\n", __FILE__, __LINE__);
          return (TNG_FAILURE);
      }
  
      *new_len = max_len;
  
      free(*data);
  
      *data = (char*)dest;
  
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_gzip_uncompress(const struct tng_trajectory* tng_data,
                                                 char**                       data,
                                                 const int64_t                compressed_len,
                                                 const int64_t                uncompressed_len)
  {
      Bytef*        dest;
      unsigned long stat;
      (void)tng_data;
      uLongf new_len = uncompressed_len;
  
      dest = (Bytef*)malloc(uncompressed_len);
      if (!dest)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      stat = uncompress(dest, &new_len, (Bytef*)*data, compressed_len);
  
      if (stat != Z_OK)
      {
          free(dest);
          if (stat == (unsigned long)Z_MEM_ERROR)
          {
              fprintf(stderr, "TNG library: Not enough memory. ");
          }
          else if (stat == (unsigned long)Z_BUF_ERROR)
          {
              fprintf(stderr, "TNG library: Destination buffer too small. ");
          }
          else if (stat == (unsigned long)Z_DATA_ERROR)
          {
              fprintf(stderr, "TNG library: Data corrupt. ");
          }
          fprintf(stderr, "TNG library: Error uncompressing gzipped data. %s: %d\n", __FILE__, __LINE__);
          return (TNG_FAILURE);
      }
  
      free(*data);
  
      *data = (char*)dest;
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Allocate memory for storing particle data.
   * The allocated block will be refered to by data->values.
   * @param tng_data is a trajectory data container.
   * @param data is the data struct, which will contain the allocated memory in
   * data->values.
   * @param n_frames is the number of frames of data to store.
   * @param n_particles is the number of particles with data.
   * @param n_values_per_frame is the number of data values per particle and
   * frame.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_allocate_particle_data_mem(const struct tng_trajectory* tng_data,
                                                            struct tng_data*             data,
                                                            int64_t                      n_frames,
                                                            const int64_t stride_length,
                                                            const int64_t n_particles,
                                                            const int64_t n_values_per_frame)
  {
      void*** values;
      int64_t i, j, k, size, frame_alloc;
      (void)tng_data;
  
      if (n_particles == 0 || n_values_per_frame == 0)
      {
          return (TNG_FAILURE);
      }
  
      if (data->strings && data->datatype == TNG_CHAR_DATA)
      {
          for (i = 0; i < data->n_frames; i++)
          {
              for (j = 0; j < n_particles; j++)
              {
                  for (k = 0; k < data->n_values_per_frame; k++)
                  {
                      if (data->strings[i][j][k])
                      {
                          free(data->strings[i][j][k]);
                      }
                  }
                  free(data->strings[i][j]);
              }
              free(data->strings[i]);
          }
          free(data->strings);
      }
      data->n_frames           = n_frames;
      n_frames                 = tng_max_i64(1, n_frames);
      data->stride_length      = tng_max_i64(1, stride_length);
      data->n_values_per_frame = n_values_per_frame;
      frame_alloc              = (n_frames - 1) / stride_length + 1;
  
      if (data->datatype == TNG_CHAR_DATA)
      {
          data->strings = (char****)malloc(sizeof(char***) * frame_alloc);
          for (i = 0; i < frame_alloc; i++)
          {
              data->strings[i] = (char***)malloc(sizeof(char**) * n_particles);
              if (!data->strings[i])
              {
                  fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                  return (TNG_CRITICAL);
              }
              for (j = 0; j < n_particles; j++)
              {
                  data->strings[i][j] = (char**)malloc(sizeof(char*) * n_values_per_frame);
                  if (!data->strings[i][j])
                  {
                      fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                      return (TNG_CRITICAL);
                  }
                  for (k = 0; k < n_values_per_frame; k++)
                  {
                      data->strings[i][j][k] = 0;
                  }
              }
          }
      }
      else
      {
          switch (data->datatype)
          {
              case TNG_INT_DATA: size = sizeof(int64_t); break;
              case TNG_FLOAT_DATA: size = sizeof(float); break;
              case TNG_DOUBLE_DATA:
              default: size = sizeof(double);
          }
  
          values = (void***)realloc(data->values, size * frame_alloc * n_particles * n_values_per_frame);
          if (!values)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              free(data->values);
              data->values = 0;
              return (TNG_CRITICAL);
          }
          data->values = values;
      }
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_particle_data_find(struct tng_trajectory* tng_data,
                                                    const int64_t          id,
                                                    tng_data_t*            data)
  {
      int64_t                    block_index, i;
      tng_trajectory_frame_set_t frame_set = &tng_data->current_trajectory_frame_set;
      char                       block_type_flag;
  
      if (tng_data->current_trajectory_frame_set_input_file_pos > 0
          || tng_data->current_trajectory_frame_set_output_file_pos > 0)
      {
          block_type_flag = TNG_TRAJECTORY_BLOCK;
      }
      else
      {
          block_type_flag = TNG_NON_TRAJECTORY_BLOCK;
      }
  
      block_index = -1;
      if (block_type_flag == TNG_TRAJECTORY_BLOCK)
      {
          for (i = 0; i < frame_set->n_particle_data_blocks; i++)
          {
              *data = &frame_set->tr_particle_data[i];
              if ((*data)->block_id == id)
              {
                  block_index = i;
                  break;
              }
          }
      }
      else
      {
          for (i = 0; i < tng_data->n_particle_data_blocks; i++)
          {
              *data = &tng_data->non_tr_particle_data[i];
              if ((*data)->block_id == id)
              {
                  block_index = i;
                  break;
              }
          }
      }
      if (block_index == -1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_data_find(struct tng_trajectory* tng_data, const int64_t id, tng_data_t* data)
  {
      int64_t                    block_index, i;
      tng_trajectory_frame_set_t frame_set = &tng_data->current_trajectory_frame_set;
      char                       block_type_flag;
  
      if (tng_data->current_trajectory_frame_set_input_file_pos > 0
          || tng_data->current_trajectory_frame_set_output_file_pos > 0)
      {
          block_type_flag = TNG_TRAJECTORY_BLOCK;
      }
      else
      {
          block_type_flag = TNG_NON_TRAJECTORY_BLOCK;
      }
  
      block_index = -1;
      if (block_type_flag == TNG_TRAJECTORY_BLOCK)
      {
          for (i = 0; i < frame_set->n_data_blocks; i++)
          {
              *data = &frame_set->tr_data[i];
              if ((*data)->block_id == id)
              {
                  block_index = i;
                  break;
              }
          }
          if (block_index == -1)
          {
              for (i = 0; i < tng_data->n_data_blocks; i++)
              {
                  *data = &tng_data->non_tr_data[i];
                  if ((*data)->block_id == id)
                  {
                      block_index = i;
                      break;
                  }
              }
          }
      }
      else
      {
          for (i = 0; i < tng_data->n_data_blocks; i++)
          {
              *data = &tng_data->non_tr_data[i];
              if ((*data)->block_id == id)
              {
                  block_index = i;
                  break;
              }
          }
      }
      if (block_index == -1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_data_block_len_calculate(const struct tng_trajectory* tng_data,
                                                          const struct tng_data*       data,
                                                          const tng_bool is_particle_data,
                                                          const int64_t  n_frames,
                                                          const int64_t  frame_step,
                                                          const int64_t  stride_length,
                                                          const int64_t  num_first_particle,
                                                          const int64_t  n_particles,
                                                          int64_t*       data_start_pos,
                                                          int64_t*       len)
  {
      int     size;
      int64_t i, j, k;
      char ***first_dim_values, **second_dim_values;
      (void)tng_data;
  
      if (data == 0)
      {
          return (TNG_SUCCESS);
      }
  
      switch (data->datatype)
      {
          case TNG_CHAR_DATA: size = 1; break;
          case TNG_INT_DATA: size = sizeof(int64_t); break;
          case TNG_FLOAT_DATA: size = sizeof(float); break;
          case TNG_DOUBLE_DATA:
          default: size = sizeof(double);
      }
  
      *len = sizeof(char) * 2 + sizeof(data->n_values_per_frame) + sizeof(data->codec_id);
      if (is_particle_data)
      {
          *len += sizeof(num_first_particle) + sizeof(n_particles);
      }
  
      if (stride_length > 1)
      {
          *len += sizeof(data->first_frame_with_data) + sizeof(data->stride_length);
      }
  
      if (data->codec_id != TNG_UNCOMPRESSED)
      {
          *len += sizeof(data->compression_multiplier);
      }
  
      if (data->dependency & TNG_FRAME_DEPENDENT)
      {
          *len += sizeof(char);
      }
  
      *data_start_pos = *len;
  
      if (data->datatype == TNG_CHAR_DATA)
      {
          if (is_particle_data)
          {
              for (i = 0; i < n_frames; i++)
              {
                  first_dim_values = data->strings[i];
                  for (j = num_first_particle; j < num_first_particle + n_particles; j++)
                  {
                      second_dim_values = first_dim_values[j];
                      for (k = 0; k < data->n_values_per_frame; k++)
                      {
                          *len += strlen(second_dim_values[k]) + 1;
                      }
                  }
              }
          }
          else
          {
              for (i = 0; i < n_frames; i++)
              {
                  second_dim_values = data->strings[0][i];
                  for (j = 0; j < data->n_values_per_frame; j++)
                  {
                      *len += strlen(second_dim_values[j]) + 1;
                  }
              }
          }
      }
      else
      {
          *len += size * frame_step * n_particles * data->n_values_per_frame;
      }
  
      return (TNG_SUCCESS);
  }
  
  /* TEST: */
  /**
   * @brief Create a non-particle data block
   * @param tng_data is a trajectory data container.
   * @param block_type_flag specifies if this is a trajectory block or a
   * non-trajectory block. (TNG_TRAJECTORY_BLOCK or TNG_NON_TRAJECTORY_BLOCK)
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_data_block_create(struct tng_trajectory* tng_data, const char block_type_flag)
  {
      tng_trajectory_frame_set_t frame_set = &tng_data->current_trajectory_frame_set;
  
      tng_data_t data;
  
      if (block_type_flag == TNG_TRAJECTORY_BLOCK)
      {
          frame_set->n_data_blocks++;
          data = (tng_data_t)realloc(frame_set->tr_data, sizeof(struct tng_data) * frame_set->n_data_blocks);
          if (!data)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              free(frame_set->tr_data);
              frame_set->tr_data = 0;
              return (TNG_CRITICAL);
          }
          frame_set->tr_data = data;
      }
      else
      {
          tng_data->n_data_blocks++;
          data = (tng_data_t)realloc(tng_data->non_tr_data, sizeof(struct tng_data) * tng_data->n_data_blocks);
          if (!data)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              free(tng_data->non_tr_data);
              tng_data->non_tr_data = 0;
              return (TNG_CRITICAL);
          }
          tng_data->non_tr_data = data;
      }
  
      return (TNG_SUCCESS);
  }
  
  /* TEST: */
  /**
   * @brief Allocate memory for storing non-particle data.
   * The allocated block will be refered to by data->values.
   * @param tng_data is a trajectory data container.
   * @param data is the data struct, which will contain the allocated memory in
   * data->values.
   * @param n_frames is the number of frames of data to store.
   * @param n_values_per_frame is the number of data values per frame.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_allocate_data_mem(const struct tng_trajectory* tng_data,
                                                   struct tng_data*             data,
                                                   int64_t                      n_frames,
                                                   const int64_t                stride_length,
                                                   const int64_t                n_values_per_frame)
  {
      void**  values;
      int64_t i, j, size, frame_alloc;
      (void)tng_data;
  
      if (n_values_per_frame == 0)
      {
          return (TNG_FAILURE);
      }
  
      if (data->strings && data->datatype == TNG_CHAR_DATA)
      {
          for (i = 0; i < data->n_frames; i++)
          {
              for (j = 0; j < data->n_values_per_frame; j++)
              {
                  if (data->strings[0][i][j])
                  {
                      free(data->strings[0][i][j]);
                      data->strings[0][i][j] = 0;
                  }
              }
              free(data->strings[0][i]);
              data->strings[0][i] = 0;
          }
          free(data->strings[0]);
          data->strings[0] = 0;
          free(data->strings);
      }
      data->n_frames           = n_frames;
      data->stride_length      = tng_max_i64(1, stride_length);
      n_frames                 = tng_max_i64(1, n_frames);
      data->n_values_per_frame = n_values_per_frame;
      frame_alloc              = (n_frames - 1) / stride_length + 1;
  
      if (data->datatype == TNG_CHAR_DATA)
      {
          data->strings    = (char****)malloc(sizeof(char***));
          data->strings[0] = (char***)malloc(sizeof(char**) * frame_alloc);
          for (i = 0; i < frame_alloc; i++)
          {
              data->strings[0][i] = (char**)malloc(sizeof(char*) * n_values_per_frame);
              if (!data->strings[0][i])
              {
                  fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                  return (TNG_CRITICAL);
              }
              for (j = 0; j < n_values_per_frame; j++)
              {
                  data->strings[0][i][j] = 0;
              }
          }
      }
      else
      {
          switch (data->datatype)
          {
              case TNG_INT_DATA: size = sizeof(int64_t); break;
              case TNG_FLOAT_DATA: size = sizeof(float); break;
              case TNG_DOUBLE_DATA:
              default: size = sizeof(double);
          }
  
          values = (void**)realloc(data->values, size * frame_alloc * n_values_per_frame);
          if (!values)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              free(data->values);
              data->values = 0;
              return (TNG_CRITICAL);
          }
          data->values = values;
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Read the values of a data block
   * @param tng_data is a trajectory data container.
   * @param block is the block to store the data (should already contain
   * the block headers and the block contents).
   * @param block_data_len is the length of the data contents of the block.
   * @param datatype is the type of data of the data block (char, int, float or
   * double).
   * @param num_first_particle is the number of the first particle in the data
   * block. This should be the same as in the corresponding particle mapping
   * block. Only used if reading particle dependent data.
   * @param n_particles is the number of particles in the data block. This should
   * be the same as in the corresponding particle mapping block. Only used if
   * reading particle dependent data.
   * @param first_frame_with_data is the frame number of the first frame with data
   * in this data block.
   * @param stride_length is the number of frames between each data entry.
   * @param n_frames is the number of frames in this data block.
   * @param n_values is the number of values per frame stored in this data block.
   * @param codec_id is the ID of the codec to compress the data.
   * @param multiplier is the multiplication factor applied to each data value
   * before compression. This factor is applied since some compression algorithms
   * work only on integers.
   * @param hash_mode is an option to decide whether to generate/update the relevant md5 hashes.
   * @param md5_state is a pointer to the current md5 storage, which will be updated appropriately
   * if hash_mode == TNG_USE_HASH.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_data_read(struct tng_trajectory*      tng_data,
                                           const struct tng_gen_block* block,
                                           const int64_t               block_data_len,
                                           const char                  datatype,
                                           const int64_t               num_first_particle,
                                           const int64_t               n_particles,
                                           const int64_t               first_frame_with_data,
                                           const int64_t               stride_length,
                                           int64_t                     n_frames,
                                           const int64_t               n_values,
                                           const int64_t               codec_id,
                                           const double                multiplier,
                                           const char                  hash_mode,
                                           md5_state_t*                md5_state)
  {
      int64_t                    i, j, k, tot_n_particles, n_frames_div, offset;
      int64_t                    full_data_len;
      int                        size, len;
      char ***                   first_dim_values, **second_dim_values;
      tng_data_t                 data;
      tng_trajectory_frame_set_t frame_set = &tng_data->current_trajectory_frame_set;
      char                       block_type_flag, *contents;
      tng_bool                   is_particle_data;
      tng_function_status        stat;
  
      /*     fprintf(stderr, "TNG library: %s\n", block->name);*/
  
      switch (datatype)
      {
          case TNG_CHAR_DATA: size = 1; break;
          case TNG_INT_DATA: size = sizeof(int64_t); break;
          case TNG_FLOAT_DATA: size = sizeof(float); break;
          case TNG_DOUBLE_DATA:
          default: size = sizeof(double);
      }
  
      if (n_particles > 0)
      {
          is_particle_data = TNG_TRUE;
      }
      else
      {
          if (codec_id == TNG_XTC_COMPRESSION || codec_id == TNG_TNG_COMPRESSION)
          {
              fprintf(stderr, "TNG library: Cannot uncompress data block. %s: %d\n", __FILE__, __LINE__);
              return (TNG_FAILURE);
          }
          is_particle_data = TNG_FALSE;
      }
  
      if (is_particle_data == TNG_TRUE)
      {
          stat = tng_particle_data_find(tng_data, block->id, &data);
      }
      else
      {
          stat = tng_data_find(tng_data, block->id, &data);
      }
  
      if (tng_data->current_trajectory_frame_set_input_file_pos > 0)
      {
          block_type_flag = TNG_TRAJECTORY_BLOCK;
      }
      else
      {
          block_type_flag = TNG_NON_TRAJECTORY_BLOCK;
      }
  
      /* If the block does not exist, create it */
      if (stat != TNG_SUCCESS)
      {
          if (is_particle_data == TNG_TRUE)
          {
              stat = tng_particle_data_block_create(tng_data, block_type_flag);
          }
          else
          {
              stat = tng_data_block_create(tng_data, block_type_flag);
          }
  
          if (stat != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot create data block. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
  
          if (is_particle_data == TNG_TRUE)
          {
              if (block_type_flag == TNG_TRAJECTORY_BLOCK)
              {
                  data = &frame_set->tr_particle_data[frame_set->n_particle_data_blocks - 1];
              }
              else
              {
                  data = &tng_data->non_tr_particle_data[tng_data->n_particle_data_blocks - 1];
              }
          }
          else
          {
              if (block_type_flag == TNG_TRAJECTORY_BLOCK)
              {
                  data = &frame_set->tr_data[frame_set->n_data_blocks - 1];
              }
              else
              {
                  data = &tng_data->non_tr_data[tng_data->n_data_blocks - 1];
              }
          }
  
          data->block_id = block->id;
  
          data->block_name = (char*)malloc(strlen(block->name) + 1);
          if (!data->block_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          strcpy(data->block_name, block->name);
  
          data->datatype = datatype;
  
          data->values = 0;
          /* FIXME: Memory leak from strings. */
          data->strings    = 0;
          data->n_frames   = 0;
          data->dependency = 0;
          if (is_particle_data == TNG_TRUE)
          {
              data->dependency += TNG_PARTICLE_DEPENDENT;
          }
          if (block_type_flag == TNG_TRAJECTORY_BLOCK
              && (n_frames > 1 || frame_set->n_frames == n_frames || stride_length > 1))
          {
              data->dependency += TNG_FRAME_DEPENDENT;
          }
          data->codec_id               = codec_id;
          data->compression_multiplier = multiplier;
          data->last_retrieved_frame   = -1;
      }
  
      if (is_particle_data == TNG_TRUE)
      {
          if (block_type_flag == TNG_TRAJECTORY_BLOCK && tng_data->var_num_atoms_flag)
          {
              tot_n_particles = frame_set->n_particles;
          }
          else
          {
              tot_n_particles = tng_data->n_particles;
          }
      }
      /* If there are no particles in this data block, still set tot_n_particles = 1
       * to calculate block lengths etc properly. */
      else
      {
          tot_n_particles = 1;
      }
  
      n_frames_div = (n_frames - 1) / stride_length + 1;
  
      contents = (char*)malloc(block_data_len);
      if (!contents)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      if (fread(contents, block_data_len, 1, tng_data->input_file) == 0)
      {
          fprintf(stderr, "TNG library: Cannot read block. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      if (hash_mode == TNG_USE_HASH)
      {
          md5_append(md5_state, (md5_byte_t*)contents, block_data_len);
      }
  
      if (codec_id != TNG_UNCOMPRESSED)
      {
          full_data_len = n_frames_div * size * n_values;
          if (is_particle_data == TNG_TRUE)
          {
              full_data_len *= n_particles;
          }
          switch (codec_id)
          {
              case TNG_XTC_COMPRESSION:
                  fprintf(stderr, "TNG library: XTC compression not implemented yet.\n");
                  break;
              case TNG_TNG_COMPRESSION:
                  /*            fprintf(stderr, "TNG library: Before TNG uncompression: %" PRId64 "\n", block->block_contents_size);*/
                  if (tng_uncompress(tng_data, block, datatype, &contents, full_data_len) != TNG_SUCCESS)
                  {
                      fprintf(stderr,
                              "TNG library: Could not read tng compressed block data. %s: %d\n",
                              __FILE__, __LINE__);
                      free(contents);
                      return (TNG_CRITICAL);
                  }
                  /*            fprintf(stderr, "TNG library: After TNG uncompression: %" PRId64 "\n", block->block_contents_size);*/
                  break;
              case TNG_GZIP_COMPRESSION:
                  /*         fprintf(stderr, "TNG library: Before compression: %" PRId64 "\n", block->block_contents_size); */
                  if (tng_gzip_uncompress(tng_data, &contents, block_data_len, full_data_len) != TNG_SUCCESS)
                  {
                      fprintf(stderr, "TNG library: Could not read gzipped block data. %s: %d\n",
                              __FILE__, __LINE__);
                      free(contents);
                      return (TNG_CRITICAL);
                  }
                  /*         fprintf(stderr, "TNG library: After compression: %" PRId64 "\n", block->block_contents_size); */
                  break;
          }
      }
      else
      {
          full_data_len = block_data_len;
      }
  
      /* Allocate memory */
      if (!data->values || data->n_frames != n_frames || data->n_values_per_frame != n_values)
      {
          if (is_particle_data == TNG_TRUE)
          {
              stat = tng_allocate_particle_data_mem(tng_data, data, n_frames, stride_length,
                                                    tot_n_particles, n_values);
          }
          else
          {
              stat = tng_allocate_data_mem(tng_data, data, n_frames, stride_length, n_values);
          }
          if (stat != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory for data. %s: %d\n", __FILE__, __LINE__);
              free(contents);
              return (TNG_CRITICAL);
          }
      }
  
      data->first_frame_with_data = first_frame_with_data;
  
      if (datatype == TNG_CHAR_DATA)
      {
          offset = 0;
          /* Strings are stores slightly differently if the data block contains particle
           * data (frames * particles * n_values) or not (frames * n_values). */
          if (is_particle_data == TNG_TRUE)
          {
              for (i = 0; i < n_frames_div; i++)
              {
                  first_dim_values = data->strings[i];
                  for (j = num_first_particle; j < num_first_particle + n_particles; j++)
                  {
                      second_dim_values = first_dim_values[j];
                      for (k = 0; k < n_values; k++)
                      {
                          len = tng_min_size(strlen(contents + offset) + 1, TNG_MAX_STR_LEN);
                          if (second_dim_values[k])
                          {
                              free(second_dim_values[k]);
                          }
                          second_dim_values[k] = (char*)malloc(len);
                          if (!second_dim_values[k])
                          {
                              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n",
                                      __FILE__, __LINE__);
                              free(contents);
                              return (TNG_CRITICAL);
                          }
                          strncpy(second_dim_values[k], contents + offset, len);
                          offset += len;
                      }
                  }
              }
          }
          else
          {
              for (i = 0; i < n_frames_div; i++)
              {
                  for (j = 0; j < n_values; j++)
                  {
                      len = tng_min_size(strlen(contents + offset) + 1, TNG_MAX_STR_LEN);
                      if (data->strings[0][i][j])
                      {
                          free(data->strings[0][i][j]);
                      }
                      data->strings[0][i][j] = (char*)malloc(len);
                      if (!data->strings[0][i][j])
                      {
                          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                          free(contents);
                          return (TNG_CRITICAL);
                      }
                      strncpy(data->strings[0][i][j], contents + offset, len);
                      offset += len;
                  }
              }
          }
      }
      else
      {
          if (is_particle_data)
          {
              memcpy((char*)data->values + n_frames_div * size * n_values * num_first_particle,
                     contents, full_data_len);
          }
          else
          {
              memcpy(data->values, contents, full_data_len);
          }
          /* Endianness is handled by the TNG compression library. TNG compressed blocks are always
           * written as little endian by the compression library. */
          if (codec_id != TNG_TNG_COMPRESSION)
          {
              switch (datatype)
              {
                  case TNG_FLOAT_DATA:
                      if (tng_data->input_endianness_swap_func_32)
                      {
                          for (i = 0; i < full_data_len; i += size)
                          {
                              if (tng_data->input_endianness_swap_func_32(
                                          tng_data, (uint32_t*)((char*)data->values + i))
                                  != TNG_SUCCESS)
                              {
                                  fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n",
                                          __FILE__, __LINE__);
                              }
                          }
                      }
                      break;
                  case TNG_INT_DATA:
                  case TNG_DOUBLE_DATA:
                      if (tng_data->input_endianness_swap_func_64)
                      {
                          for (i = 0; i < full_data_len; i += size)
                          {
                              if (tng_data->input_endianness_swap_func_64(
                                          tng_data, (uint64_t*)((char*)data->values + i))
                                  != TNG_SUCCESS)
                              {
                                  fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n",
                                          __FILE__, __LINE__);
                              }
                          }
                      }
                      break;
                  case TNG_CHAR_DATA: break;
              }
          }
      }
  
      free(contents);
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Write a data block (particle or non-particle data)
   * @param tng_data is a trajectory data container.
   * @param block is the block to store the data (should already contain
   * the block headers and the block contents).
   * @param block_index is the index number of the data block in the frame set.
   * @param is_particle_data is a flag to specify if the data to write is
   * particle dependent or not.
   * @param mapping is the particle mapping that is relevant for the data block.
   * Only relevant if writing particle dependent data.
   * @param hash_mode is an option to decide whether to use the md5 hash or not.
   * If hash_mode == TNG_USE_HASH an md5 hash will be generated and written.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_data_block_write(struct tng_trajectory*             tng_data,
                                                  struct tng_gen_block*              block,
                                                  const int64_t                      block_index,
                                                  const tng_bool                     is_particle_data,
                                                  const struct tng_particle_mapping* mapping,
                                                  const char                         hash_mode)
  {
      int64_t             n_particles, num_first_particle, n_frames, stride_length;
      int64_t             full_data_len, block_data_len, frame_step, data_start_pos;
      int64_t             i, j, k, curr_file_pos, header_file_pos;
      int                 size;
      size_t              len;
      tng_function_status stat;
      char                temp, *temp_name, ***first_dim_values, **second_dim_values, *contents;
      double              multiplier;
      tng_trajectory_frame_set_t frame_set = &tng_data->current_trajectory_frame_set;
      tng_data_t                 data;
      char                       block_type_flag;
      md5_state_t                md5_state;
  
      /* If we have already started writing frame sets it is too late to write
       * non-trajectory data blocks */
      if (tng_data->current_trajectory_frame_set_output_file_pos > 0)
      {
          block_type_flag = TNG_TRAJECTORY_BLOCK;
      }
      else
      {
          block_type_flag = TNG_NON_TRAJECTORY_BLOCK;
      }
  
      if (tng_output_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      if (is_particle_data == TNG_TRUE)
      {
          if (block_type_flag == TNG_TRAJECTORY_BLOCK)
          {
              data = &frame_set->tr_particle_data[block_index];
  
              /* If this data block has not had any data added in this frame set
               * do not write it. */
              if (data->first_frame_with_data < frame_set->first_frame)
              {
                  return (TNG_SUCCESS);
              }
  
              stride_length = tng_max_i64(1, data->stride_length);
          }
          else
          {
              data          = &tng_data->non_tr_particle_data[block_index];
              stride_length = 1;
          }
      }
      else
      {
          if (block_type_flag == TNG_TRAJECTORY_BLOCK)
          {
              data = &frame_set->tr_data[block_index];
  
              /* If this data block has not had any data added in this frame set
               * do not write it. */
              if (data->first_frame_with_data < frame_set->first_frame)
              {
                  return (TNG_SUCCESS);
              }
  
              stride_length = tng_max_i64(1, data->stride_length);
          }
          else
          {
              data          = &tng_data->non_tr_data[block_index];
              stride_length = 1;
          }
      }
  
      switch (data->datatype)
      {
          case TNG_CHAR_DATA: size = 1; break;
          case TNG_INT_DATA: size = sizeof(int64_t); break;
          case TNG_FLOAT_DATA: size = sizeof(float); break;
          case TNG_DOUBLE_DATA:
          default: size = sizeof(double);
      }
  
      len = strlen(data->block_name) + 1;
  
      if (!block->name || strlen(block->name) < len)
      {
          temp_name = (char*)realloc(block->name, len);
          if (!temp_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              free(block->name);
              block->name = 0;
              return (TNG_CRITICAL);
          }
          block->name = temp_name;
      }
      strncpy(block->name, data->block_name, len);
      block->id = data->block_id;
  
      /* If writing frame independent data data->n_frames is 0, but n_frames
         is used for the loop writing the data (and reserving memory) and needs
         to be at least 1 */
      n_frames = tng_max_i64(1, data->n_frames);
  
      if (block_type_flag == TNG_TRAJECTORY_BLOCK)
      {
          /* If the frame set is finished before writing the full number of frames
             make sure the data block is not longer than the frame set. */
          n_frames = tng_min_i64(n_frames, frame_set->n_frames);
  
          n_frames -= (data->first_frame_with_data - frame_set->first_frame);
      }
  
      frame_step = (n_frames - 1) / stride_length + 1;
  
      /* TNG compression will use compression precision to get integers from
       * floating point data. The compression multiplier stores that information
       * to be able to return the precision of the compressed data. */
      if (data->codec_id == TNG_TNG_COMPRESSION)
      {
          data->compression_multiplier = tng_data->compression_precision;
      }
      /* Uncompressed data blocks do not use compression multipliers at all.
       * GZip compression does not need it either. */
      else if (data->codec_id == TNG_UNCOMPRESSED || data->codec_id == TNG_GZIP_COMPRESSION)
      {
          data->compression_multiplier = 1.0;
      }
  
      if (data->dependency & TNG_PARTICLE_DEPENDENT)
      {
          if (mapping && mapping->n_particles != 0)
          {
              n_particles        = mapping->n_particles;
              num_first_particle = mapping->num_first_particle;
          }
          else
          {
              num_first_particle = 0;
              if (tng_data->var_num_atoms_flag)
              {
                  n_particles = frame_set->n_particles;
              }
              else
              {
                  n_particles = tng_data->n_particles;
              }
          }
      }
      else
      {
          /* This just appeases gcc-7 -Wmaybe-uninitialized.
           * FIXME: It would be better to refactor so that
           * TNG_PARTICLE_DEPENDENT triggers two distinct code paths.
           */
          num_first_particle = -1;
          n_particles        = -1;
      }
  
      if (data->dependency & TNG_PARTICLE_DEPENDENT)
      {
          if (tng_data_block_len_calculate(tng_data, data, TNG_TRUE, n_frames, frame_step,
                                           stride_length, num_first_particle, n_particles,
                                           &data_start_pos, &block->block_contents_size)
              != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot calculate length of particle data block. %s: %d\n",
                      __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
      else
      {
          if (tng_data_block_len_calculate(tng_data, data, TNG_FALSE, n_frames, frame_step, stride_length,
                                           0, 1, &data_start_pos, &block->block_contents_size)
              != TNG_SUCCESS)
          {
              fprintf(stderr,
                      "TNG library: Cannot calculate length of non-particle data block. %s: %d\n",
                      __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      header_file_pos = ftello(tng_data->output_file);
  
      if (tng_block_header_write(tng_data, block) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot write header of file %s. %s: %d\n",
                  tng_data->output_file_path, __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      if (hash_mode == TNG_USE_HASH)
      {
          md5_init(&md5_state);
      }
  
      if (tng_file_output_numerical(tng_data, &data->datatype, sizeof(data->datatype), hash_mode,
                                    &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &data->dependency, sizeof(data->dependency), hash_mode,
                                    &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (data->dependency & TNG_FRAME_DEPENDENT)
      {
          if (stride_length > 1)
          {
              temp = 1;
          }
          else
          {
              temp = 0;
          }
          if (tng_file_output_numerical(tng_data, &temp, sizeof(temp), hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
      }
  
      if (tng_file_output_numerical(tng_data, &data->n_values_per_frame,
                                    sizeof(data->n_values_per_frame), hash_mode, &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_output_numerical(tng_data, &data->codec_id, sizeof(data->codec_id), hash_mode,
                                    &md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (data->codec_id != TNG_UNCOMPRESSED)
      {
          if (tng_file_output_numerical(tng_data, &data->compression_multiplier,
                                        sizeof(data->compression_multiplier), hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
      }
  
      if (data->n_frames > 0 && stride_length > 1)
      {
          /* FIXME: first_frame_with_data is not reliably set */
          if (data->first_frame_with_data == 0)
          {
              data->first_frame_with_data = frame_set->first_frame;
          }
          if (tng_file_output_numerical(tng_data, &data->first_frame_with_data,
                                        sizeof(data->first_frame_with_data), hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          if (tng_file_output_numerical(tng_data, &stride_length, sizeof(stride_length), hash_mode,
                                        &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
      }
  
      if (data->dependency & TNG_PARTICLE_DEPENDENT)
      {
          if (tng_file_output_numerical(tng_data, &num_first_particle, sizeof(num_first_particle),
                                        hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          if (tng_file_output_numerical(tng_data, &n_particles, sizeof(n_particles), hash_mode, &md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
      }
  
      if (data->datatype == TNG_CHAR_DATA)
      {
          if (data->strings)
          {
              if (data->dependency & TNG_PARTICLE_DEPENDENT)
              {
                  for (i = 0; i < frame_step; i++)
                  {
                      first_dim_values = data->strings[i];
                      for (j = num_first_particle; j < num_first_particle + n_particles; j++)
                      {
                          second_dim_values = first_dim_values[j];
                          for (k = 0; k < data->n_values_per_frame; k++)
                          {
                              if (tng_fwritestr(tng_data, second_dim_values[k], hash_mode, &md5_state, __LINE__)
                                  == TNG_CRITICAL)
                              {
                                  return (TNG_CRITICAL);
                              }
                          }
                      }
                  }
              }
              else
              {
                  for (i = 0; i < frame_step; i++)
                  {
                      for (j = 0; j < data->n_values_per_frame; j++)
                      {
                          if (tng_fwritestr(tng_data, data->strings[0][i][j], hash_mode, &md5_state, __LINE__)
                              == TNG_CRITICAL)
                          {
                              return (TNG_CRITICAL);
                          }
                      }
                  }
              }
          }
      }
      else
      {
          if (data->dependency & TNG_PARTICLE_DEPENDENT)
          {
              full_data_len = size * frame_step * n_particles * data->n_values_per_frame;
          }
          else
          {
              full_data_len = size * frame_step * data->n_values_per_frame;
          }
          contents = (char*)malloc(full_data_len);
          if (!contents)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
  
          if (data->values)
          {
              memcpy(contents, data->values, full_data_len);
              /* If writing TNG compressed data the endianness is taken into account by the
               * compression routines. TNG compressed data is always written as little endian. */
              if (data->codec_id != TNG_TNG_COMPRESSION)
              {
                  switch (data->datatype)
                  {
                      case TNG_FLOAT_DATA:
                          if (data->codec_id == TNG_UNCOMPRESSED || data->codec_id == TNG_GZIP_COMPRESSION)
                          {
                              if (tng_data->output_endianness_swap_func_32)
                              {
                                  for (i = 0; i < full_data_len; i += size)
                                  {
                                      if (tng_data->output_endianness_swap_func_32(
                                                  tng_data, (uint32_t*)(contents + i))
                                          != TNG_SUCCESS)
                                      {
                                          fprintf(stderr,
                                                  "TNG library: Cannot swap byte order. %s: %d\n",
                                                  __FILE__, __LINE__);
                                      }
                                  }
                              }
                          }
                          else
                          {
                              multiplier = data->compression_multiplier;
                              if (fabs(multiplier - 1.0) > 0.00001 || tng_data->output_endianness_swap_func_32)
                              {
                                  for (i = 0; i < full_data_len; i += size)
                                  {
                                      *(float*)(contents + i) *= (float)multiplier;
                                      if (tng_data->output_endianness_swap_func_32
                                          && tng_data->output_endianness_swap_func_32(
                                                     tng_data, (uint32_t*)(contents + i))
                                                     != TNG_SUCCESS)
                                      {
                                          fprintf(stderr,
                                                  "TNG library: Cannot swap byte order. %s: %d\n",
                                                  __FILE__, __LINE__);
                                      }
                                  }
                              }
                          }
                          break;
                      case TNG_INT_DATA:
                          if (tng_data->output_endianness_swap_func_64)
                          {
                              for (i = 0; i < full_data_len; i += size)
                              {
                                  if (tng_data->output_endianness_swap_func_64(
                                              tng_data, (uint64_t*)(contents + i))
                                      != TNG_SUCCESS)
                                  {
                                      fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n",
                                              __FILE__, __LINE__);
                                  }
                              }
                          }
                          break;
                      case TNG_DOUBLE_DATA:
                          if (data->codec_id == TNG_UNCOMPRESSED || data->codec_id == TNG_GZIP_COMPRESSION)
                          {
                              if (tng_data->output_endianness_swap_func_64)
                              {
                                  for (i = 0; i < full_data_len; i += size)
                                  {
                                      if (tng_data->output_endianness_swap_func_64(
                                                  tng_data, (uint64_t*)(contents + i))
                                          != TNG_SUCCESS)
                                      {
                                          fprintf(stderr,
                                                  "TNG library: Cannot swap byte order. %s: %d\n",
                                                  __FILE__, __LINE__);
                                      }
                                  }
                              }
                          }
                          else
                          {
                              multiplier = data->compression_multiplier;
                              if (fabs(multiplier - 1.0) > 0.00001 || tng_data->output_endianness_swap_func_64)
                              {
                                  for (i = 0; i < full_data_len; i += size)
                                  {
                                      *(double*)(contents + i) *= multiplier;
                                      if (tng_data->output_endianness_swap_func_64
                                          && tng_data->output_endianness_swap_func_64(
                                                     tng_data, (uint64_t*)(contents + i))
                                                     != TNG_SUCCESS)
                                      {
                                          fprintf(stderr,
                                                  "TNG library: Cannot swap byte order. %s: %d\n",
                                                  __FILE__, __LINE__);
                                      }
                                  }
                              }
                          }
                          break;
                      case TNG_CHAR_DATA: break;
                  }
              }
          }
          else
          {
              memset(contents, 0, full_data_len);
          }
  
          block_data_len = full_data_len;
  
          switch (data->codec_id)
          {
              case TNG_XTC_COMPRESSION:
                  fprintf(stderr, "TNG library: XTC compression not implemented yet.\n");
                  data->codec_id = TNG_UNCOMPRESSED;
                  break;
              case TNG_TNG_COMPRESSION:
                  stat = tng_compress(tng_data, block, frame_step, n_particles, data->datatype,
                                      &contents, &block_data_len);
                  if (stat != TNG_SUCCESS)
                  {
                      fprintf(stderr,
                              "TNG library: Could not write TNG compressed block data. %s: %d\n",
                              __FILE__, __LINE__);
                      if (stat == TNG_CRITICAL)
                      {
                          return (TNG_CRITICAL);
                      }
                      /* Set the data again, but with no compression (to write only
                       * the relevant data) */
                      data->codec_id = TNG_UNCOMPRESSED;
                      stat = tng_data_block_write(tng_data, block, block_index, is_particle_data,
                                                  mapping, hash_mode);
                      free(contents);
                      return (stat);
                  }
                  break;
              case TNG_GZIP_COMPRESSION:
                  /*         fprintf(stderr, "TNG library: Before compression: %" PRId64 "\n", block->block_contents_size); */
                  stat = tng_gzip_compress(tng_data, &contents, full_data_len, &block_data_len);
                  if (stat != TNG_SUCCESS)
                  {
                      fprintf(stderr, "TNG library: Could not write gzipped block data. %s: %d\n",
                              __FILE__, __LINE__);
                      if (stat == TNG_CRITICAL)
                      {
                          return (TNG_CRITICAL);
                      }
                      data->codec_id = TNG_UNCOMPRESSED;
                  }
                  /*         fprintf(stderr, "TNG library: After compression: %" PRId64 "\n", block->block_contents_size); */
                  break;
          }
          if (block_data_len != full_data_len)
          {
              block->block_contents_size -= full_data_len - block_data_len;
  
              curr_file_pos = ftello(tng_data->output_file);
              fseeko(tng_data->output_file, header_file_pos + sizeof(block->header_contents_size), SEEK_SET);
  
              if (tng_file_output_numerical(tng_data, &block->block_contents_size,
                                            sizeof(block->block_contents_size), TNG_SKIP_HASH, 0, __LINE__)
                  == TNG_CRITICAL)
              {
                  return (TNG_CRITICAL);
              }
              fseeko(tng_data->output_file, curr_file_pos, SEEK_SET);
          }
          if (fwrite(contents, block_data_len, 1, tng_data->output_file) != 1)
          {
              fprintf(stderr, "TNG library: Could not write all block data. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          if (hash_mode == TNG_USE_HASH)
          {
              md5_append(&md5_state, (md5_byte_t*)contents, block_data_len);
          }
  
          free(contents);
      }
  
      if (hash_mode == TNG_USE_HASH)
      {
          md5_finish(&md5_state, (md5_byte_t*)block->md5_hash);
          curr_file_pos = ftello(tng_data->output_file);
          fseeko(tng_data->output_file, header_file_pos + 3 * sizeof(int64_t), SEEK_SET);
          if (fwrite(block->md5_hash, TNG_MD5_HASH_LEN, 1, tng_data->output_file) != 1)
          {
              fprintf(stderr, "TNG library: Could not write MD5 hash. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          fseeko(tng_data->output_file, curr_file_pos, SEEK_SET);
      }
  
      frame_set->n_written_frames += frame_set->n_unwritten_frames;
      frame_set->n_unwritten_frames = 0;
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Read the meta information of a data block (particle or non-particle data).
   * @param tng_data is a trajectory data container.
   * @param datatype is set to the datatype of the data block.
   * @param dependency is set to the dependency (particle and/or frame dependent)
   * @param sparse_data is set to TRUE if data is not written every frame.
   * @param n_values is set to the number of values per frame of the data.
   * @param codec_id is set to the ID of the codec used to compress the data.
   * @param first_frame_with_data is set to the first frame with data (only relevant if
   * sparse_data == TRUE).
   * @param stride_length is set to the writing interval of the data (1 if sparse_data
   * == FALSE).
   * @param num_first_particle is set to the number of the first particle with data written
   * in this block.
   * @param block_n_particles is set to the number of particles in this data block.
   * @param multiplier is set to the compression multiplier.
   * @param hash_mode specifies whether to check if the hash matches the contents or not.
   * @param md5_state is the md5 hash of the block (only used if hash_mode == TNG_USE_HASH).
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_data_block_meta_information_read(const struct tng_trajectory* tng_data,
                                                                  char*        datatype,
                                                                  char*        dependency,
                                                                  char*        sparse_data,
                                                                  int64_t*     n_values,
                                                                  int64_t*     codec_id,
                                                                  int64_t*     first_frame_with_data,
                                                                  int64_t*     stride_length,
                                                                  int64_t*     n_frames,
                                                                  int64_t*     num_first_particle,
                                                                  int64_t*     block_n_particles,
                                                                  double*      multiplier,
                                                                  const char   hash_mode,
                                                                  md5_state_t* md5_state)
  {
      if (tng_file_input_numerical(tng_data, datatype, sizeof(*datatype), hash_mode, md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, dependency, sizeof(*dependency), hash_mode, md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (*dependency & TNG_FRAME_DEPENDENT)
      {
          if (tng_file_input_numerical(tng_data, sparse_data, sizeof(*sparse_data), hash_mode, md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
      }
  
      if (tng_file_input_numerical(tng_data, n_values, sizeof(*n_values), hash_mode, md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, codec_id, sizeof(*codec_id), hash_mode, md5_state, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (*codec_id != TNG_UNCOMPRESSED)
      {
          if (tng_file_input_numerical(tng_data, multiplier, sizeof(*multiplier), hash_mode, md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
      }
      else
      {
          *multiplier = 1;
      }
  
      if (*dependency & TNG_FRAME_DEPENDENT)
      {
          if (*sparse_data)
          {
              if (tng_file_input_numerical(tng_data, first_frame_with_data,
                                           sizeof(*first_frame_with_data), hash_mode, md5_state, __LINE__)
                  == TNG_CRITICAL)
              {
                  return (TNG_CRITICAL);
              }
  
              if (tng_file_input_numerical(tng_data, stride_length, sizeof(*stride_length), hash_mode,
                                           md5_state, __LINE__)
                  == TNG_CRITICAL)
              {
                  return (TNG_CRITICAL);
              }
  
              *n_frames = tng_data->current_trajectory_frame_set.n_frames
                          - (*first_frame_with_data - tng_data->current_trajectory_frame_set.first_frame);
          }
          else
          {
              *first_frame_with_data = tng_data->current_trajectory_frame_set.first_frame;
              *stride_length         = 1;
              *n_frames              = tng_data->current_trajectory_frame_set.n_frames;
          }
      }
      else
      {
          *first_frame_with_data = 0;
          *stride_length         = 1;
          *n_frames              = 1;
      }
  
      if (*dependency & TNG_PARTICLE_DEPENDENT)
      {
          if (tng_file_input_numerical(tng_data, num_first_particle, sizeof(*num_first_particle),
                                       hash_mode, md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
  
          if (tng_file_input_numerical(tng_data, block_n_particles, sizeof(*block_n_particles),
                                       hash_mode, md5_state, __LINE__)
              == TNG_CRITICAL)
          {
              return (TNG_CRITICAL);
          }
      }
      else
      {
          *num_first_particle = -1;
          *block_n_particles  = 0;
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Read the contents of a data block (particle or non-particle data).
   * @param tng_data is a trajectory data container.
   * @param block is the block to store the data (should already contain
   * the block headers).
   * @param hash_mode is an option to decide whether to use the md5 hash or not.
   * If hash_mode == TNG_USE_HASH the written md5 hash in the file will be
   * compared to the md5 hash of the read contents to ensure valid data.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_data_block_contents_read(struct tng_trajectory*      tng_data,
                                                          const struct tng_gen_block* block,
                                                          const char                  hash_mode)
  {
      int64_t             start_pos, n_values, codec_id, n_frames, first_frame_with_data;
      int64_t             remaining_len, stride_length, block_n_particles, num_first_particle;
      double              multiplier;
      char                datatype, dependency, sparse_data;
      tng_function_status stat = TNG_SUCCESS;
      char                hash[TNG_MD5_HASH_LEN];
      md5_state_t         md5_state;
  
      if (tng_input_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      start_pos = ftello(tng_data->input_file);
  
      if (hash_mode == TNG_USE_HASH)
      {
          md5_init(&md5_state);
      }
  
      /* FIXME: Does not check if the size of the contents matches the expected
       * size or if the contents can be read. */
  
      if (tng_data_block_meta_information_read(tng_data, &datatype, &dependency, &sparse_data,
                                               &n_values, &codec_id, &first_frame_with_data,
                                               &stride_length, &n_frames, &num_first_particle,
                                               &block_n_particles, &multiplier, hash_mode, &md5_state)
          == TNG_CRITICAL)
      {
          fprintf(stderr, "TNG library: Cannot read data block (%s) meta information. %s: %d\n",
                  block->name, __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      remaining_len = block->block_contents_size - (ftello(tng_data->input_file) - start_pos);
  
      stat = tng_data_read(tng_data, block, remaining_len, datatype, num_first_particle,
                           block_n_particles, first_frame_with_data, stride_length, n_frames,
                           n_values, codec_id, multiplier, hash_mode, &md5_state);
  
      if (hash_mode == TNG_USE_HASH)
      {
          /* If there is data left in the block that the current version of the library
           * cannot interpret still read that to generate the MD5 hash. */
          tng_md5_remaining_append(tng_data, block, start_pos, &md5_state);
  
          md5_finish(&md5_state, (md5_byte_t*)hash);
          if (strncmp(block->md5_hash, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", TNG_MD5_HASH_LEN) != 0)
          {
              if (strncmp(block->md5_hash, hash, TNG_MD5_HASH_LEN) != 0)
              {
                  fprintf(stderr,
                          "TNG library: Data block contents corrupt (%s). Hashes do not match. "
                          "%s: %d\n",
                          block->name, __FILE__, __LINE__);
              }
          }
      }
      else
      {
          /* Seek to the end of the block */
          fseeko(tng_data->input_file, start_pos + block->block_contents_size, SEEK_SET);
      }
  
      return (stat);
  }
  
  /*
  // ** Move the blocks in a frame set so that there is no unused space between
  //  * them. This can only be done on the last frame set in the file and should
  //  * be done e.g. if the last frame set in the file has fewer frames than
  //  * default or after compressing data blocks in a frame set.
  //  * @param tng_data is a trajectory data container.
  //  * @details the current_trajectory_frame_set is the one that will be modified.
  //  * @return TNG_SUCCESS (0) if successful, TNG_FAILURE (1) if the frame set
  //  * cannot be aligned or TNG_CRITICAL (2) if a major error has occured.
  //  * FIXME: This function is not finished!!!
  //  *
  // static tng_function_status tng_frame_set_align(tng_trajectory_t tng_data)
  // {
  //     tng_gen_block_t block;
  //     tng_trajectory_frame_set_t frame_set;
  //     FILE *temp = tng_data->input_file;
  //     int64_t pos, contents_start_pos, output_file_len;
  //
  //     frame_set = &tng_data->current_trajectory_frame_set;
  //
  //     if(frame_set->n_written_frames == frame_set->n_frames)
  //     {
  //         return(TNG_SUCCESS);
  //     }
  //
  //     if(tng_data->current_trajectory_frame_set_output_file_pos !=
  //        tng_data->last_trajectory_frame_set_output_file_pos)
  //     {
  //     }
  //
  //     if(tng_output_file_init(tng_data) != TNG_SUCCESS)
  //     {
  //         fprintf(stderr, "TNG library: Cannot initialise destination file. %s: %d\n",
  //                __FILE__, __LINE__);
  //         return(TNG_CRITICAL);
  //     }
  //
  //     tng_block_init(&block);
  // //     output_file_pos = ftello(tng_data->output_file);
  //
  //     tng_data->input_file = tng_data->output_file;
  //
  //     pos = tng_data->current_trajectory_frame_set_output_file_pos;
  //
  //     fseeko(tng_data->output_file, pos, SEEK_SET);
  //     if(tng_block_header_read(tng_data, block) != TNG_SUCCESS)
  //     {
  //         fprintf(stderr, "TNG library: Cannot read frame set header. %s: %d\n",
  //             __FILE__, __LINE__);
  //         tng_data->input_file = temp;
  //         tng_block_destroy(&block);
  //         return(TNG_CRITICAL);
  //     }
  //
  //     contents_start_pos = ftello(tng_data->output_file);
  //
  //     fseeko(tng_data->output_file, 0, SEEK_END);
  //     output_file_len = ftello(tng_data->output_file);
  //     pos = contents_start_pos + block->block_contents_size;
  //     fseeko(tng_data->output_file, pos,
  //           SEEK_SET);
  //
  //     while(pos < output_file_len)
  //     {
  //         if(tng_block_header_read(tng_data, block) != TNG_SUCCESS)
  //         {
  //             fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n", pos,
  //                    __FILE__, __LINE__);
  //             tng_data->input_file = temp;
  //             tng_block_destroy(&block);
  //             return(TNG_CRITICAL);
  //         }
  //         pos += block->header_contents_size + block->block_contents_size;
  //         fseeko(tng_data->output_file, pos, SEEK_SET);
  //     }
  //
  //     return(TNG_SUCCESS);
  // }
  */
  /**
   * @brief Finish writing the current frame set. Update the number of frames
   * and the hashes of the frame set and all its data blocks (if hash_mode
   * == TNG_USE_HASH).
   * @param tng_data is a trajectory data container.
   * @param hash_mode specifies whether to update the block md5 hash when
   * updating the pointers.
   * @return TNG_SUCCESS (0) if successful or TNG_CRITICAL (2) if a major
   * error has occured.
   */
  static tng_function_status tng_frame_set_finalize(struct tng_trajectory* tng_data, const char hash_mode)
  {
      tng_gen_block_t            block;
      tng_trajectory_frame_set_t frame_set;
      FILE*                      temp = tng_data->input_file;
      int64_t                    pos, curr_file_pos;
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      if (frame_set->n_written_frames == frame_set->n_frames)
      {
          return (TNG_SUCCESS);
      }
  
      frame_set->n_written_frames = frame_set->n_frames;
  
      if (tng_output_file_init(tng_data) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot initialise destination file. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      tng_block_init(&block);
      /*     output_file_pos = ftello(tng_data->output_file); */
  
      tng_data->input_file = tng_data->output_file;
  
      curr_file_pos = ftello(tng_data->output_file);
  
      pos = tng_data->current_trajectory_frame_set_output_file_pos;
  
      fseeko(tng_data->output_file, pos, SEEK_SET);
  
      if (tng_block_header_read(tng_data, block) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot read frame set header. %s: %d\n", __FILE__, __LINE__);
          tng_data->input_file = temp;
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      //     contents_start_pos = ftello(tng_data->output_file);
  
      fseeko(tng_data->output_file, sizeof(frame_set->first_frame), SEEK_CUR);
      if (fwrite(&frame_set->n_frames, sizeof(frame_set->n_frames), 1, tng_data->output_file) != 1)
      {
          tng_data->input_file = temp;
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      if (hash_mode == TNG_USE_HASH)
      {
          tng_md5_hash_update(tng_data, block, pos, pos + block->header_contents_size);
      }
  
      fseeko(tng_data->output_file, curr_file_pos, SEEK_SET);
  
      tng_data->input_file = temp;
      tng_block_destroy(&block);
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_atom_residue_get(struct tng_trajectory* tng_data,
                                                             struct tng_atom*       atom,
                                                             tng_residue_t*         residue)
  {
      (void)tng_data;
  
      TNG_ASSERT(atom, "TNG library: atom must not be NULL");
  
      *residue = atom->residue;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_atom_name_get(struct tng_trajectory* tng_data,
                                                          struct tng_atom*       atom,
                                                          char*                  name,
                                                          const int              max_len)
  {
      (void)tng_data;
      TNG_ASSERT(atom, "TNG library: atom must not be NULL");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer");
  
      strncpy(name, atom->name, max_len - 1);
      name[max_len - 1] = 0;
  
      if (strlen(atom->name) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_atom_name_set(struct tng_trajectory* tng_data,
                                                          struct tng_atom*       atom,
                                                          const char*            new_name)
  {
      unsigned int len;
      (void)tng_data;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(new_name, "TNG library: new_name must not be a NULL pointer.");
  
      len = tng_min_size(strlen(new_name) + 1, TNG_MAX_STR_LEN);
  
      /* If the currently stored string length is not enough to store the new
       * string it is freed and reallocated. */
      if (atom->name && strlen(atom->name) < len)
      {
          free(atom->name);
          atom->name = 0;
      }
      if (!atom->name)
      {
          atom->name = (char*)malloc(len);
          if (!atom->name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      strncpy(atom->name, new_name, len);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_atom_type_get(struct tng_trajectory* tng_data,
                                                          struct tng_atom*       atom,
                                                          char*                  type,
                                                          const int              max_len)
  {
      (void)tng_data;
      TNG_ASSERT(atom, "TNG library: atom must not be NULL");
      TNG_ASSERT(type, "TNG library: type must not be a NULL pointer");
  
      strncpy(type, atom->atom_type, max_len - 1);
      type[max_len - 1] = 0;
  
      if (strlen(atom->atom_type) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_atom_type_set(struct tng_trajectory* tng_data,
                                                          struct tng_atom*       atom,
                                                          const char*            new_type)
  {
      unsigned int len;
      (void)tng_data;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(new_type, "TNG library: new_type must not be a NULL pointer.");
  
      len = tng_min_size(strlen(new_type) + 1, TNG_MAX_STR_LEN);
  
      /* If the currently stored string length is not enough to store the new
       * string it is freed and reallocated. */
      if (atom->atom_type && strlen(atom->atom_type) < len)
      {
          free(atom->atom_type);
          atom->atom_type = 0;
      }
      if (!atom->atom_type)
      {
          atom->atom_type = (char*)malloc(len);
          if (!atom->atom_type)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      strncpy(atom->atom_type, new_type, len);
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Initialise an atom struct
   * @param atom is the atom to initialise.
   * @return TNG_SUCCESS (0) if successful.
   */
  static tng_function_status tng_atom_init(struct tng_atom* atom)
  {
      atom->name      = 0;
      atom->atom_type = 0;
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Free the memory in an atom struct
   * @param atom is the atom to destroy.
   * @return TNG_SUCCESS (0) if successful.
   */
  static tng_function_status tng_atom_destroy(struct tng_atom* atom)
  {
      if (atom->name)
      {
          free(atom->name);
          atom->name = 0;
      }
      if (atom->atom_type)
      {
          free(atom->atom_type);
          atom->atom_type = 0;
      }
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Update chain->residue pointers (after new memory for
   * molecule->residues has been allocated).
   * @param tng_data The trajectory container containing the molecule.
   * @param mol The molecule that contains the chains that need to be
   * updated.
   * @returns TNG_SUCCESS (0) if successful.
   */
  static tng_function_status tng_molecule_chains_residue_pointers_update(const struct tng_trajectory* tng_data,
                                                                         const struct tng_molecule* mol)
  {
      tng_chain_t chain;
      int64_t     i, res_cnt = 0;
      (void)tng_data;
  
      for (i = 0; i < mol->n_chains; i++)
      {
          chain           = &mol->chains[i];
          chain->residues = mol->residues + res_cnt;
          res_cnt += chain->n_residues;
      }
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Update atoms->residue pointers (after new memory for
   * molecule->residues has been allocated).
   * @param tng_data The trajectory container containing the molecule.
   * @param mol The molecule that contains the chains that need to be
   * updated.
   * @returns TNG_SUCCESS (0) if successful.
   */
  static tng_function_status tng_molecule_atoms_residue_pointers_update(const struct tng_trajectory* tng_data,
                                                                        const struct tng_molecule* mol)
  {
      tng_atom_t    atom;
      tng_residue_t residue;
      int64_t       i, j, atom_offset = 0;
      (void)tng_data;
  
      for (i = 0; i < mol->n_residues; i++)
      {
          residue = &mol->residues[i];
          for (j = 0; j < residue->n_atoms; j++)
          {
              atom          = &mol->atoms[j + atom_offset];
              atom->residue = residue;
          }
          atom_offset += residue->n_atoms;
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_version_major(struct tng_trajectory* tng_data, int* version)
  {
      (void)tng_data;
  
      *version = TNG_VERSION_MAJOR;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_version_minor(struct tng_trajectory* tng_data, int* version)
  {
      (void)tng_data;
  
      *version = TNG_VERSION_MINOR;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_version_patchlevel(struct tng_trajectory* tng_data, int* patch_level)
  {
      (void)tng_data;
  
      *patch_level = TNG_VERSION_PATCHLEVEL;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_version(struct tng_trajectory* tng_data, char* version, const int max_len)
  {
      (void)tng_data;
      TNG_ASSERT(version, "TNG library: version must not be a NULL pointer");
  
      TNG_SNPRINTF(version, max_len, "%s", TNG_VERSION);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_add(struct tng_trajectory* tng_data,
                                                         const char*            name,
                                                         tng_molecule_t*        molecule)
  {
      int64_t id;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer.");
  
      /* Set ID to the ID of the last molecule + 1 */
      if (tng_data->n_molecules)
      {
          id = tng_data->molecules[tng_data->n_molecules - 1].id + 1;
      }
      else
      {
          id = 1;
      }
  
      return (tng_molecule_w_id_add(tng_data, name, id, molecule));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_w_id_add(struct tng_trajectory* tng_data,
                                                              const char*            name,
                                                              const int64_t          id,
                                                              tng_molecule_t*        molecule)
  {
      tng_molecule_t      new_molecules;
      int64_t*            new_molecule_cnt_list;
      tng_function_status stat = TNG_SUCCESS;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer.");
  
      new_molecules = (tng_molecule_t)realloc(
              tng_data->molecules, sizeof(struct tng_molecule) * (tng_data->n_molecules + 1));
  
      if (!new_molecules)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(tng_data->molecules);
          tng_data->molecules = 0;
          return (TNG_CRITICAL);
      }
  
      new_molecule_cnt_list = (int64_t*)realloc(tng_data->molecule_cnt_list,
                                                sizeof(int64_t) * (tng_data->n_molecules + 1));
  
      if (!new_molecule_cnt_list)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(tng_data->molecule_cnt_list);
          tng_data->molecule_cnt_list = 0;
          free(new_molecules);
          return (TNG_CRITICAL);
      }
  
      tng_data->molecules         = new_molecules;
      tng_data->molecule_cnt_list = new_molecule_cnt_list;
  
      *molecule = &new_molecules[tng_data->n_molecules];
  
      tng_molecule_init(tng_data, *molecule);
      tng_molecule_name_set(tng_data, *molecule, name);
  
      /* FIXME: Should this be a function argument instead? */
      tng_data->molecule_cnt_list[tng_data->n_molecules] = 0;
  
      (*molecule)->id = id;
  
      tng_data->n_molecules++;
  
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_existing_add(struct tng_trajectory* tng_data,
                                                                  tng_molecule_t*        molecule_p)
  {
      int64_t *      new_molecule_cnt_list, id;
      tng_molecule_t new_molecules, molecule;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      /* Set ID to the ID of the last molecule + 1 */
      if (tng_data->n_molecules)
      {
          id = tng_data->molecules[tng_data->n_molecules - 1].id + 1;
      }
      else
      {
          id = 1;
      }
  
      new_molecules = (tng_molecule_t)realloc(
              tng_data->molecules, sizeof(struct tng_molecule) * (tng_data->n_molecules + 1));
  
      if (!new_molecules)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(tng_data->molecules);
          tng_data->molecules = 0;
          return (TNG_CRITICAL);
      }
  
      new_molecule_cnt_list = (int64_t*)realloc(tng_data->molecule_cnt_list,
                                                sizeof(int64_t) * (tng_data->n_molecules + 1));
  
      if (!new_molecule_cnt_list)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(tng_data->molecule_cnt_list);
          tng_data->molecule_cnt_list = 0;
          free(new_molecules);
          return (TNG_CRITICAL);
      }
  
      molecule = *molecule_p;
  
      tng_data->molecules         = new_molecules;
      tng_data->molecule_cnt_list = new_molecule_cnt_list;
  
      new_molecules[tng_data->n_molecules] = *molecule;
  
      tng_data->molecule_cnt_list[tng_data->n_molecules] = 0;
  
      free(*molecule_p);
  
      molecule = &new_molecules[tng_data->n_molecules];
  
      *molecule_p = molecule;
  
      molecule->id = id;
  
      tng_data->n_molecules++;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status tng_molecule_name_get(struct tng_trajectory* tng_data,
                                            struct tng_molecule*   molecule,
                                            char*                  name,
                                            const int              max_len)
  {
      (void)tng_data;
      TNG_ASSERT(molecule, "TNG library: molecule must not be NULL");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer");
  
      strncpy(name, molecule->name, max_len - 1);
      name[max_len - 1] = 0;
  
      if (strlen(molecule->name) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_name_set(struct tng_trajectory* tng_data,
                                                              struct tng_molecule*   molecule,
                                                              const char*            new_name)
  {
      unsigned int len;
      (void)tng_data;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(new_name, "TNG library: new_name must not be a NULL pointer.");
  
      len = tng_min_size(strlen(new_name) + 1, TNG_MAX_STR_LEN);
  
      /* If the currently stored string length is not enough to store the new
       * string it is freed and reallocated. */
      if (molecule->name && strlen(molecule->name) < len)
      {
          free(molecule->name);
          molecule->name = 0;
      }
      if (!molecule->name)
      {
          molecule->name = (char*)malloc(len);
          if (!molecule->name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      strncpy(molecule->name, new_name, len);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_cnt_get(struct tng_trajectory* tng_data,
                                                             struct tng_molecule*   molecule,
                                                             int64_t*               cnt)
  {
      int64_t i, index = -1;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(cnt, "TNG library: cnt must not be a NULL pointer.");
  
      for (i = 0; i < tng_data->n_molecules; i++)
      {
          if (&tng_data->molecules[i] == molecule)
          {
              index = i;
              break;
          }
      }
      if (index == -1)
      {
          return (TNG_FAILURE);
      }
      *cnt = tng_data->molecule_cnt_list[index];
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_cnt_set(struct tng_trajectory* tng_data,
                                                             struct tng_molecule*   molecule,
                                                             const int64_t          cnt)
  {
      int64_t i, old_cnt, index = -1;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      for (i = 0; i < tng_data->n_molecules; i++)
      {
          if (&tng_data->molecules[i] == molecule)
          {
              index = i;
              break;
          }
      }
      if (index == -1)
      {
          fprintf(stderr, "TNG library: Could not find molecule in TNG trajectory. %s: %d\n",
                  __FILE__, __LINE__);
          return (TNG_FAILURE);
      }
      if (tng_data->var_num_atoms_flag == TNG_CONSTANT_N_ATOMS)
      {
          old_cnt                            = tng_data->molecule_cnt_list[index];
          tng_data->molecule_cnt_list[index] = cnt;
  
          tng_data->n_particles += (cnt - old_cnt) * tng_data->molecules[index].n_atoms;
      }
      else
      {
          old_cnt = tng_data->current_trajectory_frame_set.molecule_cnt_list[index];
          tng_data->current_trajectory_frame_set.molecule_cnt_list[index] = cnt;
  
          tng_data->current_trajectory_frame_set.n_particles +=
                  (cnt - old_cnt) * tng_data->molecules[index].n_atoms;
      }
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_find(struct tng_trajectory* tng_data,
                                                          const char*            name,
                                                          const int64_t          nr,
                                                          tng_molecule_t*        molecule)
  {
      int64_t i, n_molecules;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer.");
      TNG_ASSERT(molecule, "TNG library: molecule must not be a NULL pointer.");
  
      n_molecules = tng_data->n_molecules;
  
      for (i = n_molecules - 1; i >= 0; i--)
      {
          *molecule = &tng_data->molecules[i];
          if (name[0] == 0 || strcmp(name, (*molecule)->name) == 0)
          {
              if (nr == -1 || nr == (*molecule)->id)
              {
                  return (TNG_SUCCESS);
              }
          }
      }
  
      *molecule = 0;
  
      return (TNG_FAILURE);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_of_index_get(struct tng_trajectory* tng_data,
                                                                  const int64_t          index,
                                                                  tng_molecule_t*        molecule)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(molecule, "TNG library: molecule must not be a NULL pointer.");
  
      if (index >= tng_data->n_molecules)
      {
          *molecule = 0;
          return (TNG_FAILURE);
      }
      *molecule = &tng_data->molecules[index];
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_system_copy(struct tng_trajectory* tng_data_src,
                                                                 struct tng_trajectory* tng_data_dest)
  {
      tng_molecule_t      molecule, molecule_temp;
      tng_chain_t         chain, chain_temp;
      tng_residue_t       residue, residue_temp;
      tng_atom_t          atom, atom_temp;
      tng_bond_t          bond_temp;
      tng_function_status stat;
      int64_t             i, j, k, l, *list_temp;
  
      TNG_ASSERT(tng_data_src, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(tng_data_dest, "TNG library: Trajectory container not properly setup.");
  
      for (i = 0; i < tng_data_dest->n_molecules; i++)
      {
          molecule = &tng_data_dest->molecules[i];
          tng_molecule_destroy(tng_data_dest, molecule);
      }
  
      tng_data_dest->n_molecules = 0;
      tng_data_dest->n_particles = 0;
  
      molecule_temp = (tng_molecule_t)realloc(
              tng_data_dest->molecules, sizeof(struct tng_molecule) * tng_data_src->n_molecules);
      if (!molecule_temp)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(tng_data_dest->molecules);
          tng_data_dest->molecules = 0;
          return (TNG_CRITICAL);
      }
      list_temp = (int64_t*)realloc(tng_data_dest->molecule_cnt_list,
                                    sizeof(int64_t) * tng_data_src->n_molecules);
      if (!list_temp)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(tng_data_dest->molecule_cnt_list);
          tng_data_dest->molecule_cnt_list = 0;
          free(molecule_temp);
          return (TNG_CRITICAL);
      }
  
      tng_data_dest->molecules         = molecule_temp;
      tng_data_dest->molecule_cnt_list = list_temp;
  
      for (i = 0; i < tng_data_src->n_molecules; i++)
      {
          molecule = &tng_data_src->molecules[i];
          stat = tng_molecule_w_id_add(tng_data_dest, molecule->name, molecule->id, &molecule_temp);
          if (stat != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot create new molecule to make a copy. %s: %d\n",
                      __FILE__, __LINE__);
              return (stat);
          }
          molecule_temp->quaternary_str = molecule->quaternary_str;
          for (j = 0; j < molecule->n_chains; j++)
          {
              chain = &molecule->chains[j];
              stat = tng_molecule_chain_w_id_add(tng_data_dest, molecule_temp, chain->name, chain->id,
                                                 &chain_temp);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot create new chain to make a copy. %s: %d\n",
                          __FILE__, __LINE__);
                  return (stat);
              }
              for (k = 0; k < chain->n_residues; k++)
              {
                  residue = &chain->residues[k];
                  stat    = tng_chain_residue_w_id_add(tng_data_dest, chain_temp, residue->name,
                                                    residue->id, &residue_temp);
                  if (stat != TNG_SUCCESS)
                  {
                      fprintf(stderr,
                              "TNG library: Cannot create new residue to make a copy. %s: %d\n",
                              __FILE__, __LINE__);
                      return (stat);
                  }
                  for (l = 0; l < residue->n_atoms; l++)
                  {
                      atom = &molecule->atoms[residue->atoms_offset + l];
                      stat = tng_residue_atom_w_id_add(tng_data_dest, residue_temp, atom->name,
                                                       atom->atom_type, atom->id, &atom_temp);
                      if (stat != TNG_SUCCESS)
                      {
                          fprintf(stderr,
                                  "TNG library: Cannot create new atom to make a copy. %s: %d\n",
                                  __FILE__, __LINE__);
                          return (stat);
                      }
                  }
              }
          }
          molecule_temp->n_bonds = molecule->n_bonds;
          if (molecule->n_bonds > 0)
          {
              bond_temp = (tng_bond_t)realloc(molecule_temp->bonds,
                                              sizeof(struct tng_bond) * molecule->n_bonds);
              if (!bond_temp)
              {
                  fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                  free(molecule_temp->bonds);
                  molecule_temp->n_bonds = 0;
                  return (TNG_CRITICAL);
              }
              molecule_temp->bonds = bond_temp;
              for (j = 0; j < molecule->n_bonds; j++)
              {
                  molecule_temp->bonds[j] = molecule->bonds[j];
              }
          }
          stat = tng_molecule_cnt_set(tng_data_dest, molecule_temp, tng_data_src->molecule_cnt_list[i]);
          if (stat != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot set molecule count. %s: %d.\n", __FILE__, __LINE__);
              return (stat);
          }
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_num_chains_get(struct tng_trajectory* tng_data,
                                                                    struct tng_molecule*   molecule,
                                                                    int64_t*               n)
  {
      (void)tng_data;
      TNG_ASSERT(molecule, "TNG library: molecule must not be NULL");
      TNG_ASSERT(n, "TNG library: n must not be a NULL pointer");
  
      *n = molecule->n_chains;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_chain_of_index_get(struct tng_trajectory* tng_data,
                                                                        struct tng_molecule* molecule,
                                                                        const int64_t        index,
                                                                        tng_chain_t*         chain)
  {
      (void)tng_data;
      TNG_ASSERT(molecule, "TNG library: molecule must not be a NULL pointer.");
      TNG_ASSERT(chain, "TNG library: chain must not be a NULL pointer.");
  
      if (index >= molecule->n_chains)
      {
          *chain = 0;
          return (TNG_FAILURE);
      }
      *chain = &molecule->chains[index];
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_num_residues_get(struct tng_trajectory* tng_data,
                                                                      struct tng_molecule*   molecule,
                                                                      int64_t*               n)
  {
      (void)tng_data;
      TNG_ASSERT(molecule, "TNG library: molecule must not be NULL");
      TNG_ASSERT(n, "TNG library: n must not be a NULL pointer");
  
      *n = molecule->n_residues;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_residue_of_index_get(struct tng_trajectory* tng_data,
                                                                          struct tng_molecule* molecule,
                                                                          const int64_t        index,
                                                                          tng_residue_t* residue)
  {
      (void)tng_data;
      TNG_ASSERT(molecule, "TNG library: molecule must not be a NULL pointer.");
      TNG_ASSERT(residue, "TNG library: residue must not be a NULL pointer.");
  
      if (index >= molecule->n_residues)
      {
          *residue = 0;
          return (TNG_FAILURE);
      }
      *residue = &molecule->residues[index];
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_num_atoms_get(struct tng_trajectory* tng_data,
                                                                   struct tng_molecule*   molecule,
                                                                   int64_t*               n)
  {
      (void)tng_data;
      TNG_ASSERT(molecule, "TNG library: molecule must not be NULL");
      TNG_ASSERT(n, "TNG library: n must not be a NULL pointer");
  
      *n = molecule->n_atoms;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_atom_of_index_get(struct tng_trajectory* tng_data,
                                                                       struct tng_molecule* molecule,
                                                                       const int64_t        index,
                                                                       tng_atom_t*          atom)
  {
      (void)tng_data;
      TNG_ASSERT(molecule, "TNG library: molecule must not be a NULL pointer.");
      TNG_ASSERT(atom, "TNG library: atom must not be a NULL pointer.");
  
      if (index >= molecule->n_atoms)
      {
          *atom = 0;
          return (TNG_FAILURE);
      }
      *atom = &molecule->atoms[index];
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_chain_find(struct tng_trajectory* tng_data,
                                                                struct tng_molecule*   molecule,
                                                                const char*            name,
                                                                const int64_t          nr,
                                                                tng_chain_t*           chain)
  {
      int64_t i, n_chains;
      (void)tng_data;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer.");
  
      n_chains = molecule->n_chains;
  
      for (i = n_chains - 1; i >= 0; i--)
      {
          *chain = &molecule->chains[i];
          if (name[0] == 0 || strcmp(name, (*chain)->name) == 0)
          {
              if (nr == -1 || nr == (*chain)->id)
              {
                  return (TNG_SUCCESS);
              }
          }
      }
  
      *chain = 0;
  
      return (TNG_FAILURE);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_chain_add(struct tng_trajectory* tng_data,
                                                               struct tng_molecule*   molecule,
                                                               const char*            name,
                                                               tng_chain_t*           chain)
  {
      int64_t id;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer.");
  
      /* Set ID to the ID of the last chain + 1 */
      if (molecule->n_chains)
      {
          id = molecule->chains[molecule->n_chains - 1].id + 1;
      }
      else
      {
          id = 1;
      }
  
      return (tng_molecule_chain_w_id_add(tng_data, molecule, name, id, chain));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_chain_w_id_add(struct tng_trajectory* tng_data,
                                                                    struct tng_molecule*   molecule,
                                                                    const char*            name,
                                                                    const int64_t          id,
                                                                    tng_chain_t*           chain)
  {
      tng_chain_t         new_chains;
      tng_function_status stat = TNG_SUCCESS;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer.");
  
      new_chains = (tng_chain_t)realloc(molecule->chains,
                                        sizeof(struct tng_chain) * (molecule->n_chains + 1));
  
      if (!new_chains)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(molecule->chains);
          molecule->chains = 0;
          return (TNG_CRITICAL);
      }
  
      molecule->chains = new_chains;
  
      *chain         = &new_chains[molecule->n_chains];
      (*chain)->name = 0;
  
      tng_chain_name_set(tng_data, *chain, name);
  
      (*chain)->molecule   = molecule;
      (*chain)->n_residues = 0;
  
      molecule->n_chains++;
  
      (*chain)->id = id;
  
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_bond_add(struct tng_trajectory* tng_data,
                                                              struct tng_molecule*   molecule,
                                                              const int64_t          from_atom_id,
                                                              const int64_t          to_atom_id,
                                                              tng_bond_t*            bond)
  {
      tng_bond_t new_bonds;
      (void)tng_data;
  
      new_bonds = (tng_bond_t)realloc(molecule->bonds, sizeof(struct tng_bond) * (molecule->n_bonds + 1));
  
      if (!new_bonds)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          *bond = 0;
          free(molecule->bonds);
          molecule->bonds = 0;
          return (TNG_CRITICAL);
      }
  
      molecule->bonds = new_bonds;
  
      *bond = &new_bonds[molecule->n_bonds];
  
      (*bond)->from_atom_id = from_atom_id;
      (*bond)->to_atom_id   = to_atom_id;
  
      molecule->n_bonds++;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_atom_find(struct tng_trajectory* tng_data,
                                                               struct tng_molecule*   molecule,
                                                               const char*            name,
                                                               const int64_t          id,
                                                               tng_atom_t*            atom)
  {
      int64_t i, n_atoms;
      (void)tng_data;
  
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer.");
  
      n_atoms = molecule->n_atoms;
  
      for (i = n_atoms - 1; i >= 0; i--)
      {
          *atom = &molecule->atoms[i];
          if (name[0] == 0 || strcmp(name, (*atom)->name) == 0)
          {
              if (id == -1 || id == (*atom)->id)
              {
                  return (TNG_SUCCESS);
              }
          }
      }
  
      *atom = 0;
  
      return (TNG_FAILURE);
  }
  
  tng_function_status tng_chain_name_get(struct tng_trajectory* tng_data,
                                         struct tng_chain*      chain,
                                         char*                  name,
                                         const int              max_len)
  {
      (void)tng_data;
      TNG_ASSERT(chain, "TNG library: chain must not be NULL");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer");
  
      strncpy(name, chain->name, max_len - 1);
      name[max_len - 1] = 0;
  
      if (strlen(chain->name) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_chain_name_set(struct tng_trajectory* tng_data,
                                                           struct tng_chain*      chain,
                                                           const char*            new_name)
  {
      unsigned int len;
      (void)tng_data;
  
      TNG_ASSERT(new_name, "TNG library: new_name must not be a NULL pointer.");
  
      len = tng_min_size(strlen(new_name) + 1, TNG_MAX_STR_LEN);
  
      /* If the currently stored string length is not enough to store the new
       * string it is freed and reallocated. */
      if (chain->name && strlen(chain->name) < len)
      {
          free(chain->name);
          chain->name = 0;
      }
      if (!chain->name)
      {
          chain->name = (char*)malloc(len);
          if (!chain->name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      strncpy(chain->name, new_name, len);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_chain_num_residues_get(struct tng_trajectory* tng_data,
                                                                   struct tng_chain*      chain,
                                                                   int64_t*               n)
  {
      (void)tng_data;
      TNG_ASSERT(chain, "TNG library: chain must not be NULL");
      TNG_ASSERT(n, "TNG library: n must not be a NULL pointer");
  
      *n = chain->n_residues;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_chain_residue_of_index_get(struct tng_trajectory* tng_data,
                                                                       struct tng_chain*      chain,
                                                                       const int64_t          index,
                                                                       tng_residue_t*         residue)
  {
      (void)tng_data;
      TNG_ASSERT(chain, "TNG library: chain must not be a NULL pointer.");
      TNG_ASSERT(residue, "TNG library: residue must not be a NULL pointer.");
  
      if (index >= chain->n_residues)
      {
          *residue = 0;
          return (TNG_FAILURE);
      }
      *residue = &chain->residues[index];
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_chain_residue_find(struct tng_trajectory* tng_data,
                                                               struct tng_chain*      chain,
                                                               const char*            name,
                                                               const int64_t          id,
                                                               tng_residue_t*         residue)
  {
      int64_t i, n_residues;
      (void)tng_data;
  
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer.");
  
      n_residues = chain->n_residues;
  
      for (i = n_residues - 1; i >= 0; i--)
      {
          *residue = &chain->residues[i];
          if (name[0] == 0 || strcmp(name, (*residue)->name) == 0)
          {
              if (id == -1 || id == (*residue)->id)
              {
                  return (TNG_SUCCESS);
              }
          }
      }
  
      *residue = 0;
  
      return (TNG_FAILURE);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_chain_residue_add(struct tng_trajectory* tng_data,
                                                              struct tng_chain*      chain,
                                                              const char*            name,
                                                              tng_residue_t*         residue)
  {
      int64_t id;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer.");
  
      /* Set ID to the ID of the last residue + 1 */
      if (chain->n_residues)
      {
          id = chain->residues[chain->n_residues - 1].id + 1;
      }
      else
      {
          id = 0;
      }
  
      return (tng_chain_residue_w_id_add(tng_data, chain, name, id, residue));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_chain_residue_w_id_add(struct tng_trajectory* tng_data,
                                                                   struct tng_chain*      chain,
                                                                   const char*            name,
                                                                   const int64_t          id,
                                                                   tng_residue_t*         residue)
  {
      int64_t             curr_index;
      tng_residue_t       new_residues, temp_residue, last_residue;
      tng_molecule_t      molecule = chain->molecule;
      tng_function_status stat     = TNG_SUCCESS;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer.");
  
      if (chain->n_residues)
      {
          curr_index = chain->residues - molecule->residues;
      }
      else
      {
          curr_index = -1;
      }
  
      new_residues = (tng_residue_t)realloc(molecule->residues,
                                            sizeof(struct tng_residue) * (molecule->n_residues + 1));
  
      if (!new_residues)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(molecule->residues);
          molecule->residues = 0;
          return (TNG_CRITICAL);
      }
  
      molecule->residues = new_residues;
  
      if (curr_index != -1)
      {
          chain->residues = new_residues + curr_index;
          if (molecule->n_residues)
          {
              last_residue = &new_residues[molecule->n_residues - 1];
  
              temp_residue = chain->residues + (chain->n_residues - 1);
              /* Make space in list of residues to add the new residues together with the other
               * residues of this chain */
              if (temp_residue != last_residue)
              {
                  ++temp_residue;
                  memmove(temp_residue + 1, temp_residue, last_residue - temp_residue);
              }
          }
      }
      else
      {
          curr_index = molecule->n_residues;
      }
  
      *residue = &molecule->residues[curr_index + chain->n_residues];
  
      tng_molecule_chains_residue_pointers_update(tng_data, molecule);
      tng_molecule_atoms_residue_pointers_update(tng_data, molecule);
  
      (*residue)->name = 0;
      tng_residue_name_set(tng_data, *residue, name);
  
      (*residue)->chain        = chain;
      (*residue)->n_atoms      = 0;
      (*residue)->atoms_offset = 0;
  
      chain->n_residues++;
      molecule->n_residues++;
  
      (*residue)->id = id;
  
      return (stat);
  }
  
  tng_function_status tng_residue_name_get(struct tng_trajectory* tng_data,
                                           struct tng_residue*    residue,
                                           char*                  name,
                                           const int              max_len)
  {
      (void)tng_data;
      TNG_ASSERT(residue, "TNG library: residue must not be NULL");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer");
  
      strncpy(name, residue->name, max_len - 1);
      name[max_len - 1] = 0;
  
      if (strlen(residue->name) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_residue_name_set(struct tng_trajectory* tng_data,
                                                             struct tng_residue*    residue,
                                                             const char*            new_name)
  {
      unsigned int len;
      (void)tng_data;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(new_name, "TNG library: new_name must not be a NULL pointer");
  
      len = tng_min_size(strlen(new_name) + 1, TNG_MAX_STR_LEN);
  
      /* If the currently stored string length is not enough to store the new
       * string it is freed and reallocated. */
      if (residue->name && strlen(residue->name) < len)
      {
          free(residue->name);
          residue->name = 0;
      }
      if (!residue->name)
      {
          residue->name = (char*)malloc(len);
          if (!residue->name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      strncpy(residue->name, new_name, len);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_residue_num_atoms_get(struct tng_trajectory* tng_data,
                                                                  struct tng_residue*    residue,
                                                                  int64_t*               n)
  {
      (void)tng_data;
      TNG_ASSERT(residue, "TNG library: residue must not be NULL");
      TNG_ASSERT(n, "TNG library: n must not be a NULL pointer");
  
      *n = residue->n_atoms;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_residue_atom_of_index_get(struct tng_trajectory* tng_data,
                                                                      struct tng_residue*    residue,
                                                                      const int64_t          index,
                                                                      tng_atom_t*            atom)
  {
      tng_chain_t    chain;
      tng_molecule_t molecule;
  
      (void)tng_data;
      TNG_ASSERT(residue, "TNG library: residue must not be a NULL pointer.");
      TNG_ASSERT(atom, "TNG library: atom must not be a NULL pointer.");
  
      if (index >= residue->n_atoms)
      {
          *atom = 0;
          return (TNG_FAILURE);
      }
      chain    = residue->chain;
      molecule = chain->molecule;
  
      if (index + residue->atoms_offset >= molecule->n_atoms)
      {
          *atom = 0;
          return (TNG_FAILURE);
      }
  
      *atom = &molecule->atoms[residue->atoms_offset + index];
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_residue_atom_add(struct tng_trajectory* tng_data,
                                                             struct tng_residue*    residue,
                                                             const char*            atom_name,
                                                             const char*            atom_type,
                                                             tng_atom_t*            atom)
  {
      int64_t id;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(atom_name, "TNG library: atom_name must not be a NULL pointer.");
      TNG_ASSERT(atom_type, "TNG library: atom_type must not be a NULL pointer.");
  
      /* Set ID to the ID of the last atom + 1 */
      if (residue->chain->molecule->n_atoms)
      {
          id = residue->chain->molecule->atoms[residue->chain->molecule->n_atoms - 1].id + 1;
      }
      else
      {
          id = 0;
      }
  
      return (tng_residue_atom_w_id_add(tng_data, residue, atom_name, atom_type, id, atom));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_residue_atom_w_id_add(struct tng_trajectory* tng_data,
                                                                  struct tng_residue*    residue,
                                                                  const char*            atom_name,
                                                                  const char*            atom_type,
                                                                  const int64_t          id,
                                                                  tng_atom_t*            atom)
  {
      tng_atom_t          new_atoms;
      tng_molecule_t      molecule = residue->chain->molecule;
      tng_function_status stat     = TNG_SUCCESS;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(atom_name, "TNG library: atom_name must not be a NULL pointer.");
      TNG_ASSERT(atom_type, "TNG library: atom_type must not be a NULL pointer.");
  
      if (!residue->n_atoms)
      {
          residue->atoms_offset = molecule->n_atoms;
      }
  
      new_atoms = (tng_atom_t)realloc(molecule->atoms, sizeof(struct tng_atom) * (molecule->n_atoms + 1));
  
      if (!new_atoms)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(molecule->atoms);
          molecule->atoms = 0;
          return (TNG_CRITICAL);
      }
  
      molecule->atoms = new_atoms;
  
      *atom = &new_atoms[molecule->n_atoms];
  
      tng_atom_init(*atom);
      tng_atom_name_set(tng_data, *atom, atom_name);
      tng_atom_type_set(tng_data, *atom, atom_type);
  
      (*atom)->residue = residue;
  
      residue->n_atoms++;
      molecule->n_atoms++;
  
      (*atom)->id = id;
  
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_alloc(struct tng_trajectory* tng_data,
                                                           tng_molecule_t*        molecule_p)
  {
      *molecule_p = (tng_molecule_t)malloc(sizeof(struct tng_molecule));
      if (!*molecule_p)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      tng_molecule_init(tng_data, *molecule_p);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_free(struct tng_trajectory* tng_data,
                                                          tng_molecule_t*        molecule_p)
  {
      if (!*molecule_p)
      {
          return (TNG_SUCCESS);
      }
  
      tng_molecule_destroy(tng_data, *molecule_p);
  
      free(*molecule_p);
      *molecule_p = 0;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_init(struct tng_trajectory* tng_data,
                                                          struct tng_molecule*   molecule)
  {
      (void)tng_data;
      molecule->quaternary_str = 1;
      molecule->name           = 0;
      molecule->n_chains       = 0;
      molecule->chains         = 0;
      molecule->n_residues     = 0;
      molecule->residues       = 0;
      molecule->n_atoms        = 0;
      molecule->atoms          = 0;
      molecule->n_bonds        = 0;
      molecule->bonds          = 0;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_destroy(struct tng_trajectory* tng_data,
                                                             struct tng_molecule*   molecule)
  {
      int64_t i;
      (void)tng_data;
  
      if (molecule->name)
      {
          free(molecule->name);
          molecule->name = 0;
      }
  
      if (molecule->chains)
      {
          for (i = 0; i < molecule->n_chains; i++)
          {
              if (molecule->chains[i].name)
              {
                  free(molecule->chains[i].name);
                  molecule->chains[i].name = 0;
              }
          }
          free(molecule->chains);
          molecule->chains = 0;
      }
      molecule->n_chains = 0;
  
      if (molecule->residues)
      {
          for (i = 0; i < molecule->n_residues; i++)
          {
              if (molecule->residues[i].name)
              {
                  free(molecule->residues[i].name);
                  molecule->residues[i].name = 0;
              }
          }
          free(molecule->residues);
          molecule->residues = 0;
      }
      molecule->n_residues = 0;
  
      if (molecule->atoms)
      {
          for (i = 0; i < molecule->n_atoms; i++)
          {
              tng_atom_destroy(&molecule->atoms[i]);
          }
          free(molecule->atoms);
          molecule->atoms = 0;
      }
      molecule->n_atoms = 0;
  
      if (molecule->bonds)
      {
          free(molecule->bonds);
          molecule->bonds = 0;
      }
      molecule->n_bonds = 0;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_name_of_particle_nr_get(struct tng_trajectory* tng_data,
                                                                             const int64_t nr,
                                                                             char*         name,
                                                                             const int     max_len)
  {
      int64_t        cnt = 0, i, *molecule_cnt_list = 0;
      tng_molecule_t mol;
      tng_bool       found = TNG_FALSE;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer.");
  
      tng_molecule_cnt_list_get(tng_data, &molecule_cnt_list);
  
      if (!molecule_cnt_list)
      {
          return (TNG_FAILURE);
      }
  
      for (i = 0; i < tng_data->n_molecules; i++)
      {
          mol = &tng_data->molecules[i];
          if (cnt + mol->n_atoms * molecule_cnt_list[i] - 1 < nr)
          {
              cnt += mol->n_atoms * molecule_cnt_list[i];
              continue;
          }
          found = TNG_TRUE;
          break;
      }
      if (!found)
      {
          return (TNG_FAILURE);
      }
  
      strncpy(name, mol->name, max_len - 1);
      name[max_len - 1] = 0;
  
      if (strlen(mol->name) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_id_of_particle_nr_get(struct tng_trajectory* tng_data,
                                                                           const int64_t          nr,
                                                                           int64_t*               id)
  {
      int64_t        cnt = 0, i, *molecule_cnt_list = 0;
      tng_molecule_t mol;
      tng_bool       found = TNG_FALSE;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(id, "TNG library: id must not be a NULL pointer.");
  
      tng_molecule_cnt_list_get(tng_data, &molecule_cnt_list);
  
      if (!molecule_cnt_list)
      {
          return (TNG_FAILURE);
      }
  
      for (i = 0; i < tng_data->n_molecules; i++)
      {
          mol = &tng_data->molecules[i];
          if (cnt + mol->n_atoms * molecule_cnt_list[i] - 1 < nr)
          {
              cnt += mol->n_atoms * molecule_cnt_list[i];
              continue;
          }
          found = TNG_TRUE;
          break;
      }
      if (!found)
      {
          return (TNG_FAILURE);
      }
  
      *id = mol->id;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molsystem_bonds_get(struct tng_trajectory* tng_data,
                                                                int64_t*               n_bonds,
                                                                int64_t**              from_atoms,
                                                                int64_t**              to_atoms)
  {
      int64_t        atom_cnt = 0, cnt, mol_cnt, i, j, k;
      int64_t        from_atom, to_atom, *molecule_cnt_list = 0;
      tng_molecule_t mol;
      tng_bond_t     bond;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(n_bonds, "TNG library: n_bonds must not be a NULL pointer.");
      TNG_ASSERT(from_atoms, "TNG library: from_atoms must not be a NULL pointer.");
      TNG_ASSERT(to_atoms, "TNG library: to_atoms must not be a NULL pointer.");
  
      tng_molecule_cnt_list_get(tng_data, &molecule_cnt_list);
  
      if (!molecule_cnt_list)
      {
          return (TNG_FAILURE);
      }
  
      *n_bonds = 0;
      /* First count the total number of bonds to allocate memory */
      for (i = 0; i < tng_data->n_molecules; i++)
      {
          mol     = &tng_data->molecules[i];
          mol_cnt = molecule_cnt_list[i];
          *n_bonds += mol_cnt * mol->n_bonds;
      }
      if (*n_bonds == 0)
      {
          return (TNG_SUCCESS);
      }
  
      *from_atoms = (int64_t*)malloc(sizeof(int64_t) * (*n_bonds));
      if (!*from_atoms)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
      *to_atoms = (int64_t*)malloc(sizeof(int64_t) * (*n_bonds));
      if (!*to_atoms)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(*from_atoms);
          *from_atoms = 0;
          return (TNG_CRITICAL);
      }
  
      cnt = 0;
      for (i = 0; i < tng_data->n_molecules; i++)
      {
          mol     = &tng_data->molecules[i];
          mol_cnt = molecule_cnt_list[i];
          for (j = 0; j < mol_cnt; j++)
          {
              for (k = 0; k < mol->n_bonds; k++)
              {
                  bond               = &mol->bonds[k];
                  from_atom          = atom_cnt + bond->from_atom_id;
                  to_atom            = atom_cnt + bond->to_atom_id;
                  (*from_atoms)[cnt] = from_atom;
                  (*to_atoms)[cnt++] = to_atom;
              }
              atom_cnt += mol->n_atoms;
          }
      }
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_chain_name_of_particle_nr_get(struct tng_trajectory* tng_data,
                                                                          const int64_t          nr,
                                                                          char*                  name,
                                                                          const int max_len)
  {
      int64_t        cnt = 0, i, *molecule_cnt_list = 0;
      tng_molecule_t mol;
      tng_atom_t     atom;
      tng_bool       found = TNG_FALSE;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer.");
  
      tng_molecule_cnt_list_get(tng_data, &molecule_cnt_list);
  
      if (!molecule_cnt_list)
      {
          return (TNG_FAILURE);
      }
  
      for (i = 0; i < tng_data->n_molecules; i++)
      {
          mol = &tng_data->molecules[i];
          if (cnt + mol->n_atoms * molecule_cnt_list[i] - 1 < nr)
          {
              cnt += mol->n_atoms * molecule_cnt_list[i];
              continue;
          }
          atom  = &mol->atoms[nr % mol->n_atoms];
          found = TNG_TRUE;
          break;
      }
      if (!found)
      {
          return (TNG_FAILURE);
      }
      if (!atom->residue || !atom->residue->chain)
      {
          return (TNG_FAILURE);
      }
  
      strncpy(name, atom->residue->chain->name, max_len - 1);
      name[max_len - 1] = 0;
  
      if (strlen(atom->residue->chain->name) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_residue_name_of_particle_nr_get(struct tng_trajectory* tng_data,
                                                                            const int64_t          nr,
                                                                            char*     name,
                                                                            const int max_len)
  {
      int64_t        cnt = 0, i, *molecule_cnt_list = 0;
      tng_molecule_t mol;
      tng_atom_t     atom;
      tng_bool       found = TNG_FALSE;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer.");
  
      tng_molecule_cnt_list_get(tng_data, &molecule_cnt_list);
  
      if (!molecule_cnt_list)
      {
          return (TNG_FAILURE);
      }
  
      for (i = 0; i < tng_data->n_molecules; i++)
      {
          mol = &tng_data->molecules[i];
          if (cnt + mol->n_atoms * molecule_cnt_list[i] - 1 < nr)
          {
              cnt += mol->n_atoms * molecule_cnt_list[i];
              continue;
          }
          atom  = &mol->atoms[nr % mol->n_atoms];
          found = TNG_TRUE;
          break;
      }
      if (!found)
      {
          return (TNG_FAILURE);
      }
      if (!atom->residue)
      {
          return (TNG_FAILURE);
      }
  
      strncpy(name, atom->residue->name, max_len - 1);
      name[max_len - 1] = 0;
  
      if (strlen(atom->residue->name) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_residue_id_of_particle_nr_get(struct tng_trajectory* tng_data,
                                                                          const int64_t          nr,
                                                                          int64_t*               id)
  {
      int64_t        cnt = 0, i, *molecule_cnt_list = 0;
      tng_molecule_t mol;
      tng_atom_t     atom;
      tng_bool       found = TNG_FALSE;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(id, "TNG library: id must not be a NULL pointer.");
  
      tng_molecule_cnt_list_get(tng_data, &molecule_cnt_list);
  
      if (!molecule_cnt_list)
      {
          return (TNG_FAILURE);
      }
  
      for (i = 0; i < tng_data->n_molecules; i++)
      {
          mol = &tng_data->molecules[i];
          if (cnt + mol->n_atoms * molecule_cnt_list[i] - 1 < nr)
          {
              cnt += mol->n_atoms * molecule_cnt_list[i];
              continue;
          }
          atom  = &mol->atoms[nr % mol->n_atoms];
          found = TNG_TRUE;
          break;
      }
      if (!found)
      {
          return (TNG_FAILURE);
      }
      if (!atom->residue)
      {
          return (TNG_FAILURE);
      }
  
      *id = atom->residue->id;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_global_residue_id_of_particle_nr_get(struct tng_trajectory* tng_data,
                                                                                 const int64_t nr,
                                                                                 int64_t*      id)
  {
      int64_t        cnt = 0, i, offset = 0, *molecule_cnt_list = 0;
      tng_molecule_t mol;
      tng_atom_t     atom;
      tng_bool       found = TNG_FALSE;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(id, "TNG library: id must not be a NULL pointer.");
  
      tng_molecule_cnt_list_get(tng_data, &molecule_cnt_list);
  
      if (!molecule_cnt_list)
      {
          return (TNG_FAILURE);
      }
  
      for (i = 0; i < tng_data->n_molecules; i++)
      {
          mol = &tng_data->molecules[i];
          if (cnt + mol->n_atoms * molecule_cnt_list[i] - 1 < nr)
          {
              cnt += mol->n_atoms * molecule_cnt_list[i];
              offset += mol->n_residues * molecule_cnt_list[i];
              continue;
          }
          atom  = &mol->atoms[nr % mol->n_atoms];
          found = TNG_TRUE;
          break;
      }
      if (!found)
      {
          return (TNG_FAILURE);
      }
      if (!atom->residue)
      {
          return (TNG_FAILURE);
      }
  
      offset += mol->n_residues * ((nr - cnt) / mol->n_atoms);
  
      *id = atom->residue->id + offset;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_atom_name_of_particle_nr_get(struct tng_trajectory* tng_data,
                                                                         const int64_t          nr,
                                                                         char*                  name,
                                                                         const int max_len)
  {
      int64_t        cnt = 0, i, *molecule_cnt_list = 0;
      tng_molecule_t mol;
      tng_atom_t     atom;
      tng_bool       found = TNG_FALSE;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer.");
  
      tng_molecule_cnt_list_get(tng_data, &molecule_cnt_list);
  
      if (!molecule_cnt_list)
      {
          return (TNG_FAILURE);
      }
  
      for (i = 0; i < tng_data->n_molecules; i++)
      {
          mol = &tng_data->molecules[i];
          if (cnt + mol->n_atoms * molecule_cnt_list[i] - 1 < nr)
          {
              cnt += mol->n_atoms * molecule_cnt_list[i];
              continue;
          }
          atom  = &mol->atoms[nr % mol->n_atoms];
          found = TNG_TRUE;
          break;
      }
      if (!found)
      {
          return (TNG_FAILURE);
      }
  
      strncpy(name, atom->name, max_len - 1);
      name[max_len - 1] = 0;
  
      if (strlen(atom->name) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status tng_atom_type_of_particle_nr_get(struct tng_trajectory* tng_data,
                                                       const int64_t          nr,
                                                       char*                  type,
                                                       const int              max_len)
  {
      int64_t        cnt = 0, i, *molecule_cnt_list = 0;
      tng_molecule_t mol;
      tng_atom_t     atom;
      tng_bool       found = TNG_FALSE;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(type, "TNG library: type must not be a NULL pointer.");
  
      tng_molecule_cnt_list_get(tng_data, &molecule_cnt_list);
  
      if (!molecule_cnt_list)
      {
          return (TNG_FAILURE);
      }
  
      for (i = 0; i < tng_data->n_molecules; i++)
      {
          mol = &tng_data->molecules[i];
          if (cnt + mol->n_atoms * molecule_cnt_list[i] - 1 < nr)
          {
              cnt += mol->n_atoms * molecule_cnt_list[i];
              continue;
          }
          atom  = &mol->atoms[nr % mol->n_atoms];
          found = TNG_TRUE;
          break;
      }
      if (!found)
      {
          return (TNG_FAILURE);
      }
  
      strncpy(type, atom->atom_type, max_len - 1);
      type[max_len - 1] = 0;
  
      if (strlen(atom->atom_type) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_particle_mapping_add(struct tng_trajectory* tng_data,
                                                                 const int64_t  num_first_particle,
                                                                 const int64_t  n_particles,
                                                                 const int64_t* mapping_table)
  {
      int64_t                    i;
      tng_particle_mapping_t     mapping;
      tng_trajectory_frame_set_t frame_set;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      /* Sanity check of the particle ranges. Split into multiple if
       * statements for improved readability */
      for (i = 0; i < frame_set->n_mapping_blocks; i++)
      {
          mapping = &frame_set->mappings[i];
          if (num_first_particle >= mapping->num_first_particle
              && num_first_particle < mapping->num_first_particle + mapping->n_particles)
          {
              fprintf(stderr, "TNG library: Particle mapping overlap. %s: %d\n", __FILE__, __LINE__);
              return (TNG_FAILURE);
          }
          if (num_first_particle + n_particles >= mapping->num_first_particle
              && num_first_particle + n_particles < mapping->num_first_particle + mapping->n_particles)
          {
              fprintf(stderr, "TNG library: Particle mapping overlap. %s: %d\n", __FILE__, __LINE__);
              return (TNG_FAILURE);
          }
          if (mapping->num_first_particle >= num_first_particle
              && mapping->num_first_particle < num_first_particle + n_particles)
          {
              fprintf(stderr, "TNG library: Particle mapping overlap. %s: %d\n", __FILE__, __LINE__);
              return (TNG_FAILURE);
          }
          if (mapping->num_first_particle + mapping->n_particles > num_first_particle
              && mapping->num_first_particle + mapping->n_particles < num_first_particle + n_particles)
          {
              fprintf(stderr, "TNG library: Particle mapping overlap. %s: %d\n", __FILE__, __LINE__);
              return (TNG_FAILURE);
          }
      }
  
      frame_set->n_mapping_blocks++;
  
      mapping = (tng_particle_mapping_t)realloc(
              frame_set->mappings, sizeof(struct tng_particle_mapping) * frame_set->n_mapping_blocks);
  
      if (!mapping)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(frame_set->mappings);
          frame_set->mappings = 0;
          return (TNG_CRITICAL);
      }
      frame_set->mappings = mapping;
  
      frame_set->mappings[frame_set->n_mapping_blocks - 1].num_first_particle = num_first_particle;
      frame_set->mappings[frame_set->n_mapping_blocks - 1].n_particles        = n_particles;
  
      frame_set->mappings[frame_set->n_mapping_blocks - 1].real_particle_numbers =
              (int64_t*)malloc(sizeof(int64_t) * n_particles);
      if (!frame_set->mappings[frame_set->n_mapping_blocks - 1].real_particle_numbers)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      for (i = 0; i < n_particles; i++)
      {
          frame_set->mappings[frame_set->n_mapping_blocks - 1].real_particle_numbers[i] = mapping_table[i];
      }
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_frame_set_particle_mapping_free(struct tng_trajectory* tng_data)
  {
      tng_trajectory_frame_set_t frame_set;
      tng_particle_mapping_t     mapping;
      int64_t                    i;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      if (frame_set->n_mapping_blocks && frame_set->mappings)
      {
          for (i = 0; i < frame_set->n_mapping_blocks; i++)
          {
              mapping = &frame_set->mappings[i];
              if (mapping->real_particle_numbers)
              {
                  free(mapping->real_particle_numbers);
                  mapping->real_particle_numbers = 0;
              }
          }
          free(frame_set->mappings);
          frame_set->mappings         = 0;
          frame_set->n_mapping_blocks = 0;
      }
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_trajectory_init(tng_trajectory_t* tng_data_p)
  {
      time_t                     seconds;
      tng_trajectory_frame_set_t frame_set;
      tng_trajectory_t           tng_data;
  
      *tng_data_p = (tng_trajectory_t)malloc(sizeof(struct tng_trajectory));
      if (!*tng_data_p)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      tng_data = *tng_data_p;
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      tng_data->input_file_path  = 0;
      tng_data->input_file       = 0;
      tng_data->input_file_len   = 0;
      tng_data->output_file_path = 0;
      tng_data->output_file      = 0;
  
      tng_data->first_program_name  = 0;
      tng_data->first_user_name     = 0;
      tng_data->first_computer_name = 0;
      tng_data->first_pgp_signature = 0;
      tng_data->last_program_name   = 0;
      tng_data->last_user_name      = 0;
      tng_data->last_computer_name  = 0;
      tng_data->last_pgp_signature  = 0;
      tng_data->forcefield_name     = 0;
  
      seconds = time(0);
      if (seconds == -1)
      {
          fprintf(stderr, "TNG library: Cannot get time. %s: %d\n", __FILE__, __LINE__);
      }
      else
      {
          tng_data->time = seconds;
      }
  
      tng_data->var_num_atoms_flag                           = TNG_CONSTANT_N_ATOMS;
      tng_data->first_trajectory_frame_set_input_file_pos    = -1;
      tng_data->last_trajectory_frame_set_input_file_pos     = -1;
      tng_data->current_trajectory_frame_set_input_file_pos  = -1;
      tng_data->first_trajectory_frame_set_output_file_pos   = -1;
      tng_data->last_trajectory_frame_set_output_file_pos    = -1;
      tng_data->current_trajectory_frame_set_output_file_pos = -1;
      tng_data->frame_set_n_frames                           = 100;
      tng_data->n_trajectory_frame_sets                      = 0;
      tng_data->medium_stride_length                         = 100;
      tng_data->long_stride_length                           = 10000;
  
      tng_data->time_per_frame = -1;
  
      tng_data->n_particle_data_blocks = 0;
      tng_data->n_data_blocks          = 0;
  
      tng_data->non_tr_particle_data = 0;
      tng_data->non_tr_data          = 0;
  
      tng_data->compress_algo_pos         = 0;
      tng_data->compress_algo_vel         = 0;
      tng_data->compression_precision     = 1000;
      tng_data->distance_unit_exponential = -9;
  
      frame_set->first_frame       = -1;
      frame_set->n_mapping_blocks  = 0;
      frame_set->mappings          = 0;
      frame_set->molecule_cnt_list = 0;
  
      frame_set->n_particle_data_blocks = 0;
      frame_set->n_data_blocks          = 0;
  
      frame_set->tr_particle_data = 0;
      frame_set->tr_data          = 0;
  
      frame_set->n_written_frames   = 0;
      frame_set->n_unwritten_frames = 0;
  
      frame_set->next_frame_set_file_pos               = -1;
      frame_set->prev_frame_set_file_pos               = -1;
      frame_set->medium_stride_next_frame_set_file_pos = -1;
      frame_set->medium_stride_prev_frame_set_file_pos = -1;
      frame_set->long_stride_next_frame_set_file_pos   = -1;
      frame_set->long_stride_prev_frame_set_file_pos   = -1;
  
      frame_set->first_frame_time = -1;
  
      tng_data->n_molecules       = 0;
      tng_data->molecules         = 0;
      tng_data->molecule_cnt_list = 0;
      tng_data->n_particles       = 0;
  
      {
          /* Check the endianness of the computer */
          static int32_t endianness_32 = 0x01234567;
          /* 0x01234567 */
          if (*(const unsigned char*)&endianness_32 == 0x01)
          {
              tng_data->endianness_32 = TNG_BIG_ENDIAN_32;
          }
  
          /* 0x67452301 */
          else if (*(const unsigned char*)&endianness_32 == 0x67)
          {
              tng_data->endianness_32 = TNG_LITTLE_ENDIAN_32;
          }
  
          /* 0x45670123 */
          else if (*(const unsigned char*)&endianness_32 == 0x45)
          {
              tng_data->endianness_32 = TNG_BYTE_PAIR_SWAP_32;
          }
      }
      {
          static int64_t endianness_64 = 0x0123456789ABCDEFLL;
          /* 0x0123456789ABCDEF */
          if (*(const unsigned char*)&endianness_64 == 0x01)
          {
              tng_data->endianness_64 = TNG_BIG_ENDIAN_64;
          }
  
          /* 0xEFCDAB8967452301 */
          else if (*(const unsigned char*)&endianness_64 == 0xEF)
          {
              tng_data->endianness_64 = TNG_LITTLE_ENDIAN_64;
          }
  
          /* 0x89ABCDEF01234567 */
          else if (*(const unsigned char*)&endianness_64 == 0x89)
          {
              tng_data->endianness_64 = TNG_QUAD_SWAP_64;
          }
  
          /* 0x45670123CDEF89AB */
          else if (*(const unsigned char*)&endianness_64 == 0x45)
          {
              tng_data->endianness_64 = TNG_BYTE_PAIR_SWAP_64;
          }
  
          /* 0x23016745AB89EFCD */
          else if (*(const unsigned char*)&endianness_64 == 0x23)
          {
              tng_data->endianness_64 = TNG_BYTE_SWAP_64;
          }
      }
  
      /* By default do not swap the byte order, i.e. keep the byte order of the
       * architecture. The input file endianness will be set when reading the
       * header. The output endianness can be changed - before the file is
       * written. */
      tng_data->input_endianness_swap_func_32  = 0;
      tng_data->input_endianness_swap_func_64  = 0;
      tng_data->output_endianness_swap_func_32 = 0;
      tng_data->output_endianness_swap_func_64 = 0;
  
      tng_data->current_trajectory_frame_set.next_frame_set_file_pos = -1;
      tng_data->current_trajectory_frame_set.prev_frame_set_file_pos = -1;
      tng_data->current_trajectory_frame_set.n_frames                = 0;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_trajectory_destroy(tng_trajectory_t* tng_data_p)
  {
      int64_t                    i, j, k, l;
      int64_t                    n_particles, n_values_per_frame;
      tng_trajectory_t           tng_data = *tng_data_p;
      tng_trajectory_frame_set_t frame_set;
  
      if (!*tng_data_p)
      {
          return (TNG_SUCCESS);
      }
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      if (tng_data->input_file)
      {
          if (tng_data->output_file == tng_data->input_file)
          {
              tng_frame_set_finalize(tng_data, TNG_USE_HASH);
              tng_data->output_file = 0;
          }
          fclose(tng_data->input_file);
          tng_data->input_file = 0;
      }
  
      if (tng_data->input_file_path)
      {
          free(tng_data->input_file_path);
          tng_data->input_file_path = 0;
      }
  
      if (tng_data->output_file)
      {
          /* FIXME: Do not always write the hash */
          tng_frame_set_finalize(tng_data, TNG_USE_HASH);
          fclose(tng_data->output_file);
          tng_data->output_file = 0;
      }
  
      if (tng_data->output_file_path)
      {
          free(tng_data->output_file_path);
          tng_data->output_file_path = 0;
      }
  
      if (tng_data->first_program_name)
      {
          free(tng_data->first_program_name);
          tng_data->first_program_name = 0;
      }
  
      if (tng_data->last_program_name)
      {
          free(tng_data->last_program_name);
          tng_data->last_program_name = 0;
      }
  
      if (tng_data->first_user_name)
      {
          free(tng_data->first_user_name);
          tng_data->first_user_name = 0;
      }
  
      if (tng_data->last_user_name)
      {
          free(tng_data->last_user_name);
          tng_data->last_user_name = 0;
      }
  
      if (tng_data->first_computer_name)
      {
          free(tng_data->first_computer_name);
          tng_data->first_computer_name = 0;
      }
  
      if (tng_data->last_computer_name)
      {
          free(tng_data->last_computer_name);
          tng_data->last_computer_name = 0;
      }
  
      if (tng_data->first_pgp_signature)
      {
          free(tng_data->first_pgp_signature);
          tng_data->first_pgp_signature = 0;
      }
  
      if (tng_data->last_pgp_signature)
      {
          free(tng_data->last_pgp_signature);
          tng_data->last_pgp_signature = 0;
      }
  
      if (tng_data->forcefield_name)
      {
          free(tng_data->forcefield_name);
          tng_data->forcefield_name = 0;
      }
  
      tng_frame_set_particle_mapping_free(tng_data);
  
      if (frame_set->molecule_cnt_list)
      {
          free(frame_set->molecule_cnt_list);
          frame_set->molecule_cnt_list = 0;
      }
  
      if (tng_data->var_num_atoms_flag)
      {
          n_particles = frame_set->n_particles;
      }
      else
      {
          n_particles = tng_data->n_particles;
      }
  
      if (tng_data->non_tr_particle_data)
      {
          for (i = 0; i < tng_data->n_particle_data_blocks; i++)
          {
              if (tng_data->non_tr_particle_data[i].values)
              {
                  free(tng_data->non_tr_particle_data[i].values);
                  tng_data->non_tr_particle_data[i].values = 0;
              }
  
              if (tng_data->non_tr_particle_data[i].strings)
              {
                  n_values_per_frame = tng_data->non_tr_particle_data[i].n_values_per_frame;
                  if (tng_data->non_tr_particle_data[i].strings[0])
                  {
                      for (j = 0; j < n_particles; j++)
                      {
                          if (tng_data->non_tr_particle_data[i].strings[0][j])
                          {
                              for (k = 0; k < n_values_per_frame; k++)
                              {
                                  if (tng_data->non_tr_particle_data[i].strings[0][j][k])
                                  {
                                      free(tng_data->non_tr_particle_data[i].strings[0][j][k]);
                                      tng_data->non_tr_particle_data[i].strings[0][j][k] = 0;
                                  }
                              }
                              free(tng_data->non_tr_particle_data[i].strings[0][j]);
                              tng_data->non_tr_particle_data[i].strings[0][j] = 0;
                          }
                      }
                      free(tng_data->non_tr_particle_data[i].strings[0]);
                      tng_data->non_tr_particle_data[i].strings[0] = 0;
                  }
                  free(tng_data->non_tr_particle_data[i].strings);
                  tng_data->non_tr_particle_data[i].strings = 0;
              }
  
              if (tng_data->non_tr_particle_data[i].block_name)
              {
                  free(tng_data->non_tr_particle_data[i].block_name);
                  tng_data->non_tr_particle_data[i].block_name = 0;
              }
          }
          free(tng_data->non_tr_particle_data);
          tng_data->non_tr_particle_data = 0;
      }
  
      if (tng_data->non_tr_data)
      {
          for (i = 0; i < tng_data->n_data_blocks; i++)
          {
              if (tng_data->non_tr_data[i].values)
              {
                  free(tng_data->non_tr_data[i].values);
                  tng_data->non_tr_data[i].values = 0;
              }
  
              if (tng_data->non_tr_data[i].strings)
              {
                  n_values_per_frame = tng_data->non_tr_data[i].n_values_per_frame;
                  if (tng_data->non_tr_data[i].strings[0][0])
                  {
                      for (j = 0; j < n_values_per_frame; j++)
                      {
                          if (tng_data->non_tr_data[i].strings[0][0][j])
                          {
                              free(tng_data->non_tr_data[i].strings[0][0][j]);
                              tng_data->non_tr_data[i].strings[0][0][j] = 0;
                          }
                      }
                      free(tng_data->non_tr_data[i].strings[0][0]);
                      tng_data->non_tr_data[i].strings[0][0] = 0;
                  }
                  free(tng_data->non_tr_data[i].strings[0]);
                  tng_data->non_tr_data[i].strings[0] = 0;
                  free(tng_data->non_tr_data[i].strings);
                  tng_data->non_tr_data[i].strings = 0;
              }
  
              if (tng_data->non_tr_data[i].block_name)
              {
                  free(tng_data->non_tr_data[i].block_name);
                  tng_data->non_tr_data[i].block_name = 0;
              }
          }
          free(tng_data->non_tr_data);
          tng_data->non_tr_data = 0;
      }
  
      tng_data->n_particle_data_blocks = 0;
      tng_data->n_data_blocks          = 0;
  
      if (tng_data->compress_algo_pos)
      {
          free(tng_data->compress_algo_pos);
          tng_data->compress_algo_pos = 0;
      }
      if (tng_data->compress_algo_vel)
      {
          free(tng_data->compress_algo_vel);
          tng_data->compress_algo_vel = 0;
      }
  
      if (frame_set->tr_particle_data)
      {
          for (i = 0; i < frame_set->n_particle_data_blocks; i++)
          {
              if (frame_set->tr_particle_data[i].values)
              {
                  free(frame_set->tr_particle_data[i].values);
                  frame_set->tr_particle_data[i].values = 0;
              }
  
              if (frame_set->tr_particle_data[i].strings)
              {
                  n_values_per_frame = frame_set->tr_particle_data[i].n_values_per_frame;
                  for (j = 0; j < frame_set->tr_particle_data[i].n_frames; j++)
                  {
                      if (frame_set->tr_particle_data[i].strings[j])
                      {
                          for (k = 0; k < n_particles; k++)
                          {
                              if (frame_set->tr_particle_data[i].strings[j][k])
                              {
                                  for (l = 0; l < n_values_per_frame; l++)
                                  {
                                      if (frame_set->tr_particle_data[i].strings[j][k][l])
                                      {
                                          free(frame_set->tr_particle_data[i].strings[j][k][l]);
                                          frame_set->tr_particle_data[i].strings[j][k][l] = 0;
                                      }
                                  }
                                  free(frame_set->tr_particle_data[i].strings[j][k]);
                                  frame_set->tr_particle_data[i].strings[j][k] = 0;
                              }
                          }
                          free(frame_set->tr_particle_data[i].strings[j]);
                          frame_set->tr_particle_data[i].strings[j] = 0;
                      }
                  }
                  free(frame_set->tr_particle_data[i].strings);
                  frame_set->tr_particle_data[i].strings = 0;
              }
  
              if (frame_set->tr_particle_data[i].block_name)
              {
                  free(frame_set->tr_particle_data[i].block_name);
                  frame_set->tr_particle_data[i].block_name = 0;
              }
          }
          free(frame_set->tr_particle_data);
          frame_set->tr_particle_data = 0;
      }
  
      if (frame_set->tr_data)
      {
          for (i = 0; i < frame_set->n_data_blocks; i++)
          {
              if (frame_set->tr_data[i].values)
              {
                  free(frame_set->tr_data[i].values);
                  frame_set->tr_data[i].values = 0;
              }
  
              if (frame_set->tr_data[i].strings)
              {
                  n_values_per_frame = frame_set->tr_data[i].n_values_per_frame;
                  for (j = 0; j < frame_set->tr_data[i].n_frames; j++)
                  {
                      if (frame_set->tr_data[i].strings[j])
                      {
                          for (k = 0; k < n_values_per_frame; k++)
                          {
                              if (frame_set->tr_data[i].strings[j][k])
                              {
                                  free(frame_set->tr_data[i].strings[j][k]);
                                  frame_set->tr_data[i].strings[j][k] = 0;
                              }
                          }
                          free(frame_set->tr_data[i].strings[j]);
                          frame_set->tr_data[i].strings[j] = 0;
                      }
                  }
                  free(frame_set->tr_data[i].strings);
                  frame_set->tr_data[i].strings = 0;
              }
  
              if (frame_set->tr_data[i].block_name)
              {
                  free(frame_set->tr_data[i].block_name);
                  frame_set->tr_data[i].block_name = 0;
              }
          }
          free(frame_set->tr_data);
          frame_set->tr_data = 0;
      }
  
      frame_set->n_particle_data_blocks = 0;
      frame_set->n_data_blocks          = 0;
  
      if (tng_data->molecules)
      {
          for (i = 0; i < tng_data->n_molecules; i++)
          {
              tng_molecule_destroy(tng_data, &tng_data->molecules[i]);
          }
          free(tng_data->molecules);
          tng_data->molecules   = 0;
          tng_data->n_molecules = 0;
      }
      if (tng_data->molecule_cnt_list)
      {
          free(tng_data->molecule_cnt_list);
          tng_data->molecule_cnt_list = 0;
      }
  
      free(*tng_data_p);
      *tng_data_p = 0;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_trajectory_init_from_src(struct tng_trajectory* src,
                                                                     tng_trajectory_t*      dest_p)
  {
      tng_trajectory_frame_set_t frame_set;
      tng_trajectory_t           dest;
  
      TNG_ASSERT(src != 0, "TNG library: Source trajectory must not be NULL.");
  
      *dest_p = (tng_trajectory_t)malloc(sizeof(struct tng_trajectory));
      if (!*dest_p)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      dest = *dest_p;
  
      frame_set = &dest->current_trajectory_frame_set;
  
      if (src->input_file_path)
      {
          dest->input_file_path = (char*)malloc(strlen(src->input_file_path) + 1);
          if (!dest->input_file_path)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          strcpy(dest->input_file_path, src->input_file_path);
          dest->input_file_len = src->input_file_len;
      }
      else
      {
          dest->input_file_path = 0;
      }
      dest->input_file = 0;
      if (src->output_file_path)
      {
          dest->output_file_path = (char*)malloc(strlen(src->output_file_path) + 1);
          if (!dest->output_file_path)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          strcpy(dest->output_file_path, src->output_file_path);
      }
      else
      {
          dest->output_file_path = 0;
      }
      dest->output_file = 0;
  
      dest->first_program_name  = 0;
      dest->first_user_name     = 0;
      dest->first_computer_name = 0;
      dest->first_pgp_signature = 0;
      dest->last_program_name   = 0;
      dest->last_user_name      = 0;
      dest->last_computer_name  = 0;
      dest->last_pgp_signature  = 0;
      dest->forcefield_name     = 0;
  
      dest->var_num_atoms_flag                        = src->var_num_atoms_flag;
      dest->first_trajectory_frame_set_input_file_pos = src->first_trajectory_frame_set_input_file_pos;
      dest->last_trajectory_frame_set_input_file_pos  = src->last_trajectory_frame_set_input_file_pos;
      dest->current_trajectory_frame_set_input_file_pos = src->current_trajectory_frame_set_input_file_pos;
      dest->first_trajectory_frame_set_output_file_pos = src->first_trajectory_frame_set_output_file_pos;
      dest->last_trajectory_frame_set_output_file_pos = src->last_trajectory_frame_set_output_file_pos;
      dest->current_trajectory_frame_set_output_file_pos = src->current_trajectory_frame_set_output_file_pos;
      dest->frame_set_n_frames                           = src->frame_set_n_frames;
      dest->n_trajectory_frame_sets                      = src->n_trajectory_frame_sets;
      dest->medium_stride_length                         = src->medium_stride_length;
      dest->long_stride_length                           = src->long_stride_length;
  
      dest->time_per_frame = src->time_per_frame;
  
      /* Currently the non trajectory data blocks are not copied since it
       * can lead to problems when freeing memory in a parallel block. */
      dest->n_particle_data_blocks = 0;
      dest->n_data_blocks          = 0;
      dest->non_tr_particle_data   = 0;
      dest->non_tr_data            = 0;
  
      dest->compress_algo_pos         = 0;
      dest->compress_algo_vel         = 0;
      dest->distance_unit_exponential = -9;
      dest->compression_precision     = 1000;
  
      frame_set->n_mapping_blocks  = 0;
      frame_set->mappings          = 0;
      frame_set->molecule_cnt_list = 0;
  
      frame_set->n_particle_data_blocks = 0;
      frame_set->n_data_blocks          = 0;
  
      frame_set->tr_particle_data = 0;
      frame_set->tr_data          = 0;
  
      frame_set->n_written_frames   = 0;
      frame_set->n_unwritten_frames = 0;
  
      frame_set->next_frame_set_file_pos               = -1;
      frame_set->prev_frame_set_file_pos               = -1;
      frame_set->medium_stride_next_frame_set_file_pos = -1;
      frame_set->medium_stride_prev_frame_set_file_pos = -1;
      frame_set->long_stride_next_frame_set_file_pos   = -1;
      frame_set->long_stride_prev_frame_set_file_pos   = -1;
      frame_set->first_frame                           = -1;
  
      dest->n_molecules       = 0;
      dest->molecules         = 0;
      dest->molecule_cnt_list = 0;
      dest->n_particles       = src->n_particles;
  
      dest->endianness_32                  = src->endianness_32;
      dest->endianness_64                  = src->endianness_64;
      dest->input_endianness_swap_func_32  = src->input_endianness_swap_func_32;
      dest->input_endianness_swap_func_64  = src->input_endianness_swap_func_64;
      dest->output_endianness_swap_func_32 = src->output_endianness_swap_func_32;
      dest->output_endianness_swap_func_64 = src->output_endianness_swap_func_64;
  
      dest->current_trajectory_frame_set.next_frame_set_file_pos = -1;
      dest->current_trajectory_frame_set.prev_frame_set_file_pos = -1;
      dest->current_trajectory_frame_set.n_frames                = 0;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_input_file_get(struct tng_trajectory* tng_data,
                                                           char*                  file_name,
                                                           const int              max_len)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(file_name, "TNG library: file_name must not be a NULL pointer");
  
      strncpy(file_name, tng_data->input_file_path, max_len - 1);
      file_name[max_len - 1] = 0;
  
      if (strlen(tng_data->input_file_path) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_input_file_set(struct tng_trajectory* tng_data, const char* file_name)
  {
      unsigned int len;
      char*        temp;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(file_name, "TNG library: file_name must not be a NULL pointer");
  
  
      if (tng_data->input_file_path && strcmp(tng_data->input_file_path, file_name) == 0)
      {
          return (TNG_SUCCESS);
      }
  
      if (tng_data->input_file)
      {
          fclose(tng_data->input_file);
      }
  
      len  = tng_min_size(strlen(file_name) + 1, TNG_MAX_STR_LEN);
      temp = (char*)realloc(tng_data->input_file_path, len);
      if (!temp)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(tng_data->input_file_path);
          tng_data->input_file_path = 0;
          return (TNG_CRITICAL);
      }
      tng_data->input_file_path = temp;
  
      strncpy(tng_data->input_file_path, file_name, len);
  
      return (tng_input_file_init(tng_data));
  }
  
  tng_function_status tng_output_file_get(struct tng_trajectory* tng_data, char* file_name, const int max_len)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(file_name, "TNG library: file_name must not be a NULL pointer");
  
      strncpy(file_name, tng_data->output_file_path, max_len - 1);
      file_name[max_len - 1] = 0;
  
      if (strlen(tng_data->output_file_path) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_output_file_set(struct tng_trajectory* tng_data, const char* file_name)
  {
      int   len;
      char* temp;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(file_name, "TNG library: file_name must not be a NULL pointer");
  
      if (tng_data->output_file_path && strcmp(tng_data->output_file_path, file_name) == 0)
      {
          return (TNG_SUCCESS);
      }
  
      if (tng_data->output_file)
      {
          fclose(tng_data->output_file);
      }
  
      len  = tng_min_size(strlen(file_name) + 1, TNG_MAX_STR_LEN);
      temp = (char*)realloc(tng_data->output_file_path, len);
      if (!temp)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(tng_data->output_file_path);
          tng_data->output_file_path = 0;
          return (TNG_CRITICAL);
      }
      tng_data->output_file_path = temp;
  
      strncpy(tng_data->output_file_path, file_name, len);
  
      return (tng_output_file_init(tng_data));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_output_append_file_set(struct tng_trajectory* tng_data,
                                                                   const char*            file_name)
  {
      int   len;
      char* temp;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(file_name, "TNG library: file_name must not be a NULL pointer");
  
      if (tng_data->output_file_path && strcmp(tng_data->output_file_path, file_name) == 0)
      {
          return (TNG_SUCCESS);
      }
  
      if (tng_data->output_file)
      {
          fclose(tng_data->output_file);
      }
  
      len  = tng_min_size(strlen(file_name) + 1, TNG_MAX_STR_LEN);
      temp = (char*)realloc(tng_data->output_file_path, len);
      if (!temp)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(tng_data->output_file_path);
          tng_data->output_file_path = 0;
          return (TNG_CRITICAL);
      }
      tng_data->output_file_path = temp;
  
      strncpy(tng_data->output_file_path, file_name, len);
  
      tng_data->output_file = fopen(tng_data->output_file_path, "rb+");
      if (!tng_data->output_file)
      {
          fprintf(stderr, "TNG library: Cannot open file %s. %s: %d\n", tng_data->output_file_path,
                  __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
      tng_data->input_file = tng_data->output_file;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_output_file_endianness_get(struct tng_trajectory* tng_data,
                                                                       tng_file_endianness* endianness)
  {
      tng_endianness_32 end_32;
      tng_endianness_64 end_64;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(endianness, "TNG library: endianness must not be a NULL pointer");
  
      if (tng_data->output_endianness_swap_func_32)
      {
          /* If other endianness variants are added they must be added here as well */
          if (tng_data->output_endianness_swap_func_32 == &tng_swap_byte_order_big_endian_32)
          {
              end_32 = TNG_BIG_ENDIAN_32;
          }
          else if (tng_data->output_endianness_swap_func_32 == &tng_swap_byte_order_little_endian_32)
          {
              end_32 = TNG_LITTLE_ENDIAN_32;
          }
          else
          {
              return (TNG_FAILURE);
          }
      }
      else
      {
          end_32 = (tng_endianness_32)tng_data->endianness_32;
      }
  
      if (tng_data->output_endianness_swap_func_64)
      {
          /* If other endianness variants are added they must be added here as well */
          if (tng_data->output_endianness_swap_func_64 == &tng_swap_byte_order_big_endian_64)
          {
              end_64 = TNG_BIG_ENDIAN_64;
          }
          else if (tng_data->output_endianness_swap_func_64 == &tng_swap_byte_order_little_endian_64)
          {
              end_64 = TNG_LITTLE_ENDIAN_64;
          }
          else
          {
              return (TNG_FAILURE);
          }
      }
      else
      {
          end_64 = (tng_endianness_64)tng_data->endianness_64;
      }
  
      if ((int)end_32 != (int)end_64)
      {
          return (TNG_FAILURE);
      }
  
      if (end_32 == TNG_LITTLE_ENDIAN_32)
      {
          *endianness = TNG_LITTLE_ENDIAN;
      }
  
      else if (end_32 == TNG_BIG_ENDIAN_32)
      {
          *endianness = TNG_BIG_ENDIAN;
      }
      else
      {
          return (TNG_FAILURE);
      }
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_output_file_endianness_set(struct tng_trajectory* tng_data,
                                                                       const tng_file_endianness endianness)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      /* Tne endianness cannot be changed if the data has already been written
       * to the output file. */
      if (ftello(tng_data->output_file) > 0)
      {
          return (TNG_FAILURE);
      }
  
      if (endianness == TNG_BIG_ENDIAN)
      {
          if (tng_data->endianness_32 == TNG_BIG_ENDIAN_32)
          {
              tng_data->output_endianness_swap_func_32 = 0;
          }
          else
          {
              tng_data->output_endianness_swap_func_32 = &tng_swap_byte_order_big_endian_32;
          }
          if (tng_data->endianness_64 == TNG_BIG_ENDIAN_64)
          {
              tng_data->output_endianness_swap_func_64 = 0;
          }
          else
          {
              tng_data->output_endianness_swap_func_64 = &tng_swap_byte_order_big_endian_64;
          }
          return (TNG_SUCCESS);
      }
      else if (endianness == TNG_LITTLE_ENDIAN)
      {
          if (tng_data->endianness_32 == TNG_LITTLE_ENDIAN_32)
          {
              tng_data->output_endianness_swap_func_32 = 0;
          }
          else
          {
              tng_data->output_endianness_swap_func_32 = &tng_swap_byte_order_little_endian_32;
          }
          if (tng_data->endianness_64 == TNG_LITTLE_ENDIAN_64)
          {
              tng_data->output_endianness_swap_func_64 = 0;
          }
          else
          {
              tng_data->output_endianness_swap_func_64 = &tng_swap_byte_order_little_endian_64;
          }
          return (TNG_SUCCESS);
      }
  
      /* If the specified endianness is neither big nor little endian return a
       * failure. */
      return (TNG_FAILURE);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_first_program_name_get(struct tng_trajectory* tng_data,
                                                                   char*                  name,
                                                                   const int              max_len)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer");
  
      strncpy(name, tng_data->first_program_name, max_len - 1);
      name[max_len - 1] = 0;
  
      if (strlen(tng_data->first_program_name) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_first_program_name_set(struct tng_trajectory* tng_data,
                                                                   const char*            new_name)
  {
      unsigned int len;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(new_name, "TNG library: new_name must not be a NULL pointer");
  
      len = tng_min_size(strlen(new_name) + 1, TNG_MAX_STR_LEN);
  
      if (tng_data->first_program_name && strlen(tng_data->first_program_name) < len)
      {
          free(tng_data->first_program_name);
          tng_data->first_program_name = 0;
      }
      if (!tng_data->first_program_name)
      {
          tng_data->first_program_name = (char*)malloc(len);
          if (!tng_data->first_program_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      strncpy(tng_data->first_program_name, new_name, len);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_last_program_name_get(struct tng_trajectory* tng_data,
                                                                  char*                  name,
                                                                  const int              max_len)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer");
  
      strncpy(name, tng_data->last_program_name, max_len - 1);
      name[max_len - 1] = 0;
  
      if (strlen(tng_data->last_program_name) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_last_program_name_set(struct tng_trajectory* tng_data,
                                                                  const char*            new_name)
  {
      unsigned int len;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(new_name, "TNG library: new_name must not be a NULL pointer");
  
      len = tng_min_size(strlen(new_name) + 1, TNG_MAX_STR_LEN);
  
      if (tng_data->last_program_name && strlen(tng_data->last_program_name) < len)
      {
          free(tng_data->last_program_name);
          tng_data->last_program_name = 0;
      }
      if (!tng_data->last_program_name)
      {
          tng_data->last_program_name = (char*)malloc(len);
          if (!tng_data->last_program_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      strncpy(tng_data->last_program_name, new_name, len);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_first_user_name_get(struct tng_trajectory* tng_data,
                                                                char*                  name,
                                                                const int              max_len)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer");
  
      strncpy(name, tng_data->first_user_name, max_len - 1);
      name[max_len - 1] = 0;
  
      if (strlen(tng_data->first_user_name) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_first_user_name_set(struct tng_trajectory* tng_data,
                                                                const char*            new_name)
  {
      unsigned int len;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(new_name, "TNG library: new_name must not be a NULL pointer");
  
      len = tng_min_size(strlen(new_name) + 1, TNG_MAX_STR_LEN);
  
      /* If the currently stored string length is not enough to store the new
       * string it is freed and reallocated. */
      if (tng_data->first_user_name && strlen(tng_data->first_user_name) < len)
      {
          free(tng_data->first_user_name);
          tng_data->first_user_name = 0;
      }
      if (!tng_data->first_user_name)
      {
          tng_data->first_user_name = (char*)malloc(len);
          if (!tng_data->first_user_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      strncpy(tng_data->first_user_name, new_name, len);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_last_user_name_get(struct tng_trajectory* tng_data,
                                                               char*                  name,
                                                               const int              max_len)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer");
  
      strncpy(name, tng_data->last_user_name, max_len - 1);
      name[max_len - 1] = 0;
  
      if (strlen(tng_data->last_user_name) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_last_user_name_set(struct tng_trajectory* tng_data,
                                                               const char*            new_name)
  {
      unsigned int len;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(new_name, "TNG library: new_name must not be a NULL pointer");
  
      len = tng_min_size(strlen(new_name) + 1, TNG_MAX_STR_LEN);
  
      /* If the currently stored string length is not enough to store the new
       * string it is freed and reallocated. */
      if (tng_data->last_user_name && strlen(tng_data->last_user_name) < len)
      {
          free(tng_data->last_user_name);
          tng_data->last_user_name = 0;
      }
      if (!tng_data->last_user_name)
      {
          tng_data->last_user_name = (char*)malloc(len);
          if (!tng_data->last_user_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      strncpy(tng_data->last_user_name, new_name, len);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_first_computer_name_get(struct tng_trajectory* tng_data,
                                                                    char*                  name,
                                                                    const int              max_len)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer");
  
      strncpy(name, tng_data->first_computer_name, max_len - 1);
      name[max_len - 1] = 0;
  
      if (strlen(tng_data->first_computer_name) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_first_computer_name_set(struct tng_trajectory* tng_data,
                                                                    const char*            new_name)
  {
      unsigned int len;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(new_name, "TNG library: new_name must not be a NULL pointer");
  
      len = tng_min_size(strlen(new_name) + 1, TNG_MAX_STR_LEN);
  
      /* If the currently stored string length is not enough to store the new
       * string it is freed and reallocated. */
      if (tng_data->first_computer_name && strlen(tng_data->first_computer_name) < len)
      {
          free(tng_data->first_computer_name);
          tng_data->first_computer_name = 0;
      }
      if (!tng_data->first_computer_name)
      {
          tng_data->first_computer_name = (char*)malloc(len);
          if (!tng_data->first_computer_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      strncpy(tng_data->first_computer_name, new_name, len);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_last_computer_name_get(struct tng_trajectory* tng_data,
                                                                   char*                  name,
                                                                   const int              max_len)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer");
  
      strncpy(name, tng_data->last_computer_name, max_len - 1);
      name[max_len - 1] = 0;
  
      if (strlen(tng_data->last_computer_name) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_last_computer_name_set(struct tng_trajectory* tng_data,
                                                                   const char*            new_name)
  {
      unsigned int len;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(new_name, "TNG library: new_name must not be a NULL pointer");
  
      len = tng_min_size(strlen(new_name) + 1, TNG_MAX_STR_LEN);
  
      /* If the currently stored string length is not enough to store the new
       * string it is freed and reallocated. */
      if (tng_data->last_computer_name && strlen(tng_data->last_computer_name) < len)
      {
          free(tng_data->last_computer_name);
          tng_data->last_computer_name = 0;
      }
      if (!tng_data->last_computer_name)
      {
          tng_data->last_computer_name = (char*)malloc(len);
          if (!tng_data->last_computer_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      strncpy(tng_data->last_computer_name, new_name, len);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_first_signature_get(struct tng_trajectory* tng_data,
                                                                char*                  signature,
                                                                const int              max_len)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(signature, "TNG library: signature must not be a NULL pointer");
  
      strncpy(signature, tng_data->first_pgp_signature, max_len - 1);
      signature[max_len - 1] = 0;
  
      if (strlen(tng_data->first_pgp_signature) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_first_signature_set(struct tng_trajectory* tng_data,
                                                                const char*            signature)
  {
      unsigned int len;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(signature, "TNG library: signature must not be a NULL pointer");
  
      len = tng_min_size(strlen(signature) + 1, TNG_MAX_STR_LEN);
  
      /* If the currently stored string length is not enough to store the new
       * string it is freed and reallocated. */
      if (tng_data->first_pgp_signature && strlen(tng_data->first_pgp_signature) < len)
      {
          free(tng_data->first_pgp_signature);
          tng_data->first_pgp_signature = 0;
      }
      if (!tng_data->first_pgp_signature)
      {
          tng_data->first_pgp_signature = (char*)malloc(len);
          if (!tng_data->first_pgp_signature)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      strncpy(tng_data->first_pgp_signature, signature, len);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_last_signature_get(struct tng_trajectory* tng_data,
                                                               char*                  signature,
                                                               const int              max_len)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(signature, "TNG library: signature must not be a NULL pointer");
  
      strncpy(signature, tng_data->last_pgp_signature, max_len - 1);
      signature[max_len - 1] = 0;
  
      if (strlen(tng_data->last_pgp_signature) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_last_signature_set(struct tng_trajectory* tng_data,
                                                               const char*            signature)
  {
      unsigned int len;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(signature, "TNG library: signature must not be a NULL pointer");
  
      len = tng_min_size(strlen(signature) + 1, TNG_MAX_STR_LEN);
  
      /* If the currently stored string length is not enough to store the new
       * string it is freed and reallocated. */
      if (tng_data->last_pgp_signature && strlen(tng_data->last_pgp_signature) < len)
      {
          free(tng_data->last_pgp_signature);
          tng_data->last_pgp_signature = 0;
      }
      if (!tng_data->last_pgp_signature)
      {
          tng_data->last_pgp_signature = (char*)malloc(len);
          if (!tng_data->last_pgp_signature)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      strncpy(tng_data->last_pgp_signature, signature, len);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_forcefield_name_get(struct tng_trajectory* tng_data,
                                                                char*                  name,
                                                                const int              max_len)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer");
  
      strncpy(name, tng_data->forcefield_name, max_len - 1);
      name[max_len - 1] = 0;
  
      if (strlen(tng_data->forcefield_name) > (unsigned int)max_len - 1)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_forcefield_name_set(struct tng_trajectory* tng_data,
                                                                const char*            new_name)
  {
      unsigned int len;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(new_name, "TNG library: new_name must not be a NULL pointer");
  
      len = tng_min_size(strlen(new_name) + 1, TNG_MAX_STR_LEN);
  
      /* If the currently stored string length is not enough to store the new
       * string it is freed and reallocated. */
      if (tng_data->forcefield_name && strlen(tng_data->forcefield_name) < len)
      {
          free(tng_data->forcefield_name);
          tng_data->forcefield_name = 0;
      }
      if (!tng_data->forcefield_name)
      {
          tng_data->forcefield_name = (char*)malloc(len);
          if (!tng_data->forcefield_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
      }
  
      strncpy(tng_data->forcefield_name, new_name, len);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_medium_stride_length_get(struct tng_trajectory* tng_data,
                                                                     int64_t*               len)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(len, "TNG library: len must not be a NULL pointer");
  
      *len = tng_data->medium_stride_length;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_medium_stride_length_set(struct tng_trajectory* tng_data,
                                                                     const int64_t          len)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      if (len >= tng_data->long_stride_length)
      {
          return (TNG_FAILURE);
      }
      tng_data->medium_stride_length = len;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_long_stride_length_get(struct tng_trajectory* tng_data,
                                                                   int64_t*               len)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(len, "TNG library: len must not be a NULL pointer");
  
      *len = tng_data->long_stride_length;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_long_stride_length_set(struct tng_trajectory* tng_data,
                                                                   const int64_t          len)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      if (len <= tng_data->medium_stride_length)
      {
          return (TNG_FAILURE);
      }
      tng_data->long_stride_length = len;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_time_per_frame_get(struct tng_trajectory* tng_data, double* time)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(time, "TNG library: time must not be a NULL pointer");
  
      *time = tng_data->time_per_frame;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_time_per_frame_set(struct tng_trajectory* tng_data,
                                                               const double           time)
  {
      tng_trajectory_frame_set_t frame_set;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(time >= 0, "TNG library: The time per frame must be >= 0.");
  
      if (fabs(time - tng_data->time_per_frame) < 0.00001)
      {
          return (TNG_SUCCESS);
      }
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      /* If the current frame set is not finished write it to disk before
         changing time per frame. */
      if (tng_data->time_per_frame > 0 && frame_set->n_unwritten_frames > 0)
      {
          frame_set->n_frames = frame_set->n_unwritten_frames;
          tng_frame_set_write(tng_data, TNG_USE_HASH);
      }
      tng_data->time_per_frame = time;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_input_file_len_get(struct tng_trajectory* tng_data, int64_t* len)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(len, "TNG library: len must not be a NULL pointer");
  
      *len = tng_data->input_file_len;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_num_frames_get(struct tng_trajectory* tng_data, int64_t* n)
  {
      tng_gen_block_t     block;
      tng_function_status stat;
      int64_t             file_pos, last_file_pos, first_frame, n_frames;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(tng_data->input_file,
                 "TNG library: An input file must be open to find the next frame set");
      TNG_ASSERT(n, "TNG library: n must not be a NULL pointer");
  
      file_pos      = ftello(tng_data->input_file);
      last_file_pos = tng_data->last_trajectory_frame_set_input_file_pos;
  
      if (last_file_pos <= 0)
      {
          return (TNG_FAILURE);
      }
  
      tng_block_init(&block);
      fseeko(tng_data->input_file, last_file_pos, SEEK_SET);
      /* Read block headers first to see that a frame set block is found. */
      stat = tng_block_header_read(tng_data, block);
      if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
      {
          fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                  last_file_pos, __FILE__, __LINE__);
          tng_block_destroy(&block);
          return (TNG_FAILURE);
      }
      tng_block_destroy(&block);
  
      if (tng_file_input_numerical(tng_data, &first_frame, sizeof(first_frame), TNG_SKIP_HASH, 0, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, &n_frames, sizeof(n_frames), TNG_SKIP_HASH, 0, __LINE__)
          == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
  
      fseeko(tng_data->input_file, file_pos, SEEK_SET);
  
      *n = first_frame + n_frames;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_compression_precision_get(struct tng_trajectory* tng_data,
                                                                      double* precision)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      *precision = tng_data->compression_precision;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_compression_precision_set(struct tng_trajectory* tng_data,
                                                                      const double precision)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      tng_data->compression_precision = precision;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_implicit_num_particles_set(struct tng_trajectory* tng_data,
                                                                       const int64_t          n)
  {
      tng_molecule_t      mol;
      tng_chain_t         chain;
      tng_residue_t       res;
      tng_atom_t          atom;
      tng_function_status stat;
      int64_t             diff, n_mod, n_impl;
  
      TNG_ASSERT(n >= 0, "TNG library: The requested number of particles must be >= 0");
  
      diff = n - tng_data->n_particles;
  
      stat = tng_molecule_find(tng_data, "TNG_IMPLICIT_MOL", -1, &mol);
      if (stat == TNG_SUCCESS)
      {
          if (tng_molecule_cnt_get(tng_data, mol, &n_impl) != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot get the number of implicit molecules. %s: %d\n",
                      __FILE__, __LINE__);
              return (TNG_FAILURE);
          }
          diff -= n_impl * mol->n_atoms;
      }
  
      if (diff == 0)
      {
          if (stat == TNG_SUCCESS)
          {
              stat = tng_molecule_cnt_set(tng_data, mol, 0);
              return (stat);
          }
          return (TNG_SUCCESS);
      }
      else if (diff < 0)
      {
          fprintf(stderr, "TNG library: Already more actual particles than requested implicit ");
          fprintf(stderr, "particle count.\n");
          fprintf(stderr, "TNG library: Cannot set implicit particle count. %s: %d\n", __FILE__, __LINE__);
          /* FIXME: Should we set the count of all other molecules to 0 and add
           * implicit molecules? */
          return (TNG_FAILURE);
      }
      if (stat != TNG_SUCCESS)
      {
          stat = tng_molecule_add(tng_data, "TNG_IMPLICIT_MOL", &mol);
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
          stat = tng_molecule_chain_add(tng_data, mol, "", &chain);
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
          stat = tng_chain_residue_add(tng_data, chain, "", &res);
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
          stat = tng_residue_atom_add(tng_data, res, "", "", &atom);
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
      }
      else
      {
          if (mol->n_atoms > 1)
          {
              n_mod = diff % mol->n_atoms;
              if (n_mod != 0)
              {
                  fprintf(stderr, "TNG library: Number of atoms in implicit molecule ");
                  fprintf(stderr, "not compatible with requested implicit particle cnt.\n");
                  fprintf(stderr, "TNG library: Cannot set implicit particle count. %s: %d\n",
                          __FILE__, __LINE__);
                  return (TNG_FAILURE);
              }
              diff /= mol->n_atoms;
          }
      }
      stat = tng_molecule_cnt_set(tng_data, mol, diff);
  
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_num_particles_get(struct tng_trajectory* tng_data, int64_t* n)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(n, "TNG library: n must not be a NULL pointer");
  
      if (tng_data->var_num_atoms_flag == TNG_CONSTANT_N_ATOMS)
      {
          *n = tng_data->n_particles;
      }
      else
      {
          *n = tng_data->current_trajectory_frame_set.n_particles;
      }
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_num_particles_variable_get(struct tng_trajectory* tng_data,
                                                                       char* variable)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(variable, "TNG library: variable must not be a NULL pointer");
  
      *variable = tng_data->var_num_atoms_flag;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_num_molecule_types_get(struct tng_trajectory* tng_data, int64_t* n)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(n, "TNG library: n must not be a NULL pointer");
  
      *n = tng_data->n_molecules;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_num_molecules_get(struct tng_trajectory* tng_data, int64_t* n)
  {
      int64_t *cnt_list = 0, cnt = 0, i;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(n, "TNG library: n must not be a NULL pointer");
  
      tng_molecule_cnt_list_get(tng_data, &cnt_list);
  
      if (!cnt_list)
      {
          return (TNG_FAILURE);
      }
  
      for (i = 0; i < tng_data->n_molecules; i++)
      {
          cnt += cnt_list[i];
      }
  
      *n = cnt;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_molecule_cnt_list_get(struct tng_trajectory* tng_data,
                                                                  int64_t**              mol_cnt_list)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      if (tng_data->var_num_atoms_flag)
      {
          *mol_cnt_list = tng_data->current_trajectory_frame_set.molecule_cnt_list;
      }
      else
      {
          *mol_cnt_list = tng_data->molecule_cnt_list;
      }
      if (*mol_cnt_list == 0)
      {
          return (TNG_FAILURE);
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_distance_unit_exponential_get(struct tng_trajectory* tng_data,
                                                                          int64_t*               exp)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(exp, "TNG library: exp must not be a NULL pointer");
  
      *exp = tng_data->distance_unit_exponential;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_distance_unit_exponential_set(struct tng_trajectory* tng_data,
                                                                          const int64_t          exp)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      tng_data->distance_unit_exponential = exp;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_num_frames_per_frame_set_get(struct tng_trajectory* tng_data,
                                                                         int64_t*               n)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(n, "TNG library: n must not be a NULL pointer");
  
      *n = tng_data->frame_set_n_frames;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_num_frames_per_frame_set_set(struct tng_trajectory* tng_data,
                                                                         const int64_t          n)
  {
      tng_trajectory_frame_set_t frame_set;
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      tng_data->frame_set_n_frames = n;
      frame_set                    = &tng_data->current_trajectory_frame_set;
      if (frame_set)
      {
          frame_set->n_frames = n;
      }
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_num_frame_sets_get(struct tng_trajectory* tng_data, int64_t* n)
  {
      int64_t                         long_stride_length, medium_stride_length;
      int64_t                         file_pos, orig_frame_set_file_pos;
      tng_trajectory_frame_set_t      frame_set;
      struct tng_trajectory_frame_set orig_frame_set;
      tng_gen_block_t                 block;
      tng_function_status             stat;
      int64_t                         cnt = 0;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(n, "TNG library: n must not be a NULL pointer");
  
      orig_frame_set = tng_data->current_trajectory_frame_set;
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      orig_frame_set_file_pos = tng_data->current_trajectory_frame_set_input_file_pos;
      file_pos                = tng_data->first_trajectory_frame_set_input_file_pos;
  
      if (file_pos < 0)
      {
          *n = tng_data->n_trajectory_frame_sets = cnt;
          return (TNG_SUCCESS);
      }
  
      tng_block_init(&block);
      fseeko(tng_data->input_file, file_pos, SEEK_SET);
      tng_data->current_trajectory_frame_set_input_file_pos = file_pos;
      /* Read block headers first to see what block is found. */
      stat = tng_block_header_read(tng_data, block);
      if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
      {
          fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                  file_pos, __FILE__, __LINE__);
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
      {
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      ++cnt;
  
      long_stride_length   = tng_data->long_stride_length;
      medium_stride_length = tng_data->medium_stride_length;
  
      /* Take long steps forward until a long step forward would be too long or
       * the last frame set is found */
      file_pos = frame_set->long_stride_next_frame_set_file_pos;
      while (file_pos > 0)
      {
          if (file_pos > 0)
          {
              cnt += long_stride_length;
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
          }
          file_pos = frame_set->long_stride_next_frame_set_file_pos;
      }
  
      /* Take medium steps forward until a medium step forward would be too long
       * or the last frame set is found */
      file_pos = frame_set->medium_stride_next_frame_set_file_pos;
      while (file_pos > 0)
      {
          if (file_pos > 0)
          {
              cnt += medium_stride_length;
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
          }
          file_pos = frame_set->medium_stride_next_frame_set_file_pos;
      }
  
      /* Take one step forward until the last frame set is found */
      file_pos = frame_set->next_frame_set_file_pos;
      while (file_pos > 0)
      {
          if (file_pos > 0)
          {
              ++cnt;
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
          }
          file_pos = frame_set->next_frame_set_file_pos;
      }
  
      tng_block_destroy(&block);
  
      *n = tng_data->n_trajectory_frame_sets = cnt;
  
      *frame_set = orig_frame_set;
      /* The mapping block in the original frame set has been freed when reading
       * other frame sets. */
      frame_set->mappings         = 0;
      frame_set->n_mapping_blocks = 0;
  
      fseeko(tng_data->input_file, tng_data->first_trajectory_frame_set_input_file_pos, SEEK_SET);
  
      tng_data->current_trajectory_frame_set_input_file_pos = orig_frame_set_file_pos;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_current_frame_set_get(struct tng_trajectory* tng_data,
                                                                  tng_trajectory_frame_set_t* frame_set_p)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      *frame_set_p = &tng_data->current_trajectory_frame_set;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_frame_set_nr_find(struct tng_trajectory* tng_data, const int64_t nr)
  {
      int64_t                    long_stride_length, medium_stride_length;
      int64_t                    file_pos, curr_nr = 0, n_frame_sets;
      tng_trajectory_frame_set_t frame_set;
      tng_gen_block_t            block;
      tng_function_status        stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(nr >= 0, "The frame set number (nr) must be >= 0");
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      stat = tng_num_frame_sets_get(tng_data, &n_frame_sets);
  
      if (stat != TNG_SUCCESS)
      {
          return (stat);
      }
  
      if (nr >= n_frame_sets)
      {
          return (TNG_FAILURE);
      }
  
      long_stride_length   = tng_data->long_stride_length;
      medium_stride_length = tng_data->medium_stride_length;
  
      /* FIXME: The frame set number of the current frame set is not stored */
  
      if (nr < n_frame_sets - 1 - nr)
      {
          /* Start from the beginning */
          file_pos = tng_data->first_trajectory_frame_set_input_file_pos;
      }
      else
      {
          /* Start from the end */
          file_pos = tng_data->last_trajectory_frame_set_input_file_pos;
          curr_nr  = n_frame_sets - 1;
      }
      if (file_pos <= 0)
      {
          return (TNG_FAILURE);
      }
  
      tng_block_init(&block);
      fseeko(tng_data->input_file, file_pos, SEEK_SET);
      tng_data->current_trajectory_frame_set_input_file_pos = file_pos;
      /* Read block headers first to see what block is found. */
      stat = tng_block_header_read(tng_data, block);
      if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
      {
          fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                  file_pos, __FILE__, __LINE__);
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
      {
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      if (curr_nr == nr)
      {
          tng_block_destroy(&block);
          return (TNG_SUCCESS);
      }
  
      file_pos = tng_data->current_trajectory_frame_set_input_file_pos;
  
      /* Take long steps forward until a long step forward would be too long or
       * the right frame set is found */
      while (file_pos > 0 && curr_nr + long_stride_length <= nr)
      {
          file_pos = frame_set->long_stride_next_frame_set_file_pos;
          if (file_pos > 0)
          {
              curr_nr += long_stride_length;
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
              if (curr_nr == nr)
              {
                  tng_block_destroy(&block);
                  return (TNG_SUCCESS);
              }
          }
      }
  
      /* Take medium steps forward until a medium step forward would be too long
       * or the right frame set is found */
      while (file_pos > 0 && curr_nr + medium_stride_length <= nr)
      {
          file_pos = frame_set->medium_stride_next_frame_set_file_pos;
          if (file_pos > 0)
          {
              curr_nr += medium_stride_length;
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
              if (curr_nr == nr)
              {
                  tng_block_destroy(&block);
                  return (TNG_SUCCESS);
              }
          }
      }
  
      /* Take one step forward until the right frame set is found */
      while (file_pos > 0 && curr_nr < nr)
      {
          file_pos = frame_set->next_frame_set_file_pos;
  
          if (file_pos > 0)
          {
              ++curr_nr;
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
              if (curr_nr == nr)
              {
                  tng_block_destroy(&block);
                  return (TNG_SUCCESS);
              }
          }
      }
  
      /* Take long steps backward until a long step backward would be too long
       * or the right frame set is found */
      while (file_pos > 0 && curr_nr - long_stride_length >= nr)
      {
          file_pos = frame_set->long_stride_prev_frame_set_file_pos;
          if (file_pos > 0)
          {
              curr_nr -= long_stride_length;
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
              if (curr_nr == nr)
              {
                  tng_block_destroy(&block);
                  return (TNG_SUCCESS);
              }
          }
      }
  
      /* Take medium steps backward until a medium step backward would be too long
       * or the right frame set is found */
      while (file_pos > 0 && curr_nr - medium_stride_length >= nr)
      {
          file_pos = frame_set->medium_stride_prev_frame_set_file_pos;
          if (file_pos > 0)
          {
              curr_nr -= medium_stride_length;
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
              if (curr_nr == nr)
              {
                  tng_block_destroy(&block);
                  return (TNG_SUCCESS);
              }
          }
      }
  
      /* Take one step backward until the right frame set is found */
      while (file_pos > 0 && curr_nr > nr)
      {
          file_pos = frame_set->prev_frame_set_file_pos;
          if (file_pos > 0)
          {
              --curr_nr;
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
              if (curr_nr == nr)
              {
                  tng_block_destroy(&block);
                  return (TNG_SUCCESS);
              }
          }
      }
  
      /* If for some reason the current frame set is not yet found,
       * take one step forward until the right frame set is found */
      while (file_pos > 0 && curr_nr < nr)
      {
          file_pos = frame_set->next_frame_set_file_pos;
          if (file_pos > 0)
          {
              ++curr_nr;
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
              if (curr_nr == nr)
              {
                  tng_block_destroy(&block);
                  return (TNG_SUCCESS);
              }
          }
      }
  
      tng_block_destroy(&block);
      return (TNG_FAILURE);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_frame_set_of_frame_find(struct tng_trajectory* tng_data,
                                                                    const int64_t          frame)
  {
      int64_t                    first_frame, last_frame, n_frames_per_frame_set;
      int64_t                    long_stride_length, medium_stride_length;
      int64_t                    file_pos, temp_frame, n_frames;
      tng_trajectory_frame_set_t frame_set;
      tng_gen_block_t            block;
      tng_function_status        stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(frame >= 0, "TNG library: frame must be >= 0.");
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      tng_block_init(&block);
  
      if (tng_data->current_trajectory_frame_set_input_file_pos < 0)
      {
          file_pos = tng_data->first_trajectory_frame_set_input_file_pos;
          fseeko(tng_data->input_file, file_pos, SEEK_SET);
          tng_data->current_trajectory_frame_set_input_file_pos = file_pos;
          /* Read block headers first to see what block is found. */
          stat = tng_block_header_read(tng_data, block);
          if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
          {
              fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                      file_pos, __FILE__, __LINE__);
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
  
          if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
          {
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
      }
  
      first_frame = tng_max_i64(frame_set->first_frame, 0);
      last_frame  = first_frame + frame_set->n_frames - 1;
      /* Is this the right frame set? */
      if (first_frame <= frame && frame <= last_frame)
      {
          tng_block_destroy(&block);
          return (TNG_SUCCESS);
      }
  
      n_frames_per_frame_set = tng_data->frame_set_n_frames;
      long_stride_length     = tng_data->long_stride_length;
      medium_stride_length   = tng_data->medium_stride_length;
  
      if (tng_first_frame_nr_of_next_frame_set_get(tng_data, &temp_frame) == TNG_SUCCESS)
      {
          if (temp_frame - first_frame > n_frames_per_frame_set)
          {
              n_frames_per_frame_set = temp_frame - first_frame;
          }
      }
  
      tng_num_frames_get(tng_data, &n_frames);
  
      if (frame >= n_frames)
      {
          tng_block_destroy(&block);
          return (TNG_FAILURE);
      }
  
      if (first_frame - frame >= frame
          || frame - last_frame > tng_data->n_trajectory_frame_sets * n_frames_per_frame_set - frame)
      {
          /* Start from the beginning */
          if (first_frame - frame >= frame)
          {
              file_pos = tng_data->first_trajectory_frame_set_input_file_pos;
  
              if (file_pos <= 0)
              {
                  tng_block_destroy(&block);
                  return (TNG_FAILURE);
              }
          }
          /* Start from the end */
          else if (frame - first_frame > (n_frames - 1) - frame)
          {
              file_pos = tng_data->last_trajectory_frame_set_input_file_pos;
  
              /* If the last frame set position is not set start from the current
               * frame set, since it will be closer than the first frame set. */
          }
          /* Start from current */
          else
          {
              file_pos = tng_data->current_trajectory_frame_set_input_file_pos;
          }
  
          if (file_pos > 0)
          {
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              tng_data->current_trajectory_frame_set_input_file_pos = file_pos;
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
          }
      }
  
      first_frame = tng_max_i64(frame_set->first_frame, 0);
      last_frame  = first_frame + frame_set->n_frames - 1;
  
      if (frame >= first_frame && frame <= last_frame)
      {
          tng_block_destroy(&block);
          return (TNG_SUCCESS);
      }
  
      file_pos = tng_data->current_trajectory_frame_set_input_file_pos;
  
      /* Take long steps forward until a long step forward would be too long or
       * the right frame set is found */
      while (file_pos > 0 && first_frame + long_stride_length * n_frames_per_frame_set <= frame)
      {
          file_pos = frame_set->long_stride_next_frame_set_file_pos;
          if (file_pos > 0)
          {
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
          }
          first_frame = tng_max_i64(frame_set->first_frame, 0);
          last_frame  = first_frame + frame_set->n_frames - 1;
          if (frame >= first_frame && frame <= last_frame)
          {
              tng_block_destroy(&block);
              return (TNG_SUCCESS);
          }
      }
  
      /* Take medium steps forward until a medium step forward would be too long
       * or the right frame set is found */
      while (file_pos > 0 && first_frame + medium_stride_length * n_frames_per_frame_set <= frame)
      {
          file_pos = frame_set->medium_stride_next_frame_set_file_pos;
          if (file_pos > 0)
          {
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
          }
          first_frame = tng_max_i64(frame_set->first_frame, 0);
          last_frame  = first_frame + frame_set->n_frames - 1;
          if (frame >= first_frame && frame <= last_frame)
          {
              tng_block_destroy(&block);
              return (TNG_SUCCESS);
          }
      }
  
      /* Take one step forward until the right frame set is found */
      while (file_pos > 0 && first_frame < frame && last_frame < frame)
      {
          file_pos = frame_set->next_frame_set_file_pos;
          if (file_pos > 0)
          {
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
          }
          first_frame = tng_max_i64(frame_set->first_frame, 0);
          last_frame  = first_frame + frame_set->n_frames - 1;
          if (frame >= first_frame && frame <= last_frame)
          {
              tng_block_destroy(&block);
              return (TNG_SUCCESS);
          }
      }
  
      /* Take long steps backward until a long step backward would be too long
       * or the right frame set is found */
      while (file_pos > 0 && first_frame - long_stride_length * n_frames_per_frame_set >= frame)
      {
          file_pos = frame_set->long_stride_prev_frame_set_file_pos;
          if (file_pos > 0)
          {
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
          }
          first_frame = tng_max_i64(frame_set->first_frame, 0);
          last_frame  = first_frame + frame_set->n_frames - 1;
          if (frame >= first_frame && frame <= last_frame)
          {
              tng_block_destroy(&block);
              return (TNG_SUCCESS);
          }
      }
  
      /* Take medium steps backward until a medium step backward would be too long
       * or the right frame set is found */
      while (file_pos > 0 && first_frame - medium_stride_length * n_frames_per_frame_set >= frame)
      {
          file_pos = frame_set->medium_stride_prev_frame_set_file_pos;
          if (file_pos > 0)
          {
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
          }
          first_frame = tng_max_i64(frame_set->first_frame, 0);
          last_frame  = first_frame + frame_set->n_frames - 1;
          if (frame >= first_frame && frame <= last_frame)
          {
              tng_block_destroy(&block);
              return (TNG_SUCCESS);
          }
      }
  
      /* Take one step backward until the right frame set is found */
      while (file_pos > 0 && first_frame > frame && last_frame > frame)
      {
          file_pos = frame_set->prev_frame_set_file_pos;
          if (file_pos > 0)
          {
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
          }
          first_frame = tng_max_i64(frame_set->first_frame, 0);
          last_frame  = first_frame + frame_set->n_frames - 1;
          if (frame >= first_frame && frame <= last_frame)
          {
              tng_block_destroy(&block);
              return (TNG_SUCCESS);
          }
      }
  
      /* If for some reason the current frame set is not yet found,
       * take one step forward until the right frame set is found */
      while (file_pos > 0 && first_frame < frame && last_frame < frame)
      {
          file_pos = frame_set->next_frame_set_file_pos;
          if (file_pos > 0)
          {
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
              /* Read block headers first to see what block is found. */
              stat = tng_block_header_read(tng_data, block);
              if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_block_read_next(tng_data, block, TNG_SKIP_HASH) != TNG_SUCCESS)
              {
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
          }
          first_frame = tng_max_i64(frame_set->first_frame, 0);
          last_frame  = first_frame + frame_set->n_frames - 1;
          if (frame >= first_frame && frame <= last_frame)
          {
              tng_block_destroy(&block);
              return (TNG_SUCCESS);
          }
      }
  
      tng_block_destroy(&block);
      return (TNG_FAILURE);
  }
  
  tng_function_status DECLSPECDLLEXPORT
                      tng_frame_set_next_frame_set_file_pos_get(struct tng_trajectory*           tng_data,
                                                                struct tng_trajectory_frame_set* frame_set,
                                                                int64_t*                         pos)
  {
      (void)tng_data;
  
      TNG_ASSERT(frame_set, "TNG library: frame_set not initialised before accessing data.");
      TNG_ASSERT(pos, "TNG library: pos must not be a NULL pointer");
  
      *pos = frame_set->next_frame_set_file_pos;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT
                      tng_frame_set_prev_frame_set_file_pos_get(struct tng_trajectory*           tng_data,
                                                                struct tng_trajectory_frame_set* frame_set,
                                                                int64_t*                         pos)
  {
      (void)tng_data;
  
      TNG_ASSERT(frame_set, "TNG library: frame_set not initialised before accessing data.");
      TNG_ASSERT(pos, "TNG library: pos must not be a NULL pointer");
  
      *pos = frame_set->prev_frame_set_file_pos;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_frame_set_frame_range_get(struct tng_trajectory* tng_data,
                                                                      struct tng_trajectory_frame_set* frame_set,
                                                                      int64_t* first_frame,
                                                                      int64_t* last_frame)
  {
      (void)tng_data;
  
      TNG_ASSERT(first_frame, "TNG library: first_frame must not be a NULL pointer");
      TNG_ASSERT(last_frame, "TNG library: last_frame must not be a NULL pointer");
      TNG_ASSERT(frame_set, "TNG library: frame_set must not be a NULL pointer");
  
      *first_frame = frame_set->first_frame;
      *last_frame  = *first_frame + frame_set->n_frames - 1;
  
      return (TNG_SUCCESS);
  }
  
  /**
   * @brief Translate from the particle numbering used in a frame set to the real
   *  particle numbering - used in the molecule description.
   * @param frame_set is the frame_set containing the mappings to use.
   * @param local is the index number of the atom in this frame set
   * @param real is set to the index of the atom in the molecular system.
   * @return TNG_SUCCESS (0) if successful or TNG_FAILURE (1) if the mapping
   * cannot be found.
   */
  static TNG_INLINE tng_function_status tng_particle_mapping_get_real_particle(struct tng_trajectory_frame_set* frame_set,
                                                                               const int64_t local,
                                                                               int64_t*      real)
  {
      int64_t                i, n_blocks = frame_set->n_mapping_blocks, first;
      tng_particle_mapping_t mapping;
      if (n_blocks <= 0)
      {
          *real = local;
          return (TNG_SUCCESS);
      }
      for (i = 0; i < n_blocks; i++)
      {
          mapping = &frame_set->mappings[i];
          first   = mapping->num_first_particle;
          if (local < first || local >= first + mapping->n_particles)
          {
              continue;
          }
          *real = mapping->real_particle_numbers[local - first];
          return (TNG_SUCCESS);
      }
      *real = local;
      return (TNG_FAILURE);
  }
  
  /**
   * @brief Translate from the real particle numbering to the particle numbering
   *  used in a frame set.
   * @param frame_set is the frame_set containing the mappings to use.
   * @param real is the index number of the atom in the molecular system.
   * @param local is set to the index of the atom in this frame set.
   * @return TNG_SUCCESS (0) if successful or TNG_FAILURE (1) if the mapping
   * cannot be found.
   */
  /*static TNG_INLINE tng_function_status tng_particle_mapping_get_local_particle
                  (struct tng_trajectory_frame_set* frame_set,
                   const int64_t real,
                   int64_t *local)
  {
      int64_t i, j, n_blocks = frame_set->n_mapping_blocks;
      tng_particle_mapping_t mapping;
      if(n_blocks <= 0)
      {
          *local = real;
          return(TNG_SUCCESS);
      }
      for(i = 0; i < n_blocks; i++)
      {
          mapping = &frame_set->mappings[i];
          for(j = mapping->n_particles; j--;)
          {
              if(mapping->real_particle_numbers[j] == real)
              {
                  *local = j;
                  return(TNG_SUCCESS);
              }
          }
      }
      return(TNG_FAILURE);
  }
  */
  
  static tng_function_status tng_file_headers_len_get(struct tng_trajectory* tng_data, int64_t* len)
  {
      int64_t         orig_pos;
      tng_gen_block_t block;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      if (tng_input_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      *len = 0;
  
      orig_pos = ftello(tng_data->input_file);
  
      fseeko(tng_data->input_file, 0, SEEK_SET);
  
      tng_block_init(&block);
      /* Read through the headers of non-trajectory blocks (they come before the
       * trajectory blocks in the file) */
      while (*len < tng_data->input_file_len && tng_block_header_read(tng_data, block) != TNG_CRITICAL
             && block->id != -1 && block->id != TNG_TRAJECTORY_FRAME_SET)
      {
          *len += block->header_contents_size + block->block_contents_size;
          fseeko(tng_data->input_file, block->block_contents_size, SEEK_CUR);
      }
  
      fseeko(tng_data->input_file, orig_pos, SEEK_SET);
  
      tng_block_destroy(&block);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_file_headers_read(struct tng_trajectory* tng_data,
                                                              const char             hash_mode)
  {
      int64_t         prev_pos = 0;
      tng_gen_block_t block;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      tng_data->n_trajectory_frame_sets = 0;
  
      if (tng_input_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      fseeko(tng_data->input_file, 0, SEEK_SET);
  
      tng_block_init(&block);
      /* Non trajectory blocks (they come before the trajectory
       * blocks in the file) */
      while (prev_pos < tng_data->input_file_len && tng_block_header_read(tng_data, block) != TNG_CRITICAL
             && block->id != -1 && block->id != TNG_TRAJECTORY_FRAME_SET)
      {
          tng_block_read_next(tng_data, block, hash_mode);
          prev_pos = ftello(tng_data->input_file);
      }
  
      /* Go back if a trajectory block was encountered */
      if (block->id == TNG_TRAJECTORY_FRAME_SET)
      {
          fseeko(tng_data->input_file, prev_pos, SEEK_SET);
      }
  
      tng_block_destroy(&block);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_file_headers_write(struct tng_trajectory* tng_data,
                                                               const char             hash_mode)
  {
      int                 i;
      int64_t             len, orig_len, tot_len = 0, data_start_pos, temp_pos = -1;
      tng_function_status stat;
      tng_gen_block_t     block;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      if (tng_output_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      if (tng_data->n_trajectory_frame_sets > 0)
      {
          stat = tng_file_headers_len_get(tng_data, &orig_len);
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
  
          tng_block_init(&block);
          block->name = (char*)malloc(TNG_MAX_STR_LEN);
          if (!block->name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
          strcpy(block->name, "GENERAL INFO");
          tng_block_header_len_calculate(tng_data, block, &len);
          tot_len += len;
          tng_general_info_block_len_calculate(tng_data, &len);
          tot_len += len;
          strcpy(block->name, "MOLECULES");
          tng_block_header_len_calculate(tng_data, block, &len);
          tot_len += len;
          tng_molecules_block_len_calculate(tng_data, &len);
          tot_len += len;
  
          for (i = 0; i < tng_data->n_data_blocks; i++)
          {
              strcpy(block->name, tng_data->non_tr_data[i].block_name);
              tng_block_header_len_calculate(tng_data, block, &len);
              tot_len += len;
              tng_data_block_len_calculate(tng_data, (tng_data_t)&tng_data->non_tr_data[i], TNG_FALSE,
                                           1, 1, 1, 0, 1, &data_start_pos, &len);
              tot_len += len;
          }
          for (i = 0; i < tng_data->n_particle_data_blocks; i++)
          {
              strcpy(block->name, tng_data->non_tr_particle_data[i].block_name);
              tng_block_header_len_calculate(tng_data, block, &len);
              tot_len += len;
              tng_data_block_len_calculate(tng_data, &tng_data->non_tr_particle_data[i], TNG_TRUE, 1,
                                           1, 1, 0, tng_data->n_particles, &data_start_pos, &len);
              tot_len += len;
          }
          tng_block_destroy(&block);
  
          if (tot_len > orig_len)
          {
              tng_migrate_data_in_file(tng_data, orig_len + 1, tot_len - orig_len, hash_mode);
              tng_data->last_trajectory_frame_set_input_file_pos =
                      tng_data->last_trajectory_frame_set_output_file_pos;
          }
  
          stat = tng_reread_frame_set_at_file_pos(tng_data, tng_data->last_trajectory_frame_set_input_file_pos);
          if (stat == TNG_CRITICAL)
          {
              fprintf(stderr, "TNG library: Cannot read frame set. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
  
          /* In order to write non-trajectory data the current_trajectory_frame_set_output_file_pos
           * must temporarily be reset */
          temp_pos = tng_data->current_trajectory_frame_set_output_file_pos;
          tng_data->current_trajectory_frame_set_output_file_pos = -1;
      }
  
      if (tng_general_info_block_write(tng_data, hash_mode) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Error writing general info block of file %s. %s: %d\n",
                  tng_data->input_file_path, __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      if (tng_molecules_block_write(tng_data, hash_mode) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Error writing atom names block of file %s. %s: %d\n",
                  tng_data->input_file_path, __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      /* FIXME: Currently writing non-trajectory data blocks here.
       * Should perhaps be moved. */
      tng_block_init(&block);
      for (i = 0; i < tng_data->n_data_blocks; i++)
      {
          block->id = tng_data->non_tr_data[i].block_id;
          tng_data_block_write(tng_data, block, i, TNG_FALSE, 0, hash_mode);
      }
  
      for (i = 0; i < tng_data->n_particle_data_blocks; i++)
      {
          block->id = tng_data->non_tr_particle_data[i].block_id;
          tng_data_block_write(tng_data, block, i, TNG_TRUE, 0, hash_mode);
      }
  
      tng_block_destroy(&block);
  
      /* Continue writing at the end of the file. */
      fseeko(tng_data->output_file, 0, SEEK_END);
      if (temp_pos > 0)
      {
          tng_data->current_trajectory_frame_set_output_file_pos = temp_pos;
      }
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_block_read_next(struct tng_trajectory* tng_data,
                                                            struct tng_gen_block*  block,
                                                            const char             hash_mode)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(block, "TNG library: block must be initialised and must not be a NULL pointer.");
  
      switch (block->id)
      {
          case TNG_TRAJECTORY_FRAME_SET:
              return (tng_frame_set_block_read(tng_data, block, hash_mode));
          case TNG_PARTICLE_MAPPING:
              return (tng_trajectory_mapping_block_read(tng_data, block, hash_mode));
          case TNG_GENERAL_INFO: return (tng_general_info_block_read(tng_data, block, hash_mode));
          case TNG_MOLECULES: return (tng_molecules_block_read(tng_data, block, hash_mode));
          default:
              if (block->id >= TNG_TRAJ_BOX_SHAPE)
              {
                  return (tng_data_block_contents_read(tng_data, block, hash_mode));
              }
              else
              {
                  /* Skip to the next block */
                  fseeko(tng_data->input_file, block->block_contents_size, SEEK_CUR);
                  return (TNG_FAILURE);
              }
      }
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_frame_set_read(struct tng_trajectory* tng_data, const char hash_mode)
  {
      int64_t             file_pos;
      tng_gen_block_t     block;
      tng_function_status stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      if (tng_input_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      file_pos = ftello(tng_data->input_file);
  
      tng_block_init(&block);
  
      /* Read block headers first to see what block is found. */
      stat = tng_block_header_read(tng_data, block);
      if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET || block->id == -1)
      {
          fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                  file_pos, __FILE__, __LINE__);
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      tng_data->current_trajectory_frame_set_input_file_pos = file_pos;
  
      if (tng_block_read_next(tng_data, block, hash_mode) == TNG_SUCCESS)
      {
          tng_data->n_trajectory_frame_sets++;
          file_pos = ftello(tng_data->input_file);
          /* Read all blocks until next frame set block */
          stat = tng_block_header_read(tng_data, block);
          while (file_pos < tng_data->input_file_len && stat != TNG_CRITICAL
                 && block->id != TNG_TRAJECTORY_FRAME_SET && block->id != -1)
          {
              stat = tng_block_read_next(tng_data, block, hash_mode);
              if (stat != TNG_CRITICAL)
              {
                  file_pos = ftello(tng_data->input_file);
                  if (file_pos < tng_data->input_file_len)
                  {
                      stat = tng_block_header_read(tng_data, block);
                  }
              }
          }
          if (stat == TNG_CRITICAL)
          {
              fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                      file_pos, __FILE__, __LINE__);
              tng_block_destroy(&block);
              return (stat);
          }
  
          if (block->id == TNG_TRAJECTORY_FRAME_SET)
          {
              fseeko(tng_data->input_file, file_pos, SEEK_SET);
          }
      }
  
      tng_block_destroy(&block);
  
      return (TNG_SUCCESS);
  }
  
  
  tng_function_status DECLSPECDLLEXPORT
                      tng_frame_set_read_current_only_data_from_block_id(struct tng_trajectory* tng_data,
                                                                         const char             hash_mode,
                                                                         const int64_t          block_id)
  {
      int64_t             file_pos;
      tng_gen_block_t     block;
      tng_function_status stat;
      int                 found_flag = 1;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      if (tng_input_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      file_pos = tng_data->current_trajectory_frame_set_input_file_pos;
  
      if (file_pos < 0)
      {
          /* No current frame set. This means that the first frame set must be
           * read */
          found_flag = 0;
          file_pos   = tng_data->first_trajectory_frame_set_input_file_pos;
      }
  
      if (file_pos > 0)
      {
          fseeko(tng_data->input_file, file_pos, SEEK_SET);
      }
      else
      {
          return (TNG_FAILURE);
      }
  
      tng_block_init(&block);
  
      /* Read block headers first to see what block is found. */
      stat = tng_block_header_read(tng_data, block);
      if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
      {
          fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                  file_pos, __FILE__, __LINE__);
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
      /* If the current frame set had already been read skip its block contents */
      if (found_flag)
      {
          fseeko(tng_data->input_file, block->block_contents_size, SEEK_CUR);
      }
      /* Otherwise read the frame set block */
      else
      {
          stat = tng_block_read_next(tng_data, block, hash_mode);
          if (stat != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot read frame set block. %s: %d\n", __FILE__, __LINE__);
              tng_block_destroy(&block);
              return (stat);
          }
      }
      file_pos = ftello(tng_data->input_file);
  
      found_flag = 0;
  
      /* Read only blocks of the requested ID
       * until next frame set block */
      stat = tng_block_header_read(tng_data, block);
      while (file_pos < tng_data->input_file_len && stat != TNG_CRITICAL
             && block->id != TNG_TRAJECTORY_FRAME_SET && block->id != -1)
      {
          if (block->id == block_id)
          {
              stat = tng_block_read_next(tng_data, block, hash_mode);
              if (stat != TNG_CRITICAL)
              {
                  file_pos   = ftello(tng_data->input_file);
                  found_flag = 1;
                  if (file_pos < tng_data->input_file_len)
                  {
                      stat = tng_block_header_read(tng_data, block);
                  }
              }
          }
          else
          {
              file_pos += block->block_contents_size + block->header_contents_size;
              fseeko(tng_data->input_file, block->block_contents_size, SEEK_CUR);
              if (file_pos < tng_data->input_file_len)
              {
                  stat = tng_block_header_read(tng_data, block);
              }
          }
      }
      if (stat == TNG_CRITICAL)
      {
          fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                  file_pos, __FILE__, __LINE__);
          tng_block_destroy(&block);
          return (stat);
      }
  
      if (block->id == TNG_TRAJECTORY_FRAME_SET)
      {
          fseeko(tng_data->input_file, file_pos, SEEK_SET);
      }
  
      tng_block_destroy(&block);
  
      if (found_flag)
      {
          return (TNG_SUCCESS);
      }
      else
      {
          return (TNG_FAILURE);
      }
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_frame_set_read_next(struct tng_trajectory* tng_data,
                                                                const char             hash_mode)
  {
      int64_t file_pos;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      if (tng_input_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      file_pos = tng_data->current_trajectory_frame_set.next_frame_set_file_pos;
  
      if (file_pos < 0 && tng_data->current_trajectory_frame_set_input_file_pos <= 0)
      {
          file_pos = tng_data->first_trajectory_frame_set_input_file_pos;
      }
  
      if (file_pos > 0)
      {
          fseeko(tng_data->input_file, file_pos, SEEK_SET);
      }
      else
      {
          return (TNG_FAILURE);
      }
  
      return (tng_frame_set_read(tng_data, hash_mode));
  }
  
  tng_function_status DECLSPECDLLEXPORT
                      tng_frame_set_read_next_only_data_from_block_id(struct tng_trajectory* tng_data,
                                                                      const char             hash_mode,
                                                                      const int64_t          block_id)
  {
      int64_t             file_pos;
      tng_gen_block_t     block;
      tng_function_status stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      if (tng_input_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      file_pos = tng_data->current_trajectory_frame_set.next_frame_set_file_pos;
  
      if (file_pos < 0 && tng_data->current_trajectory_frame_set_input_file_pos <= 0)
      {
          file_pos = tng_data->first_trajectory_frame_set_input_file_pos;
      }
  
      if (file_pos > 0)
      {
          fseeko(tng_data->input_file, file_pos, SEEK_SET);
      }
      else
      {
          return (TNG_FAILURE);
      }
  
      tng_block_init(&block);
  
      /* Read block headers first to see what block is found. */
      stat = tng_block_header_read(tng_data, block);
      if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
      {
          fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                  file_pos, __FILE__, __LINE__);
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      tng_data->current_trajectory_frame_set_input_file_pos = file_pos;
  
      if (tng_block_read_next(tng_data, block, hash_mode) == TNG_SUCCESS)
      {
          stat = tng_frame_set_read_current_only_data_from_block_id(tng_data, hash_mode, block_id);
      }
  
      tng_block_destroy(&block);
  
      return (stat);
  }
  
  tng_function_status tng_frame_set_write(struct tng_trajectory* tng_data, const char hash_mode)
  {
      int                        i, j;
      tng_gen_block_t            block;
      tng_trajectory_frame_set_t frame_set;
      tng_function_status        stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      if (frame_set->n_written_frames == frame_set->n_frames)
      {
          return (TNG_SUCCESS);
      }
  
      tng_data->current_trajectory_frame_set_output_file_pos = ftello(tng_data->output_file);
      tng_data->last_trajectory_frame_set_output_file_pos =
              tng_data->current_trajectory_frame_set_output_file_pos;
  
      if (tng_data->current_trajectory_frame_set_output_file_pos <= 0)
      {
          return (TNG_FAILURE);
      }
  
      if (tng_data->first_trajectory_frame_set_output_file_pos == -1)
      {
          tng_data->first_trajectory_frame_set_output_file_pos =
                  tng_data->current_trajectory_frame_set_output_file_pos;
      }
  
      tng_block_init(&block);
  
      if (tng_frame_set_block_write(tng_data, block, hash_mode) != TNG_SUCCESS)
      {
          tng_block_destroy(&block);
          return (TNG_FAILURE);
      }
  
      /* Write non-particle data blocks */
      for (i = 0; i < frame_set->n_data_blocks; i++)
      {
          block->id = frame_set->tr_data[i].block_id;
          tng_data_block_write(tng_data, block, i, TNG_FALSE, 0, hash_mode);
      }
      /* Write the mapping blocks and particle data blocks*/
      if (frame_set->n_mapping_blocks)
      {
          for (i = 0; i < frame_set->n_mapping_blocks; i++)
          {
              block->id = TNG_PARTICLE_MAPPING;
              if (frame_set->mappings[i].n_particles > 0)
              {
                  tng_trajectory_mapping_block_write(tng_data, block, i, hash_mode);
                  for (j = 0; j < frame_set->n_particle_data_blocks; j++)
                  {
                      block->id = frame_set->tr_particle_data[j].block_id;
                      tng_data_block_write(tng_data, block, j, TNG_TRUE, &frame_set->mappings[i], hash_mode);
                  }
              }
          }
      }
      else
      {
          for (i = 0; i < frame_set->n_particle_data_blocks; i++)
          {
              block->id = frame_set->tr_particle_data[i].block_id;
              tng_data_block_write(tng_data, block, i, TNG_TRUE, 0, hash_mode);
          }
      }
  
  
      /* Update pointers in the general info block */
      stat = tng_header_pointers_update(tng_data, hash_mode);
  
      if (stat == TNG_SUCCESS)
      {
          stat = tng_frame_set_pointers_update(tng_data, hash_mode);
      }
  
      tng_block_destroy(&block);
  
      frame_set->n_unwritten_frames = 0;
  
      fflush(tng_data->output_file);
  
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_frame_set_premature_write(struct tng_trajectory* tng_data,
                                                                      const char hash_mode)
  {
      tng_trajectory_frame_set_t frame_set;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      if (frame_set->n_unwritten_frames == 0)
      {
          return (TNG_SUCCESS);
      }
      frame_set->n_frames = frame_set->n_unwritten_frames;
  
      return (tng_frame_set_write(tng_data, hash_mode));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_frame_set_new(struct tng_trajectory* tng_data,
                                                          const int64_t          first_frame,
                                                          const int64_t          n_frames)
  {
      tng_gen_block_t            block;
      tng_trajectory_frame_set_t frame_set;
      FILE*                      temp = tng_data->input_file;
      int64_t                    curr_file_pos;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(first_frame >= 0, "TNG library: first_frame must be >= 0.");
      TNG_ASSERT(n_frames >= 0, "TNG library: n_frames must be >= 0.");
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      curr_file_pos = ftello(tng_data->output_file);
  
      if (curr_file_pos <= 10)
      {
          tng_file_headers_write(tng_data, TNG_USE_HASH);
      }
  
      /* Set pointer to previous frame set to the one that was loaded
       * before.
       * FIXME: This is a bit risky. If they are not added in order
       * it will be wrong. */
      if (tng_data->n_trajectory_frame_sets)
      {
          frame_set->prev_frame_set_file_pos = tng_data->last_trajectory_frame_set_output_file_pos;
      }
  
      frame_set->next_frame_set_file_pos = -1;
  
      tng_data->current_trajectory_frame_set_output_file_pos = ftello(tng_data->output_file);
  
      tng_data->n_trajectory_frame_sets++;
  
      /* Set the medium range pointers */
      if (tng_data->n_trajectory_frame_sets == tng_data->medium_stride_length + 1)
      {
          frame_set->medium_stride_prev_frame_set_file_pos =
                  tng_data->first_trajectory_frame_set_output_file_pos;
      }
      else if (tng_data->n_trajectory_frame_sets > tng_data->medium_stride_length + 1)
      {
          /* FIXME: Currently only working if the previous frame set has its
           * medium stride pointer already set. This might need some fixing. */
          if (frame_set->medium_stride_prev_frame_set_file_pos != -1
              && frame_set->medium_stride_prev_frame_set_file_pos != 0)
          {
              tng_block_init(&block);
              tng_data->input_file = tng_data->output_file;
  
              curr_file_pos = ftello(tng_data->output_file);
              fseeko(tng_data->output_file, frame_set->medium_stride_prev_frame_set_file_pos, SEEK_SET);
  
              if (tng_block_header_read(tng_data, block) != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot read frame set header. %s: %d\n", __FILE__, __LINE__);
                  tng_data->input_file = temp;
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              /* Read the next frame set from the previous frame set and one
               * medium stride step back */
              fseeko(tng_data->output_file,
                     block->block_contents_size - (6 * sizeof(int64_t) + 2 * sizeof(double)), SEEK_CUR);
              if (fread(&frame_set->medium_stride_prev_frame_set_file_pos,
                        sizeof(frame_set->medium_stride_prev_frame_set_file_pos), 1, tng_data->output_file)
                  == 0)
              {
                  fprintf(stderr, "TNG library: Cannot read block. %s: %d\n", __FILE__, __LINE__);
                  tng_data->input_file = temp;
                  tng_block_destroy(&block);
                  return (TNG_CRITICAL);
              }
  
              if (tng_data->input_endianness_swap_func_64)
              {
                  if (tng_data->input_endianness_swap_func_64(
                              tng_data, (uint64_t*)&frame_set->medium_stride_prev_frame_set_file_pos)
                      != TNG_SUCCESS)
                  {
                      fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n", __FILE__, __LINE__);
                  }
              }
  
              tng_block_destroy(&block);
  
              /* Set the long range pointers */
              if (tng_data->n_trajectory_frame_sets == tng_data->long_stride_length + 1)
              {
                  frame_set->long_stride_prev_frame_set_file_pos =
                          tng_data->first_trajectory_frame_set_output_file_pos;
              }
              else if (tng_data->n_trajectory_frame_sets > tng_data->medium_stride_length + 1)
              {
                  /* FIXME: Currently only working if the previous frame set has its
                   * long stride pointer already set. This might need some fixing. */
                  if (frame_set->long_stride_prev_frame_set_file_pos != -1
                      && frame_set->long_stride_prev_frame_set_file_pos != 0)
                  {
                      tng_block_init(&block);
                      tng_data->input_file = tng_data->output_file;
  
                      fseeko(tng_data->output_file, frame_set->long_stride_prev_frame_set_file_pos, SEEK_SET);
  
                      if (tng_block_header_read(tng_data, block) != TNG_SUCCESS)
                      {
                          fprintf(stderr, "TNG library: Cannot read frame set header. %s: %d\n",
                                  __FILE__, __LINE__);
                          tng_data->input_file = temp;
                          tng_block_destroy(&block);
                          return (TNG_CRITICAL);
                      }
  
                      /* Read the next frame set from the previous frame set and one
                       * long stride step back */
                      fseeko(tng_data->output_file,
                             block->block_contents_size - (6 * sizeof(int64_t) + 2 * sizeof(double)),
                             SEEK_CUR);
  
                      tng_block_destroy(&block);
  
                      if (fread(&frame_set->long_stride_prev_frame_set_file_pos,
                                sizeof(frame_set->long_stride_prev_frame_set_file_pos), 1, tng_data->output_file)
                          == 0)
                      {
                          fprintf(stderr, "TNG library: Cannot read block. %s: %d\n", __FILE__, __LINE__);
                          tng_data->input_file = temp;
                          return (TNG_CRITICAL);
                      }
  
                      if (tng_data->input_endianness_swap_func_64)
                      {
                          if (tng_data->input_endianness_swap_func_64(
                                      tng_data, (uint64_t*)&frame_set->long_stride_prev_frame_set_file_pos)
                              != TNG_SUCCESS)
                          {
                              fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n",
                                      __FILE__, __LINE__);
                          }
                      }
                  }
              }
  
              tng_data->input_file = temp;
              fseeko(tng_data->output_file, curr_file_pos, SEEK_SET);
          }
      }
  
      frame_set->first_frame        = first_frame;
      frame_set->n_frames           = n_frames;
      frame_set->n_written_frames   = 0;
      frame_set->n_unwritten_frames = 0;
      frame_set->first_frame_time   = -1;
  
      if (tng_data->first_trajectory_frame_set_output_file_pos == -1
          || tng_data->first_trajectory_frame_set_output_file_pos == 0)
      {
          tng_data->first_trajectory_frame_set_output_file_pos =
                  tng_data->current_trajectory_frame_set_output_file_pos;
      }
      /* FIXME: Should check the frame number instead of the file_pos,
       * in case frame sets are not in order */
      if (tng_data->last_trajectory_frame_set_output_file_pos == -1
          || tng_data->last_trajectory_frame_set_output_file_pos == 0
          || tng_data->last_trajectory_frame_set_output_file_pos
                     < tng_data->current_trajectory_frame_set_output_file_pos)
      {
          tng_data->last_trajectory_frame_set_output_file_pos =
                  tng_data->current_trajectory_frame_set_output_file_pos;
      }
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_frame_set_with_time_new(struct tng_trajectory* tng_data,
                                                                    const int64_t first_frame,
                                                                    const int64_t n_frames,
                                                                    const double  first_frame_time)
  {
      tng_function_status stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(first_frame >= 0, "TNG library: first_frame must be >= 0.");
      TNG_ASSERT(n_frames >= 0, "TNG library: n_frames must be >= 0.");
      TNG_ASSERT(first_frame_time >= 0, "TNG library: first_frame_time must be >= 0.");
  
  
      stat = tng_frame_set_new(tng_data, first_frame, n_frames);
      if (stat != TNG_SUCCESS)
      {
          return (stat);
      }
      stat = tng_frame_set_first_frame_time_set(tng_data, first_frame_time);
  
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_frame_set_first_frame_time_set(struct tng_trajectory* tng_data,
                                                                           const double first_frame_time)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(first_frame_time >= 0, "TNG library: first_frame_time must be >= 0.");
  
      tng_data->current_trajectory_frame_set.first_frame_time = first_frame_time;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_first_frame_nr_of_next_frame_set_get(struct tng_trajectory* tng_data,
                                                                                 int64_t* frame)
  {
      int64_t             file_pos, next_frame_set_file_pos;
      tng_gen_block_t     block;
      tng_function_status stat;
  
      tng_trajectory_frame_set_t frame_set;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(tng_data->input_file,
                 "TNG library: An input file must be open to find the next frame set");
      TNG_ASSERT(frame, "TNG library: frame must not be a NULL pointer");
  
      file_pos = ftello(tng_data->input_file);
  
      if (tng_data->current_trajectory_frame_set_input_file_pos <= 0)
      {
          next_frame_set_file_pos = tng_data->first_trajectory_frame_set_input_file_pos;
      }
      else
      {
          frame_set               = &tng_data->current_trajectory_frame_set;
          next_frame_set_file_pos = frame_set->next_frame_set_file_pos;
      }
  
      if (next_frame_set_file_pos <= 0)
      {
          return (TNG_FAILURE);
      }
  
      fseeko(tng_data->input_file, next_frame_set_file_pos, SEEK_SET);
      /* Read block headers first to see that a frame set block is found. */
      tng_block_init(&block);
      stat = tng_block_header_read(tng_data, block);
      if (stat == TNG_CRITICAL || block->id != TNG_TRAJECTORY_FRAME_SET)
      {
          fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                  file_pos, __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
      /*    if(tng_data->current_trajectory_frame_set_input_file_pos <= 0)
          {
              tng_block_read_next(tng_data, block, TNG_USE_HASH);
          }*/
      tng_block_destroy(&block);
  
      if (fread(frame, sizeof(int64_t), 1, tng_data->input_file) == 0)
      {
          fprintf(stderr, "TNG library: Cannot read first frame of next frame set. %s: %d\n",
                  __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
      fseeko(tng_data->input_file, file_pos, SEEK_SET);
  
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_gen_data_block_add(struct tng_trajectory* tng_data,
                                                    const int64_t          id,
                                                    const tng_bool         is_particle_data,
                                                    const char*            block_name,
                                                    const char             datatype,
                                                    const char             block_type_flag,
                                                    int64_t                n_frames,
                                                    const int64_t          n_values_per_frame,
                                                    int64_t                stride_length,
                                                    const int64_t          num_first_particle,
                                                    const int64_t          n_particles,
                                                    const int64_t          codec_id,
                                                    void*                  new_data)
  {
      int                        i, size, len;
      int64_t                    j, k;
      int64_t                    tot_n_particles, n_frames_div;
      char ***                   first_dim_values, **second_dim_values;
      tng_trajectory_frame_set_t frame_set;
      tng_data_t                 data;
      char*                      new_data_c = (char*)new_data;
      tng_function_status        stat;
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      if (stride_length <= 0)
      {
          stride_length = 1;
      }
  
      if (is_particle_data)
      {
          stat = tng_particle_data_find(tng_data, id, &data);
      }
      else
      {
          stat = tng_data_find(tng_data, id, &data);
      }
      /* If the block does not exist, create it */
      if (stat != TNG_SUCCESS)
      {
          if (is_particle_data)
          {
              stat = tng_particle_data_block_create(tng_data, block_type_flag);
          }
          else
          {
              stat = tng_data_block_create(tng_data, block_type_flag);
          }
  
          if (stat != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot create particle data block. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          if (is_particle_data)
          {
              if (block_type_flag == TNG_TRAJECTORY_BLOCK)
              {
                  data = &frame_set->tr_particle_data[frame_set->n_particle_data_blocks - 1];
              }
              else
              {
                  data = &tng_data->non_tr_particle_data[tng_data->n_particle_data_blocks - 1];
              }
          }
          else
          {
              if (block_type_flag == TNG_TRAJECTORY_BLOCK)
              {
                  data = &frame_set->tr_data[frame_set->n_data_blocks - 1];
              }
              else
              {
                  data = &tng_data->non_tr_data[tng_data->n_data_blocks - 1];
              }
          }
          data->block_id = id;
  
          data->block_name = (char*)malloc(strlen(block_name) + 1);
          if (!data->block_name)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              return (TNG_CRITICAL);
          }
          strncpy(data->block_name, block_name, strlen(block_name) + 1);
  
          data->values = 0;
          /* FIXME: Memory leak from strings. */
          data->strings              = 0;
          data->last_retrieved_frame = -1;
      }
  
      data->datatype           = datatype;
      data->stride_length      = tng_max_i64(stride_length, 1);
      data->n_values_per_frame = n_values_per_frame;
      data->n_frames           = n_frames;
      if (is_particle_data)
      {
          data->dependency = TNG_PARTICLE_DEPENDENT;
      }
      else
      {
          data->dependency = 0;
      }
      if (block_type_flag == TNG_TRAJECTORY_BLOCK
          && (n_frames > 1 || frame_set->n_frames == n_frames || stride_length > 1))
      {
          data->dependency += TNG_FRAME_DEPENDENT;
      }
      data->codec_id               = codec_id;
      data->compression_multiplier = 1.0;
      /* FIXME: This can cause problems. */
      data->first_frame_with_data = frame_set->first_frame;
  
      if (is_particle_data)
      {
          if (block_type_flag == TNG_TRAJECTORY_BLOCK && tng_data->var_num_atoms_flag)
          {
              tot_n_particles = frame_set->n_particles;
          }
          else
          {
              tot_n_particles = tng_data->n_particles;
          }
      }
      /* This is just to keep the compiler happy - avoid it considering tot_n_particles
       * uninitialized. */
      else
      {
          tot_n_particles = 0;
      }
  
      /* If data values are supplied add that data to the data block. */
      if (new_data_c)
      {
          /* Allocate memory */
          if (is_particle_data)
          {
              stat = tng_allocate_particle_data_mem(tng_data, data, n_frames, stride_length,
                                                    tot_n_particles, n_values_per_frame);
          }
          else
          {
              stat = tng_allocate_data_mem(tng_data, data, n_frames, stride_length, n_values_per_frame);
          }
          if (stat != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot allocate particle data memory. %s: %d\n", __FILE__,
                      __LINE__);
              return (TNG_CRITICAL);
          }
  
          if (n_frames > frame_set->n_unwritten_frames)
          {
              frame_set->n_unwritten_frames = n_frames;
          }
  
          n_frames_div = (n_frames - 1) / stride_length + 1;
  
          if (datatype == TNG_CHAR_DATA)
          {
              if (is_particle_data)
              {
                  for (i = 0; i < n_frames_div; i++)
                  {
                      first_dim_values = data->strings[i];
                      for (j = num_first_particle; j < num_first_particle + n_particles; j++)
                      {
                          second_dim_values = first_dim_values[j];
                          for (k = 0; k < n_values_per_frame; k++)
                          {
                              len = tng_min_size(strlen(new_data_c) + 1, TNG_MAX_STR_LEN);
                              if (second_dim_values[k])
                              {
                                  free(second_dim_values[k]);
                              }
                              second_dim_values[k] = (char*)malloc(len);
                              if (!second_dim_values[k])
                              {
                                  fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n",
                                          __FILE__, __LINE__);
                                  return (TNG_CRITICAL);
                              }
                              strncpy(second_dim_values[k], new_data_c, len);
                              new_data_c += len;
                          }
                      }
                  }
              }
              else
              {
                  for (i = 0; i < n_frames_div; i++)
                  {
                      second_dim_values = data->strings[0][i];
                      for (j = 0; j < n_values_per_frame; j++)
                      {
                          len = tng_min_size(strlen(new_data_c) + 1, TNG_MAX_STR_LEN);
                          if (second_dim_values[j])
                          {
                              free(second_dim_values[j]);
                          }
                          second_dim_values[j] = (char*)malloc(len);
                          if (!second_dim_values[j])
                          {
                              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n",
                                      __FILE__, __LINE__);
                              return (TNG_CRITICAL);
                          }
                          strncpy(second_dim_values[j], new_data_c, len);
                          new_data_c += len;
                      }
                  }
              }
          }
          else
          {
              switch (datatype)
              {
                  case TNG_INT_DATA: size = sizeof(int64_t); break;
                  case TNG_FLOAT_DATA: size = sizeof(float); break;
                  case TNG_DOUBLE_DATA:
                  default: size = sizeof(double);
              }
  
              if (is_particle_data)
              {
                  memcpy(data->values, new_data, size * n_frames_div * n_particles * n_values_per_frame);
              }
              else
              {
                  memcpy(data->values, new_data, size * n_frames_div * n_values_per_frame);
              }
          }
      }
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_data_block_add(struct tng_trajectory* tng_data,
                                                           const int64_t          id,
                                                           const char*            block_name,
                                                           const char             datatype,
                                                           const char             block_type_flag,
                                                           int64_t                n_frames,
                                                           const int64_t          n_values_per_frame,
                                                           int64_t                stride_length,
                                                           const int64_t          codec_id,
                                                           void*                  new_data)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(block_name, "TNG library: block_name must not be a NULL pointer.");
      TNG_ASSERT(n_values_per_frame > 0,
                 "TNG library: n_values_per_frame must be a positive integer.");
  
      return (tng_gen_data_block_add(tng_data, id, TNG_FALSE, block_name, datatype, block_type_flag, n_frames,
                                     n_values_per_frame, stride_length, 0, 0, codec_id, new_data));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_particle_data_block_add(struct tng_trajectory* tng_data,
                                                                    const int64_t          id,
                                                                    const char*            block_name,
                                                                    const char             datatype,
                                                                    const char    block_type_flag,
                                                                    int64_t       n_frames,
                                                                    const int64_t n_values_per_frame,
                                                                    int64_t       stride_length,
                                                                    const int64_t num_first_particle,
                                                                    const int64_t n_particles,
                                                                    const int64_t codec_id,
                                                                    void*         new_data)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(block_name, "TNG library: block_name mustnot be a NULL pointer.");
      TNG_ASSERT(n_values_per_frame > 0,
                 "TNG library: n_values_per_frame must be a positive integer.");
      TNG_ASSERT(num_first_particle >= 0, "TNG library: num_first_particle must be >= 0.");
      TNG_ASSERT(n_particles >= 0, "TNG library: n_particles must be >= 0.");
  
      return (tng_gen_data_block_add(tng_data, id, TNG_TRUE, block_name, datatype, block_type_flag,
                                     n_frames, n_values_per_frame, stride_length, num_first_particle,
                                     n_particles, codec_id, new_data));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_data_block_name_get(struct tng_trajectory* tng_data,
                                                                const int64_t          block_id,
                                                                char*                  name,
                                                                const int              max_len)
  {
      int64_t                    i;
      tng_trajectory_frame_set_t frame_set;
      tng_function_status        stat;
      tng_data_t                 data;
      int                        block_type = -1;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(name, "TNG library: name must not be a NULL pointer.");
  
      for (i = 0; i < tng_data->n_particle_data_blocks; i++)
      {
          data = &tng_data->non_tr_particle_data[i];
          if (data->block_id == block_id)
          {
              strncpy(name, data->block_name, max_len);
              name[max_len - 1] = '\0';
              return (TNG_SUCCESS);
          }
      }
      for (i = 0; i < tng_data->n_data_blocks; i++)
      {
          data = &tng_data->non_tr_data[i];
          if (data->block_id == block_id)
          {
              strncpy(name, data->block_name, max_len);
              name[max_len - 1] = '\0';
              return (TNG_SUCCESS);
          }
      }
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      stat = tng_particle_data_find(tng_data, block_id, &data);
      if (stat == TNG_SUCCESS)
      {
          block_type = TNG_PARTICLE_BLOCK_DATA;
      }
      else
      {
          stat = tng_data_find(tng_data, block_id, &data);
          if (stat == TNG_SUCCESS)
          {
              block_type = TNG_NON_PARTICLE_BLOCK_DATA;
          }
          else
          {
              stat = tng_frame_set_read_current_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
              if (stat != TNG_SUCCESS)
              {
                  return (stat);
              }
              stat = tng_particle_data_find(tng_data, block_id, &data);
              if (stat == TNG_SUCCESS)
              {
                  block_type = TNG_PARTICLE_BLOCK_DATA;
              }
              else
              {
                  stat = tng_data_find(tng_data, block_id, &data);
                  if (stat == TNG_SUCCESS)
                  {
                      block_type = TNG_NON_PARTICLE_BLOCK_DATA;
                  }
              }
          }
      }
      if (block_type == TNG_PARTICLE_BLOCK_DATA)
      {
          for (i = 0; i < frame_set->n_particle_data_blocks; i++)
          {
              data = &frame_set->tr_particle_data[i];
              if (data->block_id == block_id)
              {
                  strncpy(name, data->block_name, max_len);
                  name[max_len - 1] = '\0';
                  return (TNG_SUCCESS);
              }
          }
      }
      else if (block_type == TNG_NON_PARTICLE_BLOCK_DATA)
      {
          for (i = 0; i < frame_set->n_data_blocks; i++)
          {
              data = &frame_set->tr_data[i];
              if (data->block_id == block_id)
              {
                  strncpy(name, data->block_name, max_len);
                  name[max_len - 1] = '\0';
                  return (TNG_SUCCESS);
              }
          }
      }
  
      return (TNG_FAILURE);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_data_block_dependency_get(struct tng_trajectory* tng_data,
                                                                      const int64_t          block_id,
                                                                      int* block_dependency)
  {
      int64_t             i;
      tng_function_status stat;
      tng_data_t          data;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(block_dependency, "TNG library: block_dependency must not be a NULL pointer.");
  
      for (i = 0; i < tng_data->n_particle_data_blocks; i++)
      {
          data = &tng_data->non_tr_particle_data[i];
          if (data->block_id == block_id)
          {
              *block_dependency = TNG_PARTICLE_DEPENDENT;
              return (TNG_SUCCESS);
          }
      }
      for (i = 0; i < tng_data->n_data_blocks; i++)
      {
          data = &tng_data->non_tr_data[i];
          if (data->block_id == block_id)
          {
              *block_dependency = 0;
              return (TNG_SUCCESS);
          }
      }
  
      stat = tng_particle_data_find(tng_data, block_id, &data);
      if (stat == TNG_SUCCESS)
      {
          *block_dependency = TNG_PARTICLE_DEPENDENT + TNG_FRAME_DEPENDENT;
          return (TNG_SUCCESS);
      }
      else
      {
          stat = tng_data_find(tng_data, block_id, &data);
          if (stat == TNG_SUCCESS)
          {
              *block_dependency = TNG_FRAME_DEPENDENT;
              return (TNG_SUCCESS);
          }
          else
          {
              stat = tng_frame_set_read_current_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
              if (stat != TNG_SUCCESS)
              {
                  return (stat);
              }
              stat = tng_particle_data_find(tng_data, block_id, &data);
              if (stat == TNG_SUCCESS)
              {
                  *block_dependency = TNG_PARTICLE_DEPENDENT + TNG_FRAME_DEPENDENT;
                  return (TNG_SUCCESS);
              }
              else
              {
                  stat = tng_data_find(tng_data, block_id, &data);
                  if (stat == TNG_SUCCESS)
                  {
                      *block_dependency = TNG_FRAME_DEPENDENT;
                      return (TNG_SUCCESS);
                  }
              }
          }
      }
  
      return (TNG_FAILURE);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_data_block_num_values_per_frame_get(struct tng_trajectory* tng_data,
                                                                                const int64_t block_id,
                                                                                int64_t* n_values_per_frame)
  {
      int64_t             i;
      tng_function_status stat;
      tng_data_t          data;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(n_values_per_frame, "TNG library: n_values_per_frame must not be a NULL pointer.");
  
      for (i = 0; i < tng_data->n_particle_data_blocks; i++)
      {
          data = &tng_data->non_tr_particle_data[i];
          if (data->block_id == block_id)
          {
              *n_values_per_frame = data->n_values_per_frame;
              return (TNG_SUCCESS);
          }
      }
      for (i = 0; i < tng_data->n_data_blocks; i++)
      {
          data = &tng_data->non_tr_data[i];
          if (data->block_id == block_id)
          {
              *n_values_per_frame = data->n_values_per_frame;
              return (TNG_SUCCESS);
          }
      }
  
      stat = tng_particle_data_find(tng_data, block_id, &data);
      if (stat == TNG_SUCCESS)
      {
          *n_values_per_frame = data->n_values_per_frame;
          return (TNG_SUCCESS);
      }
      else
      {
          stat = tng_data_find(tng_data, block_id, &data);
          if (stat == TNG_SUCCESS)
          {
              *n_values_per_frame = data->n_values_per_frame;
              return (TNG_SUCCESS);
          }
          else
          {
              stat = tng_frame_set_read_current_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
              if (stat != TNG_SUCCESS)
              {
                  return (stat);
              }
              stat = tng_particle_data_find(tng_data, block_id, &data);
              if (stat == TNG_SUCCESS)
              {
                  *n_values_per_frame = data->n_values_per_frame;
                  return (TNG_SUCCESS);
              }
              else
              {
                  stat = tng_data_find(tng_data, block_id, &data);
                  if (stat == TNG_SUCCESS)
                  {
                      *n_values_per_frame = data->n_values_per_frame;
                      return (TNG_SUCCESS);
                  }
              }
          }
      }
  
      return (TNG_FAILURE);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_frame_set_n_frames_of_data_block_get(struct tng_trajectory* tng_data,
                                                                                 const int64_t block_id,
                                                                                 int64_t* n_frames)
  {
      tng_gen_block_t     block;
      tng_function_status stat;
      char                datatype, dependency, sparse_data;
      int64_t             n_values, codec_id, first_frame_with_data, stride_length, curr_n_frames;
      int64_t             num_first_particle, block_n_particles;
      double              multiplier;
      md5_state_t         md5_state;
      int                 found = TNG_FALSE;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      tng_block_init(&block);
  
      stat = tng_block_header_read(tng_data, block);
      /* If the block header could not be read the reading position might not have been
       * at the start of a block. Try again from the file position of the current frame
       * set. */
      if (stat != TNG_SUCCESS)
      {
          fseeko(tng_data->input_file, tng_data->current_trajectory_frame_set_input_file_pos, SEEK_SET);
          stat = tng_block_header_read(tng_data, block);
          if (stat != TNG_SUCCESS)
          {
              tng_block_destroy(&block);
              return (stat);
          }
      }
      if (block->id == TNG_TRAJECTORY_FRAME_SET)
      {
          stat = tng_block_read_next(tng_data, block, TNG_SKIP_HASH);
          if (stat != TNG_SUCCESS)
          {
              tng_block_destroy(&block);
              return (stat);
          }
          stat = tng_block_header_read(tng_data, block);
      }
      while (stat == TNG_SUCCESS && block->id != TNG_TRAJECTORY_FRAME_SET && found == TNG_FALSE)
      {
          if (block->id == block_id)
          {
              stat = tng_data_block_meta_information_read(
                      tng_data, &datatype, &dependency, &sparse_data, &n_values, &codec_id,
                      &first_frame_with_data, &stride_length, &curr_n_frames, &num_first_particle,
                      &block_n_particles, &multiplier, TNG_SKIP_HASH, &md5_state);
              if (stat == TNG_SUCCESS)
              {
                  found = TNG_TRUE;
              }
          }
          else
          {
              fseeko(tng_data->input_file, block->block_contents_size, SEEK_CUR);
              stat = tng_block_header_read(tng_data, block);
          }
      }
      if (found == TNG_TRUE)
      {
          *n_frames = (tng_data->current_trajectory_frame_set.n_frames
                       - (tng_data->current_trajectory_frame_set.first_frame - first_frame_with_data))
                      / stride_length;
      }
      else if (stat == TNG_SUCCESS)
      {
          *n_frames = 0;
      }
  
      tng_block_destroy(&block);
  
      return (stat);
  }
  
  static tng_function_status tng_frame_gen_data_write(struct tng_trajectory* tng_data,
                                                      const int64_t          frame_nr,
                                                      const int64_t          block_id,
                                                      const tng_bool         is_particle_data,
                                                      const int64_t          val_first_particle,
                                                      const int64_t          val_n_particles,
                                                      const void*            values,
                                                      const char             hash_mode)
  {
      int64_t                    header_pos, file_pos, tot_n_particles;
      int64_t                    output_file_len, n_values_per_frame, size, contents_size;
      int64_t                    header_size, temp_first, temp_last;
      int64_t                    mapping_block_end_pos, num_first_particle, block_n_particles;
      int64_t                    i, last_frame, temp_current, write_n_particles;
      tng_gen_block_t            block;
      tng_trajectory_frame_set_t frame_set;
      FILE*                      temp = tng_data->input_file;
      struct tng_data            data;
      tng_function_status        stat;
      tng_particle_mapping_t     mapping;
      char                       dependency, sparse_data, datatype;
      void*                      copy;
  
      if (tng_output_file_init(tng_data) != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot initialise destination file. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      temp_first   = tng_data->first_trajectory_frame_set_input_file_pos;
      temp_last    = tng_data->last_trajectory_frame_set_input_file_pos;
      temp_current = tng_data->current_trajectory_frame_set_input_file_pos;
      tng_data->first_trajectory_frame_set_input_file_pos =
              tng_data->first_trajectory_frame_set_output_file_pos;
      tng_data->last_trajectory_frame_set_input_file_pos = tng_data->last_trajectory_frame_set_output_file_pos;
      tng_data->current_trajectory_frame_set_input_file_pos =
              tng_data->current_trajectory_frame_set_output_file_pos;
  
      tng_data->input_file = tng_data->output_file;
  
      stat = tng_frame_set_of_frame_find(tng_data, frame_nr);
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      if (stat != TNG_SUCCESS)
      {
          last_frame = frame_set->first_frame + frame_set->n_frames - 1;
          /* If the wanted frame would be in the frame set after the last
           * frame set create a new frame set. */
          if (stat == TNG_FAILURE && last_frame < frame_nr)
          /*           (last_frame < frame_nr &&
                      tng_data->current_trajectory_frame_set.first_frame +
                      tng_data->frame_set_n_frames >= frame_nr))*/
          {
              if (last_frame + tng_data->frame_set_n_frames < frame_nr)
              {
                  last_frame = frame_nr - 1;
              }
              tng_frame_set_new(tng_data, last_frame + 1, tng_data->frame_set_n_frames);
              file_pos = ftello(tng_data->output_file);
              fseeko(tng_data->output_file, 0, SEEK_END);
              output_file_len = ftello(tng_data->output_file);
              fseeko(tng_data->output_file, file_pos, SEEK_SET);
  
              /* Read mapping blocks from the last frame set */
              tng_block_init(&block);
  
              stat = tng_block_header_read(tng_data, block);
              while (file_pos < output_file_len && stat != TNG_CRITICAL
                     && block->id != TNG_TRAJECTORY_FRAME_SET && block->id != -1)
              {
                  if (block->id == TNG_PARTICLE_MAPPING)
                  {
                      tng_trajectory_mapping_block_read(tng_data, block, hash_mode);
                  }
                  else
                  {
                      fseeko(tng_data->output_file, block->block_contents_size, SEEK_CUR);
                  }
                  file_pos = ftello(tng_data->output_file);
                  if (file_pos < output_file_len)
                  {
                      stat = tng_block_header_read(tng_data, block);
                  }
              }
  
              tng_block_destroy(&block);
              /* Write the frame set to disk */
              if (tng_frame_set_write(tng_data, hash_mode) != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error writing frame set. %s: %d\n", __FILE__, __LINE__);
                  return (TNG_CRITICAL);
              }
          }
          else
          {
              tng_data->input_file                                  = temp;
              tng_data->first_trajectory_frame_set_input_file_pos   = temp_first;
              tng_data->last_trajectory_frame_set_input_file_pos    = temp_last;
              tng_data->current_trajectory_frame_set_input_file_pos = temp_current;
              return (stat);
          }
      }
  
      tng_block_init(&block);
  
      file_pos = tng_data->current_trajectory_frame_set_output_file_pos;
  
      fseeko(tng_data->output_file, 0, SEEK_END);
      output_file_len = ftello(tng_data->output_file);
      fseeko(tng_data->output_file, file_pos, SEEK_SET);
  
      /* Read past the frame set block first */
      stat = tng_block_header_read(tng_data, block);
      if (stat == TNG_CRITICAL)
      {
          fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                  file_pos, __FILE__, __LINE__);
          tng_block_destroy(&block);
          tng_data->input_file = temp;
  
          tng_data->first_trajectory_frame_set_input_file_pos   = temp_first;
          tng_data->last_trajectory_frame_set_input_file_pos    = temp_last;
          tng_data->current_trajectory_frame_set_input_file_pos = temp_current;
          return (stat);
      }
      fseeko(tng_data->output_file, block->block_contents_size, SEEK_CUR);
  
      if (is_particle_data == TNG_TRUE)
      {
          if (tng_data->var_num_atoms_flag)
          {
              tot_n_particles = frame_set->n_particles;
          }
          else
          {
              tot_n_particles = tng_data->n_particles;
          }
  
          if (val_n_particles < tot_n_particles)
          {
              mapping_block_end_pos = -1;
              /* Read all mapping blocks to find the right place to put the data */
              stat = tng_block_header_read(tng_data, block);
              while (file_pos < output_file_len && stat != TNG_CRITICAL
                     && block->id != TNG_TRAJECTORY_FRAME_SET && block->id != -1)
              {
                  if (block->id == TNG_PARTICLE_MAPPING)
                  {
                      tng_trajectory_mapping_block_read(tng_data, block, hash_mode);
                  }
                  else
                  {
                      fseeko(tng_data->output_file, block->block_contents_size, SEEK_CUR);
                  }
                  file_pos = ftello(tng_data->output_file);
                  if (block->id == TNG_PARTICLE_MAPPING)
                  {
                      mapping = &frame_set->mappings[frame_set->n_mapping_blocks - 1];
                      if (val_first_particle >= mapping->num_first_particle
                          && val_first_particle < mapping->num_first_particle + mapping->n_particles
                          && val_first_particle + val_n_particles
                                     <= mapping->num_first_particle + mapping->n_particles)
                      {
                          mapping_block_end_pos = file_pos;
                      }
                  }
                  if (file_pos < output_file_len)
                  {
                      stat = tng_block_header_read(tng_data, block);
                  }
              }
              if (stat == TNG_CRITICAL)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  tng_block_destroy(&block);
                  tng_data->input_file = temp;
  
                  tng_data->first_trajectory_frame_set_input_file_pos   = temp_first;
                  tng_data->last_trajectory_frame_set_input_file_pos    = temp_last;
                  tng_data->current_trajectory_frame_set_input_file_pos = temp_current;
                  return (stat);
              }
              if (mapping_block_end_pos < 0)
              {
                  tng_block_destroy(&block);
                  tng_data->input_file = temp;
  
                  tng_data->first_trajectory_frame_set_input_file_pos   = temp_first;
                  tng_data->last_trajectory_frame_set_input_file_pos    = temp_last;
                  tng_data->current_trajectory_frame_set_input_file_pos = temp_current;
                  return (TNG_FAILURE);
              }
              fseeko(tng_data->output_file, mapping_block_end_pos, SEEK_SET);
          }
      }
  
      /* Read all block headers until next frame set block or
       * until the wanted block id is found */
      stat = tng_block_header_read(tng_data, block);
      while (file_pos < output_file_len && stat != TNG_CRITICAL && block->id != block_id
             && (is_particle_data != TNG_TRUE || block->id != TNG_PARTICLE_MAPPING)
             && block->id != TNG_TRAJECTORY_FRAME_SET && block->id != -1)
      {
          fseeko(tng_data->output_file, block->block_contents_size, SEEK_CUR);
          file_pos = ftello(tng_data->output_file);
          if (file_pos < output_file_len)
          {
              stat = tng_block_header_read(tng_data, block);
          }
      }
      if (stat == TNG_CRITICAL)
      {
          fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                  file_pos, __FILE__, __LINE__);
          tng_block_destroy(&block);
          tng_data->input_file                                  = temp;
          tng_data->first_trajectory_frame_set_input_file_pos   = temp_first;
          tng_data->last_trajectory_frame_set_input_file_pos    = temp_last;
          tng_data->current_trajectory_frame_set_input_file_pos = temp_current;
          return (stat);
      }
  
      contents_size = block->block_contents_size;
      header_size   = block->header_contents_size;
  
      header_pos = ftello(tng_data->output_file) - header_size;
      frame_set  = &tng_data->current_trajectory_frame_set;
  
      if (tng_file_input_numerical(tng_data, &datatype, sizeof(datatype), TNG_SKIP_HASH, 0, __LINE__)
          == TNG_CRITICAL)
      {
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
      if (tng_file_input_numerical(tng_data, &dependency, sizeof(dependency), TNG_SKIP_HASH, 0, __LINE__)
          == TNG_CRITICAL)
      {
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
      data.datatype = datatype;
  
      if (!(dependency & TNG_FRAME_DEPENDENT)
          || (is_particle_data == TNG_FALSE && dependency & TNG_PARTICLE_DEPENDENT)
          || (is_particle_data == TNG_TRUE && !(dependency & TNG_PARTICLE_DEPENDENT)))
      {
          tng_block_destroy(&block);
          tng_data->input_file = temp;
  
          tng_data->first_trajectory_frame_set_input_file_pos   = temp_first;
          tng_data->last_trajectory_frame_set_input_file_pos    = temp_last;
          tng_data->current_trajectory_frame_set_input_file_pos = temp_current;
          return (TNG_FAILURE);
      }
  
      if (tng_file_input_numerical(tng_data, &sparse_data, sizeof(sparse_data), TNG_SKIP_HASH, 0, __LINE__)
          == TNG_CRITICAL)
      {
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, &data.n_values_per_frame,
                                   sizeof(data.n_values_per_frame), TNG_SKIP_HASH, 0, __LINE__)
          == TNG_CRITICAL)
      {
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      if (tng_file_input_numerical(tng_data, &data.codec_id, sizeof(data.codec_id), TNG_SKIP_HASH, 0, __LINE__)
          == TNG_CRITICAL)
      {
          tng_block_destroy(&block);
          return (TNG_CRITICAL);
      }
  
      if (data.codec_id != TNG_UNCOMPRESSED)
      {
          if (tng_file_input_numerical(tng_data, &data.compression_multiplier,
                                       sizeof(data.compression_multiplier), TNG_SKIP_HASH, 0, __LINE__)
              == TNG_CRITICAL)
          {
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
      }
      else
      {
          data.compression_multiplier = 1;
      }
  
      if (sparse_data)
      {
          if (tng_file_input_numerical(tng_data, &data.first_frame_with_data,
                                       sizeof(data.first_frame_with_data), TNG_SKIP_HASH, 0, __LINE__)
              == TNG_CRITICAL)
          {
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
  
          if (tng_file_input_numerical(tng_data, &data.stride_length, sizeof(data.stride_length),
                                       TNG_SKIP_HASH, 0, __LINE__)
              == TNG_CRITICAL)
          {
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
      }
      else
      {
          data.first_frame_with_data = 0;
          data.stride_length         = 1;
      }
      data.n_frames = tng_data->current_trajectory_frame_set.n_frames;
  
      if (is_particle_data == TNG_TRUE)
      {
          if (tng_file_input_numerical(tng_data, &num_first_particle, sizeof(num_first_particle),
                                       TNG_SKIP_HASH, 0, __LINE__)
              == TNG_CRITICAL)
          {
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
  
          if (tng_file_input_numerical(tng_data, &block_n_particles, sizeof(block_n_particles),
                                       TNG_SKIP_HASH, 0, __LINE__)
              == TNG_CRITICAL)
          {
              tng_block_destroy(&block);
              return (TNG_CRITICAL);
          }
      }
  
      tng_data->input_file = temp;
  
      tng_data->first_trajectory_frame_set_input_file_pos   = temp_first;
      tng_data->last_trajectory_frame_set_input_file_pos    = temp_last;
      tng_data->current_trajectory_frame_set_input_file_pos = temp_current;
  
      switch (data.datatype)
      {
          case (TNG_INT_DATA): size = sizeof(int64_t); break;
          case (TNG_FLOAT_DATA): size = sizeof(float); break;
          case (TNG_DOUBLE_DATA): size = sizeof(double); break;
          default:
              fprintf(stderr, "TNG library: Cannot calculate writing locations. %s: %d.\n", __FILE__, __LINE__);
              tng_block_destroy(&block);
              return (TNG_FAILURE);
      }
  
      n_values_per_frame = data.n_values_per_frame;
  
      file_pos = (frame_nr - tng_max_i64(frame_set->first_frame, data.first_frame_with_data))
                 / data.stride_length;
      if (is_particle_data == TNG_TRUE)
      {
          file_pos *= block_n_particles * size * n_values_per_frame;
      }
      else
      {
          file_pos *= size * n_values_per_frame;
      }
  
      if (file_pos > contents_size)
      {
          fprintf(stderr, "TNG library: Attempting to write outside the block. %s: %d\n", __FILE__, __LINE__);
          tng_block_destroy(&block);
          return (TNG_FAILURE);
      }
  
      fseeko(tng_data->output_file, file_pos, SEEK_CUR);
  
      if (is_particle_data == TNG_TRUE)
      {
          write_n_particles = val_n_particles;
      }
      else
      {
          write_n_particles = 1;
      }
  
      /* If the endianness is not big endian the data needs to be swapped */
      if ((data.datatype == TNG_INT_DATA || data.datatype == TNG_DOUBLE_DATA)
          && tng_data->output_endianness_swap_func_64)
      {
          copy = (char*)malloc(write_n_particles * n_values_per_frame * size);
          memcpy(copy, values, write_n_particles * n_values_per_frame * size);
          for (i = 0; i < write_n_particles * n_values_per_frame; i++)
          {
              if (tng_data->output_endianness_swap_func_64(tng_data, (uint64_t*)copy + i) != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n", __FILE__, __LINE__);
              }
          }
          fwrite(copy, write_n_particles * n_values_per_frame, size, tng_data->output_file);
          free(copy);
      }
      else if (data.datatype == TNG_FLOAT_DATA && tng_data->output_endianness_swap_func_32)
      {
          copy = (char*)malloc(write_n_particles * n_values_per_frame * size);
          memcpy(copy, values, write_n_particles * n_values_per_frame * size);
          for (i = 0; i < write_n_particles * n_values_per_frame; i++)
          {
              if (tng_data->output_endianness_swap_func_32(tng_data, (uint32_t*)copy + i) != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot swap byte order. %s: %d\n", __FILE__, __LINE__);
              }
          }
          fwrite(copy, write_n_particles * n_values_per_frame, size, tng_data->output_file);
          free(copy);
      }
  
      else
      {
          fwrite(values, write_n_particles * n_values_per_frame, size, tng_data->output_file);
      }
  
      fflush(tng_data->output_file);
  
      /* Update the number of written frames in the frame set. */
      if (frame_nr - frame_set->first_frame + 1 > frame_set->n_written_frames)
      {
          frame_set->n_written_frames = frame_nr - frame_set->first_frame + 1;
      }
  
      /* If the last frame has been written update the hash */
      if (hash_mode == TNG_USE_HASH
          && (frame_nr + data.stride_length - data.first_frame_with_data) >= frame_set->n_frames)
      {
          tng_md5_hash_update(tng_data, block, header_pos, header_pos + header_size);
      }
  
      tng_block_destroy(&block);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_frame_data_write(struct tng_trajectory* tng_data,
                                                             const int64_t          frame_nr,
                                                             const int64_t          block_id,
                                                             const void*            values,
                                                             const char             hash_mode)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(frame_nr >= 0, "TNG library: frame_nr must be >= 0.");
      TNG_ASSERT(values, "TNG library: values must not be a NULL pointer.");
  
      /* This is now just calling the generic data writing function. This
       * function must keep its signature to let the API be backwards
       * compatible. */
      return (tng_frame_gen_data_write(tng_data, frame_nr, block_id, TNG_FALSE, 0, 0, values, hash_mode));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_frame_particle_data_write(struct tng_trajectory* tng_data,
                                                                      const int64_t          frame_nr,
                                                                      const int64_t          block_id,
                                                                      const int64_t val_first_particle,
                                                                      const int64_t val_n_particles,
                                                                      const void*   values,
                                                                      const char    hash_mode)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(frame_nr >= 0, "TNG library: frame_nr must be >= 0.");
      TNG_ASSERT(values, "TNG library: values must not be a NULL pointer.");
      TNG_ASSERT(val_first_particle >= 0, "TNG library: val_first_particle must be >= 0.");
      TNG_ASSERT(val_n_particles >= 0, "TNG library: val_n_particles must be >= 0.");
  
      /* This is now just calling the generic data writing function. This
       * function must keep its signature to let the API be backwards
       * compatible. */
      return (tng_frame_gen_data_write(tng_data, frame_nr, block_id, TNG_TRUE, val_first_particle,
                                       val_n_particles, values, hash_mode));
  }
  
  static tng_function_status tng_data_values_alloc(struct tng_trajectory* tng_data,
                                                   union data_values***   values,
                                                   const int64_t          n_frames,
                                                   const int64_t          n_values_per_frame,
                                                   const char             type)
  {
      int64_t             i;
      tng_function_status stat;
  
      if (n_frames <= 0 || n_values_per_frame <= 0)
      {
          return (TNG_FAILURE);
      }
  
      if (*values)
      {
          stat = tng_data_values_free(tng_data, *values, n_frames, n_values_per_frame, type);
          if (stat != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot free particle data values. %s: %d\n", __FILE__, __LINE__);
              return (stat);
          }
      }
      *values = (union data_values**)malloc(sizeof(union data_values*) * n_frames);
      if (!*values)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      for (i = 0; i < n_frames; i++)
      {
          (*values)[i] = (union data_values*)malloc(sizeof(union data_values) * n_values_per_frame);
          if (!(*values)[i])
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              free(values);
              values = 0;
              return (TNG_CRITICAL);
          }
      }
      return (TNG_SUCCESS);
  }
  
  /* FIXME: This needs ***values */
  tng_function_status DECLSPECDLLEXPORT tng_data_values_free(struct tng_trajectory* tng_data,
                                                             union data_values**    values,
                                                             const int64_t          n_frames,
                                                             const int64_t n_values_per_frame,
                                                             const char    type)
  {
      int64_t i, j;
      (void)tng_data;
  
      if (values)
      {
          for (i = 0; i < n_frames; i++)
          {
              if (values[i])
              {
                  if (type == TNG_CHAR_DATA)
                  {
                      for (j = 0; j < n_values_per_frame; j++)
                      {
                          if (values[i][j].c)
                          {
                              free(values[i][j].c);
                              values[i][j].c = 0;
                          }
                      }
                  }
                  free(values[i]);
                  values[i] = 0;
              }
          }
          free(values);
          values = 0;
      }
  
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_particle_data_values_alloc(struct tng_trajectory* tng_data,
                                                            union data_values****  values,
                                                            const int64_t          n_frames,
                                                            const int64_t          n_particles,
                                                            const int64_t          n_values_per_frame,
                                                            const char             type)
  {
      int64_t             i, j;
      tng_function_status stat;
  
      if (n_particles == 0 || n_values_per_frame == 0)
      {
          return (TNG_FAILURE);
      }
  
      if (*values)
      {
          stat = tng_particle_data_values_free(tng_data, *values, n_frames, n_particles,
                                               n_values_per_frame, type);
          if (stat != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot free particle data values. %s: %d\n", __FILE__, __LINE__);
              return (stat);
          }
      }
      *values = (union data_values***)malloc(sizeof(union data_values**) * n_frames);
      if (!*values)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          return (TNG_CRITICAL);
      }
  
      for (i = 0; i < n_frames; i++)
      {
          (*values)[i] = (union data_values**)malloc(sizeof(union data_values*) * n_particles);
          if (!(*values)[i])
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              free(*values);
              *values = 0;
              return (TNG_CRITICAL);
          }
          for (j = 0; j < n_particles; j++)
          {
              (*values)[i][j] = (union data_values*)malloc(sizeof(union data_values) * n_values_per_frame);
              if (!(*values)[i][j])
              {
                  fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                  tng_particle_data_values_free(tng_data, *values, n_frames, n_particles,
                                                n_values_per_frame, type);
                  *values = 0;
                  return (TNG_CRITICAL);
              }
          }
      }
      return (TNG_SUCCESS);
  }
  
  /* FIXME: This needs ****values */
  tng_function_status DECLSPECDLLEXPORT tng_particle_data_values_free(struct tng_trajectory* tng_data,
                                                                      union data_values***   values,
                                                                      const int64_t          n_frames,
                                                                      const int64_t n_particles,
                                                                      const int64_t n_values_per_frame,
                                                                      const char    type)
  {
      int64_t i, j, k;
      (void)tng_data;
  
      if (values)
      {
          for (i = 0; i < n_frames; i++)
          {
              if (values[i])
              {
                  for (j = 0; j < n_particles; j++)
                  {
                      if (type == TNG_CHAR_DATA)
                      {
                          for (k = 0; k < n_values_per_frame; k++)
                          {
                              if (values[i][j][k].c)
                              {
                                  free(values[i][j][k].c);
                                  values[i][j][k].c = 0;
                              }
                          }
                      }
                      free(values[i][j]);
                      values[i][j] = 0;
                  }
                  free(values[i]);
                  values[i] = 0;
              }
          }
          free(values);
          values = 0;
      }
  
      return (TNG_SUCCESS);
  }
  
  static tng_function_status tng_gen_data_get(struct tng_trajectory* tng_data,
                                              const int64_t          block_id,
                                              const tng_bool         is_particle_data,
                                              union data_values****  values,
                                              int64_t*               n_frames,
                                              int64_t*               n_particles,
                                              int64_t*               n_values_per_frame,
                                              char*                  type)
  {
      int64_t                    i, j, k, mapping, file_pos, i_step, block_index;
      int                        size;
      size_t                     len;
      tng_data_t                 data;
      tng_trajectory_frame_set_t frame_set;
      tng_gen_block_t            block;
      char                       block_type_flag;
      tng_function_status        stat;
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      block_index = -1;
      data        = 0;
  
      if (is_particle_data == TNG_TRUE)
      {
          stat = tng_particle_data_find(tng_data, block_id, &data);
      }
      else
      {
          stat = tng_data_find(tng_data, block_id, &data);
      }
  
      if (tng_data->current_trajectory_frame_set_input_file_pos > 0)
      {
          block_type_flag = TNG_TRAJECTORY_BLOCK;
      }
      else
      {
          block_type_flag = TNG_NON_TRAJECTORY_BLOCK;
      }
  
      if (stat != TNG_SUCCESS)
      {
          tng_block_init(&block);
          file_pos = ftello(tng_data->input_file);
          /* Read all blocks until next frame set block */
          stat = tng_block_header_read(tng_data, block);
          while (file_pos < tng_data->input_file_len && stat != TNG_CRITICAL
                 && block->id != TNG_TRAJECTORY_FRAME_SET && block->id != -1)
          {
              /* Use hash by default */
              stat = tng_block_read_next(tng_data, block, TNG_USE_HASH);
              if (stat != TNG_CRITICAL)
              {
                  file_pos = ftello(tng_data->input_file);
                  if (file_pos < tng_data->input_file_len)
                  {
                      stat = tng_block_header_read(tng_data, block);
                  }
              }
          }
          tng_block_destroy(&block);
          if (stat == TNG_CRITICAL)
          {
              fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                      file_pos, __FILE__, __LINE__);
              return (stat);
          }
  
          if (is_particle_data == TNG_TRUE)
          {
              for (i = 0; i < frame_set->n_particle_data_blocks; i++)
              {
                  data = &frame_set->tr_particle_data[i];
                  if (data->block_id == block_id)
                  {
                      block_index     = i;
                      block_type_flag = TNG_TRAJECTORY_BLOCK;
                      break;
                  }
              }
          }
          else
          {
              for (i = 0; i < frame_set->n_data_blocks; i++)
              {
                  data = &frame_set->tr_data[i];
                  if (data->block_id == block_id)
                  {
                      block_index = i;
                      break;
                  }
              }
          }
          if (block_index < 0)
          {
              return (TNG_FAILURE);
          }
      }
  
      if (is_particle_data == TNG_TRUE)
      {
          if (block_type_flag == TNG_TRAJECTORY_BLOCK && tng_data->var_num_atoms_flag)
          {
              *n_particles = frame_set->n_particles;
          }
          else
          {
              *n_particles = tng_data->n_particles;
          }
      }
  
      *n_frames           = tng_max_i64(1, data->n_frames);
      *n_values_per_frame = data->n_values_per_frame;
      *type               = data->datatype;
  
      if (is_particle_data == TNG_TRUE)
      {
          if (*values == 0)
          {
              if (tng_particle_data_values_alloc(tng_data, values, *n_frames, *n_particles,
                                                 *n_values_per_frame, *type)
                  != TNG_SUCCESS)
              {
                  return (TNG_CRITICAL);
              }
          }
  
          i_step = (*n_particles) * (*n_values_per_frame);
  
          /* It's not very elegant to reuse so much of the code in the different case
           * statements, but it's unnecessarily slow to have the switch-case block
           * inside the for loops. */
          switch (*type)
          {
              case TNG_CHAR_DATA:
                  for (i = 0; i < *n_frames; i++)
                  {
                      for (j = 0; j < *n_particles; j++)
                      {
                          tng_particle_mapping_get_real_particle(frame_set, j, &mapping);
                          for (k = 0; k < *n_values_per_frame; k++)
                          {
                              len                        = strlen(data->strings[i][j][k]) + 1;
                              (*values)[i][mapping][k].c = (char*)malloc(len);
                              strncpy((*values)[i][mapping][k].c, data->strings[i][j][k], len);
                          }
                      }
                  }
                  break;
              case TNG_INT_DATA:
                  size = sizeof(int);
                  for (i = 0; i < *n_frames; i++)
                  {
                      for (j = 0; j < *n_particles; j++)
                      {
                          tng_particle_mapping_get_real_particle(frame_set, j, &mapping);
                          for (k = 0; k < *n_values_per_frame; k++)
                          {
                              (*values)[i][mapping][k].i =
                                      *(int*)((char*)data->values
                                              + size * (i * i_step + j * (*n_values_per_frame) + k));
                          }
                      }
                  }
                  break;
              case TNG_FLOAT_DATA:
                  size = sizeof(float);
                  for (i = 0; i < *n_frames; i++)
                  {
                      for (j = 0; j < *n_particles; j++)
                      {
                          tng_particle_mapping_get_real_particle(frame_set, j, &mapping);
                          for (k = 0; k < *n_values_per_frame; k++)
                          {
                              (*values)[i][mapping][k].f =
                                      *(float*)((char*)data->values
                                                + size * (i * i_step + j * (*n_values_per_frame) + k));
                          }
                      }
                  }
                  break;
              case TNG_DOUBLE_DATA:
              default:
                  size = sizeof(double);
                  for (i = 0; i < *n_frames; i++)
                  {
                      for (j = 0; j < *n_particles; j++)
                      {
                          tng_particle_mapping_get_real_particle(frame_set, j, &mapping);
                          for (k = 0; k < *n_values_per_frame; k++)
                          {
                              (*values)[i][mapping][k].d =
                                      *(double*)((char*)data->values
                                                 + size * (i * i_step + j * (*n_values_per_frame) + k));
                          }
                      }
                  }
          }
      }
      else
      {
          if (*(values[0]) == 0)
          {
              if (tng_data_values_alloc(tng_data, values[0], *n_frames, *n_values_per_frame, *type) != TNG_SUCCESS)
              {
                  return (TNG_CRITICAL);
              }
          }
          switch (*type)
          {
              case TNG_CHAR_DATA:
                  for (i = 0; i < *n_frames; i++)
                  {
                      for (j = 0; j < *n_values_per_frame; j++)
                      {
                          len                  = strlen(data->strings[0][i][j]) + 1;
                          (*values)[0][i][j].c = (char*)malloc(len);
                          strncpy((*values)[0][i][j].c, data->strings[0][i][j], len);
                      }
                  }
                  break;
              case TNG_INT_DATA:
                  size = sizeof(int);
                  for (i = 0; i < *n_frames; i++)
                  {
                      for (j = 0; j < *n_values_per_frame; j++)
                      {
                          (*values)[0][i][j].i =
                                  *(int*)((char*)data->values + size * (i * (*n_values_per_frame) + j));
                      }
                  }
                  break;
              case TNG_FLOAT_DATA:
                  size = sizeof(float);
                  for (i = 0; i < *n_frames; i++)
                  {
                      for (j = 0; j < *n_values_per_frame; j++)
                      {
                          (*values)[0][i][j].f =
                                  *(float*)((char*)data->values + size * (i * (*n_values_per_frame) + j));
                      }
                  }
                  break;
              case TNG_DOUBLE_DATA:
              default:
                  size = sizeof(double);
                  for (i = 0; i < *n_frames; i++)
                  {
                      for (j = 0; j < *n_values_per_frame; j++)
                      {
                          (*values)[0][i][j].d =
                                  *(double*)((char*)data->values + size * (i * (*n_values_per_frame) + j));
                      }
                  }
          }
      }
  
      data->last_retrieved_frame = data->first_frame_with_data + data->n_frames - 1;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_data_get(struct tng_trajectory* tng_data,
                                                     const int64_t          block_id,
                                                     union data_values***   values,
                                                     int64_t*               n_frames,
                                                     int64_t*               n_values_per_frame,
                                                     char*                  type)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(n_frames, "TNG library: n_frames must not be a NULL pointer.");
      TNG_ASSERT(n_values_per_frame, "TNG library: n_values_per_frame must not be a NULL pointer.");
      TNG_ASSERT(type, "TNG library: type must not be a NULL pointer.");
  
      return (tng_gen_data_get(tng_data, block_id, TNG_FALSE, &values, n_frames, 0, n_values_per_frame, type));
  }
  
  static tng_function_status tng_gen_data_vector_get(struct tng_trajectory* tng_data,
                                                     const int64_t          block_id,
                                                     const tng_bool         is_particle_data,
                                                     void**                 values,
                                                     int64_t*               n_frames,
                                                     int64_t*               stride_length,
                                                     int64_t*               n_particles,
                                                     int64_t*               n_values_per_frame,
                                                     char*                  type)
  {
      int64_t                    i, j, mapping, file_pos, i_step, full_data_len, n_frames_div;
      int64_t                    block_index;
      int                        size;
      tng_data_t                 data;
      tng_trajectory_frame_set_t frame_set;
      tng_gen_block_t            block;
      void*                      temp;
      char                       block_type_flag;
      tng_function_status        stat;
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      block_index = -1;
      data        = 0;
  
      if (is_particle_data == TNG_TRUE)
      {
          stat = tng_particle_data_find(tng_data, block_id, &data);
      }
      else
      {
          stat = tng_data_find(tng_data, block_id, &data);
      }
  
      if (stat != TNG_SUCCESS)
      {
          tng_block_init(&block);
          file_pos = ftello(tng_data->input_file);
          /* Read all blocks until next frame set block */
          stat = tng_block_header_read(tng_data, block);
          while (file_pos < tng_data->input_file_len && stat != TNG_CRITICAL
                 && block->id != TNG_TRAJECTORY_FRAME_SET && block->id != -1)
          {
              /* Use hash by default */
              stat = tng_block_read_next(tng_data, block, TNG_USE_HASH);
              if (stat != TNG_CRITICAL)
              {
                  file_pos = ftello(tng_data->input_file);
                  if (file_pos < tng_data->input_file_len)
                  {
                      stat = tng_block_header_read(tng_data, block);
                  }
              }
          }
          tng_block_destroy(&block);
          if (stat == TNG_CRITICAL)
          {
              fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                      file_pos, __FILE__, __LINE__);
              return (stat);
          }
  
          for (i = 0; i < frame_set->n_particle_data_blocks; i++)
          {
              data = &frame_set->tr_particle_data[i];
              if (data->block_id == block_id)
              {
                  block_index = i;
                  break;
              }
          }
          if (block_index < 0)
          {
              return (TNG_FAILURE);
          }
      }
  
      if (is_particle_data == TNG_TRUE)
      {
          if (tng_data->current_trajectory_frame_set_input_file_pos > 0)
          {
              block_type_flag = TNG_TRAJECTORY_BLOCK;
          }
          else
          {
              block_type_flag = TNG_NON_TRAJECTORY_BLOCK;
          }
  
          if (block_type_flag == TNG_TRAJECTORY_BLOCK && tng_data->var_num_atoms_flag)
          {
              *n_particles = frame_set->n_particles;
          }
          else
          {
              *n_particles = tng_data->n_particles;
          }
      }
  
      *type = data->datatype;
  
      switch (*type)
      {
          case TNG_CHAR_DATA: return (TNG_FAILURE);
          case TNG_INT_DATA: size = sizeof(int64_t); break;
          case TNG_FLOAT_DATA: size = sizeof(float); break;
          case TNG_DOUBLE_DATA:
          default: size = sizeof(double);
      }
  
      *n_frames           = tng_max_i64(1, data->n_frames);
      *n_values_per_frame = data->n_values_per_frame;
      *stride_length      = data->stride_length;
  
      n_frames_div = (*n_frames - 1) / *stride_length + 1;
  
      full_data_len = n_frames_div * size * (*n_values_per_frame);
      if (is_particle_data == TNG_TRUE)
      {
          full_data_len *= (*n_particles);
      }
  
      temp = (char*)realloc(*values, full_data_len);
      if (!temp)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(*values);
          *values = 0;
          return (TNG_CRITICAL);
      }
  
      *values = temp;
  
      if (is_particle_data != TNG_TRUE || frame_set->n_mapping_blocks <= 0)
      {
          memcpy(*values, data->values, full_data_len);
      }
      else
      {
          i_step = (*n_particles) * (*n_values_per_frame);
          for (i = 0; i < *n_frames; i++)
          {
              for (j = 0; j < *n_particles; j++)
              {
                  tng_particle_mapping_get_real_particle(frame_set, j, &mapping);
                  memcpy(((char*)*values) + size * (i * i_step + mapping * (*n_values_per_frame)),
                         (char*)data->values + size * (i * i_step + j * (*n_values_per_frame)),
                         size * (*n_values_per_frame));
              }
          }
      }
  
      data->last_retrieved_frame = data->first_frame_with_data + data->n_frames - 1;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_data_vector_get(struct tng_trajectory* tng_data,
                                                            const int64_t          block_id,
                                                            void**                 values,
                                                            int64_t*               n_frames,
                                                            int64_t*               stride_length,
                                                            int64_t*               n_values_per_frame,
                                                            char*                  type)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(n_frames, "TNG library: n_frames must not be a NULL pointer.");
      TNG_ASSERT(stride_length, "TNG library: stride_length must not be a NULL pointer.");
      TNG_ASSERT(n_values_per_frame, "TNG library: n_values_per_frame must not be a NULL pointer.");
      TNG_ASSERT(type, "TNG library: type must not be a NULL pointer.");
  
      return (tng_gen_data_vector_get(tng_data, block_id, TNG_FALSE, values, n_frames, stride_length,
                                      0, n_values_per_frame, type));
  }
  
  static tng_function_status tng_gen_data_interval_get(struct tng_trajectory* tng_data,
                                                       const int64_t          block_id,
                                                       const tng_bool         is_particle_data,
                                                       const int64_t          start_frame_nr,
                                                       const int64_t          end_frame_nr,
                                                       const char             hash_mode,
                                                       union data_values****  values,
                                                       int64_t*               n_particles,
                                                       int64_t*               n_values_per_frame,
                                                       char*                  type)
  {
      int64_t                    i, j, k, mapping, n_frames, file_pos, current_frame_pos, i_step;
      int64_t                    first_frame, block_index;
      int                        size;
      size_t                     len;
      tng_data_t                 data;
      tng_trajectory_frame_set_t frame_set;
      tng_gen_block_t            block;
      char                       block_type_flag;
      tng_function_status        stat;
  
      block_index = -1;
  
      frame_set   = &tng_data->current_trajectory_frame_set;
      first_frame = frame_set->first_frame;
  
      stat = tng_frame_set_of_frame_find(tng_data, start_frame_nr);
      if (stat != TNG_SUCCESS)
      {
          return (stat);
      }
  
      /* Do not re-read the frame set. */
      if ((is_particle_data == TNG_TRUE
           && (first_frame != frame_set->first_frame || frame_set->n_particle_data_blocks <= 0))
          || (is_particle_data == TNG_FALSE
              && (first_frame != frame_set->first_frame || frame_set->n_data_blocks <= 0)))
      {
          tng_block_init(&block);
          file_pos = ftello(tng_data->input_file);
          /* Read all blocks until next frame set block */
          stat = tng_block_header_read(tng_data, block);
          while (file_pos < tng_data->input_file_len && stat != TNG_CRITICAL
                 && block->id != TNG_TRAJECTORY_FRAME_SET && block->id != -1)
          {
              stat = tng_block_read_next(tng_data, block, hash_mode);
              if (stat != TNG_CRITICAL)
              {
                  file_pos = ftello(tng_data->input_file);
                  if (file_pos < tng_data->input_file_len)
                  {
                      stat = tng_block_header_read(tng_data, block);
                  }
              }
          }
          tng_block_destroy(&block);
          if (stat == TNG_CRITICAL)
          {
              fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                      file_pos, __FILE__, __LINE__);
              return (stat);
          }
      }
  
      /* See if there is already a data block of this ID.
       * Start checking the last read frame set */
      if (is_particle_data == TNG_TRUE)
      {
          for (i = frame_set->n_particle_data_blocks; i--;)
          {
              data = &frame_set->tr_particle_data[i];
              if (data->block_id == block_id)
              {
                  block_index     = i;
                  block_type_flag = TNG_TRAJECTORY_BLOCK;
                  break;
              }
          }
      }
      else
      {
          for (i = 0; i < frame_set->n_data_blocks; i++)
          {
              data = &frame_set->tr_data[i];
              if (data->block_id == block_id)
              {
                  block_index = i;
                  break;
              }
          }
      }
  
      if (block_index < 0)
      {
          fprintf(stderr,
                  "TNG library: Could not find particle data block with id %" PRId64 ". %s: %d\n",
                  block_id, __FILE__, __LINE__);
          return (TNG_FAILURE);
      }
  
      if (is_particle_data == TNG_TRUE)
      {
          if (block_type_flag == TNG_TRAJECTORY_BLOCK && tng_data->var_num_atoms_flag)
          {
              *n_particles = frame_set->n_particles;
          }
          else
          {
              *n_particles = tng_data->n_particles;
          }
      }
  
      n_frames            = end_frame_nr - start_frame_nr + 1;
      *n_values_per_frame = data->n_values_per_frame;
      *type               = data->datatype;
  
      if (*values == 0)
      {
          if (is_particle_data == TNG_TRUE)
          {
              if (tng_particle_data_values_alloc(tng_data, values, n_frames, *n_particles,
                                                 *n_values_per_frame, *type)
                  != TNG_SUCCESS)
              {
                  return (TNG_CRITICAL);
              }
          }
          else
          {
              if (tng_data_values_alloc(tng_data, *values, n_frames, *n_values_per_frame, *type) != TNG_SUCCESS)
              {
                  return (TNG_CRITICAL);
              }
          }
      }
  
      current_frame_pos = start_frame_nr - frame_set->first_frame;
  
      if (is_particle_data == TNG_TRUE)
      {
          i_step = (*n_particles) * (*n_values_per_frame);
      }
      else
      {
          i_step = (*n_values_per_frame);
      }
      /* It's not very elegant to reuse so much of the code in the different case
       * statements, but it's unnecessarily slow to have the switch-case block
       * inside the for loops. */
      switch (*type)
      {
          case TNG_CHAR_DATA:
              for (i = 0; i < n_frames; i++)
              {
                  if (current_frame_pos == frame_set->n_frames)
                  {
                      stat = tng_frame_set_read_next(tng_data, hash_mode);
                      if (stat != TNG_SUCCESS)
                      {
                          return (stat);
                      }
                      current_frame_pos = 0;
                  }
                  if (is_particle_data == TNG_TRUE)
                  {
                      for (j = 0; j < *n_particles; j++)
                      {
                          tng_particle_mapping_get_real_particle(frame_set, j, &mapping);
                          for (k = 0; k < *n_values_per_frame; k++)
                          {
                              len = strlen(data->strings[current_frame_pos][j][k]) + 1;
                              (*values)[i][mapping][k].c = (char*)malloc(len);
                              strncpy((*values)[i][mapping][k].c,
                                      data->strings[current_frame_pos][j][k], len);
                          }
                      }
                  }
                  else
                  {
                      for (j = 0; j < *n_values_per_frame; j++)
                      {
                          len                  = strlen(data->strings[0][current_frame_pos][j]) + 1;
                          (*values)[0][i][j].c = (char*)malloc(len);
                          strncpy((*values)[0][i][j].c, data->strings[0][current_frame_pos][j], len);
                      }
                  }
                  current_frame_pos++;
              }
              break;
          case TNG_INT_DATA:
              size = sizeof(int);
              for (i = 0; i < n_frames; i++)
              {
                  if (current_frame_pos == frame_set->n_frames)
                  {
                      stat = tng_frame_set_read_next(tng_data, hash_mode);
                      if (stat != TNG_SUCCESS)
                      {
                          return (stat);
                      }
                      current_frame_pos = 0;
                  }
                  if (is_particle_data == TNG_TRUE)
                  {
                      for (j = 0; j < *n_particles; j++)
                      {
                          tng_particle_mapping_get_real_particle(frame_set, j, &mapping);
                          for (k = 0; k < *n_values_per_frame; k++)
                          {
                              (*values)[i][mapping][k].i =
                                      *(int*)((char*)data->values
                                              + size
                                                        * (current_frame_pos * i_step
                                                           + j * (*n_values_per_frame) + k));
                          }
                      }
                      current_frame_pos++;
                  }
                  else
                  {
                      for (j = 0; j < *n_values_per_frame; j++)
                      {
                          (*values)[0][i][j].i =
                                  *(int*)((char*)data->values + size * (current_frame_pos * i_step + j));
                      }
                  }
              }
              break;
          case TNG_FLOAT_DATA:
              size = sizeof(float);
              for (i = 0; i < n_frames; i++)
              {
                  if (current_frame_pos == frame_set->n_frames)
                  {
                      stat = tng_frame_set_read_next(tng_data, hash_mode);
                      if (stat != TNG_SUCCESS)
                      {
                          return (stat);
                      }
                      current_frame_pos = 0;
                  }
                  if (is_particle_data == TNG_TRUE)
                  {
                      for (j = 0; j < *n_particles; j++)
                      {
                          tng_particle_mapping_get_real_particle(frame_set, j, &mapping);
                          for (k = 0; k < *n_values_per_frame; k++)
                          {
                              (*values)[i][mapping][k].f =
                                      *(float*)((char*)data->values
                                                + size
                                                          * (current_frame_pos * i_step
                                                             + j * (*n_values_per_frame) + k));
                          }
                      }
                  }
                  else
                  {
                      for (j = 0; j < *n_values_per_frame; j++)
                      {
                          (*values)[0][i][j].f =
                                  *(float*)((char*)data->values + size * (current_frame_pos * i_step + j));
                      }
                  }
                  current_frame_pos++;
              }
              break;
          case TNG_DOUBLE_DATA:
          default:
              size = sizeof(double);
              for (i = 0; i < n_frames; i++)
              {
                  if (current_frame_pos == frame_set->n_frames)
                  {
                      stat = tng_frame_set_read_next(tng_data, hash_mode);
                      if (stat != TNG_SUCCESS)
                      {
                          return (stat);
                      }
                      current_frame_pos = 0;
                  }
                  if (is_particle_data == TNG_TRUE)
                  {
                      for (j = 0; j < *n_particles; j++)
                      {
                          tng_particle_mapping_get_real_particle(frame_set, j, &mapping);
                          for (k = 0; k < *n_values_per_frame; k++)
                          {
                              (*values)[i][mapping][k].d =
                                      *(double*)((char*)data->values
                                                 + size
                                                           * (current_frame_pos * i_step
                                                              + j * (*n_values_per_frame) + k));
                          }
                      }
                  }
                  else
                  {
                      for (j = 0; j < *n_values_per_frame; j++)
                      {
                          (*values)[0][i][j].d = *(double*)((char*)data->values
                                                            + size * (current_frame_pos * i_step + j));
                      }
                  }
                  current_frame_pos++;
              }
      }
  
      data->last_retrieved_frame = end_frame_nr;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_data_interval_get(struct tng_trajectory* tng_data,
                                                              const int64_t          block_id,
                                                              const int64_t          start_frame_nr,
                                                              const int64_t          end_frame_nr,
                                                              const char             hash_mode,
                                                              union data_values***   values,
                                                              int64_t* n_values_per_frame,
                                                              char*    type)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(start_frame_nr <= end_frame_nr,
                 "TNG library: start_frame_nr must not be higher than tne end_frame_nr.");
      TNG_ASSERT(n_values_per_frame, "TNG library: n_values_per_frame must not be a NULL pointer.");
      TNG_ASSERT(type, "TNG library: type must not be a NULL pointer.");
  
      return (tng_gen_data_interval_get(tng_data, block_id, TNG_FALSE, start_frame_nr, end_frame_nr,
                                        hash_mode, &values, 0, n_values_per_frame, type));
  }
  
  tng_function_status tng_gen_data_vector_interval_get(struct tng_trajectory* tng_data,
                                                              const int64_t          block_id,
                                                              const tng_bool         is_particle_data,
                                                              const int64_t          start_frame_nr,
                                                              const int64_t          end_frame_nr,
                                                              const char             hash_mode,
                                                              void**                 values,
                                                              int64_t*               n_particles,
                                                              int64_t*               stride_length,
                                                              int64_t* n_values_per_frame,
                                                              char*    type)
  {
      int64_t n_frames, tot_n_frames, n_frames_div, n_frames_div_2, first_frame;
      int64_t file_pos, current_frame_pos, last_frame_pos, full_data_len, frame_size;
      int     size;
      tng_trajectory_frame_set_t frame_set;
      tng_data_t                 data;
      tng_gen_block_t            block;
      void *                     current_values = 0, *temp;
      tng_function_status        stat;
  
      frame_set   = &tng_data->current_trajectory_frame_set;
      first_frame = frame_set->first_frame;
  
      /* Do not re-read the frame set if not necessary. */
      if (tng_data->current_trajectory_frame_set_input_file_pos < 0 || start_frame_nr < first_frame
          || start_frame_nr >= first_frame + frame_set->n_frames)
      {
          stat = tng_frame_set_of_frame_find(tng_data, start_frame_nr);
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
      }
  
      /* Re-read the relevant data block. */
      stat = tng_frame_set_read_current_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
      if (stat != TNG_SUCCESS)
      {
          return (stat);
      }
  
      /* TODO: Test that blocks are read correctly. */
      if (is_particle_data == TNG_TRUE)
      {
          stat = tng_particle_data_find(tng_data, block_id, &data);
      }
      else
      {
          stat = tng_data_find(tng_data, block_id, &data);
      }
  
      if (first_frame != frame_set->first_frame || stat != TNG_SUCCESS)
      {
          tng_block_init(&block);
          if (stat != TNG_SUCCESS)
          {
              fseeko(tng_data->input_file, tng_data->current_trajectory_frame_set_input_file_pos, SEEK_SET);
              stat = tng_block_header_read(tng_data, block);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot read block header. %s: %d\n", __FILE__, __LINE__);
                  return (stat);
              }
  
              fseeko(tng_data->input_file, block->block_contents_size, SEEK_CUR);
          }
          file_pos = ftello(tng_data->input_file);
          /* Read until next frame set block */
          while (file_pos < tng_data->input_file_len && tng_block_header_read(tng_data, block) != TNG_CRITICAL
                 && block->id != TNG_TRAJECTORY_FRAME_SET && block->id != -1)
          {
              if (block->id == block_id || block->id == TNG_PARTICLE_MAPPING)
              {
                  stat = tng_block_read_next(tng_data, block, hash_mode);
                  if (stat != TNG_CRITICAL)
                  {
                      file_pos = ftello(tng_data->input_file);
                  }
              }
              else
              {
                  file_pos += block->block_contents_size + block->header_contents_size;
                  fseeko(tng_data->input_file, block->block_contents_size, SEEK_CUR);
              }
          }
          tng_block_destroy(&block);
          if (stat == TNG_CRITICAL)
          {
              fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                      file_pos, __FILE__, __LINE__);
              return (stat);
          }
      }
      if (is_particle_data == TNG_TRUE)
      {
          stat = tng_particle_data_find(tng_data, block_id, &data);
      }
      else
      {
          stat = tng_data_find(tng_data, block_id, &data);
      }
      if (stat != TNG_SUCCESS)
      {
          return (stat);
      }
  
      stat = tng_gen_data_vector_get(tng_data, block_id, is_particle_data, &current_values, &n_frames,
                                     stride_length, n_particles, n_values_per_frame, type);
  
      if (stat != TNG_SUCCESS || (is_particle_data && *n_particles == 0))
      {
          if (current_values)
          {
              free(current_values);
          }
          return (stat);
      }
  
      if (n_frames == 1 && n_frames < frame_set->n_frames)
      {
          tot_n_frames = 1;
      }
      else
      {
          tot_n_frames = end_frame_nr - start_frame_nr + 1;
      }
  
      /* Check that the reading starts at a frame with data or that the reading range is
       * long enough to reach the next frame with data of this type. */
      if (start_frame_nr > data->first_frame_with_data
          && start_frame_nr - data->first_frame_with_data + tot_n_frames < *stride_length)
      {
          return (TNG_FAILURE);
      }
  
      switch (*type)
      {
          case TNG_CHAR_DATA: return (TNG_FAILURE);
          case TNG_INT_DATA: size = sizeof(int64_t); break;
          case TNG_FLOAT_DATA: size = sizeof(float); break;
          case TNG_DOUBLE_DATA:
          default: size = sizeof(double);
      }
  
      n_frames_div = (tot_n_frames - 1) / *stride_length + 1;
  
      full_data_len = n_frames_div * size * (*n_values_per_frame);
      if (is_particle_data)
      {
          full_data_len *= (*n_particles);
      }
  
      temp = (char*)realloc(*values, full_data_len);
      if (!temp)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(*values);
          *values = 0;
          return (TNG_CRITICAL);
      }
  
      *values = temp;
  
      if (n_frames == 1 && n_frames < frame_set->n_frames)
      {
          if (is_particle_data)
          {
              memcpy(*values, current_values, size * (*n_particles) * (*n_values_per_frame));
          }
          else
          {
              memcpy(*values, current_values, size * (*n_values_per_frame));
          }
      }
      else
      {
          current_frame_pos = start_frame_nr - frame_set->first_frame;
  
          frame_size = size * (*n_values_per_frame);
          if (is_particle_data)
          {
              frame_size *= (*n_particles);
          }
  
          last_frame_pos = tng_min_i64(n_frames - 1, end_frame_nr - start_frame_nr);
  
          n_frames_div   = current_frame_pos / *stride_length;
          n_frames_div_2 = last_frame_pos / *stride_length + 1;
  
          memcpy(*values, (char*)current_values + n_frames_div * frame_size, n_frames_div_2 * frame_size);
  
          current_frame_pos += n_frames - current_frame_pos;
  
          while (current_frame_pos <= end_frame_nr - start_frame_nr)
          {
              stat = tng_frame_set_read_next(tng_data, hash_mode);
              if (stat != TNG_SUCCESS)
              {
                  if (current_values)
                  {
                      free(current_values);
                  }
                  free(*values);
                  *values = 0;
                  return (stat);
              }
  
              if (is_particle_data == TNG_TRUE)
              {
                  stat = tng_particle_data_find(tng_data, block_id, &data);
              }
              else
              {
                  stat = tng_data_find(tng_data, block_id, &data);
              }
              if (stat != TNG_SUCCESS)
              {
                  return (stat);
              }
              stat = tng_gen_data_vector_get(tng_data, block_id, is_particle_data, &current_values, &n_frames,
                                             stride_length, n_particles, n_values_per_frame, type);
  
              if (stat != TNG_SUCCESS)
              {
                  if (current_values)
                  {
                      free(current_values);
                  }
                  free(*values);
                  *values = 0;
                  return (stat);
              }
  
              last_frame_pos = tng_min_i64(n_frames - 1, end_frame_nr - current_frame_pos);
  
              if (current_frame_pos < data->first_frame_with_data)
              {
                  if (end_frame_nr >= data->first_frame_with_data)
                  {
                      current_frame_pos = data->first_frame_with_data;
                  }
              }
              n_frames_div   = current_frame_pos / *stride_length;
              n_frames_div_2 = last_frame_pos / *stride_length + 1;
  
              memcpy(((char*)*values) + n_frames_div * frame_size, current_values, n_frames_div_2 * frame_size);
  
              current_frame_pos += n_frames * *stride_length;
          }
      }
  
      if (current_values)
      {
          free(current_values);
      }
  
      /* *data may have been reinitialized/freed when reading frame sets. Re-find the correct data block. */
      if (is_particle_data == TNG_TRUE)
      {
          stat = tng_particle_data_find(tng_data, block_id, &data);
      }
      else
      {
          stat = tng_data_find(tng_data, block_id, &data);
      }
      if (stat != TNG_SUCCESS)
      {
          return (stat);
      }
  
      data->last_retrieved_frame = end_frame_nr;
  
      return (TNG_SUCCESS);
  }
  
  
  tng_function_status DECLSPECDLLEXPORT tng_data_vector_interval_get(struct tng_trajectory* tng_data,
                                                                     const int64_t          block_id,
                                                                     const int64_t start_frame_nr,
                                                                     const int64_t end_frame_nr,
                                                                     const char    hash_mode,
                                                                     void**        values,
                                                                     int64_t*      stride_length,
                                                                     int64_t*      n_values_per_frame,
                                                                     char*         type)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(start_frame_nr <= end_frame_nr,
                 "TNG library: start_frame_nr must not be higher than the end_frame_nr.");
      TNG_ASSERT(stride_length, "TNG library: stride_length must not be a NULL pointer.");
      TNG_ASSERT(n_values_per_frame, "TNG library: n_values_per_frame must not be a NULL pointer.");
      TNG_ASSERT(type, "TNG library: type must not be a NULL pointer.");
  
      return (tng_gen_data_vector_interval_get(tng_data, block_id, TNG_FALSE, start_frame_nr,
                                               end_frame_nr, hash_mode, values, 0, stride_length,
                                               n_values_per_frame, type));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_particle_data_get(struct tng_trajectory* tng_data,
                                                              const int64_t          block_id,
                                                              union data_values****  values,
                                                              int64_t*               n_frames,
                                                              int64_t*               n_particles,
                                                              int64_t* n_values_per_frame,
                                                              char*    type)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(n_frames, "TNG library: n_frames must not be a NULL pointer.");
      TNG_ASSERT(n_particles, "TNG library: n_particles must not be a NULL pointer.");
      TNG_ASSERT(n_values_per_frame, "TNG library: n_values_per_frame must not be a NULL pointer.");
      TNG_ASSERT(type, "TNG library: type must not be a NULL pointer.");
  
      return (tng_gen_data_get(tng_data, block_id, TNG_TRUE, values, n_frames, n_particles,
                               n_values_per_frame, type));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_particle_data_vector_get(struct tng_trajectory* tng_data,
                                                                     const int64_t          block_id,
                                                                     void**                 values,
                                                                     int64_t*               n_frames,
                                                                     int64_t* stride_length,
                                                                     int64_t* n_particles,
                                                                     int64_t* n_values_per_frame,
                                                                     char*    type)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(n_particles, "TNG library: n_particles must not be a NULL pointer.");
      TNG_ASSERT(stride_length, "TNG library: stride_length must not be a NULL pointer.");
      TNG_ASSERT(n_values_per_frame, "TNG library: n_values_per_frame must not be a NULL pointer.");
      TNG_ASSERT(type, "TNG library: type must not be a NULL pointer.");
  
      return (tng_gen_data_vector_get(tng_data, block_id, TNG_TRUE, values, n_frames, stride_length,
                                      n_particles, n_values_per_frame, type));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_particle_data_interval_get(struct tng_trajectory* tng_data,
                                                                       const int64_t block_id,
                                                                       const int64_t start_frame_nr,
                                                                       const int64_t end_frame_nr,
                                                                       const char    hash_mode,
                                                                       union data_values**** values,
                                                                       int64_t* n_particles,
                                                                       int64_t* n_values_per_frame,
                                                                       char*    type)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(start_frame_nr <= end_frame_nr,
                 "TNG library: start_frame_nr must not be higher than tne end_frame_nr.");
      TNG_ASSERT(n_particles, "TNG library: n_particles must not be a NULL pointer.");
      TNG_ASSERT(n_values_per_frame, "TNG library: n_values_per_frame must not be a NULL pointer.");
      TNG_ASSERT(type, "TNG library: type must not be a NULL pointer.");
  
      return (tng_gen_data_interval_get(tng_data, block_id, TNG_TRUE, start_frame_nr, end_frame_nr,
                                        hash_mode, values, n_particles, n_values_per_frame, type));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_particle_data_vector_interval_get(struct tng_trajectory* tng_data,
                                                                              const int64_t block_id,
                                                                              const int64_t start_frame_nr,
                                                                              const int64_t end_frame_nr,
                                                                              const char    hash_mode,
                                                                              void**        values,
                                                                              int64_t* n_particles,
                                                                              int64_t* stride_length,
                                                                              int64_t* n_values_per_frame,
                                                                              char* type)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(start_frame_nr <= end_frame_nr,
                 "TNG library: start_frame_nr must not be higher than tne end_frame_nr.");
      TNG_ASSERT(n_particles, "TNG library: n_particles must not be a NULL pointer.");
      TNG_ASSERT(stride_length, "TNG library: stride_length must not be a NULL pointer.");
      TNG_ASSERT(n_values_per_frame, "TNG library: n_values_per_frame must not be a NULL pointer.");
      TNG_ASSERT(type, "TNG library: type must not be a NULL pointer.");
  
      return (tng_gen_data_vector_interval_get(tng_data, block_id, TNG_TRUE, start_frame_nr,
                                               end_frame_nr, hash_mode, values, n_particles,
                                               stride_length, n_values_per_frame, type));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_data_get_stride_length(struct tng_trajectory* tng_data,
                                                                   const int64_t          block_id,
                                                                   int64_t                frame,
                                                                   int64_t* stride_length)
  {
      tng_function_status stat;
      tng_data_t          data;
      int64_t             orig_file_pos, file_pos;
      int                 is_particle_data;
  
      if (tng_data->current_trajectory_frame_set_input_file_pos <= 0)
      {
          frame = 0;
      }
  
      if (frame >= 0)
      {
          stat = tng_frame_set_of_frame_find(tng_data, frame);
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
      }
      orig_file_pos = tng_data->current_trajectory_frame_set_input_file_pos;
      stat          = tng_data_find(tng_data, block_id, &data);
      if (stat != TNG_SUCCESS)
      {
          stat = tng_particle_data_find(tng_data, block_id, &data);
          if (stat != TNG_SUCCESS)
          {
              stat = tng_frame_set_read_current_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
              /* If no specific frame was required read until this data block is found */
              if (frame < 0)
              {
                  file_pos = ftello(tng_data->input_file);
                  while (stat != TNG_SUCCESS && file_pos < tng_data->input_file_len)
                  {
                      stat = tng_frame_set_read_next_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
                      file_pos = ftello(tng_data->input_file);
                  }
              }
              if (stat != TNG_SUCCESS)
              {
                  tng_reread_frame_set_at_file_pos(tng_data, orig_file_pos);
  
                  return (stat);
              }
              stat = tng_data_find(tng_data, block_id, &data);
              if (stat != TNG_SUCCESS)
              {
                  stat = tng_particle_data_find(tng_data, block_id, &data);
                  if (stat != TNG_SUCCESS)
                  {
                      tng_reread_frame_set_at_file_pos(tng_data, orig_file_pos);
  
                      return (stat);
                  }
                  else
                  {
                      is_particle_data = 1;
                  }
              }
              else
              {
                  is_particle_data = 0;
              }
          }
          else
          {
              is_particle_data = 1;
          }
      }
      else
      {
          is_particle_data = 0;
      }
      if (is_particle_data)
      {
          *stride_length = data->stride_length;
      }
      // Possible bug here
      //    else
      //    {
      //        *stride_length = data->stride_length;
      //    }
      tng_reread_frame_set_at_file_pos(tng_data, orig_file_pos);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_time_get_str(struct tng_trajectory* tng_data, char* time)
  {
      struct tm* time_data;
      time_t     secs;
      int        retval;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(time, "TNG library: time must not be a NULL pointer");
  
      secs = tng_data->time;
  
      time_data = localtime(&secs); /* Returns a statically allocated variable. */
      retval    = TNG_SNPRINTF(time, TNG_MAX_DATE_STR_LEN, "%4d-%02d-%02d %02d:%02d:%02d",
                            time_data->tm_year + 1900, time_data->tm_mon + 1, time_data->tm_mday,
                            time_data->tm_hour, time_data->tm_min, time_data->tm_sec);
  
      /* handle return value (also) to quiet a -Wformat-truncation warning */
      return ((retval < 0) ? TNG_SUCCESS : TNG_FAILURE);
  }
  
  
  tng_function_status DECLSPECDLLEXPORT tng_util_trajectory_open(const char*       filename,
                                                                 const char        mode,
                                                                 tng_trajectory_t* tng_data_p)
  {
      tng_function_status stat;
  
      TNG_ASSERT(filename, "TNG library: filename must not be a NULL pointer.");
  
      if (mode != 'r' && mode != 'w' && mode != 'a')
      {
          return (TNG_FAILURE);
      }
  
      if (tng_trajectory_init(tng_data_p) != TNG_SUCCESS)
      {
          tng_trajectory_destroy(tng_data_p);
          return (TNG_CRITICAL);
      }
  
      if (mode == 'w')
      {
          stat = tng_output_file_set(*tng_data_p, filename);
          return (stat);
      }
      tng_input_file_set(*tng_data_p, filename);
  
      /* Read the file headers */
      tng_file_headers_read(*tng_data_p, TNG_USE_HASH);
  
      stat = tng_num_frame_sets_get(*tng_data_p, &(*tng_data_p)->n_trajectory_frame_sets);
  
      if (stat != TNG_SUCCESS)
      {
          return (stat);
      }
  
      if (mode == 'a')
      {
          if ((*tng_data_p)->output_file)
          {
              fclose((*tng_data_p)->output_file);
          }
          (*tng_data_p)->output_file = (*tng_data_p)->input_file;
          fseeko((*tng_data_p)->input_file, (*tng_data_p)->last_trajectory_frame_set_input_file_pos, SEEK_SET);
  
          stat = tng_frame_set_read(*tng_data_p, TNG_USE_HASH);
          if (stat != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot read frame set and related blocks. %s: %d\n",
                      __FILE__, __LINE__);
          }
          (*tng_data_p)->output_file = 0;
  
          (*tng_data_p)->first_trajectory_frame_set_output_file_pos =
                  (*tng_data_p)->first_trajectory_frame_set_input_file_pos;
          (*tng_data_p)->last_trajectory_frame_set_output_file_pos =
                  (*tng_data_p)->last_trajectory_frame_set_input_file_pos;
          (*tng_data_p)->current_trajectory_frame_set_output_file_pos =
                  (*tng_data_p)->current_trajectory_frame_set_input_file_pos;
          if ((*tng_data_p)->input_file)
          {
              fclose((*tng_data_p)->input_file);
              (*tng_data_p)->input_file = 0;
          }
          if ((*tng_data_p)->input_file_path)
          {
              free((*tng_data_p)->input_file_path);
              (*tng_data_p)->input_file_path = 0;
          }
          tng_output_append_file_set(*tng_data_p, filename);
  
          fseeko((*tng_data_p)->output_file, 0, SEEK_END);
  
          (*tng_data_p)->output_endianness_swap_func_32 = (*tng_data_p)->input_endianness_swap_func_32;
          (*tng_data_p)->output_endianness_swap_func_64 = (*tng_data_p)->input_endianness_swap_func_64;
      }
  
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_trajectory_close(tng_trajectory_t* tng_data_p)
  {
      tng_trajectory_frame_set_t frame_set;
  
      if (tng_data_p == 0)
      {
          fprintf(stderr,
                  "TNG library: Empty pointer to trajectory when attempting to close. %s: %d\n",
                  __FILE__, __LINE__);
          return (TNG_FAILURE);
      }
  
      if (*tng_data_p == 0)
      {
          return (TNG_SUCCESS);
      }
  
      frame_set = &(*tng_data_p)->current_trajectory_frame_set;
  
      if (frame_set->n_unwritten_frames > 0)
      {
          frame_set->n_frames = frame_set->n_unwritten_frames;
          tng_frame_set_write(*tng_data_p, TNG_USE_HASH);
      }
  
      return (tng_trajectory_destroy(tng_data_p));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_time_of_frame_get(struct tng_trajectory* tng_data,
                                                                   const int64_t          frame_nr,
                                                                   double*                time)
  {
      int64_t                    first_frame;
      tng_trajectory_frame_set_t frame_set;
      tng_function_status        stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(time, "TNG library: time must not be a NULL pointer");
  
      stat = tng_frame_set_of_frame_find(tng_data, frame_nr);
      if (stat != TNG_SUCCESS)
      {
          fprintf(stderr, "TNG library: Cannot find frame nr %" PRId64 ". %s: %d\n", frame_nr,
                  __FILE__, __LINE__);
          return (stat);
      }
  
      frame_set   = &tng_data->current_trajectory_frame_set;
      first_frame = frame_set->first_frame;
  
      if (tng_data->time_per_frame <= 0)
      {
          return (TNG_FAILURE);
      }
  
      *time = frame_set->first_frame_time + (tng_data->time_per_frame * (frame_nr - first_frame));
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_molecule_particles_get(struct tng_trajectory* tng_data,
                                                                        struct tng_molecule*   mol,
                                                                        int64_t*  n_particles,
                                                                        char***   names,
                                                                        char***   types,
                                                                        char***   res_names,
                                                                        int64_t** res_ids,
                                                                        char***   chain_names,
                                                                        int64_t** chain_ids)
  {
      tng_atom_t    atom;
      tng_residue_t res;
      tng_chain_t   chain;
      int64_t       i;
      (void)tng_data;
  
      *n_particles = mol->n_atoms;
  
      *names       = (char**)malloc(sizeof(char*) * *n_particles);
      *types       = (char**)malloc(sizeof(char*) * *n_particles);
      *res_names   = (char**)malloc(sizeof(char*) * *n_particles);
      *chain_names = (char**)malloc(sizeof(char*) * *n_particles);
      *res_ids     = (int64_t*)malloc(sizeof(int64_t) * *n_particles);
      *chain_ids   = (int64_t*)malloc(sizeof(int64_t) * *n_particles);
  
      for (i = 0; i < *n_particles; i++)
      {
          atom        = &mol->atoms[i];
          res         = atom->residue;
          chain       = res->chain;
          (*names)[i] = (char*)malloc(strlen(atom->name));
          strcpy(*names[i], atom->name);
          (*types)[i] = (char*)malloc(strlen(atom->atom_type));
          strcpy(*types[i], atom->atom_type);
          (*res_names)[i] = (char*)malloc(strlen(res->name));
          strcpy(*res_names[i], res->name);
          (*chain_names)[i] = (char*)malloc(strlen(chain->name));
          strcpy(*chain_names[i], chain->name);
          (*res_ids)[i]   = res->id;
          (*chain_ids)[i] = chain->id;
      }
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_molecule_particles_set(struct tng_trajectory* tng_data,
                                                                        struct tng_molecule*   mol,
                                                                        const int64_t  n_particles,
                                                                        const char**   names,
                                                                        const char**   types,
                                                                        const char**   res_names,
                                                                        const int64_t* res_ids,
                                                                        const char**   chain_names,
                                                                        const int64_t* chain_ids)
  {
      int64_t             i;
      tng_chain_t         chain;
      tng_residue_t       residue;
      tng_atom_t          atom;
      tng_function_status stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(names, "TNG library: names must not be a NULL pointer");
      TNG_ASSERT(types, "TNG library: types must not be a NULL pointer");
      TNG_ASSERT(res_names, "TNG library: res_names must not be a NULL pointer");
      TNG_ASSERT(res_ids, "TNG library: res_ids must not be a NULL pointer");
      TNG_ASSERT(chain_names, "TNG library: chain_names must not be a NULL pointer");
      TNG_ASSERT(chain_ids, "TNG library: chain_ids must not be a NULL pointer");
  
      for (i = 0; i < n_particles; i++)
      {
          if (tng_molecule_chain_find(tng_data, mol, chain_names[i], chain_ids[i], &chain) == TNG_FAILURE)
          {
              stat = tng_molecule_chain_add(tng_data, mol, chain_names[i], &chain);
              if (stat != TNG_SUCCESS)
              {
                  return (stat);
              }
          }
          if (tng_chain_residue_find(tng_data, chain, res_names[i], res_ids[i], &residue) == TNG_FAILURE)
          {
              stat = tng_chain_residue_add(tng_data, chain, res_names[i], &residue);
              if (stat != TNG_SUCCESS)
              {
                  return (stat);
              }
          }
          stat = tng_residue_atom_add(tng_data, residue, names[i], types[i], &atom);
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_pos_read(struct tng_trajectory* tng_data,
                                                          float**                positions,
                                                          int64_t*               stride_length)
  {
      int64_t             n_frames, n_particles, n_values_per_frame;
      char                type;
      tng_function_status stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(positions, "TNG library: positions must not be a NULL pointer");
      TNG_ASSERT(stride_length, "TNG library: stride_length must not be a NULL pointer");
  
      stat = tng_num_frames_get(tng_data, &n_frames);
      if (stat != TNG_SUCCESS)
      {
          return (stat);
      }
  
      stat = tng_particle_data_vector_interval_get(tng_data, TNG_TRAJ_POSITIONS, 0, n_frames - 1,
                                                   TNG_USE_HASH, (void**)positions, &n_particles,
                                                   stride_length, &n_values_per_frame, &type);
  
      if (stat == TNG_SUCCESS && type != TNG_FLOAT_DATA)
      {
          return (TNG_FAILURE);
      }
  
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_vel_read(struct tng_trajectory* tng_data,
                                                          float**                velocities,
                                                          int64_t*               stride_length)
  {
      int64_t             n_frames, n_particles, n_values_per_frame;
      char                type;
      tng_function_status stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(velocities, "TNG library: velocities must not be a NULL pointer");
      TNG_ASSERT(stride_length, "TNG library: stride_length must not be a NULL pointer");
  
      stat = tng_num_frames_get(tng_data, &n_frames);
      if (stat != TNG_SUCCESS)
      {
          return (stat);
      }
  
      stat = tng_particle_data_vector_interval_get(tng_data, TNG_TRAJ_VELOCITIES, 0, n_frames - 1,
                                                   TNG_USE_HASH, (void**)velocities, &n_particles,
                                                   stride_length, &n_values_per_frame, &type);
  
      if (stat == TNG_SUCCESS && type != TNG_FLOAT_DATA)
      {
          return (TNG_FAILURE);
      }
  
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_force_read(struct tng_trajectory* tng_data,
                                                            float**                forces,
                                                            int64_t*               stride_length)
  {
      int64_t             n_frames, n_particles, n_values_per_frame;
      char                type;
      tng_function_status stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(forces, "TNG library: forces must not be a NULL pointer");
      TNG_ASSERT(stride_length, "TNG library: stride_length must not be a NULL pointer");
  
      stat = tng_num_frames_get(tng_data, &n_frames);
      if (stat != TNG_SUCCESS)
      {
          return (stat);
      }
  
      stat = tng_particle_data_vector_interval_get(tng_data, TNG_TRAJ_FORCES, 0, n_frames - 1,
                                                   TNG_USE_HASH, (void**)forces, &n_particles,
                                                   stride_length, &n_values_per_frame, &type);
  
      if (stat == TNG_SUCCESS && type != TNG_FLOAT_DATA)
      {
          return (TNG_FAILURE);
      }
  
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_box_shape_read(struct tng_trajectory* tng_data,
                                                                float**                box_shape,
                                                                int64_t*               stride_length)
  {
      int64_t             n_frames, n_values_per_frame;
      char                type;
      tng_function_status stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(box_shape, "TNG library: box_shape must not be a NULL pointer");
      TNG_ASSERT(stride_length, "TNG library: stride_length must not be a NULL pointer");
  
      stat = tng_num_frames_get(tng_data, &n_frames);
      if (stat != TNG_SUCCESS)
      {
          return (stat);
      }
  
      stat = tng_data_vector_interval_get(tng_data, TNG_TRAJ_BOX_SHAPE, 0, n_frames - 1, TNG_USE_HASH,
                                          (void**)box_shape, stride_length, &n_values_per_frame, &type);
  
      if (stat == TNG_SUCCESS && type != TNG_FLOAT_DATA)
      {
          return (TNG_FAILURE);
      }
  
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_particle_data_next_frame_read(struct tng_trajectory* tng_data,
                                                                               const int64_t block_id,
                                                                               void**        values,
                                                                               char* data_type,
                                                                               int64_t* retrieved_frame_number,
                                                                               double* retrieved_time)
  {
      tng_trajectory_frame_set_t frame_set;
      tng_data_t                 data = 0;
      tng_function_status        stat;
      int                        size;
      int64_t                    i, full_data_len, n_particles;
      void*                      temp;
      int64_t                    file_pos;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(values, "TNG library: The pointer to the values array must not be a NULL pointer");
      TNG_ASSERT(data_type,
                 "TNG library: The pointer to the data type of the returned data must not be a NULL "
                 "pointer");
      TNG_ASSERT(retrieved_frame_number,
                 "TNG library: The pointer to the frame number of the returned data must not be a "
                 "NULL pointer");
      TNG_ASSERT(
              retrieved_time,
              "TNG library: The pointer to the time of the returned data must not be a NULL pointer");
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      stat = tng_particle_data_find(tng_data, block_id, &data);
      if (stat != TNG_SUCCESS)
      {
          stat = tng_frame_set_read_current_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
          file_pos = ftello(tng_data->input_file);
          while (stat != TNG_SUCCESS && file_pos < tng_data->input_file_len)
          {
              stat = tng_frame_set_read_next_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
              file_pos = ftello(tng_data->input_file);
          }
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
          stat = tng_particle_data_find(tng_data, block_id, &data);
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
      }
      if (data->last_retrieved_frame < 0)
      {
          fseeko(tng_data->input_file, tng_data->first_trajectory_frame_set_input_file_pos, SEEK_SET);
          stat = tng_frame_set_read(tng_data, TNG_USE_HASH);
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
          stat = tng_frame_set_read_current_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
  
          i = data->first_frame_with_data;
      }
      else
      {
          if (data->n_frames == 1 && frame_set->n_frames == 1)
          {
              i = data->last_retrieved_frame + 1;
          }
          else
          {
              i = data->last_retrieved_frame + data->stride_length;
          }
          if (i < frame_set->first_frame || i >= frame_set->first_frame + frame_set->n_frames)
          {
              stat = tng_frame_set_of_frame_find(tng_data, i);
              if (stat != TNG_SUCCESS)
              {
                  /* If the frame set search found the frame set after the starting
                   * frame set there is a gap in the frame sets. So, even if the frame
                   * was not found the next frame with data is still in the found
                   * frame set. */
                  if (stat == TNG_CRITICAL)
                  {
                      return (stat);
                  }
                  if (frame_set->first_frame + frame_set->n_frames - 1 < i)
                  {
                      return (TNG_FAILURE);
                  }
                  i = frame_set->first_frame;
              }
          }
          if (data->last_retrieved_frame < frame_set->first_frame)
          {
              stat = tng_frame_set_read_current_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
              if (stat != TNG_SUCCESS)
              {
                  return (stat);
              }
          }
      }
      data->last_retrieved_frame = i;
      *retrieved_frame_number    = i;
      if (frame_set->first_frame_time >= 0 && tng_data->time_per_frame >= 0)
      {
          *retrieved_time =
                  frame_set->first_frame_time + (i - frame_set->first_frame) * tng_data->time_per_frame;
      }
      else
      {
          *retrieved_time = 0;
      }
  
      if (data->stride_length > 1)
      {
          i = (i - data->first_frame_with_data) / data->stride_length;
      }
      else
      {
          i = (i - frame_set->first_frame);
      }
  
      tng_num_particles_get(tng_data, &n_particles);
  
      *data_type = data->datatype;
  
      switch (*data_type)
      {
          case TNG_CHAR_DATA: return (TNG_FAILURE);
          case TNG_INT_DATA: size = sizeof(int64_t); break;
          case TNG_FLOAT_DATA: size = sizeof(float); break;
          case TNG_DOUBLE_DATA:
          default: size = sizeof(double);
      }
  
      full_data_len = size * n_particles * data->n_values_per_frame;
  
      //     fprintf(stderr, "TNG library: TEMP: i = %" PRId64 ", full_data_len = %" PRId64 ", size = %d, n_particles = %" PRId64 ", n_values_per_frame = %" PRId64 "\n",
      //            i, full_data_len, size, n_particles, data->n_values_per_frame);
  
      temp = (char*)realloc(*values, full_data_len);
      if (!temp)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(*values);
          *values = 0;
          return (TNG_CRITICAL);
      }
  
      *values = temp;
  
      memcpy(*values, (char*)data->values + i * full_data_len, full_data_len);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_non_particle_data_next_frame_read(struct tng_trajectory* tng_data,
                                                                                   const int64_t block_id,
                                                                                   void** values,
                                                                                   char*  data_type,
                                                                                   int64_t* retrieved_frame_number,
                                                                                   double* retrieved_time)
  {
      tng_trajectory_frame_set_t frame_set;
      tng_data_t                 data = 0;
      tng_function_status        stat;
      int                        size;
      int64_t                    i, full_data_len;
      void*                      temp;
      int64_t                    file_pos;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(values, "TNG library: The pointer to the values array must not be a NULL pointer");
      TNG_ASSERT(data_type,
                 "TNG library: The pointer to the data type of the returned data must not be a NULL "
                 "pointer");
      TNG_ASSERT(retrieved_frame_number,
                 "TNG library: The pointer to the frame number of the returned data must not be a "
                 "NULL pointer");
      TNG_ASSERT(
              retrieved_time,
              "TNG library: The pointer to the time of the returned data must not be a NULL pointer");
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      stat = tng_data_find(tng_data, block_id, &data);
      if (stat != TNG_SUCCESS)
      {
          stat = tng_frame_set_read_current_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
          file_pos = ftello(tng_data->input_file);
          while (stat != TNG_SUCCESS && file_pos < tng_data->input_file_len)
          {
              stat = tng_frame_set_read_next_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
              file_pos = ftello(tng_data->input_file);
          }
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
          stat = tng_data_find(tng_data, block_id, &data);
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
      }
      if (data->last_retrieved_frame < 0)
      {
          fseeko(tng_data->input_file, tng_data->first_trajectory_frame_set_input_file_pos, SEEK_SET);
          stat = tng_frame_set_read(tng_data, TNG_USE_HASH);
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
          stat = tng_frame_set_read_current_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
  
          i = data->first_frame_with_data;
      }
      else
      {
          if (data->n_frames == 1 && frame_set->n_frames == 1)
          {
              i = data->last_retrieved_frame + 1;
          }
          else
          {
              i = data->last_retrieved_frame + data->stride_length;
          }
          if (i < frame_set->first_frame || i >= frame_set->first_frame + frame_set->n_frames)
          {
              stat = tng_frame_set_of_frame_find(tng_data, i);
              if (stat != TNG_SUCCESS)
              {
                  /* If the frame set search found the frame set after the starting
                   * frame set there is a gap in the frame sets. So, even if the frame
                   * was not found the next frame with data is still in the found
                   * frame set. */
                  if (stat == TNG_CRITICAL)
                  {
                      return (stat);
                  }
                  if (frame_set->first_frame + frame_set->n_frames - 1 < i)
                  {
                      return (TNG_FAILURE);
                  }
                  i = frame_set->first_frame;
              }
          }
          if (data->last_retrieved_frame < frame_set->first_frame)
          {
              stat = tng_frame_set_read_current_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
              if (stat != TNG_SUCCESS)
              {
                  return (stat);
              }
          }
      }
      data->last_retrieved_frame = i;
      *retrieved_frame_number    = i;
      if (frame_set->first_frame_time >= 0 && tng_data->time_per_frame >= 0)
      {
          *retrieved_time =
                  frame_set->first_frame_time + (i - frame_set->first_frame) * tng_data->time_per_frame;
      }
      else
      {
          *retrieved_time = 0;
      }
  
      if (data->stride_length > 1)
      {
          i = (i - data->first_frame_with_data) / data->stride_length;
      }
      else
      {
          i = (i - frame_set->first_frame);
      }
  
      *data_type = data->datatype;
  
      switch (*data_type)
      {
          case TNG_CHAR_DATA: return (TNG_FAILURE);
          case TNG_INT_DATA: size = sizeof(int64_t); break;
          case TNG_FLOAT_DATA: size = sizeof(float); break;
          case TNG_DOUBLE_DATA:
          default: size = sizeof(double);
      }
  
      full_data_len = size * data->n_values_per_frame;
  
      temp = (char*)realloc(*values, full_data_len);
      if (!temp)
      {
          fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
          free(*values);
          *values = 0;
          return (TNG_CRITICAL);
      }
  
      *values = temp;
  
      memcpy(*values, (char*)data->values + i * full_data_len, full_data_len);
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_pos_read_range(struct tng_trajectory* tng_data,
                                                                const int64_t          first_frame,
                                                                const int64_t          last_frame,
                                                                float**                positions,
                                                                int64_t*               stride_length)
  {
      int64_t             n_particles, n_values_per_frame;
      char                type;
      tng_function_status stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(positions, "TNG library: positions must not be a NULL pointer");
      TNG_ASSERT(first_frame <= last_frame,
                 "TNG library: first_frame must be lower or equal to last_frame.");
      TNG_ASSERT(stride_length, "TNG library: stride_length must not be a NULL pointer");
  
      stat = tng_particle_data_vector_interval_get(tng_data, TNG_TRAJ_POSITIONS, first_frame, last_frame,
                                                   TNG_USE_HASH, (void**)positions, &n_particles,
                                                   stride_length, &n_values_per_frame, &type);
  
      if (stat == TNG_SUCCESS && type != TNG_FLOAT_DATA)
      {
          return (TNG_FAILURE);
      }
  
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_vel_read_range(struct tng_trajectory* tng_data,
                                                                const int64_t          first_frame,
                                                                const int64_t          last_frame,
                                                                float**                velocities,
                                                                int64_t*               stride_length)
  {
      int64_t             n_particles, n_values_per_frame;
      char                type;
      tng_function_status stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(velocities, "TNG library: velocities must not be a NULL pointer");
      TNG_ASSERT(first_frame <= last_frame,
                 "TNG library: first_frame must be lower or equal to last_frame.");
      TNG_ASSERT(stride_length, "TNG library: stride_length must not be a NULL pointer");
  
      stat = tng_particle_data_vector_interval_get(tng_data, TNG_TRAJ_VELOCITIES, first_frame, last_frame,
                                                   TNG_USE_HASH, (void**)velocities, &n_particles,
                                                   stride_length, &n_values_per_frame, &type);
  
      if (stat == TNG_SUCCESS && type != TNG_FLOAT_DATA)
      {
          return (TNG_FAILURE);
      }
  
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_force_read_range(struct tng_trajectory* tng_data,
                                                                  const int64_t          first_frame,
                                                                  const int64_t          last_frame,
                                                                  float**                forces,
                                                                  int64_t* stride_length)
  {
      int64_t             n_particles, n_values_per_frame;
      char                type;
      tng_function_status stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(forces, "TNG library: forces must not be a NULL pointer");
      TNG_ASSERT(first_frame <= last_frame,
                 "TNG library: first_frame must be lower or equal to last_frame.");
      TNG_ASSERT(stride_length, "TNG library: stride_length must not be a NULL pointer");
  
      stat = tng_particle_data_vector_interval_get(tng_data, TNG_TRAJ_FORCES, first_frame, last_frame,
                                                   TNG_USE_HASH, (void**)forces, &n_particles,
                                                   stride_length, &n_values_per_frame, &type);
  
      if (stat == TNG_SUCCESS && type != TNG_FLOAT_DATA)
      {
          return (TNG_FAILURE);
      }
  
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_box_shape_read_range(struct tng_trajectory* tng_data,
                                                                      const int64_t first_frame,
                                                                      const int64_t last_frame,
                                                                      float**       box_shape,
                                                                      int64_t*      stride_length)
  {
      int64_t             n_values_per_frame;
      char                type;
      tng_function_status stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(box_shape, "TNG library: box_shape must not be a NULL pointer");
      TNG_ASSERT(first_frame <= last_frame,
                 "TNG library: first_frame must be lower or equal to last_frame.");
      TNG_ASSERT(stride_length, "TNG library: stride_length must not be a NULL pointer");
  
      stat = tng_data_vector_interval_get(tng_data, TNG_TRAJ_BOX_SHAPE, first_frame, last_frame, TNG_USE_HASH,
                                          (void**)box_shape, stride_length, &n_values_per_frame, &type);
  
      if (stat == TNG_SUCCESS && type != TNG_FLOAT_DATA)
      {
          return (TNG_FAILURE);
      }
  
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_generic_write_interval_set(struct tng_trajectory* tng_data,
                                                                            const int64_t          i,
                                                                            const int64_t n_values_per_frame,
                                                                            const int64_t block_id,
                                                                            const char*   block_name,
                                                                            const char particle_dependency,
                                                                            const char compression)
  {
      tng_trajectory_frame_set_t frame_set;
      tng_data_t                 data;
      int64_t                    n_particles, n_frames;
      tng_function_status        stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(i >= 0, "TNG library: i (writing interval) must be >= 0.");
  
      if (i <= 0)
      {
          fprintf(stderr, "TNG library: Cannot set writing interval to %" PRId64 ". %s: %d\n", i,
                  __FILE__, __LINE__);
          return (TNG_FAILURE);
      }
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      if (!frame_set || tng_data->n_trajectory_frame_sets <= 0)
      {
          n_frames = tng_data->frame_set_n_frames;
  
          stat = tng_frame_set_new(tng_data, 0, n_frames);
          if (stat != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot create frame set.  %s: %d\n", __FILE__, __LINE__);
              return (stat);
          }
      }
      else
      {
          n_frames = frame_set->n_frames;
      }
  
      if (particle_dependency == TNG_PARTICLE_BLOCK_DATA)
      {
          tng_num_particles_get(tng_data, &n_particles);
          if (n_particles <= 0)
          {
              return (TNG_FAILURE);
          }
  
          if (tng_particle_data_find(tng_data, block_id, &data) != TNG_SUCCESS)
          {
              stat = tng_particle_data_block_add(tng_data, block_id, block_name, TNG_FLOAT_DATA,
                                                 TNG_TRAJECTORY_BLOCK, n_frames, n_values_per_frame,
                                                 i, 0, n_particles, compression, 0);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error %s adding data block. %s: %d\n", block_name,
                          __FILE__, __LINE__);
                  return (stat);
              }
              data = &frame_set->tr_particle_data[frame_set->n_particle_data_blocks - 1];
              stat = tng_allocate_particle_data_mem(tng_data, data, n_frames, i, n_particles,
                                                    n_values_per_frame);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error allocating particle data memory. %s: %d\n",
                          __FILE__, __LINE__);
                  return (stat);
              }
          }
          else
          {
              if (data->stride_length != i)
              {
                  data->stride_length = i;
                  stat = tng_allocate_particle_data_mem(tng_data, data, n_frames, i, n_particles,
                                                        n_values_per_frame);
                  if (stat != TNG_SUCCESS)
                  {
                      fprintf(stderr, "TNG library: Error allocating particle data memory. %s: %d\n",
                              __FILE__, __LINE__);
                      return (stat);
                  }
              }
          }
      }
      else
      {
          if (tng_data_find(tng_data, block_id, &data) != TNG_SUCCESS)
          {
              stat = tng_data_block_add(tng_data, block_id, block_name, TNG_FLOAT_DATA, TNG_TRAJECTORY_BLOCK,
                                        n_frames, n_values_per_frame, i, compression, 0);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error %s adding data block. %s: %d\n", block_name,
                          __FILE__, __LINE__);
                  return (stat);
              }
              data = &frame_set->tr_data[frame_set->n_data_blocks - 1];
              stat = tng_allocate_data_mem(tng_data, data, n_frames, i, n_values_per_frame);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error allocating particle data memory. %s: %d\n",
                          __FILE__, __LINE__);
                  return (stat);
              }
          }
          else
          {
              if (data->stride_length != i)
              {
                  data->stride_length = i;
                  stat = tng_allocate_data_mem(tng_data, data, n_frames, i, n_values_per_frame);
                  if (stat != TNG_SUCCESS)
                  {
                      fprintf(stderr, "TNG library: Error allocating particle data memory. %s: %d\n",
                              __FILE__, __LINE__);
                      return (stat);
                  }
              }
          }
      }
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT
                      tng_util_generic_write_interval_double_set(struct tng_trajectory* tng_data,
                                                                 const int64_t          i,
                                                                 const int64_t          n_values_per_frame,
                                                                 const int64_t          block_id,
                                                                 const char*            block_name,
                                                                 const char             particle_dependency,
                                                                 const char             compression)
  {
      tng_trajectory_frame_set_t frame_set;
      tng_data_t                 data;
      int64_t                    n_particles, n_frames;
      tng_function_status        stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(i >= 0, "TNG library: i (writing interval) must be >= 0.");
  
      if (i <= 0)
      {
          fprintf(stderr, "TNG library: Cannot set writing frequency to %" PRId64 ". %s: %d\n", i,
                  __FILE__, __LINE__);
          return (TNG_FAILURE);
      }
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      if (!frame_set || tng_data->n_trajectory_frame_sets <= 0)
      {
          n_frames = tng_data->frame_set_n_frames;
  
          stat = tng_frame_set_new(tng_data, 0, n_frames);
          if (stat != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot create frame set.  %s: %d\n", __FILE__, __LINE__);
              return (stat);
          }
      }
      else
      {
          n_frames = frame_set->n_frames;
      }
  
      if (particle_dependency == TNG_PARTICLE_BLOCK_DATA)
      {
          tng_num_particles_get(tng_data, &n_particles);
  
          if (n_particles <= 0)
          {
              return (TNG_FAILURE);
          }
  
          if (tng_particle_data_find(tng_data, block_id, &data) != TNG_SUCCESS)
          {
              stat = tng_particle_data_block_add(tng_data, block_id, block_name, TNG_DOUBLE_DATA,
                                                 TNG_TRAJECTORY_BLOCK, n_frames, n_values_per_frame,
                                                 i, 0, n_particles, compression, 0);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error %s adding data block. %s: %d\n", block_name,
                          __FILE__, __LINE__);
                  return (stat);
              }
              data = &frame_set->tr_particle_data[frame_set->n_particle_data_blocks - 1];
              stat = tng_allocate_particle_data_mem(tng_data, data, n_frames, i, n_particles,
                                                    n_values_per_frame);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error allocating particle data memory. %s: %d\n",
                          __FILE__, __LINE__);
                  return (stat);
              }
          }
          else
          {
              data->stride_length = i;
          }
      }
      else
      {
          if (tng_data_find(tng_data, block_id, &data) != TNG_SUCCESS)
          {
              stat = tng_data_block_add(tng_data, block_id, block_name, TNG_DOUBLE_DATA, TNG_TRAJECTORY_BLOCK,
                                        n_frames, n_values_per_frame, i, compression, 0);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error %s adding data block. %s: %d\n", block_name,
                          __FILE__, __LINE__);
                  return (stat);
              }
              data = &frame_set->tr_data[frame_set->n_data_blocks - 1];
              stat = tng_allocate_data_mem(tng_data, data, n_frames, i, n_values_per_frame);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error allocating particle data memory. %s: %d\n",
                          __FILE__, __LINE__);
                  return (stat);
              }
          }
          else
          {
              data->stride_length = i;
          }
      }
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_generic_write_frequency_set(struct tng_trajectory* tng_data,
                                                                             const int64_t          i,
                                                                             const int64_t n_values_per_frame,
                                                                             const int64_t block_id,
                                                                             const char*   block_name,
                                                                             const char particle_dependency,
                                                                             const char compression)
  {
      fprintf(stderr,
              "TNG library: Using obsolete function tng_util_generic_write_frequency_set(). "
              "See documentation. %s: %d",
              __FILE__, __LINE__);
      return (tng_util_generic_write_interval_set(tng_data, i, n_values_per_frame, block_id,
                                                  block_name, particle_dependency, compression));
  }
  tng_function_status DECLSPECDLLEXPORT tng_util_pos_write_interval_set(struct tng_trajectory* tng_data,
                                                                        const int64_t          i)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(i > 0, "TNG library: i (writing interval) must be >= 0.");
  
      return (tng_util_generic_write_interval_set(tng_data, i, 3, TNG_TRAJ_POSITIONS, "POSITIONS",
                                                  TNG_PARTICLE_BLOCK_DATA, TNG_TNG_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_pos_write_interval_double_set(struct tng_trajectory* tng_data,
                                                                               const int64_t i)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(i > 0, "TNG library: i (writing interval) must be >= 0.");
  
      return (tng_util_generic_write_interval_double_set(
              tng_data, i, 3, TNG_TRAJ_POSITIONS, "POSITIONS", TNG_PARTICLE_BLOCK_DATA, TNG_TNG_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_pos_write_frequency_set(struct tng_trajectory* tng_data,
                                                                         const int64_t          i)
  {
      fprintf(stderr,
              "TNG library: Using obsolete function tng_util_pos_write_frequency_set(). "
              "See documentation. %s: %d",
              __FILE__, __LINE__);
      return (tng_util_pos_write_interval_set(tng_data, i));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_vel_write_interval_set(struct tng_trajectory* tng_data,
                                                                        const int64_t          i)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(i > 0, "TNG library: i (writing interval) must be >= 0.");
  
      return (tng_util_generic_write_interval_set(tng_data, i, 3, TNG_TRAJ_VELOCITIES, "VELOCITIES",
                                                  TNG_PARTICLE_BLOCK_DATA, TNG_TNG_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_vel_write_interval_double_set(struct tng_trajectory* tng_data,
                                                                               const int64_t i)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(i > 0, "TNG library: i (writing interval) must be >= 0.");
  
      return (tng_util_generic_write_interval_double_set(tng_data, i, 3, TNG_TRAJ_VELOCITIES,
                                                         "VELOCITIES", TNG_PARTICLE_BLOCK_DATA,
                                                         TNG_TNG_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_vel_write_frequency_set(struct tng_trajectory* tng_data,
                                                                         const int64_t          i)
  {
      fprintf(stderr,
              "TNG library: Using obsolete function tng_util_vel_write_frequency_set(). "
              "See documentation. %s: %d",
              __FILE__, __LINE__);
      return (tng_util_vel_write_interval_set(tng_data, i));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_force_write_interval_set(struct tng_trajectory* tng_data,
                                                                          const int64_t          i)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(i > 0, "TNG library: i (writing interval) must be >= 0.");
  
      return (tng_util_generic_write_interval_set(tng_data, i, 3, TNG_TRAJ_FORCES, "FORCES",
                                                  TNG_PARTICLE_BLOCK_DATA, TNG_GZIP_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_force_write_interval_double_set(struct tng_trajectory* tng_data,
                                                                                 const int64_t i)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(i > 0, "TNG library: i (writing interval) must be >= 0.");
  
      return (tng_util_generic_write_interval_double_set(tng_data, i, 3, TNG_TRAJ_FORCES, "FORCES",
                                                         TNG_PARTICLE_BLOCK_DATA, TNG_GZIP_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_force_write_frequency_set(struct tng_trajectory* tng_data,
                                                                           const int64_t          i)
  {
      fprintf(stderr,
              "TNG library: Using obsolete function tng_util_force_write_frequency_set(). "
              "See documentation. %s: %d",
              __FILE__, __LINE__);
      return (tng_util_force_write_interval_set(tng_data, i));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_box_shape_write_interval_set(struct tng_trajectory* tng_data,
                                                                              const int64_t i)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(i > 0, "TNG library: i (writing interval) must be >= 0.");
  
      return (tng_util_generic_write_interval_set(tng_data, i, 9, TNG_TRAJ_BOX_SHAPE, "BOX SHAPE",
                                                  TNG_NON_PARTICLE_BLOCK_DATA, TNG_GZIP_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_box_shape_write_interval_double_set(struct tng_trajectory* tng_data,
                                                                                     const int64_t i)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(i > 0, "TNG library: i (writing interval) must be >= 0.");
  
      return (tng_util_generic_write_interval_double_set(tng_data, i, 9, TNG_TRAJ_BOX_SHAPE,
                                                         "BOX SHAPE", TNG_NON_PARTICLE_BLOCK_DATA,
                                                         TNG_GZIP_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_box_shape_write_frequency_set(struct tng_trajectory* tng_data,
                                                                               const int64_t i)
  {
      fprintf(stderr,
              "TNG library: Using obsolete function tng_util_box_shape_write_frequency_set(). "
              "See documentation. %s: %d",
              __FILE__, __LINE__);
      return (tng_util_box_shape_write_interval_set(tng_data, i));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_generic_write(struct tng_trajectory* tng_data,
                                                               const int64_t          frame_nr,
                                                               const float*           values,
                                                               const int64_t n_values_per_frame,
                                                               const int64_t block_id,
                                                               const char*   block_name,
                                                               const char    particle_dependency,
                                                               const char    compression)
  {
      tng_trajectory_frame_set_t frame_set;
      tng_data_t                 data;
      int64_t                    n_particles = 0, n_frames, stride_length = 100, frame_pos;
      int64_t                    last_frame;
      int                        is_first_frame_flag = 0;
      char                       block_type_flag;
      tng_function_status        stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(values, "TNG library: values must not be a NULL pointer");
  
      if (particle_dependency == TNG_PARTICLE_BLOCK_DATA)
      {
          tng_num_particles_get(tng_data, &n_particles);
          TNG_ASSERT(n_particles > 0,
                     "TNG library: There must be particles in the system to write particle data.");
      }
  
      if (values == 0)
      {
          return (TNG_FAILURE);
      }
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      if (frame_nr < 0)
      {
          block_type_flag = TNG_NON_TRAJECTORY_BLOCK;
          n_frames = stride_length = 1;
      }
      else
      {
          block_type_flag = TNG_TRAJECTORY_BLOCK;
  
          if (!frame_set || tng_data->n_trajectory_frame_sets <= 0)
          {
              stat = tng_frame_set_new(tng_data, 0, tng_data->frame_set_n_frames);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot create frame set.  %s: %d\n", __FILE__, __LINE__);
                  return (stat);
              }
          }
          last_frame = frame_set->first_frame + frame_set->n_frames - 1;
          if (frame_nr > last_frame)
          {
              stat = tng_frame_set_write(tng_data, TNG_USE_HASH);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot write frame set.  %s: %d\n", __FILE__, __LINE__);
                  return (stat);
              }
              if (last_frame + tng_data->frame_set_n_frames < frame_nr)
              {
                  last_frame = frame_nr - 1;
              }
              stat = tng_frame_set_new(tng_data, last_frame + 1, tng_data->frame_set_n_frames);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot create frame set.  %s: %d\n", __FILE__, __LINE__);
                  return (stat);
              }
          }
          if (frame_set->n_unwritten_frames == 0)
          {
              is_first_frame_flag = 1;
          }
          frame_set->n_unwritten_frames = frame_nr - frame_set->first_frame + 1;
  
          n_frames = frame_set->n_frames;
      }
  
      if (particle_dependency == TNG_PARTICLE_BLOCK_DATA)
      {
          if (tng_particle_data_find(tng_data, block_id, &data) != TNG_SUCCESS)
          {
              stat = tng_particle_data_block_add(tng_data, block_id, block_name, TNG_FLOAT_DATA,
                                                 block_type_flag, n_frames, n_values_per_frame,
                                                 stride_length, 0, n_particles, compression, 0);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error %s adding data block. %s: %d\n", block_name,
                          __FILE__, __LINE__);
                  return (stat);
              }
              if (block_type_flag == TNG_TRAJECTORY_BLOCK)
              {
                  data = &frame_set->tr_particle_data[frame_set->n_particle_data_blocks - 1];
              }
              else
              {
                  data = &tng_data->non_tr_particle_data[tng_data->n_particle_data_blocks - 1];
              }
              stat = tng_allocate_particle_data_mem(tng_data, data, n_frames, stride_length,
                                                    n_particles, n_values_per_frame);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error allocating particle data memory. %s: %d\n",
                          __FILE__, __LINE__);
                  return (stat);
              }
          }
          /* FIXME: Here we must be able to handle modified n_particles as well. */
          else if (n_frames > data->n_frames)
          {
              stat = tng_allocate_particle_data_mem(tng_data, data, n_frames, data->stride_length,
                                                    n_particles, n_values_per_frame);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error allocating particle data memory. %s: %d\n",
                          __FILE__, __LINE__);
                  return (stat);
              }
          }
  
          if (block_type_flag == TNG_TRAJECTORY_BLOCK)
          {
              stride_length = data->stride_length;
  
              if (is_first_frame_flag || data->first_frame_with_data < frame_set->first_frame)
              {
                  data->first_frame_with_data = frame_nr;
                  frame_pos                   = 0;
              }
              else
              {
                  frame_pos = (frame_nr - frame_set->first_frame) / stride_length;
              }
  
              memcpy((char*)data->values + sizeof(float) * frame_pos * n_particles * n_values_per_frame,
                     values, sizeof(float) * n_particles * n_values_per_frame);
          }
          else
          {
              memcpy(data->values, values, sizeof(float) * n_particles * n_values_per_frame);
          }
      }
      else
      {
          if (tng_data_find(tng_data, block_id, &data) != TNG_SUCCESS)
          {
              stat = tng_data_block_add(tng_data, block_id, block_name, TNG_FLOAT_DATA, block_type_flag,
                                        n_frames, n_values_per_frame, stride_length, compression, 0);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error %s adding data block. %s: %d\n", block_name,
                          __FILE__, __LINE__);
                  return (stat);
              }
              if (block_type_flag == TNG_TRAJECTORY_BLOCK)
              {
                  data = &frame_set->tr_data[frame_set->n_data_blocks - 1];
              }
              else
              {
                  data = &tng_data->non_tr_data[tng_data->n_data_blocks - 1];
              }
              stat = tng_allocate_data_mem(tng_data, data, n_frames, stride_length, n_values_per_frame);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error allocating particle data memory. %s: %d\n",
                          __FILE__, __LINE__);
                  return (stat);
              }
          }
          /* FIXME: Here we must be able to handle modified n_particles as well. */
          else if (n_frames > data->n_frames)
          {
              stat = tng_allocate_data_mem(tng_data, data, n_frames, data->stride_length, n_values_per_frame);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error allocating particle data memory. %s: %d\n",
                          __FILE__, __LINE__);
                  return (stat);
              }
          }
  
          if (block_type_flag == TNG_TRAJECTORY_BLOCK)
          {
              stride_length = data->stride_length;
  
              if (is_first_frame_flag || data->first_frame_with_data < frame_set->first_frame)
              {
                  data->first_frame_with_data = frame_nr;
                  frame_pos                   = 0;
              }
              else
              {
                  frame_pos = (frame_nr - frame_set->first_frame) / stride_length;
              }
  
              memcpy((char*)data->values + sizeof(float) * frame_pos * n_values_per_frame, values,
                     sizeof(float) * n_values_per_frame);
          }
          else
          {
              memcpy(data->values, values, sizeof(float) * n_values_per_frame);
          }
      }
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_generic_double_write(struct tng_trajectory* tng_data,
                                                                      const int64_t          frame_nr,
                                                                      const double*          values,
                                                                      const int64_t n_values_per_frame,
                                                                      const int64_t block_id,
                                                                      const char*   block_name,
                                                                      const char particle_dependency,
                                                                      const char compression)
  {
      tng_trajectory_frame_set_t frame_set;
      tng_data_t                 data;
      int64_t                    n_particles = 0, n_frames, stride_length = 100, frame_pos;
      int64_t                    last_frame;
      int                        is_first_frame_flag = 0;
      char                       block_type_flag;
      tng_function_status        stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(values, "TNG library: values must not be a NULL pointer");
  
      if (particle_dependency == TNG_PARTICLE_BLOCK_DATA)
      {
          tng_num_particles_get(tng_data, &n_particles);
          TNG_ASSERT(n_particles > 0,
                     "TNG library: There must be particles in the system to write particle data.");
      }
  
      if (values == 0)
      {
          return (TNG_FAILURE);
      }
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      if (frame_nr < 0)
      {
          block_type_flag = TNG_NON_TRAJECTORY_BLOCK;
          n_frames = stride_length = 1;
      }
      else
      {
          block_type_flag = TNG_TRAJECTORY_BLOCK;
  
          if (!frame_set || tng_data->n_trajectory_frame_sets <= 0)
          {
              stat = tng_frame_set_new(tng_data, 0, tng_data->frame_set_n_frames);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot create frame set.  %s: %d\n", __FILE__, __LINE__);
                  return (stat);
              }
          }
          last_frame = frame_set->first_frame + frame_set->n_frames - 1;
          if (frame_nr > last_frame)
          {
              stat = tng_frame_set_write(tng_data, TNG_USE_HASH);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot write frame set.  %s: %d\n", __FILE__, __LINE__);
                  return (stat);
              }
              if (last_frame + tng_data->frame_set_n_frames < frame_nr)
              {
                  last_frame = frame_nr - 1;
              }
              stat = tng_frame_set_new(tng_data, last_frame + 1, tng_data->frame_set_n_frames);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Cannot create frame set.  %s: %d\n", __FILE__, __LINE__);
                  return (stat);
              }
          }
          if (frame_set->n_unwritten_frames == 0)
          {
              is_first_frame_flag = 1;
          }
          frame_set->n_unwritten_frames = frame_nr - frame_set->first_frame + 1;
  
          n_frames = frame_set->n_frames;
      }
  
  
      if (particle_dependency == TNG_PARTICLE_BLOCK_DATA)
      {
          if (tng_particle_data_find(tng_data, block_id, &data) != TNG_SUCCESS)
          {
              stat = tng_particle_data_block_add(tng_data, block_id, block_name, TNG_DOUBLE_DATA,
                                                 block_type_flag, n_frames, n_values_per_frame,
                                                 stride_length, 0, n_particles, compression, 0);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error %s adding data block. %s: %d\n", block_name,
                          __FILE__, __LINE__);
                  return (stat);
              }
              if (block_type_flag == TNG_TRAJECTORY_BLOCK)
              {
                  data = &frame_set->tr_particle_data[frame_set->n_particle_data_blocks - 1];
              }
              else
              {
                  data = &tng_data->non_tr_particle_data[tng_data->n_particle_data_blocks - 1];
              }
              stat = tng_allocate_particle_data_mem(tng_data, data, n_frames, stride_length,
                                                    n_particles, n_values_per_frame);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error allocating particle data memory. %s: %d\n",
                          __FILE__, __LINE__);
                  return (stat);
              }
          }
          /* FIXME: Here we must be able to handle modified n_particles as well. */
          else if (n_frames > data->n_frames)
          {
              stat = tng_allocate_particle_data_mem(tng_data, data, n_frames, data->stride_length,
                                                    n_particles, n_values_per_frame);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error allocating particle data memory. %s: %d\n",
                          __FILE__, __LINE__);
                  return (stat);
              }
          }
  
          if (block_type_flag == TNG_TRAJECTORY_BLOCK)
          {
              stride_length = data->stride_length;
  
              if (is_first_frame_flag || data->first_frame_with_data < frame_set->first_frame)
              {
                  data->first_frame_with_data = frame_nr;
                  frame_pos                   = 0;
              }
              else
              {
                  frame_pos = (frame_nr - frame_set->first_frame) / stride_length;
              }
  
              memcpy((char*)data->values + sizeof(double) * frame_pos * n_particles * n_values_per_frame,
                     values, sizeof(double) * n_particles * n_values_per_frame);
          }
          else
          {
              memcpy(data->values, values, sizeof(double) * n_particles * n_values_per_frame);
          }
      }
      else
      {
          if (tng_data_find(tng_data, block_id, &data) != TNG_SUCCESS)
          {
              stat = tng_data_block_add(tng_data, block_id, block_name, TNG_DOUBLE_DATA, block_type_flag,
                                        n_frames, n_values_per_frame, stride_length, compression, 0);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error %s adding data block. %s: %d\n", block_name,
                          __FILE__, __LINE__);
                  return (stat);
              }
              if (block_type_flag == TNG_TRAJECTORY_BLOCK)
              {
                  data = &frame_set->tr_data[frame_set->n_data_blocks - 1];
              }
              else
              {
                  data = &tng_data->non_tr_data[tng_data->n_data_blocks - 1];
              }
              stat = tng_allocate_data_mem(tng_data, data, n_frames, stride_length, n_values_per_frame);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error allocating particle data memory. %s: %d\n",
                          __FILE__, __LINE__);
                  return (stat);
              }
          }
          /* FIXME: Here we must be able to handle modified n_particles as well. */
          else if (n_frames > data->n_frames)
          {
              stat = tng_allocate_data_mem(tng_data, data, n_frames, data->stride_length, n_values_per_frame);
              if (stat != TNG_SUCCESS)
              {
                  fprintf(stderr, "TNG library: Error allocating particle data memory. %s: %d\n",
                          __FILE__, __LINE__);
                  return (stat);
              }
          }
  
          if (block_type_flag == TNG_TRAJECTORY_BLOCK)
          {
              stride_length = data->stride_length;
  
              if (is_first_frame_flag || data->first_frame_with_data < frame_set->first_frame)
              {
                  data->first_frame_with_data = frame_nr;
                  frame_pos                   = 0;
              }
              else
              {
                  frame_pos = (frame_nr - frame_set->first_frame) / stride_length;
              }
  
              memcpy((char*)data->values + sizeof(double) * frame_pos * n_values_per_frame, values,
                     sizeof(double) * n_values_per_frame);
          }
          else
          {
              memcpy(data->values, values, sizeof(double) * n_values_per_frame);
          }
      }
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_pos_write(struct tng_trajectory* tng_data,
                                                           const int64_t          frame_nr,
                                                           const float*           positions)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(positions, "TNG library: positions must not be a NULL pointer");
  
      return (tng_util_generic_write(tng_data, frame_nr, positions, 3, TNG_TRAJ_POSITIONS,
                                     "POSITIONS", TNG_PARTICLE_BLOCK_DATA, TNG_TNG_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_pos_double_write(struct tng_trajectory* tng_data,
                                                                  const int64_t          frame_nr,
                                                                  const double*          positions)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(positions, "TNG library: positions must not be a NULL pointer");
  
      return (tng_util_generic_double_write(tng_data, frame_nr, positions, 3, TNG_TRAJ_POSITIONS,
                                            "POSITIONS", TNG_PARTICLE_BLOCK_DATA, TNG_TNG_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_vel_write(struct tng_trajectory* tng_data,
                                                           const int64_t          frame_nr,
                                                           const float*           velocities)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(velocities, "TNG library: velocities must not be a NULL pointer");
  
      return (tng_util_generic_write(tng_data, frame_nr, velocities, 3, TNG_TRAJ_VELOCITIES,
                                     "VELOCITIES", TNG_PARTICLE_BLOCK_DATA, TNG_TNG_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_vel_double_write(struct tng_trajectory* tng_data,
                                                                  const int64_t          frame_nr,
                                                                  const double*          velocities)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(velocities, "TNG library: velocities must not be a NULL pointer");
  
      return (tng_util_generic_double_write(tng_data, frame_nr, velocities, 3, TNG_TRAJ_VELOCITIES,
                                            "VELOCITIES", TNG_PARTICLE_BLOCK_DATA, TNG_TNG_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_force_write(struct tng_trajectory* tng_data,
                                                             const int64_t          frame_nr,
                                                             const float*           forces)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(forces, "TNG library: forces must not be a NULL pointer");
  
      return (tng_util_generic_write(tng_data, frame_nr, forces, 3, TNG_TRAJ_FORCES, "FORCES",
                                     TNG_PARTICLE_BLOCK_DATA, TNG_GZIP_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_force_double_write(struct tng_trajectory* tng_data,
                                                                    const int64_t          frame_nr,
                                                                    const double*          forces)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(forces, "TNG library: forces must not be a NULL pointer");
  
      return (tng_util_generic_double_write(tng_data, frame_nr, forces, 3, TNG_TRAJ_FORCES, "FORCES",
                                            TNG_PARTICLE_BLOCK_DATA, TNG_GZIP_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_box_shape_write(struct tng_trajectory* tng_data,
                                                                 const int64_t          frame_nr,
                                                                 const float*           box_shape)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(box_shape, "TNG library: box_shape must not be a NULL pointer");
  
      return (tng_util_generic_write(tng_data, frame_nr, box_shape, 9, TNG_TRAJ_BOX_SHAPE,
                                     "BOX SHAPE", TNG_NON_PARTICLE_BLOCK_DATA, TNG_GZIP_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_box_shape_double_write(struct tng_trajectory* tng_data,
                                                                        const int64_t frame_nr,
                                                                        const double* box_shape)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(box_shape, "TNG library: box_shape must not be a NULL pointer");
  
      return (tng_util_generic_double_write(tng_data, frame_nr, box_shape, 9, TNG_TRAJ_BOX_SHAPE,
                                            "BOX SHAPE", TNG_NON_PARTICLE_BLOCK_DATA, TNG_GZIP_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_generic_with_time_write(struct tng_trajectory* tng_data,
                                                                         const int64_t frame_nr,
                                                                         const double  time,
                                                                         const float*  values,
                                                                         const int64_t n_values_per_frame,
                                                                         const int64_t block_id,
                                                                         const char*   block_name,
                                                                         const char particle_dependency,
                                                                         const char compression)
  {
      tng_trajectory_frame_set_t frame_set;
      tng_function_status        stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(frame_nr >= 0, "TNG library: frame_nr must be >= 0.");
      TNG_ASSERT(time >= 0, "TNG library: time must be >= 0.");
      TNG_ASSERT(values, "TNG library: values must not be a NULL pointer");
  
      stat = tng_util_generic_write(tng_data, frame_nr, values, n_values_per_frame, block_id,
                                    block_name, particle_dependency, compression);
  
      if (stat != TNG_SUCCESS)
      {
          return (stat);
      }
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      /* first_frame_time is -1 when it is not yet set. */
      if (frame_set->first_frame_time < -0.1)
      {
          if (frame_nr > frame_set->first_frame)
          {
              stat = tng_frame_set_first_frame_time_set(
                      tng_data, time - (frame_nr - frame_set->first_frame) * tng_data->time_per_frame);
          }
          else
          {
              stat = tng_frame_set_first_frame_time_set(tng_data, time);
          }
      }
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_generic_with_time_double_write(struct tng_trajectory* tng_data,
                                                                                const int64_t frame_nr,
                                                                                const double  time,
                                                                                const double* values,
                                                                                const int64_t n_values_per_frame,
                                                                                const int64_t block_id,
                                                                                const char* block_name,
                                                                                const char particle_dependency,
                                                                                const char compression)
  {
      tng_trajectory_frame_set_t frame_set;
      tng_function_status        stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(frame_nr >= 0, "TNG library: frame_nr must be >= 0.");
      TNG_ASSERT(time >= 0, "TNG library: time must be >= 0.");
      TNG_ASSERT(values, "TNG library: values must not be a NULL pointer");
  
      stat = tng_util_generic_double_write(tng_data, frame_nr, values, n_values_per_frame, block_id,
                                           block_name, particle_dependency, compression);
  
      if (stat != TNG_SUCCESS)
      {
          return (stat);
      }
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      /* first_frame_time is -1 when it is not yet set. */
      if (frame_set->first_frame_time < -0.1)
      {
          if (frame_nr > frame_set->first_frame)
          {
              stat = tng_frame_set_first_frame_time_set(
                      tng_data, time - (frame_nr - frame_set->first_frame) * tng_data->time_per_frame);
          }
          else
          {
              stat = tng_frame_set_first_frame_time_set(tng_data, time);
          }
      }
      return (stat);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_pos_with_time_write(struct tng_trajectory* tng_data,
                                                                     const int64_t          frame_nr,
                                                                     const double           time,
                                                                     const float*           positions)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(frame_nr >= 0, "TNG library: frame_nr must be >= 0.");
      TNG_ASSERT(time >= 0, "TNG library: time must be >= 0.");
      TNG_ASSERT(positions, "TNG library: positions must not be a NULL pointer");
  
      return (tng_util_generic_with_time_write(tng_data, frame_nr, time, positions, 3, TNG_TRAJ_POSITIONS,
                                               "POSITIONS", TNG_PARTICLE_BLOCK_DATA, TNG_TNG_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_pos_with_time_double_write(struct tng_trajectory* tng_data,
                                                                            const int64_t frame_nr,
                                                                            const double  time,
                                                                            const double* positions)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(frame_nr >= 0, "TNG library: frame_nr must be >= 0.");
      TNG_ASSERT(time >= 0, "TNG library: time must be >= 0.");
      TNG_ASSERT(positions, "TNG library: positions must not be a NULL pointer");
  
      return (tng_util_generic_with_time_double_write(tng_data, frame_nr, time, positions, 3,
                                                      TNG_TRAJ_POSITIONS, "POSITIONS",
                                                      TNG_PARTICLE_BLOCK_DATA, TNG_TNG_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_vel_with_time_write(struct tng_trajectory* tng_data,
                                                                     const int64_t          frame_nr,
                                                                     const double           time,
                                                                     const float* velocities)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(frame_nr >= 0, "TNG library: frame_nr must be >= 0.");
      TNG_ASSERT(time >= 0, "TNG library: time must be >= 0.");
      TNG_ASSERT(velocities, "TNG library: velocities must not be a NULL pointer");
  
      return (tng_util_generic_with_time_write(tng_data, frame_nr, time, velocities, 3, TNG_TRAJ_VELOCITIES,
                                               "VELOCITIES", TNG_PARTICLE_BLOCK_DATA, TNG_TNG_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_vel_with_time_double_write(struct tng_trajectory* tng_data,
                                                                            const int64_t frame_nr,
                                                                            const double  time,
                                                                            const double* velocities)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(frame_nr >= 0, "TNG library: frame_nr must be >= 0.");
      TNG_ASSERT(time >= 0, "TNG library: time must be >= 0.");
      TNG_ASSERT(velocities, "TNG library: velocities must not be a NULL pointer");
  
      return (tng_util_generic_with_time_double_write(tng_data, frame_nr, time, velocities, 3,
                                                      TNG_TRAJ_VELOCITIES, "VELOCITIES",
                                                      TNG_PARTICLE_BLOCK_DATA, TNG_TNG_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_force_with_time_write(struct tng_trajectory* tng_data,
                                                                       const int64_t frame_nr,
                                                                       const double  time,
                                                                       const float*  forces)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(frame_nr >= 0, "TNG library: frame_nr must be >= 0.");
      TNG_ASSERT(time >= 0, "TNG library: time must be >= 0.");
      TNG_ASSERT(forces, "TNG library: forces must not be a NULL pointer");
  
      return (tng_util_generic_with_time_write(tng_data, frame_nr, time, forces, 3, TNG_TRAJ_FORCES,
                                               "FORCES", TNG_PARTICLE_BLOCK_DATA, TNG_GZIP_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_force_with_time_double_write(struct tng_trajectory* tng_data,
                                                                              const int64_t frame_nr,
                                                                              const double  time,
                                                                              const double* forces)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(frame_nr >= 0, "TNG library: frame_nr must be >= 0.");
      TNG_ASSERT(time >= 0, "TNG library: time must be >= 0.");
      TNG_ASSERT(forces, "TNG library: forces must not be a NULL pointer");
  
      return (tng_util_generic_with_time_double_write(tng_data, frame_nr, time, forces, 3,
                                                      TNG_TRAJ_FORCES, "FORCES",
                                                      TNG_PARTICLE_BLOCK_DATA, TNG_GZIP_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_box_shape_with_time_write(struct tng_trajectory* tng_data,
                                                                           const int64_t frame_nr,
                                                                           const double  time,
                                                                           const float*  box_shape)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(frame_nr >= 0, "TNG library: frame_nr must be >= 0.");
      TNG_ASSERT(time >= 0, "TNG library: time must be >= 0.");
      TNG_ASSERT(box_shape, "TNG library: box_shape must not be a NULL pointer");
  
      return (tng_util_generic_with_time_write(tng_data, frame_nr, time, box_shape, 9,
                                               TNG_TRAJ_BOX_SHAPE, "BOX SHAPE",
                                               TNG_NON_PARTICLE_BLOCK_DATA, TNG_GZIP_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_box_shape_with_time_double_write(struct tng_trajectory* tng_data,
                                                                                  const int64_t frame_nr,
                                                                                  const double time,
                                                                                  const double* box_shape)
  {
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(frame_nr >= 0, "TNG library: frame_nr must be >= 0.");
      TNG_ASSERT(time >= 0, "TNG library: time must be >= 0.");
      TNG_ASSERT(box_shape, "TNG library: box_shape must not be a NULL pointer");
  
      return (tng_util_generic_with_time_double_write(
              tng_data, frame_nr, time, box_shape, 9, TNG_TRAJ_BOX_SHAPE, "BOX SHAPE",
              TNG_NON_PARTICLE_BLOCK_DATA, TNG_GZIP_COMPRESSION));
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_frame_current_compression_get(struct tng_trajectory* tng_data,
                                                                               const int64_t block_id,
                                                                               int64_t*      codec_id,
                                                                               double*       factor)
  {
      tng_trajectory_frame_set_t frame_set;
      tng_data_t                 data = 0;
      tng_function_status        stat;
      int64_t                    i;
      int                        block_type = -1;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(codec_id,
                 "TNG library: The pointer to the returned codec id must not be a NULL pointer.");
      TNG_ASSERT(factor,
                 "TNG library: The pointer to the returned multiplication factor must not be a NULL "
                 "pointer.");
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      stat = tng_particle_data_find(tng_data, block_id, &data);
      if (stat == TNG_SUCCESS)
      {
          block_type = TNG_PARTICLE_BLOCK_DATA;
      }
      else
      {
          stat = tng_data_find(tng_data, block_id, &data);
          if (stat == TNG_SUCCESS)
          {
              block_type = TNG_NON_PARTICLE_BLOCK_DATA;
          }
          else
          {
              stat = tng_frame_set_read_current_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
              if (stat != TNG_SUCCESS)
              {
                  return (stat);
              }
              stat = tng_particle_data_find(tng_data, block_id, &data);
              if (stat == TNG_SUCCESS)
              {
                  block_type = TNG_PARTICLE_BLOCK_DATA;
              }
              else
              {
                  stat = tng_data_find(tng_data, block_id, &data);
                  if (stat == TNG_SUCCESS)
                  {
                      block_type = TNG_NON_PARTICLE_BLOCK_DATA;
                  }
                  else
                  {
                      return (stat);
                  }
              }
          }
      }
      if (block_type == TNG_PARTICLE_BLOCK_DATA || block_type == TNG_NON_PARTICLE_BLOCK_DATA)
      {
          if (data->last_retrieved_frame < 0)
          {
              i = data->first_frame_with_data;
          }
          else
          {
              i = data->last_retrieved_frame;
          }
      }
      else
      {
          return (TNG_FAILURE);
      }
      if (i < frame_set->first_frame || i >= frame_set->first_frame + frame_set->n_frames)
      {
          stat = tng_frame_set_of_frame_find(tng_data, i);
          if (stat != TNG_SUCCESS)
          {
              return (stat);
          }
          stat = tng_frame_set_read_current_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
          if (stat != TNG_SUCCESS)
          {
              fprintf(stderr, "TNG library: Cannot read data block of frame set. %s: %d\n", __FILE__, __LINE__);
              return (stat);
          }
      }
      if (block_type == TNG_PARTICLE_BLOCK_DATA || block_type == TNG_NON_PARTICLE_BLOCK_DATA)
      {
          *codec_id = data->codec_id;
          *factor   = data->compression_multiplier;
      }
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT
                      tng_util_trajectory_next_frame_present_data_blocks_find(struct tng_trajectory* tng_data,
                                                                              int64_t                current_frame,
                                                                              const int64_t  n_requested_data_block_ids,
                                                                              const int64_t* requested_data_block_ids,
                                                                              int64_t*       next_frame,
                                                                              int64_t*       n_data_blocks_in_next_frame,
                                                                              int64_t**      data_block_ids_in_next_frame)
  {
      tng_trajectory_frame_set_t frame_set;
      tng_function_status        stat;
      tng_data_t                 data;
      tng_gen_block_t            block;
      int64_t                    i, j, block_id, *temp;
      int64_t                    data_frame, frame_diff, min_diff;
      int64_t                    size, frame_set_file_pos;
      int                        found, read_all = 0;
      int64_t                    file_pos;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(next_frame, "TNG library: The pointer to the next frame must not be NULL.");
      TNG_ASSERT(n_data_blocks_in_next_frame,
                 "TNG library: The pointer to n_data_blocks_in_next_frame must not be NULL.");
      TNG_ASSERT(data_block_ids_in_next_frame,
                 "TNG library: The pointer to the list of data block IDs must not be NULL.");
  
      if (n_requested_data_block_ids)
      {
          TNG_ASSERT(requested_data_block_ids,
                     "TNG library: If the number of requested data blocks is > 0 then the array of "
                     "data block IDs must not be NULL.");
          size = sizeof(int64_t) * n_requested_data_block_ids;
          temp = (int64_t*)realloc(*data_block_ids_in_next_frame, size);
          if (!temp)
          {
              fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
              free(*data_block_ids_in_next_frame);
              *data_block_ids_in_next_frame = 0;
              return (TNG_CRITICAL);
          }
          *data_block_ids_in_next_frame = temp;
      }
  
      frame_set = &tng_data->current_trajectory_frame_set;
  
      current_frame += 1;
  
      if (current_frame < frame_set->first_frame
          || current_frame >= frame_set->first_frame + frame_set->n_frames)
      {
          frame_set_file_pos = tng_data->current_trajectory_frame_set_input_file_pos;
          stat               = tng_frame_set_of_frame_find(tng_data, current_frame);
          if (stat != TNG_SUCCESS)
          {
              /* If the frame set search found the frame set after the starting
               * frame set there is a gap in the frame sets. So, even if the frame
               * was not found the next frame with data is still in the found
               * frame set. */
              if (stat == TNG_CRITICAL || frame_set->prev_frame_set_file_pos != frame_set_file_pos)
              {
                  return (stat);
              }
              current_frame = frame_set->first_frame;
          }
      }
  
      /* Check for data blocks only if they have not already been found. */
      if (frame_set->n_particle_data_blocks <= 0 && frame_set->n_data_blocks <= 0)
      {
          file_pos = ftello(tng_data->input_file);
          if (file_pos < tng_data->input_file_len)
          {
              tng_block_init(&block);
              stat = tng_block_header_read(tng_data, block);
              while (file_pos < tng_data->input_file_len && stat != TNG_CRITICAL
                     && block->id != TNG_TRAJECTORY_FRAME_SET && block->id != -1)
              {
                  stat = tng_block_read_next(tng_data, block, TNG_USE_HASH);
                  if (stat != TNG_CRITICAL)
                  {
                      file_pos = ftello(tng_data->input_file);
                      if (file_pos < tng_data->input_file_len)
                      {
                          stat = tng_block_header_read(tng_data, block);
                      }
                  }
              }
              tng_block_destroy(&block);
              if (stat == TNG_CRITICAL)
              {
                  fprintf(stderr, "TNG library: Cannot read block header at pos %" PRId64 ". %s: %d\n",
                          file_pos, __FILE__, __LINE__);
                  return (stat);
              }
          }
          read_all = 1;
      }
  
      min_diff = -1;
  
      *n_data_blocks_in_next_frame = 0;
  
      for (i = 0; i < frame_set->n_particle_data_blocks; i++)
      {
          data     = &frame_set->tr_particle_data[i];
          block_id = data->block_id;
  
          if (n_requested_data_block_ids > 0)
          {
              found = 0;
              for (j = 0; j < n_requested_data_block_ids; j++)
              {
                  if (block_id == requested_data_block_ids[j])
                  {
                      found = 1;
                      break;
                  }
              }
              if (!found)
              {
                  continue;
              }
          }
  
          if (!read_all
              && (data->last_retrieved_frame < frame_set->first_frame
                  || data->last_retrieved_frame >= frame_set->first_frame + frame_set->n_frames))
          {
              stat = tng_frame_set_read_current_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
              if (stat == TNG_CRITICAL)
              {
                  fprintf(stderr, "TNG library: Cannot read data block of frame set. %s: %d\n",
                          __FILE__, __LINE__);
                  return (stat);
              }
              if (stat == TNG_FAILURE)
              {
                  continue;
              }
          }
          if (frame_set->first_frame != current_frame && data->last_retrieved_frame >= 0)
          {
              data_frame = data->last_retrieved_frame + data->stride_length;
          }
          else
          {
              data_frame = data->first_frame_with_data;
          }
          frame_diff = data_frame - current_frame;
          if (frame_diff < 0)
          {
              continue;
          }
          if (min_diff == -1 || frame_diff <= min_diff)
          {
              if (frame_diff < min_diff)
              {
                  *n_data_blocks_in_next_frame = 1;
              }
              else
              {
                  *n_data_blocks_in_next_frame += 1;
              }
              if (n_requested_data_block_ids <= 0)
              {
                  size = sizeof(int64_t) * (*n_data_blocks_in_next_frame);
                  temp = (int64_t*)realloc(*data_block_ids_in_next_frame, size);
                  if (!temp)
                  {
                      fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                      free(*data_block_ids_in_next_frame);
                      *data_block_ids_in_next_frame = 0;
                      return (TNG_CRITICAL);
                  }
                  *data_block_ids_in_next_frame = temp;
              }
              else
              {
                  TNG_ASSERT(*n_data_blocks_in_next_frame <= n_requested_data_block_ids,
                             "TNG library: Array of data block IDs out of bounds");
              }
              (*data_block_ids_in_next_frame)[(*n_data_blocks_in_next_frame) - 1] = block_id;
  
              min_diff = frame_diff;
          }
      }
      for (i = 0; i < frame_set->n_data_blocks; i++)
      {
          data     = &frame_set->tr_data[i];
          block_id = data->block_id;
  
          if (n_requested_data_block_ids > 0)
          {
              found = 0;
              for (j = 0; j < n_requested_data_block_ids; j++)
              {
                  if (block_id == requested_data_block_ids[j])
                  {
                      found = 1;
                      break;
                  }
              }
              if (!found)
              {
                  continue;
              }
          }
  
          if (!read_all
              && (data->last_retrieved_frame < frame_set->first_frame
                  || data->last_retrieved_frame >= frame_set->first_frame + frame_set->n_frames))
          {
              stat = tng_frame_set_read_current_only_data_from_block_id(tng_data, TNG_USE_HASH, block_id);
              if (stat == TNG_CRITICAL)
              {
                  fprintf(stderr, "TNG library: Cannot read data block of frame set. %s: %d\n",
                          __FILE__, __LINE__);
                  return (stat);
              }
              if (stat == TNG_FAILURE)
              {
                  continue;
              }
          }
          if (frame_set->first_frame != current_frame && data->last_retrieved_frame >= 0)
          {
              data_frame = data->last_retrieved_frame + data->stride_length;
          }
          else
          {
              data_frame = data->first_frame_with_data;
          }
          frame_diff = data_frame - current_frame;
          if (frame_diff < 0)
          {
              continue;
          }
          if (min_diff == -1 || frame_diff <= min_diff)
          {
              if (frame_diff < min_diff)
              {
                  *n_data_blocks_in_next_frame = 1;
              }
              else
              {
                  *n_data_blocks_in_next_frame += 1;
              }
              if (n_requested_data_block_ids <= 0)
              {
                  size = sizeof(int64_t) * (*n_data_blocks_in_next_frame);
                  temp = (int64_t*)realloc(*data_block_ids_in_next_frame, size);
                  if (!temp)
                  {
                      fprintf(stderr, "TNG library: Cannot allocate memory. %s: %d\n", __FILE__, __LINE__);
                      free(*data_block_ids_in_next_frame);
                      *data_block_ids_in_next_frame = 0;
                      return (TNG_CRITICAL);
                  }
                  *data_block_ids_in_next_frame = temp;
              }
              else
              {
                  TNG_ASSERT(*n_data_blocks_in_next_frame <= n_requested_data_block_ids,
                             "TNG library: Array of data block IDs out of bounds");
              }
              (*data_block_ids_in_next_frame)[(*n_data_blocks_in_next_frame) - 1] = block_id;
  
              min_diff = frame_diff;
          }
      }
      if (min_diff < 0)
      {
          return (TNG_FAILURE);
      }
      *next_frame = current_frame + min_diff;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_prepare_append_after_frame(struct tng_trajectory* tng_data,
                                                                            const int64_t prev_frame)
  {
      tng_function_status stat;
      FILE*               temp = tng_data->input_file;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
      TNG_ASSERT(prev_frame >= 0, "TNG library: The previous frame must not be negative.");
  
      tng_data->input_file = tng_data->output_file;
  
      stat = tng_frame_set_of_frame_find(tng_data, prev_frame);
      if (stat != TNG_SUCCESS)
      {
          return (stat);
      }
  
      tng_data->current_trajectory_frame_set_output_file_pos =
              tng_data->current_trajectory_frame_set_input_file_pos;
  
      tng_data->input_file = temp;
  
      return (TNG_SUCCESS);
  }
  
  tng_function_status DECLSPECDLLEXPORT tng_util_num_frames_with_data_of_block_id_get(struct tng_trajectory* tng_data,
                                                                                      const int64_t block_id,
                                                                                      int64_t* n_frames)
  {
      int64_t             curr_file_pos, first_frame_set_file_pos, curr_n_frames;
      tng_function_status stat;
  
      TNG_ASSERT(tng_data, "TNG library: Trajectory container not properly setup.");
  
      *n_frames = 0;
  
      if (tng_input_file_init(tng_data) != TNG_SUCCESS)
      {
          return (TNG_CRITICAL);
      }
  
      first_frame_set_file_pos = tng_data->first_trajectory_frame_set_input_file_pos;
      curr_file_pos            = ftello(tng_data->input_file);
      fseeko(tng_data->input_file, first_frame_set_file_pos, SEEK_SET);
  
      stat = tng_frame_set_n_frames_of_data_block_get(tng_data, block_id, &curr_n_frames);
  
      while (stat == TNG_SUCCESS && tng_data->current_trajectory_frame_set.next_frame_set_file_pos != -1)
      {
          *n_frames += curr_n_frames;
          fseeko(tng_data->input_file, tng_data->current_trajectory_frame_set.next_frame_set_file_pos,
                 SEEK_SET);
          stat = tng_frame_set_n_frames_of_data_block_get(tng_data, block_id, &curr_n_frames);
      }
      if (stat == TNG_SUCCESS)
      {
          *n_frames += curr_n_frames;
      }
      fseeko(tng_data->input_file, curr_file_pos, SEEK_SET);
      if (stat == TNG_CRITICAL)
      {
          return (TNG_CRITICAL);
      }
      return (TNG_SUCCESS);
  }
  