/*
 * Copyright (c) 2010, Wayne Meissner
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * * The name of the author or authors may not be used to endorse or promote
 *   products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <ruby.h>

#include <ffi.h>
#include "rbffi.h"
#include "compat.h"

#include "Pointer.h"
#include "Struct.h"
#include "StructByReference.h"


#define FFI_ALIGN(v, a)  (((((size_t) (v))-1) | ((a)-1))+1)

static VALUE sbr_allocate(VALUE);
static VALUE sbr_initialize(VALUE, VALUE);
static void sbr_mark(StructByReference *);

VALUE rbffi_StructByReferenceClass = Qnil;

static VALUE
sbr_allocate(VALUE klass)
{
    StructByReference* sbr;

    VALUE obj = Data_Make_Struct(klass, StructByReference, sbr_mark, -1, sbr);

    sbr->rbStructClass = Qnil;

    return obj;
}

static VALUE
sbr_initialize(VALUE self, VALUE rbStructClass)
{
    StructByReference* sbr = NULL;
    
    if (!rb_class_inherited_p(rbStructClass, rbffi_StructClass)) {
        rb_raise(rb_eTypeError, "wrong type (expected subclass of FFI::Struct)");
    }

    Data_Get_Struct(self, StructByReference, sbr);
    sbr->rbStructClass = rbStructClass;
    
    return self;
}

static void
sbr_mark(StructByReference *sbr)
{
    rb_gc_mark(sbr->rbStructClass);
}


static VALUE
sbr_struct_class(VALUE self)
{
    StructByReference* sbr;

    Data_Get_Struct(self, StructByReference, sbr);

    return sbr->rbStructClass;
}

static VALUE
sbr_native_type(VALUE self)
{
    return rb_const_get(rbffi_TypeClass, rb_intern("POINTER"));
}

static VALUE
sbr_to_native(VALUE self, VALUE value, VALUE ctx)
{
    StructByReference* sbr;
    Struct* s;

    if (unlikely(value == Qnil)) {
        return rbffi_NullPointerSingleton;
    }

    Data_Get_Struct(self, StructByReference, sbr);
    if (!rb_obj_is_kind_of(value, sbr->rbStructClass)) {
        rb_raise(rb_eTypeError, "wrong argument type %s (expected %s)",
                rb_obj_classname(value),
                RSTRING_PTR(rb_class_name(sbr->rbStructClass)));
    }

    Data_Get_Struct(value, Struct, s);

    return s->rbPointer;
}

static VALUE
sbr_from_native(VALUE self, VALUE value, VALUE ctx)
{
    StructByReference* sbr;

    Data_Get_Struct(self, StructByReference, sbr);

    return rb_class_new_instance(1, &value, sbr->rbStructClass);
}


void
rbffi_StructByReference_Init(VALUE moduleFFI)
{
    rbffi_StructByReferenceClass = rb_define_class_under(moduleFFI, "StructByReference", rb_cObject);
    rb_global_variable(&rbffi_StructByReferenceClass);
    rb_include_module(rbffi_StructByReferenceClass, rb_const_get(moduleFFI, rb_intern("DataConverter")));
    
    rb_define_alloc_func(rbffi_StructByReferenceClass, sbr_allocate);
    rb_define_method(rbffi_StructByReferenceClass, "initialize", sbr_initialize, 1);
    rb_define_method(rbffi_StructByReferenceClass, "struct_class", sbr_struct_class, 0);
    rb_define_method(rbffi_StructByReferenceClass, "native_type", sbr_native_type, 0);
    rb_define_method(rbffi_StructByReferenceClass, "to_native", sbr_to_native, 2);
    rb_define_method(rbffi_StructByReferenceClass, "from_native", sbr_from_native, 2);
}

