/*
 * call-seq:
 *      POSIX_MQ.new(name [, flags [, mode [, mq_attr]])     => mq
 *
 * Opens a POSIX message queue given by +name+.  +name+ should start
 * with a slash ("/") for portable applications.
 *
 * If a Symbol is given in place of integer +flags+, then:
 *
 * * +:r+ is equivalent to IO::RDONLY
 * * +:w+ is equivalent to IO::CREAT|IO::WRONLY
 * * +:rw+ is equivalent to IO::CREAT|IO::RDWR
 *
 * +mode+ is an integer and only used when IO::CREAT is used.
 * +mq_attr+ is a POSIX_MQ::Attr and only used if IO::CREAT is used.
 * If +mq_attr+ is not specified when creating a queue, then the
 * system defaults will be used.
 *
 * See the manpage for mq_open(3) for more details on this function.
 */
static VALUE init(int argc, VALUE *argv, VALUE self)
{
        struct posix_mq *mq = get(self, 0);
        struct open_args x;
        VALUE name, oflags, mode, attr;

        rb_scan_args(argc, argv, "13", &name, &oflags, &mode, &attr);

        switch (TYPE(oflags)) {
        case T_NIL:
                x.oflags = O_RDONLY;
                break;
        case T_SYMBOL:
                if (oflags == sym_r)
                        x.oflags = O_RDONLY;
                else if (oflags == sym_w)
                        x.oflags = O_CREAT|O_WRONLY;
                else if (oflags == sym_rw)
                        x.oflags = O_CREAT|O_RDWR;
                else {
                        oflags = rb_inspect(oflags);
                        rb_raise(rb_eArgError,
                                 "symbol must be :r, :w, or :rw: %s",
                                 StringValuePtr(oflags));
                }
                break;
        case T_BIGNUM:
        case T_FIXNUM:
                x.oflags = NUM2INT(oflags);
                break;
        default:
                rb_raise(rb_eArgError, "flags must be an int, :r, :w, or :wr");
        }

        x.name = StringValueCStr(name);
        x.argc = 2;

        switch (TYPE(mode)) {
        case T_FIXNUM:
                x.argc = 3;
                x.mode = NUM2UINT(mode);
                break;
        case T_NIL:
                if (x.oflags & O_CREAT) {
                        x.argc = 3;
                        x.mode = 0666;
                }
                break;
        default:
                rb_raise(rb_eArgError, "mode not an integer");
        }

        switch (TYPE(attr)) {
        case T_STRUCT:
                x.argc = 4;
                rstruct2mqattr(&x.attr, attr, 1);

                /* principle of least surprise */
                if (x.attr.mq_flags & O_NONBLOCK)
                        x.oflags |= O_NONBLOCK;
                break;
        case T_NIL:
                break;
        default:
                check_struct_type(attr);
        }

        (void)xopen(&x);
        mq->des = x.des;
        if (mq->des == MQD_INVALID) {
                switch (errno) {
                case ENOMEM:
                case EMFILE:
                case ENFILE:
                case ENOSPC:
                        rb_gc();
                        (void)xopen(&x);
                        mq->des = x.des;
                }
                if (mq->des == MQD_INVALID)
                        rb_sys_fail("mq_open");
        }

        mq->name = rb_str_dup(name);
        if (x.oflags & O_NONBLOCK)
                mq->attr.mq_flags = O_NONBLOCK;

        return self;
}