#include <nfc.h>
#include <ruby/thread.h>

VALUE cNfcDevice;

struct nogvl_ctx {
    nfc_device *dev;
    nfc_modulation *mod;
    nfc_target *ti;
};

void * nogvl_select_passive_target(void * ctx)
{
    nfc_device * dev;
    nfc_modulation * mod;
    nfc_target * ti;

    struct nogvl_ctx * myctx = (struct nogvl_ctx *)ctx;
    dev = myctx->dev;
    mod = myctx->mod;
    ti = myctx->ti;

    return nfc_initiator_select_passive_target(dev, *mod, NULL, 0, ti);
}

/*
 * call-seq:
 *  select_passive_target(tag)
 *
 * Select the +tag+ type from the device
 */
static VALUE select_passive_target(VALUE self, VALUE tag)
{
    nfc_device * dev;
    nfc_modulation * mod;
    nfc_target * ti;
    struct nogvl_ctx ctx;

    Data_Get_Struct(self, nfc_device, dev);
    Data_Get_Struct(tag, nfc_modulation, mod);

    ti = (nfc_target *)xmalloc(sizeof(nfc_target));

    ctx.dev = dev;
    ctx.mod = mod;
    ctx.ti = ti;

    if (rb_thread_call_without_gvl(nogvl_select_passive_target, &ctx, RUBY_UBF_IO, 0) ) {
	switch(mod->nmt) {
	    case NMT_ISO14443A:
		return Data_Wrap_Struct(cNfcISO14443A, 0, xfree, ti);
		break;
	    case NMT_FELICA:
		/* return Data_Wrap_Struct(cNfcFelica, 0, free, ti); */
		return Qnil;
		break;
	    default:
		rb_raise(rb_eRuntimeError, "untested type: %d", mod->nmt);
	}
    } else {
	xfree(ti);
    }

    return Qfalse;
}

/*
 * call-seq:
 *  poll_target(tag, ms)
 *
 * Poll the +tag+ type from the device
 */
static VALUE poll_target(VALUE self, VALUE tag, VALUE poll_nr, VALUE ms)
{
    nfc_device * dev;
    nfc_modulation * mod;
    nfc_target * ti;
    int code;
    int ms_c, poll_nr_c;
    Data_Get_Struct(self, nfc_device, dev);
    Data_Get_Struct(tag, nfc_modulation, mod);

    ms_c = FIX2INT(ms);
    poll_nr_c = FIX2INT(poll_nr);

    ti = (nfc_target *)xmalloc(sizeof(nfc_target));

    code = nfc_initiator_poll_target(dev, mod, 1, poll_nr_c, ms_c, ti);

    if (code > 0) {
	switch(mod->nmt) {
	    case NMT_ISO14443A:
		return Data_Wrap_Struct(cNfcISO14443A, 0, xfree, ti);
		break;
	    case NMT_FELICA:
		return Data_Wrap_Struct(cNfcFelica, 0, xfree, ti);
		break;
	    default:
		rb_raise(rb_eRuntimeError, "untested type: %d", mod->nmt);
	}
    }else {
	xfree(ti);
    }

    return INT2NUM(code);
}

/*
 * call-seq:
 *  name
 *
 * Get the name of the tag reader
 */
static VALUE name(VALUE self)
{
    nfc_device * dev;
    Data_Get_Struct(self, nfc_device, dev);

    return rb_str_new2(nfc_device_get_name(dev));
}

/*
 * call-seq:
 *  deselect
 *
 * Deselect the current tag
 */
static VALUE dev_deselect(VALUE self)
{
    nfc_device * dev;
    Data_Get_Struct(self, nfc_device, dev);

    nfc_initiator_deselect_target(dev);

    return self;
}

static VALUE mod_initialize(VALUE self, VALUE type, VALUE baud)
{
    nfc_modulation * mod;

    Data_Get_Struct(self, nfc_modulation, mod);
    mod->nmt = NUM2INT(type);
    mod->nbr = NUM2INT(baud);

    return self;
}

static VALUE mod_alloc(VALUE klass)
{
    nfc_modulation * modulation;

    modulation = xcalloc(1, sizeof(nfc_modulation));

    return Data_Wrap_Struct(klass, NULL, xfree, modulation);
}

static VALUE mod_nmt(VALUE self)
{
    nfc_modulation * mod;

    Data_Get_Struct(self, nfc_modulation, mod);

    return INT2NUM(mod->nmt);
}

static VALUE mod_nbr(VALUE self)
{
    nfc_modulation * mod;

    Data_Get_Struct(self, nfc_modulation, mod);

    return INT2NUM(mod->nbr);
}

static VALUE initiator_init(VALUE self)
{
    nfc_device * dev;
    int err;

    Data_Get_Struct(self, nfc_device, dev);

    err = nfc_initiator_init(dev);
    if (0 == err)
	return Qtrue;

    return INT2NUM(err);
}

void init_device()
{
    VALUE cNfcModulation;
    cNfcDevice = rb_define_class_under(mNfc, "Device", rb_cObject);
    rb_define_method(cNfcDevice, "initiator_init", initiator_init, 0);
    rb_define_method(cNfcDevice, "select_passive_target", select_passive_target, 1);
    rb_define_method(cNfcDevice, "poll_target", poll_target, 3);
    rb_define_method(cNfcDevice, "name", name, 0);
    rb_define_method(cNfcDevice, "deselect", dev_deselect, 0);

    cNfcModulation = rb_define_class_under(cNfcDevice, "Modulation", rb_cObject);

    /* modulation types. */
    rb_define_const(cNfcModulation, "NMT_ISO14443A", INT2NUM(NMT_ISO14443A));
    rb_define_const(cNfcModulation, "NMT_FELICA", INT2NUM(NMT_FELICA));

    /* baud rates */
    rb_define_const(cNfcModulation, "NBR_UNDEFINED", INT2NUM(NBR_UNDEFINED));
    rb_define_const(cNfcModulation, "NBR_106", INT2NUM(NBR_106));
    rb_define_const(cNfcModulation, "NBR_212", INT2NUM(NBR_212));

    rb_define_alloc_func(cNfcModulation, mod_alloc);

    rb_define_method(cNfcModulation, "initialize", mod_initialize, 2);
    rb_define_method(cNfcModulation, "nmt", mod_nmt, 0);
    rb_define_method(cNfcModulation, "nbr", mod_nbr, 0);
}

/* vim: set noet sws=4 sw=4: */
