#include "convcommon.h"
#include "utils/varbit.h"

static void pl_bit_mark(VarBit *p) {}

static VALUE
pl_bit_s_alloc(VALUE obj)
{
    VarBit *inst;

    inst = (VarBit *)ALLOC_N(char, VARBITTOTALLEN(0));
    VARATT_SIZEP(inst) = VARBITTOTALLEN(0);
    VARBITLEN(inst) = 0;
    return Data_Wrap_Struct(obj, pl_bit_mark, free, inst);
}

static VALUE
pl_bit_init_copy(VALUE copy, VALUE orig)
{
    VarBit *t0, *t1;
    int s0, s1;

    if (copy == orig) return copy;
    if (TYPE(orig) != T_DATA ||
        RDATA(orig)->dmark != (RUBY_DATA_FUNC)pl_bit_mark) {
        rb_raise(rb_eTypeError, "wrong argument type to clone");
    }
    Data_Get_Struct(orig, VarBit, t0);
    Data_Get_Struct(copy, VarBit, t1);
    s0 = VARSIZE(t0);
    s1 = VARSIZE(t1);
    if (s0 != s1) {
        free(t1);
        DATA_PTR(copy) = 0;
        t1 = (VarBit *)ALLOC_N(char, s0);
        DATA_PTR(copy) = t1;
    }
    memcpy(t1, t0, s0);
    return copy;
}

static VALUE
pl_bit_s_datum(VALUE obj, VALUE a)
{
    VarBit *ip0, *ip1;
    Oid typoid;
    VALUE res;

    ip0 = (VarBit *)plruby_datum_get(a, &typoid);
    if (typoid != BITOID && typoid != VARBITOID) {
	rb_raise(rb_eArgError, "unknown OID type %d", typoid);
    }
    ip1 = (VarBit *)ALLOC_N(char, VARSIZE(ip0));
    memcpy(ip1, ip0, VARSIZE(ip0));
    res = Data_Wrap_Struct(obj, pl_bit_mark, free, ip1);
    OBJ_TAINT(res);
    return res;
}

static VALUE
pl_bit_to_datum(VALUE obj, VALUE a)
{
    VarBit *ip0, *ip1;
    int length;
    Datum d;
    Oid typoid;

    typoid = plruby_datum_oid(a, &length);
    switch (typoid) {
    case BITOID:
    case VARBITOID:
        Data_Get_Struct(obj, VarBit, ip0);
        ip1 = (VarBit *)plruby_dfc3(bit, ip0, Int32GetDatum(length), true);
        break;
    default:
        /* a faire */
        return Qnil;
    }
    return plruby_datum_set(a, (Datum)ip1);
}

PL_MLOADVAR(pl_bit_mload, varbit_recv, VarBit, VARSIZE);
PL_MDUMP(pl_bit_mdump, varbit_send);

static VALUE
pl_bit_init(int argc, VALUE *argv, VALUE obj)
{
    VarBit *inst;
    VALUE a, b;
    void *v = 0, *v1;
    int length = -1;
    int taint = 0;

    if (rb_scan_args(argc, argv, "11", &a, &b) == 2) {
        length = NUM2INT(b);
    }
    taint = OBJ_TAINTED(a);
    if (rb_respond_to(a, rb_intern("to_int"))) {
        a = rb_funcall2(a, rb_intern("to_int"), 0, 0);
        v = (void *)plruby_dfc1(bitfromint4, Int32GetDatum(NUM2LONG(a)));
        if (length > 0) {
            int ll = DatumGetInt32(plruby_dfc1(bitlength, v));
            if (length != ll) {
                if (length < ll) {
                    v1 = (void *)plruby_dfc2(bitshiftleft, v, 
                                             Int32GetDatum(ll - length));
                    pfree(v);
                }
                else {
                    v1 = v;
                }
                v = (void *)plruby_dfc3(bit, v1, Int32GetDatum(length), true);
                pfree(v1);
            }
        }
    }
    if (!v) {
        a = plruby_to_s(a);
        v = (void *)plruby_dfc3(bit_in, RSTRING(a)->ptr, ObjectIdGetDatum(0),
                                Int32GetDatum(length));
    }
    Data_Get_Struct(obj, VarBit, inst);
    free(inst);
    inst = (VarBit *)ALLOC_N(char, VARSIZE(v));
    CPY_FREE(inst, v, VARSIZE(v));
    RDATA(obj)->data = inst;
    if (taint) OBJ_TAINT(obj);
    return obj;
}

static VALUE
pl_bit_cmp(VALUE a, VALUE b)
{
    VarBit *inst0, *inst1;
    int result;

    if (!rb_obj_is_kind_of(b, rb_obj_class(a))) {
	return Qnil;
    }
    Data_Get_Struct(a, VarBit, inst0);
    Data_Get_Struct(b, VarBit, inst1);
    result = DatumGetInt32(plruby_dfc2(bitcmp, inst0, inst1));
    return INT2FIX(result);
}

static VALUE
pl_bit_to_s(VALUE obj)
{
    VarBit *src;
    char *str;
    VALUE res;

    Data_Get_Struct(obj, VarBit, src);
    str = (char *)plruby_dfc1(bit_out, src);
    if (OBJ_TAINTED(obj)) {
	res = rb_tainted_str_new2(str);
    }
    else {
	res = rb_str_new2(str);
    }
    pfree(str);
    return res;
}

#define BIT_OPERATOR(name_, function_)                                  \
static VALUE                                                            \
name_(VALUE obj, VALUE a)                                               \
{                                                                       \
    VarBit *v0, *v1, *vp, *vr;                                          \
    VALUE res;                                                          \
                                                                        \
    if (TYPE(a) != T_DATA ||                                            \
        RDATA(a)->dmark != (RUBY_DATA_FUNC)pl_bit_mark) {               \
        rb_raise(rb_eArgError, "invalid argument for %s",               \
                 rb_id2name(rb_frame_last_func()));                     \
    }                                                                   \
    Data_Get_Struct(obj, VarBit, v0);                                   \
    Data_Get_Struct(a, VarBit, v1);                                     \
    vp = (VarBit *)plruby_dfc2(function_, v0, v1);                      \
    vr = (VarBit *)ALLOC_N(char, VARSIZE(vp));                          \
    CPY_FREE(vr, vp, VARSIZE(vp));                                      \
    res = Data_Wrap_Struct(rb_class_of(obj), pl_bit_mark, free, vr);    \
    if (OBJ_TAINTED(obj) || OBJ_TAINTED(a)) OBJ_TAINT(res);             \
    return res;                                                         \
}

BIT_OPERATOR(pl_bit_add, bitcat);
BIT_OPERATOR(pl_bit_and, bitand);
BIT_OPERATOR(pl_bit_or, bitor);
BIT_OPERATOR(pl_bit_xor, bitxor);

static VALUE
pl_bit_push(VALUE obj, VALUE a)
{
    VarBit *v0, *v1, *vp;

    if (TYPE(a) != T_DATA ||
        RDATA(a)->dmark != (RUBY_DATA_FUNC)pl_bit_mark) {
        rb_raise(rb_eArgError, "invalid argument for %s",
                 rb_id2name(rb_frame_last_func()));
    }
    Data_Get_Struct(obj, VarBit, v0);
    Data_Get_Struct(a, VarBit, v1);
    vp = (VarBit *)plruby_dfc2(bitcat, v0, v1);
    free(v0);
    v0 = (VarBit *)ALLOC_N(char, VARSIZE(vp));
    CPY_FREE(v0, vp, VARSIZE(vp));
    DATA_PTR(obj) = v0;
    return obj;
}

static VALUE
pl_bit_not(VALUE obj)
{
    VarBit *v0, *vp, *vr;
    VALUE res;

    Data_Get_Struct(obj, VarBit, v0);
    vp = (VarBit *)plruby_dfc1(bitnot, v0);
    vr = (VarBit *)ALLOC_N(char, VARSIZE(vp));
    CPY_FREE(vr, vp, VARSIZE(vp));
    res = Data_Wrap_Struct(rb_class_of(obj), pl_bit_mark, free, vr);
    if (OBJ_TAINTED(obj)) OBJ_TAINT(res);
    return res;
}

#define SHIFT_OPERATOR(name_, function_)                                \
static VALUE                                                            \
name_(VALUE obj, VALUE a)                                               \
{                                                                       \
    VarBit *v0, *vp, *vr;                                               \
    VALUE res;                                                          \
                                                                        \
    Data_Get_Struct(obj, VarBit, v0);                                   \
    a = rb_Integer(a);                                                  \
    vp = (VarBit *)plruby_dfc2(function_, v0, Int32GetDatum(NUM2INT(a)));\
    vr = (VarBit *)ALLOC_N(char, VARSIZE(vp));                          \
    CPY_FREE(vr, vp, VARSIZE(vp));                                      \
    res = Data_Wrap_Struct(rb_class_of(obj), pl_bit_mark, free, vr);    \
    if (OBJ_TAINTED(obj) || OBJ_TAINTED(a)) OBJ_TAINT(res);             \
    return res;                                                         \
}

SHIFT_OPERATOR(pl_bit_left_shift, bitshiftleft);
SHIFT_OPERATOR(pl_bit_right_shift, bitshiftright);

static VALUE
pl_bit_length(VALUE obj)
{
    VarBit *v;
    int l;

    Data_Get_Struct(obj, VarBit, v);
    l = DatumGetInt32(plruby_dfc1(bitlength, v));
    return INT2NUM(l);
}

static VALUE
pl_bit_octet_length(VALUE obj)
{
    VarBit *v;
    int l;

    Data_Get_Struct(obj, VarBit, v);
    l = DatumGetInt32(plruby_dfc1(bitoctetlength, v));
    return INT2NUM(l);
}

static VALUE
pl_bit_to_i(VALUE obj)
{
    VarBit *v;
    int l;

    Data_Get_Struct(obj, VarBit, v);
    l = DatumGetInt32(plruby_dfc1(bittoint4, v));
    return INT2NUM(l);
}

/* This is varbit_out() from utils/adt/varbit.c */
static VALUE
pl_bit_each(VALUE obj)
{
    VarBit *s;
    VALUE i1, i0;
    bits8 *sp, x;
    int i, k, len;

    i1 = INT2FIX(1);
    i0 = INT2FIX(0);
    Data_Get_Struct(obj, VarBit, s);
    len = VARBITLEN(s);
    sp = VARBITS(s);
    for (i = 0; i < len - BITS_PER_BYTE; i += BITS_PER_BYTE, sp++) {
        x = *sp;
        for (k = 0; k < BITS_PER_BYTE; k++) {
            if (x & BITHIGH) {
                rb_yield(i1);
            }
            else {
                rb_yield(i0);
            }
            x <<= 1;
        }
    }
    x = *sp;
    for (k = i; k < len; k++){
        if (x & BITHIGH) {
            rb_yield(i1);
        }
        else {
            rb_yield(i0);
        }
        x <<= 1;
    }
    return Qnil;
}

static VALUE
pl_bit_index(VALUE obj, VALUE a)
{
    VarBit *v0, *v1;
    int i;

    if (TYPE(a) != T_DATA ||
        RDATA(a)->dmark != (RUBY_DATA_FUNC)pl_bit_mark) {
        rb_raise(rb_eArgError, "invalid argument for %s",
                 rb_id2name(rb_frame_last_func()));
    }
    Data_Get_Struct(obj, VarBit, v0);
    Data_Get_Struct(a, VarBit, v1);
    i = DatumGetInt32(plruby_dfc2(bitposition, v0, v1));
    i -= 1;
    if (i < 0) return Qnil;
    return INT2NUM(i);
}

static VALUE
pl_bit_include(VALUE obj, VALUE a)
{
    if (NIL_P(pl_bit_index(obj, a))) {
        return Qfalse;
    }
    return Qtrue;
}

static VALUE
pl_bit_subpat(VALUE obj, VALUE a, int nth)
{
    VALUE res;

    obj = pl_bit_to_s(obj);
    if (rb_reg_search(a, obj, 0, 0) >= 0) {
	res = rb_reg_nth_match(nth, rb_backref_get());
        return rb_funcall(rb_obj_class(obj), rb_intern("new"), 1, res);
    }
    return Qnil;
}

static VALUE
pl_bit_substr(VALUE obj, long beg, long len)
{
    VarBit *v, *v0, *v1;
    long ll;
    VALUE res;
    
    Data_Get_Struct(obj, VarBit, v);
    ll = DatumGetInt32(plruby_dfc1(bitlength, v));
    if (len < 0) return Qnil;
    if (beg > ll) return Qnil;
    if (beg < 0) {
        beg += ll;
        if (beg < 0) return Qnil;
    }
    if (beg + len > ll) {
        len = ll - beg;
    }
    if (len < 0) {
        len = 0;
    }
    if (len == 0) {
        res = rb_funcall2(rb_obj_class(obj), rb_intern("allocate"), 0, 0);
        if (OBJ_TAINTED(obj)) OBJ_TAINT(res);
        return res;
    }
    v0 = (VarBit *)plruby_dfc3(bitsubstr, v, Int32GetDatum(beg + 1), 
                               Int32GetDatum(len));
    v1 = (VarBit *)ALLOC_N(char, VARSIZE(v0));
    CPY_FREE(v1, v0, VARSIZE(v0));
    res = Data_Wrap_Struct(rb_obj_class(obj), pl_bit_mark, free, v1);
    if (OBJ_TAINTED(obj)) OBJ_TAINT(res);
    return res;
}

static VALUE
pl_bit_aref(VALUE obj, VALUE a)
{
    VarBit *v, *v0, *v1;
    long l, idx;
    bits8 *sp, x;
    VALUE res;

    Data_Get_Struct(obj, VarBit, v);
    l = DatumGetInt32(plruby_dfc1(bitlength, v));

    switch (TYPE(a)) {
    case T_FIXNUM:
        idx = FIX2LONG(a);

    num_index:
        if (idx < 0) {
            idx = l + idx;
        }
        if (idx < 0 || l <= idx) {
            return Qnil;
        }
        sp = VARBITS(v);
        sp += (idx / BITS_PER_BYTE);
        x = *sp <<= (idx % BITS_PER_BYTE);
        if (x & BITHIGH) return INT2FIX(1);
        return INT2FIX(0);

    case T_REGEXP:
        return pl_bit_subpat(obj, a, 0);

    case T_STRING:
        a = plruby_to_s(a);
        v0 = (void *)plruby_dfc3(bit_in, RSTRING(a)->ptr, 
                                 ObjectIdGetDatum(0), Int32GetDatum(-1));
        if (DatumGetInt32(plruby_dfc2(bitposition, v, v0)) > 0) {
            v1 = (VarBit *)ALLOC_N(char, VARSIZE(v0));
            CPY_FREE(v1, v0, VARSIZE(v0));
            res = Data_Wrap_Struct(rb_class_obj(obj), pl_bit_mark, free, v1);
            if (OBJ_TAINTED(obj) || OBJ_TAINTED(a)) OBJ_TAINT(res);
            return res;
        }
        pfree(v0);
        return Qnil;

    case T_DATA:
        if (RDATA(a)->dmark != (RUBY_DATA_FUNC)pl_bit_mark) {
            rb_raise(rb_eArgError, "expected a BitString object");
        }
        Data_Get_Struct(a, VarBit, v0);
        if (DatumGetInt32(plruby_dfc2(bitposition, v, v0)) > 0) {
            return rb_funcall2(a, rb_intern("dup"), 0, 0);
        }
        return Qnil;

    default:
    {
        long beg, len;

        switch (rb_range_beg_len(a, &beg, &len, l, 0)) {
        case Qfalse:
            break;
        case Qnil:
            return Qnil;
        default:
            return pl_bit_substr(obj, beg, len);
        }
    }
    idx = NUM2LONG(a);
    goto num_index;
    }
    return Qnil;
}

static VALUE
pl_bit_aref_m(int argc, VALUE *argv, VALUE obj)
{
    if (argc == 2) {
	if (TYPE(argv[0]) == T_REGEXP) {
	    return pl_bit_subpat(obj, argv[0], NUM2INT(argv[1]));
	}
        return pl_bit_substr(obj, NUM2LONG(argv[0]), NUM2LONG(argv[1]));
    }
    if (argc != 1) {
	rb_raise(rb_eArgError, "wrong number of arguments(%d for 1)", argc);
    }
    return pl_bit_aref(obj, argv[0]);
}

static VALUE
pl_bit_aset(int argc, VALUE *argv, VALUE obj)
{
    VALUE res;
    int i;
    void *v;
    VarBit *inst;

    for (i = 0; i < argc; ++i) {
        if (TYPE(argv[i]) == T_DATA &&
            RDATA(argv[i])->dmark == (RUBY_DATA_FUNC)pl_bit_mark) {
            argv[i] = pl_bit_to_s(argv[i]);
        }
    }
    res = rb_funcall2(pl_bit_to_s(obj), rb_intern("[]="), argc, argv);
    if (NIL_P(res)) return res;
    res = plruby_to_s(res);
    v = (void *)plruby_dfc3(bit_in, RSTRING(res)->ptr, ObjectIdGetDatum(0),
                            Int32GetDatum(-1));
    Data_Get_Struct(obj, VarBit, inst);
    free(inst);
    inst = (VarBit *)ALLOC_N(char, VARSIZE(v));
    CPY_FREE(inst, v, VARSIZE(v));
    RDATA(obj)->data = inst;
    return obj;
}

void Init_plruby_bitstring()
{
    VALUE pl_cBit;

    pl_cBit = rb_define_class("BitString", rb_cObject);
    rb_include_module(pl_cBit, rb_mComparable);
    rb_include_module(pl_cBit, rb_mEnumerable);
    rb_undef_method(CLASS_OF(pl_cBit), "method_missing");
#if HAVE_RB_DEFINE_ALLOC_FUNC
    rb_define_alloc_func(pl_cBit, pl_bit_s_alloc);
#else
    rb_define_singleton_method(pl_cBit, "allocate", pl_bit_s_alloc, 0);
#endif
    rb_define_singleton_method(pl_cBit, "new", plruby_s_new, -1);
    rb_define_singleton_method(pl_cBit, "from_string", plruby_s_new, -1);
    rb_define_singleton_method(pl_cBit, "from_datum", pl_bit_s_datum, 1);
    rb_define_method(pl_cBit, "to_datum", pl_bit_to_datum, 1);
    rb_define_method(pl_cBit, "initialize", pl_bit_init, -1);
#ifndef HAVE_RB_INITIALIZE_COPY
    rb_define_method(pl_cBit, "clone", plruby_clone, 0);
#endif
    rb_define_method(pl_cBit, "initialize_copy", pl_bit_init_copy, 1);
#if PG_PL_VERSION >= 74
    rb_define_method(pl_cBit, "marshal_load", pl_bit_mload, 1);
    rb_define_method(pl_cBit, "marshal_dump", pl_bit_mdump, -1);
#ifndef RUBY_CAN_USE_MARSHAL_LOAD
    rb_define_singleton_method(pl_cBit, "_load", plruby_s_load, 1);
    rb_define_alias(pl_cBit, "_dump", "marshal_dump");
#endif
#endif
    rb_define_method(pl_cBit, "<=>", pl_bit_cmp, 1);
    rb_define_method(pl_cBit, "each", pl_bit_each, 0);
    rb_define_method(pl_cBit, "+", pl_bit_add, 1);
    rb_define_method(pl_cBit, "concat", pl_bit_push, 1);
    rb_define_method(pl_cBit, "push", pl_bit_push, 1);
    rb_define_method(pl_cBit, "index", pl_bit_index, 1);
    rb_define_method(pl_cBit, "include?", pl_bit_include, 1);
    rb_define_method(pl_cBit, "&", pl_bit_and, 1);
    rb_define_method(pl_cBit, "|", pl_bit_or, 1);
    rb_define_method(pl_cBit, "^", pl_bit_xor, 1);
    rb_define_method(pl_cBit, "~", pl_bit_not, 0);
    rb_define_method(pl_cBit, "<<", pl_bit_left_shift, 1);
    rb_define_method(pl_cBit, ">>", pl_bit_right_shift, 1);
    rb_define_method(pl_cBit, "[]", pl_bit_aref_m, -1);
    rb_define_method(pl_cBit, "[]=", pl_bit_aset, -1);
    rb_define_method(pl_cBit, "length", pl_bit_length, 0);
    rb_define_method(pl_cBit, "size", pl_bit_length, 0);
    rb_define_method(pl_cBit, "octet_length", pl_bit_octet_length, 0);
    rb_define_method(pl_cBit, "octet_size", pl_bit_octet_length, 0);
    rb_define_method(pl_cBit, "to_s", pl_bit_to_s, 0);
    rb_define_method(pl_cBit, "to_i", pl_bit_to_i, 0);
}
