#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include "labcomm_private.h"
#include "labcomm_fd_writer.h"

#define BUFFER_SIZE 2048

struct labcomm_fd_writer {
  struct labcomm_writer writer;
  int fd;
  int close_fd_on_free;
};

static int fd_flush(struct labcomm_writer *w, void *context);

static int fd_alloc(struct labcomm_writer *w, void *context, 
		    struct labcomm_encoder *encoder,
		    char *version)
{
  w->data = malloc(BUFFER_SIZE);
  if (! w->data) {
    w->error = -ENOMEM;
    w->data_size = 0;
    w->count = 0;
    w->pos = 0;
  } else {
    w->data_size = BUFFER_SIZE;
    w->count = BUFFER_SIZE;
    w->pos = 0;
    if (version && version[0]) {
      labcomm_write_string(w, version);
      fd_flush(w, context);
    }
  }

  return w->error;
}

static int fd_free(struct labcomm_writer *w, void *context)
{
  struct labcomm_fd_writer *fd_context = context;

  free(w->data);
  w->data = 0;
  w->data_size = 0;
  w->count = 0;
  w->pos = 0;

  if (fd_context->close_fd_on_free) {
    close(fd_context->fd);
  }
  return 0;
}

static int fd_start(struct labcomm_writer *w, void *context,
		    struct labcomm_encoder *encoder,
		    int index,
		    struct labcomm_signature *signature,
		    void *value)
{
  w->pos = 0;
  
  return w->error;
}

static int fd_flush(struct labcomm_writer *w, void *context)
{
  struct labcomm_fd_writer *fd_context = context;
  int start, err;
  
  start = 0;
  err = 0;
  while (start < w->pos) {
    err = write(fd_context->fd, &w->data[start], w->pos - start);
    if (err <= 0) {
      break;
    }
    start = start + err;
  }
  if (err < 0) {
    w->error = -errno;
  } else if (err == 0) {
    w->error = -EINVAL;
  }
  w->pos = 0;
   
  return w->error;
}

static int fd_ioctl(struct labcomm_writer *w, void *context, 
		    int signature_index, struct labcomm_signature *signature, 
		    int action, va_list args)
{
  return -ENOTSUP;
}

static const struct labcomm_writer_action action = {
  .alloc = fd_alloc,
  .free = fd_free,
  .start = fd_start,
  .end = fd_flush,
  .flush = fd_flush,
  .ioctl = fd_ioctl
};

struct labcomm_writer *labcomm_fd_writer_new(int fd, int close_fd_on_free)
{
  struct labcomm_fd_writer *result;

  result = malloc(sizeof(*result));
  if (result == NULL) {
    return NULL;
  } else {
    result->fd = fd;
    result->close_fd_on_free = close_fd_on_free;
    result->writer.context = result;
    result->writer.action = &action;
    return &result->writer;
  }
}