/* perlio.c - Imager's interface to PerlIO

 */
#define IMAGER_NO_CONTEXT
#include "imager.h"
#include "EXTERN.h"
#include "perl.h"
#include "imperlio.h"


static ssize_t
perlio_reader(void *handle, void *buf, size_t count);
static ssize_t
perlio_writer(void *handle, const void *buf, size_t count);
static off_t
perlio_seeker(void *handle, off_t offset, int whence);
static int
perlio_closer(void *handle);
static void
perlio_destroy(void *handle);
/* my_strerror is defined since perl 5.21.x */
#undef my_strerror
static const char *my_strerror(pTHX_ int err);

#ifndef tTHX
#define tTHX PerlInterpreter *
#endif

typedef struct {
  PerlIO *handle;
  pIMCTX;
#ifdef MULTIPLICITY
  tTHX my_perl;
#endif
} im_perlio;

#define dIMCTXperlio(state) dIMCTXctx(state->aIMCTX)

/*
=item im_io_new_perlio(PerlIO *)

Create a new perl I/O object that reads/writes/seeks on a PerlIO
handle.

The close() handle flushes output but does not close the handle.

=cut
*/

i_io_glue_t *
im_io_new_perlio(pTHX_ PerlIO *handle) {
  im_perlio *state = mymalloc(sizeof(im_perlio));
  dIMCTX;

  state->handle = handle;
#ifdef MULTIPLICITY
  state->aTHX = aTHX;
#endif
  state->aIMCTX = aIMCTX;

  return io_new_cb(state, perlio_reader, perlio_writer,
		   perlio_seeker, perlio_closer, perlio_destroy);
}

static ssize_t
perlio_reader(void *ctx, void *buf, size_t count) {
  im_perlio *state = ctx;
  dTHXa(state->my_perl);
  dIMCTXperlio(state);

  ssize_t result = PerlIO_read(state->handle, buf, count);
  if (result == 0 && PerlIO_error(state->handle)) {
    im_push_errorf(aIMCTX, errno, "read() failure (%s)", my_strerror(aTHX_ errno));
    return -1;
  }

  return result;
}

static ssize_t
perlio_writer(void *ctx, const void *buf, size_t count) {
  im_perlio *state = ctx;
  dTHXa(state->my_perl);
  dIMCTXperlio(state);
  ssize_t result;

  result = PerlIO_write(state->handle, buf, count);

  if (result == 0) {
    im_push_errorf(aIMCTX, errno, "write() failure (%s)", my_strerror(aTHX_ errno));
  }

  return result;
}

static off_t
perlio_seeker(void *ctx, off_t offset, int whence) {
  im_perlio *state = ctx;
  dTHXa(state->my_perl);
  dIMCTXperlio(state);

  if (whence != SEEK_CUR || offset != 0) {
    if (PerlIO_seek(state->handle, offset, whence) < 0) {
      im_push_errorf(aIMCTX, errno, "seek() failure (%s)", my_strerror(aTHX_ errno));
      return -1;
    }
  }

  return PerlIO_tell(state->handle);
}

static int
perlio_closer(void *ctx) {
  im_perlio *state = ctx;
  dTHXa(state->my_perl);
  dIMCTXperlio(state);

  if (PerlIO_flush(state->handle) < 0) {
    im_push_errorf(aIMCTX, errno, "flush() failure (%s)", my_strerror(aTHX_ errno));
    return -1;
  }
  return 0;
}

static void
perlio_destroy(void *ctx) {
  myfree(ctx);
}

static
const char *my_strerror(pTHX_ int err) {
  const char *result = strerror(err);
  
  if (!result)
    result = "Unknown error";
  
  return result;
}

