/* input/output stream */

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

#include "utilmem.h"
#include "utillog.h"
#include "utiliof.h"

/* commons */

void * iof_copy_data (const void *data, size_t size)
{
  return memcpy(util_malloc(size), data, size);
}

uint8_t * iof_copy_file_data (const char *filename, size_t *psize)
{
  FILE *file;
  size_t size;
  uint8_t *data;
  if ((file = fopen(filename, "rb")) == NULL)
    return NULL;
  fseek(file, 0, SEEK_END);
  size = (size_t)ftell(file);
  data = (uint8_t *)util_malloc(size);
  fseek(file, 0, SEEK_SET);
  if ((*psize = fread(data, 1, size, file)) != size)
  {
    util_free(data);
    data = NULL;
  }
  fclose(file);
  return data;
}

uint8_t * iof_copy_file_handle_data (FILE *file, size_t *psize)
{
  size_t size;
  uint8_t *data;
  //long offset = ftell(file); // keep offset intact?
  fseek(file, 0, SEEK_END);
  size = (size_t)ftell(file);
  data = (uint8_t *)util_malloc(size);
  fseek(file, 0, SEEK_SET);
  if ((*psize = fread(data, 1, size, file)) != size)
  {
    util_free(data);
    data = NULL;
  }
  //fseek(file, offset, SEEK_SET)
  return data;
}

FILE * iof_get_file (iof *F)
{
  if (F->flags & IOF_FILE)
    return iof_file_get_file(F->iofile);
  if (F->flags & IOF_FILE_HANDLE)
    return F->file;
  return NULL;
}

const char * iof_status_kind (iof_status status)
{
  switch (status)
  {
    case IOFEOF:
      return "IOFEOF";
    case IOFERR:
      return "IOFERR";
    case IOFEMPTY:
      return "IOFEMPTY";
    case IOFFULL:
      return "IOFFULL";
    default:
      break;
  }
  return "(unknown)";
}

/* shared pseudofile */

#define IOF_FILE_DEFAULTS 0

iof_file * iof_file_new (FILE *file)
{
  iof_file *iofile = (iof_file *)util_malloc(sizeof(iof_file));
  iof_file_set_fh(iofile, file);
  iofile->offset = NULL;
  iofile->size = 0;
  iofile->name = NULL;
  iofile->refcount = 0;
  iofile->flags = IOF_FILE_DEFAULTS|IOF_ALLOC;
  return iofile;
}

iof_file * iof_file_init (iof_file *iofile, FILE *file)
{
  iof_file_set_fh(iofile, file);
  iofile->offset = NULL;
  iofile->size = 0;
  iofile->name = NULL;
  iofile->refcount = 0;
  iofile->flags = IOF_FILE_DEFAULTS;
  return iofile;
}

iof_file * iof_file_rdata (const void *data, size_t size)
{
  iof_file *iofile = (iof_file *)util_malloc(sizeof(iof_file));
  iofile->rbuf = iofile->rpos = (const uint8_t *)data;
  iofile->rend = iofile->rbuf + size;
  iofile->offset = NULL;
  iofile->size = 0;
  iofile->name = NULL;
  iofile->refcount = 0;
  iofile->flags = IOF_FILE_DEFAULTS|IOF_ALLOC|IOF_DATA;
  return iofile;
}

iof_file * iof_file_rdata_init (iof_file *iofile, const void *data, size_t size)
{
  iofile->rbuf = iofile->rpos = (const uint8_t *)data;
  iofile->rend = iofile->rbuf + size;
  iofile->offset = NULL;
  iofile->size = 0; // lets keep it consequently set to zero (only for user disposition)
  iofile->name = NULL;
  iofile->refcount = 0;
  iofile->flags = IOF_FILE_DEFAULTS|IOF_DATA;
  return iofile;
}

iof_file * iof_file_wdata (void *data, size_t size)
{
  return iof_file_rdata((const void *)data, size);
}

iof_file * iof_file_wdata_init (iof_file *iofile, void *data, size_t size)
{
  return iof_file_rdata_init(iofile, (const void *)data, size);
}

/* typical uses so far */

iof_file * iof_file_reader_from_file_handle (iof_file *iofile, const char *filename, FILE *file, int preload, int closefile)
{
  uint8_t *data;
  size_t size;

  if (preload)
  {
    if ((data = iof_copy_file_handle_data(file, &size)) == NULL)
    {
      if (closefile) // callers expect close also on failure
        fclose(file);
      return NULL;
    }
    if (iofile == NULL)
      iofile = iof_file_rdata(data, size);
    else
      iof_file_rdata_init(iofile, data, size);
    iofile->flags |= IOF_BUFFER_ALLOC;
    if (closefile)
      fclose(file);
  }
  else
  {
    if (iofile == NULL)
      iofile = iof_file_new(file);
    else
      iof_file_init(iofile, file);
    if (closefile)
      iofile->flags |= IOF_CLOSE_FILE;
  }
  if (filename != NULL)
    iof_file_set_name(iofile, filename);
  return iofile;
}

iof_file * iof_file_reader_from_file (iof_file *iofile, const char *filename, int preload)
{
  FILE *file;
  if ((file = fopen(filename, "rb")) == NULL)
    return NULL;
  return iof_file_reader_from_file_handle(iofile, filename, file, preload, 1); // takes care to fclose() on failure
}

iof_file * iof_file_reader_from_data (iof_file *iofile, const void *data, size_t size, int preload, int freedata)
{
  void *newdata;
  if (data == NULL)
    return NULL;
  if (preload)
  {
    newdata = iof_copy_data(data, size);
    if (iofile == NULL)
      iofile = iof_file_rdata(newdata, size);
    else
      iof_file_rdata_init(iofile, newdata, size);
    iofile->flags |= IOF_BUFFER_ALLOC;
    //if (freedata) // hardly makes sense...  we can't free const void *
    //  util_free((void *)data);
  }
  else
  {
    if (iofile == NULL)
      iofile = iof_file_rdata(data, size);
    else
      iof_file_rdata_init(iofile, data, size);
    if (freedata)
      iofile->flags |= IOF_BUFFER_ALLOC;
  }
  return iofile;
}

/*
iof_file * iof_file_writer_from_file (iof_file *iofile, const char *filename)
{
  FILE *file;
  if ((file = fopen(filename, "wb")) == NULL)
    return NULL;
  if (iofile == NULL)
    iofile = iof_file_new(file);
  else
    iof_file_init(iofile, file);
  iofile->flags |= IOF_CLOSE_FILE;
  iof_file_set_name(iofile, filename);
  return iofile;
}
*/

/*
Because of limited number of FILE* handles available, we may need to close/reopen a file handle
when accessing it. In applications so far (fonts, images) we typically need the entire source
to parse the file on object creation and to rewrite or reload the data on dump. All iof_file API
functions assume that iofile has FILE* opened. Reopening it on every access (ftell, fseek, read/write)
makes no sense. So if the caller invalidates iofile by closing and NULLing its file handle,
it is also responsible to reopen when necessary.
*/

int iof_file_reclose_input (iof_file *iofile)
{
  FILE *file;
  if (iofile->flags & IOF_DATA)
    return 0;
  if ((file = iof_file_get_fh(iofile)) == NULL)
    return 0;
  fclose(file);
  iof_file_set_fh(iofile, NULL);
  iofile->flags &= ~IOF_RECLOSE_FILE;
  iofile->flags |= IOF_REOPEN_FILE;
  return 1;
}

int iof_file_reopen_input (iof_file *iofile)
{ // returns true if iofile readable
  FILE *file;
  const char *filename;
  if (iofile->flags & IOF_DATA)
    return 1;
  if ((file = iof_file_get_fh(iofile)) != NULL)
    return 1; // if present, assumed readable
  if ((filename = iofile->name) == NULL || (file = fopen(filename, "rb")) == NULL)
    return 0;
  iof_file_set_fh(iofile, file);
  iofile->flags &= ~IOF_REOPEN_FILE;
  iofile->flags |= IOF_RECLOSE_FILE;
  return 1;
}

/* freeing iof_file */

void iof_file_free (iof_file *iofile)
{
  FILE *file;
  if (iofile->flags & IOF_DATA)
  {
    if (iofile->flags & IOF_BUFFER_ALLOC)
    {
      iofile->flags &= ~IOF_BUFFER_ALLOC;
      if (iofile->buf != NULL)
      {
        util_free(iofile->buf);
        iofile->buf = iofile->pos = iofile->end = NULL;
      }
    }
  }
  else if ((file = iof_file_get_fh(iofile)) != NULL)
  {
    if (iofile->flags & IOF_CLOSE_FILE)
      fclose(file);
    iof_file_set_fh(iofile, NULL);
  }
  iof_file_set_name(iofile, NULL);
  if (iofile->flags & IOF_ALLOC)
    util_free(iofile);
}

/*
An attempt to close iofile input keeping things safe. In bindings we sometimes we need to force
closing the file handle, otherwise it is closed when garbage collector graciously calls destroyer.
Eg. we are done with an object representing pdf/image/font, but we can't move/replace it, as the
host language keeps the garbage that keeps a file handle. When we call fclose(), we also have to
set the handle to NULL. In many places we assume, that if the iofile wraps FILE *, than the handle
is operable (no NULL checks). To close the handle keeping iofile alive safe, we can silently convert
it dummy IOF_DATA buffer.
*/

void iof_file_close_input (iof_file *iofile)
{
  FILE *file;
  if (iofile->flags & IOF_DATA)
  {
    if (iofile->flags & IOF_BUFFER_ALLOC)
    {
      iofile->flags &= ~IOF_BUFFER_ALLOC;
      if (iofile->buf != NULL)
      {
        util_free(iofile->buf);
        //iofile->buf = iofile->pos = iofile->end = NULL;
      }
    }
  }
  else if ((file = iof_file_get_fh(iofile)) != NULL)
  {
    iof_file_set_fh(iofile, NULL);
    fclose(file);
  }
  iof_file_set_name(iofile, NULL);
  /* now make it a dummy string iofile */
  iofile->buf = iofile->pos = iofile->end = NULL;
  iofile->flags |= IOF_DATA;
}

/* set filename for reopen */

void iof_file_set_name (iof_file *iofile, const char *name)
{
  if (iofile->name != NULL)
    util_free(iofile->name);
  if (name != NULL)
    iofile->name = iof_copy_data(name, strlen(name) + 1);
  else
    iofile->name = NULL;
}

/* seek */

int iof_file_seek (iof_file *iofile, long offset, int whence)
{
  if (iofile->flags & IOF_DATA)
  {
    switch (whence)
    {
      case SEEK_SET:
        if (offset >= 0 && iofile->buf + offset <= iofile->end)
        {
          iofile->pos = iofile->buf + offset;
          return 0;
        }
        return -1;
      case SEEK_CUR:
        if ((offset >= 0 && iofile->pos + offset <= iofile->end) || (offset < 0 && iofile->pos + offset >= iofile->buf))
        {
          iofile->pos += offset;
          return 0;
        }
        return -1;
      case SEEK_END:
        if (offset <= 0 && iofile->end + offset >= iofile->buf)
        {
          iofile->pos = iofile->end + offset;
          return 0;
        }
        return -1;
    }
    return -1;
  }
  return fseek(iof_file_get_fh(iofile), offset, whence);
}

/* */

long iof_file_tell (iof_file *iofile)
{
  return (iofile->flags & IOF_DATA) ? (long)(iofile->pos - iofile->buf) : ftell(iof_file_get_fh(iofile));
}

size_t iof_file_size (iof_file *iofile)
{
  long pos, size;
  FILE *file;
  if (iofile->flags & IOF_DATA)
    return (size_t)iof_space(iofile);
  file = iof_file_get_fh(iofile);
  pos = ftell(file);
  fseek(file, 0, SEEK_END);
  size = ftell(file);
  fseek(file, pos, SEEK_SET);
  return size;
}

int iof_file_eof (iof_file *iofile)
{
  if (iofile->flags & IOF_DATA)
    return iofile->pos == iofile->end ? -1 : 0;
  return feof(iof_file_get_fh(iofile));
}

int iof_file_flush (iof_file *iofile)
{
  if (iofile->flags & IOF_DATA)
    return 0;
  return fflush(iof_file_get_fh(iofile));
}

size_t iof_file_read (void *ptr, size_t size, size_t items, iof_file *iofile)
{
  if (iofile->flags & IOF_DATA)
  {
    size_t bytes = size * items;
    if (bytes > (size_t)iof_left(iofile))
      bytes = (size_t)iof_left(iofile);
    memcpy(ptr, iofile->pos, bytes);
    iofile->pos += bytes;
    return bytes / size; // number of elements read
  }
  return fread(ptr, size, items, iof_file_get_fh(iofile));
}

static size_t iof_file_data_resizeto (iof_file *iofile, size_t space)
{
  uint8_t *newbuf;
  size_t size;
  size = iof_size(iofile);
  if (iofile->flags & IOF_BUFFER_ALLOC)
  {
    newbuf = (uint8_t *)util_realloc(iofile->buf, space);
  }
  else
  {
    newbuf = (uint8_t *)util_malloc(space);
    if (size > 0)
      memcpy(newbuf, iofile->buf, size);
    iofile->flags |= IOF_BUFFER_ALLOC;
  }
  iofile->buf = newbuf;
  iofile->pos = newbuf + size;
  iofile->end = newbuf + space;
  return space - size;
}

#define iof_file_data_resize(iofile) iof_file_data_resizeto(iofile, iof_space(iofile) << 1)

size_t iof_file_write (const void *ptr, size_t size, size_t items, iof_file *iofile)
{
  if (iofile->flags & IOF_DATA)
  {
    size_t space, sizesofar, bytes;
    bytes = size * items;
    if (bytes > (size_t)iof_left(iofile))
    {
      if ((space = iof_space(iofile)) == 0) // allow iofile->buf/end initially NULL
        space = BUFSIZ;
      for (sizesofar = iof_size(iofile), space <<= 1; sizesofar + bytes > space; space <<= 1)
        ;
      if (iof_file_data_resizeto(iofile, space) == 0)
        return 0;
    }
    memcpy(iofile->pos, ptr, bytes);
    iofile->pos += bytes;
    return bytes / size;
  }
  return fwrite(ptr, size, items, iof_file_get_fh(iofile));
}

size_t iof_file_ensure (iof_file *iofile, size_t bytes)
{
  if (iofile->flags & IOF_DATA)
  {
    size_t space, sizesofar, left;
    left = (size_t)iof_left(iofile);
    if (bytes > left)
    {
      if ((space = iof_space(iofile)) == 0) // allow iofile->buf/end initially NULL
        space = BUFSIZ;
      for (sizesofar = iof_size(iofile), space <<= 1; sizesofar + bytes > space; space <<= 1);
      return iof_file_data_resizeto(iofile, space);
    }
    return left;
  }
  return 0;
}

int iof_file_getc (iof_file *iofile)
{
  if (iofile->flags & IOF_DATA)
    return iofile->pos < iofile->end ? *iofile->pos++ : IOFEOF;
  return fgetc(iof_file_get_fh(iofile));
}

int iof_file_putc (iof_file *iofile, int c)
{
  if (iofile->flags & IOF_DATA)
  {
    if (iofile->pos >= iofile->end)
      if (iof_file_data_resize(iofile) == 0)
        return IOFEOF;
    *iofile->pos++ = (uint8_t)c;
    return c;
  }
  return fputc(c, iof_file_get_fh(iofile));
}

static int iof_file_sync (iof_file *iofile, size_t *offset)
{
  if (iofile->offset != offset)
  {
    if (iofile->offset != NULL)
      *iofile->offset = iof_file_tell(iofile);
    iofile->offset = offset;
    if (offset) // let offset be NULL
      return iof_file_seek(iofile, (long)*offset, SEEK_SET);
  }
  return 0;
}

//#define iof_file_unsync(iofile, poffset) (void)((iofile)->offset == poffset && (((iofile)->offset = NULL), 0))
#define iof_file_unsync(iofile, poffset) ((void)poffset, (iofile)->offset = NULL)

/* iof seek */

#define iof_reader_reset(I) ((I)->pos = (I)->end = (I)->buf)
#define iof_reader_reseek_file(I, offset, whence) (fseek((I)->file, offset, whence) == 0 ? (iof_reader_reset(I), 0) : -1)
#define iof_reader_reseek_iofile(I, offset, whence) (iof_file_seek((I)->iofile, offset, whence) == 0 ? (iof_reader_reset(I), 0) : -1)

#define iof_writer_reset(O) ((O)->pos = (O)->buf)
#define iof_writer_reseek_file(O, offset, whence) (iof_flush(O), (fseek((O)->file, offset, whence) == 0 ? (iof_writer_reset(O), 0) : -1))
#define iof_writer_reseek_iofile(O, offset, whence) (iof_flush(O), (iof_file_seek((O)->iofile, offset, whence) == 0 ? (iof_writer_reset(O), 0) : -1))

static int iof_reader_seek_data (iof *I, long offset, int whence)
{
  switch (whence)
  {
    case SEEK_SET:
      if (offset >= 0 && I->buf + offset <= I->end)
      {
        I->pos = I->buf + offset;
        return 0;
      }
      return -1;
    case SEEK_CUR:
      if ((offset >= 0 && I->pos + offset <= I->end) || (offset < 0 && I->pos + offset >= I->buf))
      {
        I->pos += offset;
        return 0;
      }
      return -1;
    case SEEK_END:
      if (offset <= 0 && I->end + offset >= I->buf)
      {
        I->pos = I->end + offset;
        return 0;
      }
      return -1;
  }
  return -1;
}

static int iof_reader_seek_iofile (iof *I, long offset, int whence)
{
  long fileoffset;
  switch (whence)
  {
    case SEEK_SET:
      fileoffset = iof_file_tell(I->iofile);
      if (offset <= fileoffset && offset >= fileoffset - iof_space(I))
      {
        I->pos = I->end - (fileoffset - offset);
        return 0;
      }
      return iof_reader_reseek_iofile(I, offset, SEEK_SET);
    case SEEK_CUR:
      if ((offset >= 0 && I->pos + offset <= I->end) || (offset < 0 && I->pos + offset >= I->buf))
      {
        I->pos += offset;
        return 0;
      }
      return iof_reader_reseek_iofile(I, offset, SEEK_CUR);
    case SEEK_END:
      return iof_reader_reseek_iofile(I, offset, SEEK_END); // can we do better?
  }
  return -1;
}

static int iof_reader_seek_file (iof *I, long offset, int whence)
{
  long fileoffset;
  switch (whence)
  {
    case SEEK_SET:
      fileoffset = ftell(I->file);
      if (offset <= fileoffset && offset >= fileoffset - iof_space(I))
      {
        I->pos = I->end - (fileoffset - offset);
        return 0;
      }
      return iof_reader_reseek_file(I, offset, SEEK_SET);
    case SEEK_CUR:
      if ((offset >= 0 && I->pos + offset <= I->end) || (offset < 0 && I->pos + offset >= I->buf))
      {
        I->pos += offset;
        return 0;
      }
      return iof_reader_reseek_file(I, offset, SEEK_CUR);
    case SEEK_END:
      return iof_reader_reseek_file(I, offset, SEEK_END); // can we do better?
  }
  return -1;
}

int iof_reader_seek (iof *I, long offset, int whence)
{
  I->flags &= ~IOF_STOPPED;
  if (I->flags & IOF_FILE)
    return iof_reader_seek_iofile(I, offset, whence);
  if (I->flags & IOF_FILE_HANDLE)
    return iof_reader_seek_file(I, offset, whence);
  if (I->flags & IOF_DATA)
    return iof_reader_seek_data(I, offset, whence);
  return -1;
}

int iof_reader_reseek (iof *I, long offset, int whence)
{
  I->flags &= ~IOF_STOPPED;
  if (I->flags & IOF_FILE)
    return iof_reader_reseek_iofile(I, offset, whence);
  if (I->flags & IOF_FILE_HANDLE)
    return iof_reader_reseek_file(I, offset, whence);
  if (I->flags & IOF_DATA)
    return iof_reader_seek_data(I, offset, whence);
  return -1;
}

static int iof_writer_seek_data (iof *O, long offset, int whence)
{
  /*
  fseek() allows to seek after the end of file. Seeking does not increase the output file.
  No byte is written before fwirte(). It seems to fill the gap with zeros. Until we really need that,
  no seeking out of bounds for writers.
  */
  O->flags &= ~IOF_STOPPED;
  return iof_reader_seek_data(O, offset, whence);
}

static int iof_writer_seek_iofile (iof *O, long offset, int whence)
{
  long fileoffset;
  switch (whence)
  {
    case SEEK_SET:
      fileoffset = iof_file_tell(O->iofile);
      if (offset >= fileoffset && offset <= fileoffset + iof_space(O))
      {
        O->pos = O->buf + (offset - fileoffset);
        return 0;
      }
      return iof_writer_reseek_iofile(O, offset, SEEK_SET);
    case SEEK_CUR:
      if ((offset >=0 && O->pos + offset <= O->end) || (offset < 0 && O->pos + offset >= O->buf))
      {
        O->pos += offset;
        return 0;
      }
      return iof_writer_reseek_iofile(O, offset, SEEK_CUR);
    case SEEK_END:
      return iof_writer_reseek_iofile(O, offset, SEEK_END);
  }
  return -1;
}

static int iof_writer_seek_file (iof *O, long offset, int whence)
{
  long fileoffset;
  switch (whence)
  {
    case SEEK_SET:
      fileoffset = ftell(O->file);
      if (offset >= fileoffset && offset <= fileoffset + iof_space(O))
      {
        O->pos = O->buf + (offset - fileoffset);
        return 0;
      }
      return iof_writer_reseek_file(O, offset, SEEK_SET);
    case SEEK_CUR:
      if ((offset >=0 && O->pos + offset <= O->end) || (offset < 0 && O->pos + offset >= O->buf))
      {
        O->pos += offset;
        return 0;
      }
      return iof_writer_reseek_file(O, offset, SEEK_CUR);
    case SEEK_END:
      return iof_writer_reseek_file(O, offset, SEEK_END);
  }
  return -1;
}

int iof_writer_seek (iof *I, long offset, int whence)
{
  I->flags &= ~IOF_STOPPED;
  if (I->flags & IOF_FILE)
    return iof_writer_seek_iofile(I, offset, whence);
  if (I->flags & IOF_FILE_HANDLE)
    return iof_writer_seek_file(I, offset, whence);
  if (I->flags & IOF_DATA)
    return iof_writer_seek_data(I, offset, whence);
  return -1;
}

int iof_writer_reseek (iof *I, long offset, int whence)
{
  I->flags &= ~IOF_STOPPED;
  if (I->flags & IOF_FILE)
    return iof_writer_reseek_iofile(I, offset, whence);
  if (I->flags & IOF_FILE_HANDLE)
    return iof_writer_reseek_file(I, offset, whence);
  if (I->flags & IOF_DATA)
    return iof_writer_seek_data(I, offset, whence);
  return -1;
}

int iof_seek (iof *F, long offset, int whence)
{
  return (F->flags & IOF_WRITER) ? iof_writer_seek(F, offset, whence) : iof_reader_seek(F, offset, whence);
}

int iof_reseek (iof *F, long offset, int whence)
{
  return (F->flags & IOF_WRITER) ? iof_writer_reseek(F, offset, whence) : iof_reader_reseek(F, offset, whence);
}

/* tell */

long iof_reader_tell (iof *I)
{
  if (I->flags & IOF_FILE)
    return iof_file_tell(I->iofile) - (long)iof_left(I);
  if (I->flags & IOF_FILE_HANDLE)
    return ftell(I->file) - (long)iof_left(I);
  //if (I->flags & IOF_DATA)
  return (long)iof_size(I);
}

long iof_writer_tell (iof *O)
{
  if (O->flags & IOF_FILE)
    return iof_file_tell(O->iofile) + (long)iof_size(O);
  if (O->flags & IOF_FILE_HANDLE)
    return ftell(O->file) + (long)iof_size(O);
  //if (I->flags & IOF_DATA)
  return (long)iof_size(O);
}

long iof_tell (iof *I)
{
  return (I->flags & IOF_WRITER) ? iof_writer_tell(I) : iof_reader_tell(I);
}

size_t iof_fsize (iof *I)
{
  size_t pos, size;
  if (I->flags & IOF_FILE)
    return iof_file_size(I->iofile);
  if (I->flags & IOF_FILE_HANDLE)
  {
    pos = (size_t)ftell(I->file);
    fseek(I->file, 0, SEEK_END);
    size = (size_t)ftell(I->file);
    fseek(I->file, (long)pos, SEEK_SET);
    return size;
  }
  //if (I->flags & IOF_DATA)
  return (size_t)iof_space(I);
}

/* save reader tail */

size_t iof_save_tail (iof *I)
{
  size_t size, left;
  size = iof_size(I);
  left = iof_left(I);
  if (size >= left)
    memcpy(I->buf, I->pos, left);
  else
    memmove(I->buf, I->pos, left);
  return left;
}

size_t iof_input_save_tail (iof *I, size_t back)
{
  size_t size;
  I->flags |= IOF_TAIL;
  I->pos -= back;
  size = iof_input(I);
  I->pos += back;
  I->flags &= ~IOF_TAIL;
  return size; // + back - back
}

/* read from file */

/* iof free*/

static size_t file_read (iof *I);
static size_t file_load (iof *I);

static size_t file_reader (iof *I, iof_mode mode)
{
  switch (mode)
  {
    case IOFREAD:
      return file_read(I);
    case IOFLOAD:
      return file_load(I);
    case IOFCLOSE:
      iof_free(I);
      return 0;
    default:
      return 0;
  }
}

iof * iof_setup_file_handle_reader (iof *I, void *buffer, size_t space, FILE *f)
{
  iof_setup_reader(I, buffer, space);
  iof_setup_file(I, f);
  I->more = file_reader;
  return I;
}

iof * iof_setup_file_reader (iof *I, void *buffer, size_t space, const char *filename)
{
  FILE *f;
  if ((f = fopen(filename, "rb")) == NULL)
    return NULL;
  iof_setup_reader(I, buffer, space);
  iof_setup_file(I, f);
  I->flags |= IOF_CLOSE_FILE;
  I->more = file_reader;
  return I;
}

/* write to file */

static size_t file_write (iof *O, int flush);

static size_t file_writer (iof *O, iof_mode mode)
{
  switch (mode)
  {
    case IOFWRITE:
      return file_write(O, 0);
    case IOFFLUSH:
      return file_write(O, 1);
    case IOFCLOSE:
      file_write(O, 1);
      iof_free(O);
      return 0;
    default:
      return 0;
  }
}

iof * iof_setup_file_handle_writer (iof *O, void *buffer, size_t space, FILE *f)
{
  iof_setup_writer(O, buffer, space);
  iof_setup_file(O, f);
  O->more = file_writer;
  return O;
}

iof * iof_setup_file_writer (iof *O, void *buffer, size_t space, const char *filename)
{
  FILE *f;
  if ((f = fopen(filename, "wb")) == NULL)
    return NULL;
  iof_setup_writer(O, buffer, space);
  iof_setup_file(O, f);
  O->flags |= IOF_CLOSE_FILE;
  O->more = file_writer;
  return O;
}

/* a dedicated handler for stdout/stderr */

static size_t stdout_writer (iof *O, iof_mode mode)
{
  switch(mode)
  {
    case IOFWRITE:
    {
      fwrite(O->buf, sizeof(uint8_t), iof_size(O), stdout);
      O->pos = O->buf;
      return O->space;
    }
    case IOFCLOSE:
    case IOFFLUSH:
    {
      fwrite(O->buf, sizeof(uint8_t), iof_size(O), stdout);
      fflush(stdout);
      O->pos = O->buf;
      return 0;
    }
    default:
      break;
  }
  return 0;
}

static size_t stderr_writer (iof *O, iof_mode mode)
{
  switch(mode)
  {
    case IOFWRITE:
    {
      fwrite(O->buf, sizeof(uint8_t), iof_size(O), stderr);
      O->pos = O->buf;
      return O->space;
    }
    case IOFCLOSE:
    case IOFFLUSH:
    {
      fwrite(O->buf, sizeof(uint8_t), iof_size(O), stderr);
      fflush(stderr);
      O->pos = O->buf;
      return 0;
    }
    default:
      break;
  }
  return 0;
}

static uint8_t iof_stdout_buffer[BUFSIZ];
iof iof_stdout = IOF_WRITER_INIT(stdout_writer, NULL, iof_stdout_buffer, BUFSIZ, 0);

static uint8_t iof_stderr_buffer[BUFSIZ];
iof iof_stderr = IOF_WRITER_INIT(stderr_writer, NULL, iof_stderr_buffer, BUFSIZ, 0);

/* read from somewhere */

iof * iof_reader (iof *I, void *link, iof_handler reader, const void *m, size_t bytes)
{
  I->space = 0;
  I->link = link;
  I->more = reader;
  I->flags = 0;
  I->refcount = 0;
  if (m != NULL)
  {
    I->rbuf = I->rpos = (const uint8_t *)m;
    I->rend = (const uint8_t *)m + bytes;
    return I;
  }
  return NULL;
}

iof * iof_string_reader (iof *I, const void *s, size_t bytes)
{
  I->space = 0;
  I->link = NULL;
  I->more = NULL;
  I->flags = 0; // iof_string() sets IOF_DATA
  I->refcount = 0;
  if (s != NULL)
    return iof_string(I, s, bytes);
  return NULL;
}

/* write somewhere */

iof * iof_writer (iof *O, void *link, iof_handler writer, void *m, size_t bytes)
{
  O->space = 0;
  O->link = link;
  O->more = writer;
  O->flags = 0;
  O->refcount = 0;
  if (m != NULL && bytes > 0)
  {
    O->buf = O->pos = (uint8_t *)m;
    O->end = (uint8_t *)m + bytes;
    return O;
  }
  // return iof_null(O);
  return NULL;
}

/* write to growing bytes buffer */

static size_t iof_mem_handler (iof *O, iof_mode mode)
{
  switch(mode)
  {
    case IOFWRITE:
      return iof_resize_buffer(O);
    case IOFCLOSE:
      iof_free(O);
      return 0;
    default:
      return 0;
  }
}

iof * iof_setup_buffer (iof *O, void *buffer, size_t space)
{
  iof_setup_writer(O, buffer, space);
  O->link = NULL;
  O->flags |= IOF_DATA;
  O->more = iof_mem_handler;
  return O;
}

iof * iof_setup_buffermin (iof *O, void *buffer, size_t space, size_t min)
{
  iof_setup_buffer(O, buffer, space);
  if (space < min) // allocate min to avoid further rewriting
  {
    O->buf = O->pos = (uint8_t *)util_malloc(min);
    O->flags |= IOF_BUFFER_ALLOC;
    O->end = O->buf + min;
  }
  return O;
}

iof * iof_buffer_create (size_t space)
{
  uint8_t *buffer;
  iof *O;  
  O = (iof *)util_malloc(space);
  buffer = (uint8_t *)(O + 1);
  iof_setup_buffer(O, buffer, space);
  O->flags |= IOF_ALLOC;
  return O;
}

/* set/get */

int iof_getc (iof *I)
{
  if (iof_readable(I))
    return *I->pos++;
  return IOFEOF;
}

int iof_putc (iof *O, int u)
{
  if (iof_writable(O))
  {
    iof_set(O, u);
    return (uint8_t)u;
  }
  return IOFFULL;
}

size_t iof_skip (iof *I, size_t bytes)
{
  while (bytes)
  {
    if (iof_readable(I))
      ++I->pos;
    else
      break;
    --bytes;
  }
  return bytes;
}

/* from iof to iof */

iof_status iof_pass (iof *I, iof *O)
{
  size_t leftin, leftout;
  if ((leftin = iof_left(I)) == 0)
    leftin = iof_input(I);
  while (leftin)
  {
    if ((leftout = iof_left(O)) == 0)
      if ((leftout = iof_output(O)) == 0)
        return IOFFULL;
    while (leftin > leftout)
    {
      memcpy(O->pos, I->pos, leftout);
      I->pos += leftout;
      O->pos = O->end; /* eq. += leftout */
      leftin -= leftout;
      if ((leftout = iof_output(O)) == 0)
        return IOFFULL;
    }
    if (leftin)
    {
      memcpy(O->pos, I->pos, leftin);
      I->pos = I->end; /* eq. += leftin */
      O->pos += leftin;
    }
    leftin = iof_input(I);
  }
  return IOFEOF;
}

/* read n-bytes */

size_t iof_read (iof *I, void *to, size_t size)
{
  size_t leftin, done = 0;
  char *s = (char *)to;

  if ((leftin = iof_left(I)) == 0)
    if ((leftin = iof_input(I)) == 0)
      return done;
  while (size > leftin)
  {
    memcpy(s, I->pos, leftin * sizeof(uint8_t));
    size -= leftin;
    done += leftin;
    s += leftin;
    I->pos = I->end;
    if ((leftin = iof_input(I)) == 0)
      return done;
  }
  if (size)
  {
    memcpy(s, I->pos, size * sizeof(uint8_t));
    I->pos += size;
    done += size;
  }
  return done;
}

/* rewrite FILE content (use fseek if needed) */

size_t iof_write_file_handle (iof *O, FILE *file)
{
  size_t leftout, size, readout;
  if ((leftout = iof_left(O)) == 0)
    if ((leftout = iof_output(O)) == 0)
      return 0;
  size = 0;
  do {
    readout = fread(O->pos, 1, leftout, file);
    O->pos += readout;
    size += readout;
  } while(readout == leftout && (leftout = iof_output(O)) > 0);
  return size;
}

size_t iof_write_file (iof *O, const char *filename)
{
  FILE *file;
  size_t size;
  if ((file = fopen(filename, "rb")) == NULL)
    return 0;
  size = iof_write_file_handle(O, file);
  fclose(file);
  return size;
}

size_t iof_write_iofile (iof *O, iof_file *iofile, int savepos)
{
  long offset;
  size_t size;
  FILE *file;
  if (iofile->flags & IOF_DATA)
    return iof_write(O, iofile->pos, (size_t)(iofile->end - iofile->pos));
  file = iof_file_get_fh(iofile);
  if (savepos)
  {
    offset = ftell(file);
    size = iof_write_file_handle(O, file);
    fseek(file, offset, SEEK_SET);
    return size;
  }
  return iof_write_file_handle(O, file);
}

/* write n-bytes */

size_t iof_write (iof *O, const void *data, size_t size)
{
  size_t leftout, done = 0;
  const char *s = (const char *)data;
  if ((leftout = iof_left(O)) == 0)
    if ((leftout = iof_output(O)) == 0)
      return done;
  while (size > leftout)
  {
    memcpy(O->pos, s, leftout * sizeof(uint8_t));
    size -= leftout;
    done += leftout;
    s += leftout;
    O->pos = O->end;
    if ((leftout = iof_output(O)) == 0)
      return done;
  }
  if (size)
  {
    memcpy(O->pos, s, size * sizeof(uint8_t));
    O->pos += size;
    done += size;
  }
  return done;
}

/* write '\0'-terminated string */

iof_status iof_puts (iof *O, const void *data)
{
  const char *s = (const char *)data;
  while (*s)
  {
    if (iof_writable(O))
      iof_set(O, *s++);
    else
      return IOFFULL;
  }
  return IOFEOF; // ?
}

size_t iof_put_string (iof *O, const void *data)
{
  const char *p, *s = (const char *)data;
  for (p = s; *p != '\0' && iof_writable(O); iof_set(O, *p++));
  return p - s;
}

/* write byte n-times */

/*
iof_status iof_repc (iof *O, char c, size_t bytes)
{
  while (bytes)
  {
    if (iof_writable(O))
      iof_set(O, c);
    else
      return IOFFULL;
    --bytes;
  }
  return IOFEOF; // ?
}
*/

size_t iof_repc (iof *O, char c, size_t bytes)
{
  size_t leftout, todo = bytes;
  if ((leftout = iof_left(O)) == 0)
    if ((leftout = iof_output(O)) == 0)
      return 0;
  while (bytes > leftout)
  {
    memset(O->pos, c, leftout);
    bytes -= leftout;
    O->pos = O->end;
    if ((leftout = iof_output(O)) == 0)
      return todo - bytes;
  }
  if (bytes)
  {
    memset(O->pos, c, bytes);
    O->pos += bytes;
  }
  return todo;
}

/* putfs */

#define IOF_FMT_SIZE 1024

size_t iof_putfs (iof *O, const char *format, ...)
{
  static char buffer[IOF_FMT_SIZE];
  va_list args;
  va_start(args, format);
  if (vsnprintf(buffer, IOF_FMT_SIZE, format, args) > 0)
  {
    va_end(args);
    return iof_put_string(O, buffer);
  }
  else
  {
    va_end(args);
    return iof_write(O, buffer, IOF_FMT_SIZE);
  }
}

/* integer from iof; return 1 on success, 0 otherwise */

int iof_get_int32 (iof *I, int32_t *number)
{
  int sign, c = iof_char(I);
  iof_scan_sign(I, c, sign);
  if (!base10_digit(c)) return 0;
  iof_read_integer(I, c, *number);
  if (sign) *number = -*number;
  return 1;
}

int iof_get_slong (iof *I, long *number)
{
  int sign, c = iof_char(I);
  iof_scan_sign(I, c, sign);
  if (!base10_digit(c)) return 0;
  iof_read_integer(I, c, *number);
  if (sign) *number = -*number;
  return 1;
}

int iof_get_int64 (iof *I, int64_t *number)
{
  int sign, c = iof_char(I);
  iof_scan_sign(I, c, sign);
  if (!base10_digit(c)) return 0;
  iof_read_integer(I, c, *number);
  if (sign) *number = -*number;
  return 1;
}

int iof_get_uint32 (iof *I, uint32_t *number)
{
  int c = iof_char(I);
  if (!base10_digit(c)) return 0;
  iof_read_integer(I, c, *number);
  return 1;
}

int iof_get_ulong (iof *I, unsigned long *number)
{
  int c = iof_char(I);
  if (!base10_digit(c)) return 0;
  iof_read_integer(I, c, *number);
  return 1;
}

int iof_get_usize (iof *I, size_t *number)
{
  int c = iof_char(I);
  if (!base10_digit(c)) return 0;
  iof_read_integer(I, c, *number);
  return 1;
}

int iof_get_uint64 (iof *I, uint64_t *number)
{
  int c = iof_char(I);
  if (!base10_digit(c)) return 0;
  iof_read_integer(I, c, *number);
  return 1;
}

int iof_get_int32_radix (iof *I, int32_t *number, int radix)
{
  int sign, c = iof_char(I);
  iof_scan_sign(I, c, sign);
  if (!base10_digit(c)) return 0;
  iof_read_radix(I, c, *number, radix);
  if (sign) *number = -*number;
  return 1;

}

int iof_get_slong_radix (iof *I, long *number, int radix)
{
  int sign, c = iof_char(I);
  iof_scan_sign(I, c, sign);
  if (!base10_digit(c)) return 0;
  iof_read_radix(I, c, *number, radix);
  if (sign) *number = -*number;
  return 1;
}

int iof_get_int64_radix (iof *I, int64_t *number, int radix)
{
  int sign, c = iof_char(I);
  iof_scan_sign(I, c, sign);
  if (!base10_digit(c)) return 0;
  iof_read_radix(I, c, *number, radix);
  if (sign) *number = -*number;
  return 1;
}

int iof_get_uint32_radix (iof *I, uint32_t *number, int radix)
{
  int c = iof_char(I);
  if (!base10_digit(c)) return 0;
  iof_read_radix(I, c, *number, radix);
  return 1;
}

int iof_get_ulong_radix (iof *I, unsigned long *number, int radix)
{
  int c = iof_char(I);
  if (!base10_digit(c)) return 0;
  iof_read_radix(I, c, *number, radix);
  return 1;
}

int iof_get_usize_radix (iof *I, size_t *number, int radix)
{
  int c = iof_char(I);
  if (!base10_digit(c)) return 0;
  iof_read_radix(I, c, *number, radix);
  return 1;
}

int iof_get_uint64_radix (iof *I, uint64_t *number, int radix)
{
  int c = iof_char(I);
  if (!base10_digit(c)) return 0;
  iof_read_radix(I, c, *number, radix);
  return 1;
}

/* get roman to uint16_t, cf. roman_to_uint16() from utilnumber.c*/

/* todo: some trick in place of this macro horror? */

#define roman1000(c) (c == 'M' || c == 'm')
#define roman500(c)  (c == 'D' || c == 'd')
#define roman100(c)  (c == 'C' || c == 'c')
#define roman50(c)   (c == 'L' || c == 'l')
#define roman10(c)   (c == 'X' || c == 'x')
#define roman5(c)    (c == 'V' || c == 'v')
#define roman1(c)    (c == 'I' || c == 'i')

#define roman100s(I, c) \
  (roman100(c) ? (100 + ((c = iof_next(I), roman100(c)) ? (100 + ((c = iof_next(I), roman100(c)) ? (c = iof_next(I), 100) : 0)) : 0)) : 0)
#define roman10s(I, c) \
  (roman10(c) ? (10 + ((c = iof_next(I), roman10(c)) ? (10 + ((c = iof_next(I), roman10(c)) ? (c = iof_next(I), 10) : 0)) : 0)) : 0)
#define roman1s(I, c) \
  (roman1(c) ? (1 + ((c = iof_next(I), roman1(c)) ? (1 + ((c = iof_next(I), roman1(c)) ? (c = iof_next(I), 1) : 0)) : 0)) : 0)

int iof_get_roman (iof *I, uint16_t *number)
{
  int c;
  /* M */
  for (*number = 0, c = iof_char(I); roman1000(c); *number += 1000, c = iof_next(I));
  /* D C */
  if (roman500(c))
  {
    c = iof_next(I);
    *number += 500 + roman100s(I, c);
  }
  else if (roman100(c))
  {
    c = iof_next(I);
    if (roman1000(c))
    {
      c = iof_next(I);
      *number += 900;
    }
    else if (roman500(c))
    {
      c = iof_next(I);
      *number += 400;
    }
    else
      *number += 100 + roman100s(I, c);
  }
  /* L X */
  if (roman50(c))
  {
    c = iof_next(I);
    *number += 50 + roman10s(I, c);
  }
  else if (roman10(c))
  {
    c = iof_next(I);
    if (roman100(c))
    {
      c = iof_next(I);
      *number += 90;
    }
    else if (roman50(c))
    {
      c = iof_next(I);
      *number += 40;
    }
    else
      *number += 10 + roman10s(I, c);
  }
  /* V I */
  if (roman5(c))
  {
    c = iof_next(I);
    *number += 5 + roman1s(I, c);
  }
  else if (roman1(c))
  {
    c = iof_next(I);
    if (roman10(c))
    {
      c = iof_next(I);
      *number += 9;
    }
    else if (roman5(c))
    {
      c = iof_next(I);
      *number += 4;
    }
    else
      *number += 1 + roman1s(I, c);
  }
  return 1;
}

/* double from iof; return 1 on success */

int iof_get_double (iof *I, double *number) // cf. string_to_double()
{
  int sign, exponent10, c = iof_char(I);
  iof_scan_sign(I, c, sign);
  iof_scan_decimal(I, c, *number);
  if (c == '.')
  {
    c = iof_next(I);
    iof_scan_fraction(I, c, *number, exponent10);
  }
  else
    exponent10 = 0;
  if (c == 'e' || c == 'E')
  {
    c = iof_next(I);
    iof_scan_exponent10(I, c, exponent10);
  }
  double_exp10(*number, exponent10);
  if (sign) *number = -*number;
  return 1;
}

int iof_get_float (iof *I, float *number) // cf. string_to_float()
{
  int sign, exponent10, c = iof_char(I);
  iof_scan_sign(I, c, sign);
  iof_scan_decimal(I, c, *number);
  if (c == '.')
  {
    c = iof_next(I);
    iof_scan_fraction(I, c, *number, exponent10);
  }
  else
    exponent10 = 0;
  if (c == 'e' || c == 'E')
  {
    c = iof_next(I);
    iof_scan_exponent10(I, c, exponent10);
  }
  float_exp10(*number, exponent10);
  if (sign) *number = -*number;
  return 1;
}

int iof_conv_double (iof *I, double *number) // cf. convert_to_double()
{
  int sign, exponent10, c = iof_char(I);
  iof_scan_sign(I, c, sign);
  iof_scan_decimal(I, c, *number);
  if (c == '.' || c == ',')
  {
    c = iof_next(I);
    iof_scan_fraction(I, c, *number, exponent10);
    if (exponent10 < 0)
      double_negative_exp10(*number, exponent10);
  }
  if (sign) *number = -*number;
  return 1;
}

int iof_conv_float (iof *I, float *number) // cf. convert_to_float()
{
  int sign, exponent10, c = iof_char(I);
  iof_scan_sign(I, c, sign);
  iof_scan_decimal(I, c, *number);
  if (c == '.' || c == ',')
  {
    c = iof_next(I);
    iof_scan_fraction(I, c, *number, exponent10);
    if (exponent10 < 0)
      float_negative_exp10(*number, exponent10);
  }
  if (sign) *number = -*number;
  return 1;
}

/* integer to iof; return a number of written bytes */

size_t iof_put_int32 (iof *O, int32_t number)
{
  const char *s;
  size_t size;
  s = int32_to_string(number, &size);
  return iof_write(O, s, size);
}

size_t iof_put_slong (iof *O, long number)
{
  const char *s;
  size_t size;
  s = slong_to_string(number, &size);
  return iof_write(O, s, size);
}

size_t iof_put_int64 (iof *O, int64_t number)
{
  const char *s;
  size_t size;
  s = int64_to_string(number, &size);
  return iof_write(O, s, size);
}

size_t iof_put_uint32 (iof *O, uint32_t number)
{
  const char *s;
  size_t size;
  s = uint32_to_string(number, &size);
  return iof_write(O, s, size);
}

size_t iof_put_ulong (iof *O, unsigned long number)
{
  const char *s;
  size_t size;
  s = ulong_to_string(number, &size);
  return iof_write(O, s, size);
}

size_t iof_put_usize (iof *O, size_t number)
{
  const char *s;
  size_t size;
  s = usize_to_string(number, &size);
  return iof_write(O, s, size);
}

size_t iof_put_uint64 (iof *O, uint64_t number)
{
  const char *s;
  size_t size;
  s = uint64_to_string(number, &size);
  return iof_write(O, s, size);
}

size_t iof_put_int32_radix (iof *O, int32_t number, int radix, int uc)
{
  const char *s;
  size_t size;
  s = int32_to_radix(number, radix, uc, &size);
  return iof_write(O, s, size);
}

size_t iof_put_slong_radix (iof *O, long number, int radix, int uc)
{
  const char *s;
  size_t size;
  s = slong_to_radix(number, radix, uc, &size);
  return iof_write(O, s, size);
}

size_t iof_put_int64_radix (iof *O, int64_t number, int radix, int uc)
{
  const char *s;
  size_t size;
  s = int64_to_radix(number, radix, uc, &size);
  return iof_write(O, s, size);
}

size_t iof_put_uint32_radix (iof *O, uint32_t number, int radix, int uc)
{
  const char *s;
  size_t size;
  s = uint32_to_radix(number, radix, uc, &size);
  return iof_write(O, s, size);
}

size_t iof_put_ulong_radix (iof *O, unsigned long number, int radix, int uc)
{
  const char *s;
  size_t size;
  s = ulong_to_radix(number, radix, uc, &size);
  return iof_write(O, s, size);
}

size_t iof_put_usize_radix (iof *O, size_t number, int radix, int uc)
{
  const char *s;
  size_t size;
  s = usize_to_radix(number, radix, uc, &size);
  return iof_write(O, s, size);
}

size_t iof_put_uint64_radix (iof *O, uint64_t number, int radix, int uc)
{
  const char *s;
  size_t size;
  s = uint64_to_radix(number, radix, uc, &size);
  return iof_write(O, s, size);
}

/* roman numerals */

size_t iof_put_roman (iof *O, uint16_t number, int uc)
{
  const char *s;
  size_t size;
  s = uint16_to_roman(number, uc, &size);
  return iof_write(O, s, size);
}

/* double/float to iof; return the number of written bytes */

size_t iof_put_double (iof *O, double number, int digits)
{
  const char *s;
  size_t size;
  s = double_to_string(number, digits, &size);
  return iof_write(O, s, size);
}

size_t iof_put_float (iof *O, float number, int digits)
{
  const char *s;
  size_t size;
  s = float_to_string(number, digits, &size);
  return iof_write(O, s, size);
}

/* iof to binary integer; pretty common */

int iof_get_be_uint2 (iof *I, uint32_t *pnumber)
{
  int c1, c2;
  if ((c1 = iof_get(I)) < 0 || (c2 = iof_get(I)) < 0)
    return 0;
  *pnumber = (c1<<8)|c2;
  return 1;
}

int iof_get_be_uint3 (iof *I, uint32_t *pnumber)
{
  int c1, c2, c3;
  if ((c1 = iof_get(I)) < 0 || (c2 = iof_get(I)) < 0 || (c3 = iof_get(I)) < 0)
    return 0;
  *pnumber = (c1<<16)|(c2<<8)|c3;
  return 1;
}

int iof_get_be_uint4 (iof *I, uint32_t *pnumber)
{
  int c1, c2, c3, c4;
  if ((c1 = iof_get(I)) < 0 || (c2 = iof_get(I)) < 0 || (c3 = iof_get(I)) < 0 || (c4 = iof_get(I)) < 0)
    return 0;
  *pnumber = (c1<<24)|(c2<<16)|(c3<<8)|c4;
  return 1;
}

int iof_get_le_uint2 (iof *I, uint32_t *pnumber)
{
  int c1, c2;
  if ((c1 = iof_get(I)) < 0 || (c2 = iof_get(I)) < 0)
    return 0;
  *pnumber = (c2<<8)|c1;
  return 1;
}

int iof_get_le_uint3 (iof *I, uint32_t *pnumber)
{
  int c1, c2, c3;
  if ((c1 = iof_get(I)) < 0 || (c2 = iof_get(I)) < 0 || (c3 = iof_get(I)) < 0)
    return 0;
  *pnumber = (c3<<16)|(c2<<8)|c1;
  return 1;
}

int iof_get_le_uint4 (iof *I, uint32_t *pnumber)
{
  int c1, c2, c3, c4;
  if ((c1 = iof_get(I)) < 0 || (c2 = iof_get(I)) < 0 || (c3 = iof_get(I)) < 0 || (c4 = iof_get(I)) < 0)
    return 0;
  *pnumber = (c4<<24)|(c3<<16)|(c2<<8)|c1;
  return 1;
}

/* iof input data */

uint8_t * iof_file_input_data (iof_file *iofile, size_t *psize, int *isnew)
{
  uint8_t *data;
  if (iofile->flags & IOF_DATA)
  {
    data = iofile->buf;
    *psize = iofile->end - iofile->buf;
    *isnew = 0;
    return data;
  }
  if (iof_file_reopen(iofile))
  {
    data = iof_copy_file_handle_data(iof_file_get_fh(iofile), psize);
    *isnew = 1;
    iof_file_reclose(iofile);
    return data;
  }
  return NULL;
}

/*
uint8_t * iof_file_reader_data (iof_file *iofile, size_t *size)
{
  uint8_t *data;
  if (!(iofile->flags & IOF_DATA) || iofile->pos == NULL || (*size = (size_t)iof_left(iofile)) == 0)
    return NULL;
  if (iofile->flags & IOF_BUFFER_ALLOC)
  {
    data = iofile->buf; // iofile->pos; // returned must be freeable, makes sense when ->buf == ->pos
    iofile->flags &= ~IOF_BUFFER_ALLOC;
    iofile->buf = iofile->pos = iofile->end = NULL;
    return data;
  }
  data = (uint8_t *)util_malloc(*size);
  memcpy(data, iofile->buf, *size);
  return data;
}

uint8_t * iof_file_writer_data (iof_file *iofile, size_t *size)
{
  uint8_t *data;
  if (!(iofile->flags & IOF_DATA) || iofile->buf == NULL || (*size = (size_t)iof_size(iofile)) == 0)
    return NULL;
  if (iofile->flags & IOF_BUFFER_ALLOC)
  {
    iofile->flags &= ~IOF_BUFFER_ALLOC;
    data = iofile->buf;
    iofile->buf = iofile->pos = iofile->end = NULL;
    return data;
  }
  data = (uint8_t *)util_malloc(*size);
  memcpy(data, iofile->buf, *size);
  return data;
}
*/

uint8_t * iof_reader_data (iof *I, size_t *psize)
{
  uint8_t *data;
  *psize = (size_t)iof_left(I);
  if (I->flags & IOF_BUFFER_ALLOC)
  {
    data = I->buf; // actually I->pos, but we have to return something freeable
    I->flags &= ~IOF_BUFFER_ALLOC;
    I->buf = NULL;
  }
  else
  {
    data = util_malloc(*psize);
    memcpy(data, I->pos, *psize);
  }
  iof_close(I);
  return data;
}


uint8_t * iof_writer_data (iof *O, size_t *psize)
{
  uint8_t *data;
  *psize = (size_t)iof_size(O);
  if (O->flags & IOF_BUFFER_ALLOC)
  {
    data = O->buf;
    O->flags &= ~IOF_BUFFER_ALLOC;
    O->buf = NULL;
  }
  else
  {
    data = util_malloc(*psize);
    memcpy(data, O->buf, *psize);
  }
  iof_close(O);
  return data;
}

size_t iof_reader_to_file_handle (iof *I, FILE *file)
{
  size_t size;
  for (size = 0; iof_readable(I); I->pos = I->end)
    size += fwrite(I->buf, sizeof(uint8_t), iof_left(I), file);
  return size;
}

size_t iof_reader_to_file (iof *I, const char *filename)
{
  FILE *file;
  size_t size;
  if ((file = fopen(filename, "wb")) == NULL)
    return 0;
  for (size = 0; iof_readable(I); I->pos = I->end)
    size += fwrite(I->buf, sizeof(uint8_t), iof_left(I), file);
  fclose(file);
  return size;
}

/* debug */

size_t iof_data_to_file (const void *data, size_t size, const char *filename)
{
  FILE *fh;
  if ((fh = fopen(filename, "wb")) == NULL)
    return 0;
  // size = fwrite(data, size, sizeof(uint8_t), fh); // WRONG, this always returns 1, as fwrite returns the number of elements successfully written out
  size = fwrite(data, sizeof(uint8_t), size, fh);
  fclose(fh);
  return size;
}

size_t iof_result_to_file_handle (iof *F, FILE *file)
{
  const void *data;
  size_t size;
  data = iof_result(F, size);
  return iof_data_to_file_handle(data, size, file);
}

size_t iof_result_to_file (iof *F, const char *filename)
{
  const void *data;
  size_t size;
  data = iof_result(F, size);
  return iof_data_to_file(data, size, filename);
}

void iof_debug (iof *I, const char *filename)
{
  FILE *file = fopen(filename, "wb");
  if (file != NULL)
  {
    fprintf(file, ">>> buf %p <<<\n", I->buf);
    fwrite(I->buf, sizeof(uint8_t), iof_size(I), file);
    fprintf(file, "\n>>> pos %p (%ld) <<<\n", I->pos, (long)iof_size(I));
    fwrite(I->pos, sizeof(uint8_t), iof_left(I), file);
    fprintf(file, "\n>>> end %p (%ld) <<<\n", I->end, (long)iof_left(I));
    fwrite(I->end, sizeof(uint8_t), I->space - iof_space(I), file);
    fprintf(file, "\n>>> end of buffer %p (%ld) <<<\n", I->buf + I->space, (long)(I->buf + I->space - I->end));
    fclose(file);
  }
}

/* common filters api */

/* sizes of filter states on x64
size of iof_filter: 640 (no longer used; sizeof(iof) + sizeof larger state)
size of file_state: 16
size of stream_state: 16
size of flate_state: 104
size of lzw_state: 56
size of predictor_state: 104
size of basexx_state: 48
size of basexx_state: 48
size of basexx_state: 48
size of eexec_state: 40
size of runlength_state: 24
size of rc4_state: 24
size of aes_state: 72
size of img_state: 576
size of img: 496
*/

typedef struct iof_heap iof_heap;

typedef struct {
  iof_heap *heap;
} iof_heap_ghost;


struct iof_heap {
  union { uint8_t *data; iof_heap_ghost *gdata; }; // union instead of casts (ARM)
  union { uint8_t *pos; iof_heap_ghost *gpos; };
  size_t size, space;
  iof_heap *next, *prev;
  int refcount;
  uint8_t dummy[4]; // pad to 8N bytes
};

/*
We use hidden heap pointer for every allocated buffer, so heap->data should be kept properly aligned.
Dummy 4-bytes pad doesn't really matter (the pad is there anyway), but iof_heap_take() must pad the
requested size.
*/

static iof_heap * iof_buffers_heap = NULL;
static iof_heap * iof_filters_heap = NULL;

#define IOF_HEAP_FILTERS_COUNT 4
#define IOF_BUFFER_SIZE 262144 // (1<<18)
#define IOF_FILTER_SIZE 1024
// sizeof(iof_filter) on x64 is now 640, img_state 576, img 496, others 16-104
#define IOF_BUFFER_HEAP_SIZE (IOF_HEAP_FILTERS_COUNT * (IOF_BUFFER_SIZE + sizeof(iof_heap_ghost)))
#define IOF_FILTER_HEAP_SIZE (IOF_HEAP_FILTERS_COUNT * (IOF_FILTER_SIZE + sizeof(iof_heap_ghost)))

static iof_heap * iof_heap_new (size_t space)
{
  iof_heap *iofheap;
  iofheap = (iof_heap *)util_malloc(sizeof(iof_heap) + space);
  iofheap->gdata = iofheap->gpos = (iof_heap_ghost *)(iofheap + 1);
  iofheap->size = iofheap->space = space;
  iofheap->next = NULL;
  iofheap->prev = NULL;
  iofheap->refcount = 0;
  return iofheap;
}

#define iof_heap_free(iofheap) util_free(iofheap)

void iof_filters_init (void)
{
  if (iof_buffers_heap == NULL)
    iof_buffers_heap = iof_heap_new(IOF_BUFFER_HEAP_SIZE);
  if (iof_filters_heap == NULL)
    iof_filters_heap = iof_heap_new(IOF_FILTER_HEAP_SIZE);
}

void iof_filters_free (void)
{
  iof_heap *heap, *next;
  for (heap = iof_buffers_heap; heap != NULL; heap = next)
  {
    next = heap->next;
    if (heap->refcount != 0)
      loggerf("not closed iof filters left (%d)", heap->refcount);
    if (next != NULL)
      loggerf("iof filters heap left");
    iof_heap_free(heap);
  }
  iof_buffers_heap = NULL;
  for (heap = iof_filters_heap; heap != NULL; heap = next)
  {
    next = heap->next;
    if (heap->refcount != 0)
      loggerf("not closed iof buffers left (%d)", heap->refcount);
    if (next != NULL)
      loggerf("iof buffers heap left");
    iof_heap_free(heap);
  }
  iof_filters_heap = NULL;
}

#define iof_heap_get(hp, ghost, data, siz) \
 (ghost = (hp)->gpos, ghost->heap = (hp), data = (uint8_t *)(ghost + 1), (hp)->pos += siz, (hp)->size -= siz, ++(hp)->refcount)

static void * iof_heap_take (iof_heap **pheap, size_t size)
{
  uint8_t *data;
  iof_heap_ghost *ghost;
  iof_heap *heap, *newheap, *next;

  heap = *pheap;
  if (size & 7)
  	size += 8 - (size & 7); // pad to 8N bytes so that (heap->pos + size) remains properly aligned
  size += sizeof(iof_heap_ghost);
  if (heap->size >= size)
  { /* take cheap mem from main heap */
    iof_heap_get(heap, ghost, data, size);
    return data;
  }
  if (size <= (heap->space >> 1))
  { /* make new cheap heap, make it front */
    *pheap = newheap = iof_heap_new(heap->space);
    newheap->next = heap;
    heap->prev = newheap;
    iof_heap_get(newheap, ghost, data, size);
    return data;
  }
  /* size much larger than expected? should not happen.
     make a single-item heap, keep the front heap intact. */
  newheap = iof_heap_new(size);
  if ((next = heap->next) != NULL)
  {
    newheap->next = next;
    next->prev = newheap;
  }
  heap->next = newheap;
  newheap->prev = heap;
  iof_heap_get(newheap, ghost, data, size);
  return data;
}

static void iof_heap_back (void *data)
{
  iof_heap_ghost *ghost;
  iof_heap *heap, *next, *prev;

  ghost = ((iof_heap_ghost *)data) - 1;
  heap = ghost->heap;
  if (heap->refcount == 0)
    loggerf("invalid use of iof heap, refcount < 0");
  if (--heap->refcount <= 0)
  {
    if ((prev = heap->prev) != NULL)
    { /* free the heap */
      if ((next = heap->next) != NULL)
        prev->next = next, next->prev = prev;
      else
        prev->next = NULL;
      iof_heap_free(heap);
    }
    else
    { /* this is the front heap, just reset */
      heap->pos = heap->data;
      heap->size = heap->space;
    }
  }
}

/**/

/*
void * iof_filter_new (size_t size)
{
  void *data;
  iof_filters_init();
  data = iof_heap_take(&iof_filters_heap, size);
  return memset(data, 0, size);
}
*/

iof * iof_filter_reader_new (iof_handler handler, size_t statesize, void **pstate)
{
  iof *F;
  void *filter;
  uint8_t *buffer;
  size_t buffersize;

  iof_filters_init();
  filter = iof_heap_take(&iof_filters_heap, sizeof(iof) + statesize);
  F = (iof *)memset(filter, 0, sizeof(iof) + statesize);
  buffer = iof_heap_take(&iof_buffers_heap, IOF_BUFFER_SIZE);
  buffersize = IOF_BUFFER_SIZE;
  iof_setup_reader(F, buffer, buffersize);
  F->flags |= IOF_HEAP|IOF_BUFFER_HEAP;
  F->more = handler;
  *pstate = (F + 1);
  return F;
}

iof * iof_filter_reader_with_buffer_new (iof_handler handler, size_t statesize, void **pstate, void *buffer, size_t buffersize)
{ // for filters that has own buffer (string, some image filters)
  iof *F;
  void *filter;

  iof_filters_init();
  filter = iof_heap_take(&iof_filters_heap, sizeof(iof) + statesize);
  F = (iof *)memset(filter, 0, sizeof(iof) + statesize);
  iof_setup_reader(F, buffer, buffersize);
  F->flags |= IOF_HEAP;
  F->more = handler;
  *pstate = (F + 1);
  return F;
}

iof * iof_filter_writer_new (iof_handler handler, size_t statesize, void **pstate)
{
  iof *F;
  void *filter;
  uint8_t *buffer;
  size_t buffersize;

  iof_filters_init();
  filter = iof_heap_take(&iof_filters_heap, sizeof(iof) + statesize);
  F = (iof *)memset(filter, 0, sizeof(iof) + statesize);
  buffer = iof_heap_take(&iof_buffers_heap, IOF_BUFFER_SIZE);
  buffersize = IOF_BUFFER_SIZE;
  iof_setup_writer(F, buffer, buffersize);
  F->flags |= IOF_HEAP|IOF_BUFFER_HEAP;
  F->more = handler;
  *pstate = (F + 1);
  return F;
}

iof * iof_filter_writer_with_buffer_new (iof_handler handler, size_t statesize, void **pstate, void *buffer, size_t buffersize)
{
  iof *F;
  void *filter;

  iof_filters_init();
  filter = iof_heap_take(&iof_filters_heap, sizeof(iof) + statesize);
  F = (iof *)memset(filter, 0, sizeof(iof) + statesize);
  iof_setup_writer(F, buffer, buffersize);
  F->flags |= IOF_HEAP;
  F->more = handler;
  *pstate = (F + 1);
  return F;
}

/**/

#define iof_filter_free(F) iof_heap_back(F)
#define iof_filter_buffer_free(data) iof_heap_back(data)

/* close */

#define iof_close_next(F) ((void)(iof_decref((F)->next), (F)->next = NULL, 0))
/* when filter creation fails, we should take care to destroy the filter but leave ->next intact */
#define iof_clear_next(F) ((void)(iof_unref((F)->next), (F)->next = NULL, 0))

#define iof_close_buffer(F) ((void)\
  ((F)->buf != NULL ? \
      ((F->flags & IOF_BUFFER_ALLOC) ? (util_free((F)->buf), (F)->buf = NULL, 0) : \
      ((F->flags & IOF_BUFFER_HEAP) ? (iof_filter_buffer_free((F)->buf), (F)->buf = NULL, 0) : ((F)->buf = NULL, 0))) : 0))

/* closing underlying file handle */

static void iof_close_file (iof *F)
{
  FILE *file;
  //if (F->flags & IOF_FILE_HANDLE)
  //{
    if ((file = F->file) != NULL)
    {
      if (F->flags & IOF_CLOSE_FILE)
        fclose(F->file);
      F->file = NULL;
    }
  //}
}

/* a very special variant for reader filters initiated with iof_file_reopen(). It also calls
   iof_file_reclose(), which takes an effect only if previously reopened, but better to keep
   all this thin ice separated. Used in filters: iofile_reader, iofile_stream_reader, image
   decoders. */

static void iof_close_iofile (iof *F)
{
  iof_file *iofile;
  //if (F->flags & IOF_FILE)
  //{
    if ((iofile = F->iofile) != NULL)
    {
      iof_file_unsync(iofile, NULL);
      iof_file_reclose(iofile); // takes an effect iff prevoiusly reopened
      iof_file_decref(iofile);
      F->iofile = NULL;
    }
  //}
}

void iof_free (iof *F)
{
  if (F->flags & IOF_FILE_HANDLE)
    iof_close_file(F);
  else if (F->flags & IOF_FILE)
    iof_close_iofile(F);
  else if (F->flags & IOF_NEXT)
    iof_close_next(F);
  iof_close_buffer(F);
  if (F->flags & IOF_HEAP)
    iof_filter_free(F);
  else if (F->flags & IOF_ALLOC)
    util_free(F);
}

void iof_discard (iof *F)
{ // so far used only on failed filters creation; as iof_free() but don't dare to release ->next
  if (F->flags & IOF_FILE_HANDLE)
    iof_close_file(F);
  else if (F->flags & IOF_FILE)
    iof_close_iofile(F);
  //else if (F->flags & IOF_NEXT)
  //  iof_close_next(F);
  iof_close_buffer(F);
  if (F->flags & IOF_HEAP)
    iof_filter_free(F);
  else if (F->flags & IOF_ALLOC)
    util_free(F);
}

/* resizing buffer */

size_t iof_resize_buffer_to (iof *O, size_t space)
{
  uint8_t *buf;

  if (O->flags & IOF_BUFFER_ALLOC)
  {
    buf = (uint8_t *)util_realloc(O->buf, space);
  }
  else
  {
    buf = (uint8_t *)util_malloc(space);
    memcpy(buf, O->buf, iof_size(O));
    if (O->flags & IOF_BUFFER_HEAP)
    {
      iof_filter_buffer_free(O->buf);
      O->flags &= ~IOF_BUFFER_HEAP;
    }
    O->flags |= IOF_BUFFER_ALLOC;

  }
  O->pos = buf + iof_size(O);
  O->end = buf + space;
  O->buf = buf;
  O->space = space;
  return iof_left(O);
}

/* */

size_t iof_decoder_retval (iof *I, const char *type, iof_status status)
{
  switch (status)
  {
    case IOFERR:
    case IOFEMPTY:             // should never happen as we set state.flush = 1 on decoders init
      loggerf("%s decoder error (%d, %s)", type, status, iof_status_kind(status));
      I->flags |= IOF_STOPPED;
      return 0;
    case IOFEOF:               // this is the last chunk,
      I->flags |= IOF_STOPPED; // so stop it and fall
      FALLTHRU                 // fall through
    case IOFFULL:              // prepare pointers to read from I->buf
      I->end = I->pos;
      I->pos = I->buf;
      return I->end - I->buf;
  }
  loggerf("%s decoder bug, invalid retval %d", type, status);
  return 0;
}

size_t iof_encoder_retval (iof *O, const char *type, iof_status status)
{
  switch (status)
  {
    case IOFERR:
    case IOFFULL:
      loggerf("%s encoder error (%d, %s)", type, status, iof_status_kind(status));
      return 0;
    case IOFEMPTY:
      O->pos = O->buf;
      O->end = O->buf + O->space;
      return O->space;
    case IOFEOF:
      return 0;
  }
  loggerf("%s encoder bug, invalid retval %d", type, status);
  return 0;
}

/* file/stream state */

typedef struct {
  size_t length;
  size_t offset;
} file_state;

#define file_state_init(state, off, len) ((state)->offset = off, (state)->length = len)

typedef struct {
  size_t length;
  size_t offset;
} stream_state;

#define stream_state_init(state, off, len) ((state)->offset = off, (state)->length = len)

/* union type to avoid 'dereferencing type-punned .. ' warnings on (void **) case */

typedef union { file_state *filestate; stream_state *streamstate; void *voidstate; } fs_state_pointer;

/**/

static size_t file_read (iof *I)
{
  size_t bytes, tail;
  if (I->flags & IOF_STOPPED)
    return 0;
  tail = iof_tail(I);
  if ((bytes = tail + fread(I->buf + tail, sizeof(uint8_t), I->space - tail, I->file)) < I->space)
    I->flags |= IOF_STOPPED;
  I->pos = I->buf;
  I->end = I->buf + bytes;
  return bytes;
}

static size_t iofile_read (iof *I, size_t *poffset)
{
  size_t bytes, tail;
  if (I->flags & IOF_STOPPED)
    return 0;
  iof_file_sync(I->iofile, poffset);
  tail = iof_tail(I);
  if ((bytes = tail + iof_file_read(I->buf + tail, sizeof(uint8_t), I->space - tail, I->iofile)) < I->space)
  {
    I->flags |= IOF_STOPPED;
    iof_file_unsync(I->iofile, poffset);
  }
  I->pos = I->buf;
  I->end = I->buf + bytes;
  return bytes;
}

static size_t file_load (iof *I)
{
  size_t bytes, left, tail;
  if (I->flags & IOF_STOPPED)
    return 0;
  tail = iof_tail(I);
  I->pos = I->buf + tail;
  I->end = I->buf + I->space; /* don't assume its done when initializing the filter */
  left = I->space - tail;
  do {
    bytes = fread(I->pos, sizeof(uint8_t), left, I->file);
    I->pos += bytes;
  } while (bytes == left && (left = iof_resize_buffer(I)) > 0);
  I->flags |= IOF_STOPPED;
  return iof_loaded(I);
}

static size_t iofile_load (iof *I, size_t *poffset)
{
  size_t bytes, left, tail;
  if (I->flags & IOF_STOPPED)
    return 0;
  tail = iof_tail(I);
  I->pos = I->buf + tail;
  I->end = I->buf + I->space; /* don't assume its done when initializing the filter */
  left = I->space - tail;
  iof_file_sync(I->iofile, poffset);
  do {
    bytes = iof_file_read(I->pos, sizeof(uint8_t), left, I->iofile);
    I->pos += bytes;
  } while (bytes == left && (left = iof_resize_buffer(I)) > 0);
  I->flags |= IOF_STOPPED;
  iof_file_unsync(I->iofile, poffset);
  return iof_loaded(I);
}

static size_t filter_file_reader (iof *I, iof_mode mode)
{
  switch (mode)
  {
    case IOFREAD:
      return file_read(I);
    case IOFLOAD:
      return file_load(I);
    case IOFCLOSE:
      iof_free(I);
      return 0;
    default:
      return 0;
  }
}

static size_t filter_iofile_reader (iof *I, iof_mode mode)
{
  file_state *state;
  state = iof_filter_state(file_state *, I);
  switch (mode)
  {
    case IOFREAD:
      return iofile_read(I, &state->offset);
    case IOFLOAD:
      return iofile_load(I, &state->offset);
    case IOFCLOSE:
      iof_free(I);
      return 0;
    default:
      return 0;
  }
}

static size_t file_write (iof *O, int flush)
{
  size_t bytes;
  if ((bytes = iof_size(O)) > 0)
    if (bytes != fwrite(O->buf, sizeof(uint8_t), bytes, O->file))
      return 0;
  if (flush)
    fflush(O->file);
  O->end = O->buf + O->space; // remains intact actually
  O->pos = O->buf;
  return O->space;
}

static size_t iofile_write (iof *O, size_t *poffset, int flush)
{
  size_t bytes;
  iof_file_sync(O->iofile, poffset);
  if ((bytes = iof_size(O)) > 0)
  {
    if (bytes != iof_file_write(O->buf, sizeof(uint8_t), bytes, O->iofile))
    {
      iof_file_unsync(O->iofile, poffset);
      return 0;
    }
  }
  if (flush)
    iof_file_flush(O->iofile);
  O->end = O->buf + O->space; // remains intact actually
  O->pos = O->buf;
  return O->space;
}

static size_t filter_file_writer (iof *O, iof_mode mode)
{
  switch (mode)
  {
    case IOFWRITE:
      return file_write(O, 0);
    case IOFFLUSH:
      return file_write(O, 1);
    case IOFCLOSE:
      file_write(O, 1);
      iof_free(O);
      return 0;
    default:
      return 0;
  }
}

static size_t filter_iofile_writer (iof *O, iof_mode mode)
{
  file_state *state;
  state = iof_filter_state(file_state *, O);
  switch (mode)
  {
    case IOFWRITE:
      return iofile_write(O, &state->offset, 0);
    case IOFFLUSH:
      return iofile_write(O, &state->offset, 1);
    case IOFCLOSE:
      iofile_write(O, &state->offset, 1);
      iof_free(O);
      return 0;
    default:
      return 0;
  }
}

/* filter from FILE* */

iof * iof_filter_file_handle_reader (FILE *file)
{
  iof *I;
  fs_state_pointer P;
  if (file == NULL)
    return NULL;
  I = iof_filter_reader(filter_file_reader, sizeof(file_state), &P.voidstate);
  iof_setup_file(I, file);
  file_state_init(P.filestate, 0, 0);
  return I;
}

iof * iof_filter_file_handle_writer (FILE *file)
{
  iof *O;
  fs_state_pointer P;
  if (file == NULL)
    return NULL;
  O = iof_filter_writer(filter_file_writer, sizeof(file_state), &P.voidstate);
  iof_setup_file(O, file);
  file_state_init(P.filestate, 0, 0);
  return O;
}

/* filter from iof_file * */

iof * iof_filter_iofile_reader (iof_file *iofile, size_t offset)
{
  iof *I;
  fs_state_pointer P;
  if (!iof_file_reopen(iofile))
    return NULL;
  I = iof_filter_reader(filter_iofile_reader, sizeof(file_state), &P.voidstate);
  iof_setup_iofile(I, iofile);
  file_state_init(P.filestate, offset, 0);
  return I;
}

iof * iof_filter_iofile_writer (iof_file *iofile, size_t offset)
{
  iof *O;
  fs_state_pointer P;
  O = iof_filter_writer(filter_iofile_writer, sizeof(file_state), &P.voidstate);
  iof_setup_iofile(O, iofile);
  file_state_init(P.filestate, offset, 0);
  return O;
}

/* filter from filename */

iof * iof_filter_file_reader (const char *filename)
{
  iof *I;
  fs_state_pointer P;
  FILE *file;
  if ((file = fopen(filename, "rb")) == NULL)
    return NULL;
  I = iof_filter_reader(filter_file_reader, sizeof(file_state), &P.voidstate);
  iof_setup_file(I, file);
  file_state_init(P.filestate, 0, 0);
  I->flags |= IOF_CLOSE_FILE;
  return I;
}

iof * iof_filter_file_writer (const char *filename)
{
  iof *O;
  fs_state_pointer P;
  FILE *file;
  if ((file = fopen(filename, "wb")) == NULL)
    return NULL;
  O = iof_filter_writer(filter_file_writer, sizeof(file_state), &P.voidstate);
  iof_setup_file(O, file);
  file_state_init(P.filestate, 0, 0);
  O->flags |= IOF_CLOSE_FILE;
  return O;
}

/* from string */

static size_t dummy_handler (iof *I, iof_mode mode)
{
  switch (mode)
  {
    case IOFCLOSE:
      iof_free(I);
      return 0;
    default:
      return 0;
  }
}

iof * iof_filter_string_reader (const void *s, size_t length)
{
  iof *I;
  void *dummy;
  I = iof_filter_reader_with_buffer(dummy_handler, 0, &dummy, NULL, 0);
  I->rbuf = I->rpos = (const uint8_t *)s;
  I->rend = (const uint8_t *)s + length;
  // I->space = length;
  return I;
}

iof * iof_filter_string_writer (const void *s, size_t length)
{
  iof *O;
  void *dummy;
  O = iof_filter_reader_with_buffer(dummy_handler, 0, &dummy, NULL, 0);
  O->rbuf = O->rpos = (const uint8_t *)s;
  O->rend = (const uint8_t *)s + length;
  // O->space = length;
  return O;
}

iof * iof_filter_buffer_writer (size_t size)
{ // cmp iof_buffer_create()
  iof *O;
  fs_state_pointer dummy;
  uint8_t *buffer;
  if (size > IOF_BUFFER_SIZE)
  {
    buffer = (uint8_t *)util_malloc(size);
    O = iof_filter_writer_with_buffer(iof_mem_handler, 0, &dummy.voidstate, buffer, size);
    O->flags |= IOF_BUFFER_ALLOC;
    return O;
  }
  return iof_filter_writer(iof_mem_handler, 0, &dummy.voidstate);
}

/* stream */

static size_t file_stream_read (iof *I, size_t *plength)
{
  size_t bytes, tail;
  if (I->flags & IOF_STOPPED || *plength == 0)
    return 0;
  tail = iof_tail(I);
  if (I->space - tail >= *plength)
  {
    bytes = tail + fread(I->buf + tail, sizeof(uint8_t), *plength, I->file);
    I->flags |= IOF_STOPPED;
    *plength = 0;
  }
  else
  {
    bytes = tail + fread(I->buf + tail, sizeof(uint8_t), I->space - tail, I->file);
    *plength -= bytes - tail;
  }
  I->pos = I->buf;
  I->end = I->buf + bytes;
  return bytes;
}

static size_t iofile_stream_read (iof *I, size_t *plength, size_t *poffset)
{
  size_t bytes, tail;
  if (I->flags & IOF_STOPPED || *plength == 0)
    return 0;
  tail = iof_tail(I);
  iof_file_sync(I->iofile, poffset);
  if (I->space - tail >= *plength)
  {
    bytes = tail + iof_file_read(I->buf + tail, sizeof(uint8_t), *plength, I->iofile);
    iof_file_unsync(I->iofile, poffset);
    I->flags |= IOF_STOPPED;
    *plength = 0;
  }
  else
  {
    bytes = tail + iof_file_read(I->buf + tail, sizeof(uint8_t), I->space - tail, I->iofile);
    *plength -= bytes - tail;
  }
  I->pos = I->buf;
  I->end = I->buf + bytes;
  return bytes;
}

static size_t file_stream_load (iof *I, size_t *plength)
{
  size_t bytes, tail;
  if (I->flags & IOF_STOPPED || *plength == 0)
    return 0;
  tail = iof_tail(I);
  if (I->space - tail < *plength)
    if (iof_resize_buffer_to(I, tail + *plength) == 0)
      return 0;
  bytes = tail + fread(I->buf + tail, sizeof(uint8_t), *plength, I->file);
  I->flags |= IOF_STOPPED;
  *plength = 0;
  I->pos = I->buf;
  I->end = I->buf + bytes;
  return bytes;
}

static size_t iofile_stream_load (iof *I, size_t *plength, size_t *poffset)
{
  size_t bytes, tail;
  if (I->flags & IOF_STOPPED || *plength == 0)
    return 0;
  iof_file_sync(I->iofile, poffset);
  tail = iof_tail(I);
  if (I->space - tail < *plength)
    if (iof_resize_buffer_to(I, tail + *plength) == 0)
      return 0;
  bytes = tail + iof_file_read(I->buf + tail, sizeof(uint8_t), *plength, I->iofile);
  iof_file_unsync(I->iofile, poffset);
  I->flags |= IOF_STOPPED;
  *plength = 0;
  I->pos = I->buf;
  I->end = I->buf + bytes;
  return bytes;
}

static size_t filter_file_stream_reader (iof *I, iof_mode mode)
{
  stream_state *state;
  state = iof_filter_state(stream_state *, I);
  switch(mode)
  {
    case IOFREAD:
      return file_stream_read(I, &state->length);
    case IOFLOAD:
      return file_stream_load(I, &state->length);
    case IOFCLOSE:
      iof_free(I);
      return 0;
    default:
      return 0;
  }
}

static size_t filter_iofile_stream_reader (iof *I, iof_mode mode)
{
  stream_state *state;
  state = iof_filter_state(stream_state *, I);
  switch(mode)
  {
    case IOFREAD:
      return iofile_stream_read(I, &state->length, &state->offset);
    case IOFLOAD:
      return iofile_stream_load(I, &state->length, &state->offset);
    case IOFCLOSE:
      iof_free(I);
      return 0;
    default:
      return 0;
  }
}

iof * iof_filter_stream_reader (FILE *file, size_t offset, size_t length)
{
  iof *I;
  fs_state_pointer P;
  I = iof_filter_reader(filter_file_stream_reader, sizeof(stream_state), &P.voidstate);
  iof_setup_file(I, file);
  stream_state_init(P.streamstate, offset, length);
  fseek(file, (long)offset, SEEK_SET); // or perhaps it should be call in file_stream_read(), like iof_file_sync()?
  return I;
}

iof * iof_filter_stream_coreader (iof_file *iofile, size_t offset, size_t length)
{
  iof *I;
  fs_state_pointer P;
  if (!iof_file_reopen(iofile))
    return NULL;
  I = iof_filter_reader(filter_iofile_stream_reader, sizeof(stream_state), &P.voidstate);
  iof_setup_iofile(I, iofile);
  stream_state_init(P.streamstate, offset, length);
  return I;
}

static size_t file_stream_write (iof *O, size_t *plength, int flush)
{
  size_t bytes;
  if ((bytes = iof_size(O)) > 0)
  {
    if (bytes != fwrite(O->buf, sizeof(uint8_t), bytes, O->file))
    {
      *plength += bytes;
      return 0;
    }
  }
  if (flush)
    fflush(O->file);
  *plength += bytes;
  O->end = O->buf + O->space; // remains intact
  O->pos = O->buf;
  return O->space;
}

static size_t iofile_stream_write (iof *O, size_t *plength, size_t *poffset, int flush)
{
  size_t bytes;
  if ((bytes = iof_size(O)) > 0)
  {
    iof_file_sync(O->iofile, poffset);
    if (bytes != iof_file_write(O->buf, sizeof(uint8_t), bytes, O->iofile))
    {
      *plength += bytes;
      iof_file_unsync(O->iofile, poffset);
      return 0;
    }
  }
  if (flush)
    iof_file_flush(O->iofile);
  *plength += bytes;
  O->end = O->buf + O->space; // remains intact
  O->pos = O->buf;
  return O->space;
}

static size_t filter_file_stream_writer (iof *O, iof_mode mode)
{
  stream_state *state;
  state = iof_filter_state(stream_state *, O);
  switch (mode)
  {
    case IOFWRITE:
      return file_stream_write(O, &state->length, 0);
    case IOFFLUSH:
      return file_stream_write(O, &state->length, 1);
    case IOFCLOSE:
      file_stream_write(O, &state->length, 1);
      iof_free(O);
      return 0;
    default:
      return 0;
  }
}

static size_t filter_iofile_stream_writer (iof *O, iof_mode mode)
{
  stream_state *state;
  state = iof_filter_state(stream_state *, O);
  switch (mode)
  {
    case IOFWRITE:
      return iofile_stream_write(O, &state->length, &state->offset, 0);
    case IOFFLUSH:
      return iofile_stream_write(O, &state->length, &state->offset, 1);
    case IOFCLOSE:
      iofile_stream_write(O, &state->length, &state->offset, 1);
      iof_free(O);
      return 0;
    default:
      return 0;
  }
}

iof * iof_filter_stream_writer (FILE *file)
{
  iof *O;
  fs_state_pointer P;
  O = iof_filter_writer(filter_file_stream_writer, sizeof(stream_state), &P.voidstate);
  iof_setup_file(O, file);
  stream_state_init(P.streamstate, 0, 0);
  return O;
}

iof * iof_filter_stream_cowriter (iof_file *iofile, size_t offset)
{
  iof *O;
  fs_state_pointer P;
  O = iof_filter_writer(filter_iofile_stream_writer, sizeof(stream_state), &P.voidstate);
  iof_setup_iofile(O, iofile);
  stream_state_init(P.streamstate, offset, 0);
  return O;
}

/* very specific for images; get input from already created strem filter, exchange the filter but keep the buffer */

FILE * iof_filter_file_reader_source (iof *I, size_t *poffset, size_t *plength)
{
  fs_state_pointer P;
  if (I->more == filter_file_stream_reader) // I is the result of iof_filter_stream_reader()
  {
    P.streamstate = iof_filter_state(stream_state *, I);
    *poffset = P.streamstate->offset;
    *plength = P.streamstate->length; // might be 0 but it is ok for file readers
    return I->file;
  }
  if (I->more == filter_file_reader)
  {
    P.filestate = iof_filter_state(file_state *, I);
    *poffset = P.filestate->offset;
    *plength = P.filestate->length; // might be 0 but it is ok for file readers
    return I->file;
  }
  return NULL;
}

iof_file * iof_filter_file_coreader_source (iof *I, size_t *poffset, size_t *plength)
{
  fs_state_pointer P;
  if (I->more == filter_iofile_stream_reader) // I is the result of iof_filter_stream_coreader()
  {
    P.streamstate = iof_filter_state(stream_state *, I);
    *poffset = P.streamstate->offset;
    *plength = P.streamstate->length;
    return I->iofile;
  }
  if (I->more == filter_iofile_reader)
  {
    P.filestate = iof_filter_state(file_state *, I);
    *poffset = P.filestate->offset;
    *plength = P.filestate->length;
    return I->iofile;
  }
  return NULL;
}

iof * iof_filter_reader_replacement (iof *P, iof_handler handler, size_t statesize, void **pstate)
{ // called after iof_filter_file_reader_source(), no need to check if F is filter from iof heap and if has buffer from iof heap
  iof *F;
  F = iof_filter_reader_with_buffer(handler, statesize, pstate, P->buf, P->space);
  F->flags |= IOF_BUFFER_HEAP;
  //iof_setup_reader(P, NULL, 0);
  //P->flags &= ~IOF_BUFFER_HEAP;
  iof_filter_free(P);
  return F;
}























