/*
 * call-seq:
 *   strio.read([length [, buffer]])    -> string, buffer, or nil
 *
 * See IO#read.
 */
static VALUE
strio_read(argc, argv, self)
    int argc;
    VALUE *argv;
    VALUE self;
{
    struct StringIO *ptr = readable(StringIO(self));
    VALUE str = Qnil;
    long len, olen;

    switch (argc) {
      case 2:
        str = argv[1];
        StringValue(str);
        rb_str_modify(str);
      case 1:
        if (!NIL_P(argv[0])) {
            len = olen = NUM2LONG(argv[0]);
            if (len < 0) {
                rb_raise(rb_eArgError, "negative length %ld given", len);
            }
            if (len > 0 && ptr->pos >= RSTRING(ptr->string)->len) {
                ptr->flags |= STRIO_EOF;
                if (!NIL_P(str)) rb_str_resize(str, 0);
                return Qnil;
            }
            else if (ptr->flags & STRIO_EOF) {
                if (!NIL_P(str)) rb_str_resize(str, 0);
                return Qnil;
            }
            break;
        }
        /* fall through */
      case 0:
        olen = -1;
        len = RSTRING(ptr->string)->len;
        if (len <= ptr->pos) {
            ptr->flags |= STRIO_EOF;
            if (NIL_P(str)) {
                str = rb_str_new(0, 0);
            }
            else {
                rb_str_resize(str, 0);
            }
            return str;
        }
        else {
            len -= ptr->pos;
        }
        break;
      default:
        rb_raise(rb_eArgError, "wrong number of arguments (%d for 0)", argc);
    }
    if (NIL_P(str)) {
        str = rb_str_substr(ptr->string, ptr->pos, len);
    }
    else {
        long rest = RSTRING(ptr->string)->len - ptr->pos;
        if (len > rest) len = rest;
        rb_str_resize(str, len);
        MEMCPY(RSTRING(str)->ptr, RSTRING(ptr->string)->ptr + ptr->pos, char, len);
    }
    if (NIL_P(str)) {
        if (!(ptr->flags & STRIO_EOF)) str = rb_str_new(0, 0);
        len = 0;
    }
    else {
        ptr->pos += len = RSTRING(str)->len;
    }
    if (olen < 0 || olen > len) ptr->flags |= STRIO_EOF;
    return str;
}