/* read-mem.c, liboop, copyright 2000 Ian jackson
   
   This is free software; you can redistribute it and/or modify it under the
   terms of the GNU Lesser General Public License, version 2.1 or later.
   See the file COPYING for details. */

#include "oop.h"
#include "oop-read.h"

#include <assert.h>
#include <string.h>

#include <unistd.h>
#include <limits.h>

typedef struct {
  oop_readable ra;
  oop_source *oop;
  int processing;
  enum { state_cancelled, state_active, state_dying } state;
  const char *data;
  size_t remaining;
  oop_readable_call *call;
  void *opaque;
} ram_intern;

static void *process(oop_source *oop, struct timeval when, void *ram_void);

static int set_time(ram_intern *ram) {
  int err;
  err=
    (ram->oop->on_time(ram->oop,OOP_TIME_NOW,process,ram), 0); /* fixme */
  if (err) return err;

  ram->processing= 1;
  return 0;
}

static void *process(oop_source *oop, struct timeval when, void *ram_void) {
  ram_intern *ram= ram_void;
  void *ret;
  int err;

  assert(oop == ram->oop);
  assert(ram->processing);

  ret= OOP_CONTINUE;

  while (ram->state == state_active && ret == OOP_CONTINUE) {
    ret= ram->call(oop,&ram->ra,ram->opaque);
  }

  switch (ram->state) {

  case state_active:
    err= set_time(ram);
    if (err)
      assert(!"must not lose flow of control");
         /* AAARGH! No way to avoid this I think.  Happens when:
	  *  - program calls on_read which works, setting immediate callback;
	  *  - process calls the application's function, which returns
	  *    OOP_HALT or some such, but without calling on_cancel;
	  *  Now we have to set another immediate callback.
	  *  If this fails and we were to ignore it then:
	  *  - program reenters event loop, expecting to deal with the rest
	  *    of the oop_readable_mem data.  But we've lost the flow
	  *    of control and the callback never happens, so
	  *    oop_sys_run or whatever would (lyingly) exit straight
	  *    away with OOP_CONTINUE.
	  *  Alternatively we could ignore the application's request
	  *  to abort the event loop, which seems just as bad.
	  */
    break;

  case state_cancelled:
    ram->processing= 0;
    break;

  case state_dying:
    oop_free(ram);
    break;
  }
  
  return ret;
}

static int on_read(oop_readable *ra, oop_readable_call *call, void *opaque) {
  ram_intern *ram= (void*)ra;

  assert(ram->state != state_dying);
  ram->state= state_active;
  ram->call= call;
  ram->opaque= opaque;

  if (ram->processing)
    return 0;

  return
    set_time(ram);
}

static void on_cancel(struct oop_readable *ra) {
  ram_intern *ram= (void*)ra;

  assert(ram->state != state_dying);
  ram->state= state_cancelled;
}

static ssize_t try_read(oop_readable *ra, void *buffer, size_t length) {
  ram_intern *ram= (void*)ra;

  if (length > SSIZE_MAX)
    length= SSIZE_MAX;

  if (length > ram->remaining)
    length= ram->remaining;

  memcpy(buffer,ram->data,length);
  ram->data += length;
  ram->remaining -= length;

  return length;
}

static void delete_kill(struct oop_readable *ra) {
  ram_intern *ram= (void*)ra;

  assert(ram->state != state_dying);
  ram->state= state_dying;
  if (!ram->processing)
    oop_free(ram);
}

static int delete_tidy(struct oop_readable *ra) {
  delete_kill(ra);
  return 0;
}

static const oop_readable functions= {
  on_read, on_cancel, try_read, delete_tidy, delete_kill
};

oop_readable *oop_readable_mem(oop_source *oop, const void *data, size_t length) {
  ram_intern *ram;

  ram= oop_malloc(sizeof(*ram));  if (!ram) return 0;

  ram->ra= functions;
  ram->oop= oop;
  ram->processing= 0;
  ram->state= state_cancelled;

  ram->data= data;
  ram->remaining= length;

  return (oop_readable*)ram;
}


syntax highlighted by Code2HTML, v. 0.9.1