#include "frame.h"
#include "buffer.h"

typedef struct {
    VALUE buffer;
    int index;
} frame_t;

VALUE cFrame;

static void frame_mark(void *ptr)
{
    frame_t *frame = ptr;
    rb_gc_mark(frame->buffer);
}

static size_t frame_memsize(const void *ptr)
{
    return sizeof(frame_t);
}

static const rb_data_type_t frame_data_type = {
    "stack_frames_frame",
    { frame_mark, RUBY_DEFAULT_FREE, frame_memsize, },
    NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
};

VALUE stack_frame_new(VALUE frame_buffer, int index) {
    frame_t *frame;
    VALUE obj = TypedData_Make_Struct(cFrame, frame_t, &frame_data_type, frame);
    frame->buffer = frame_buffer;
    frame->index = index;
    return obj;
}

static VALUE frame_allocate(VALUE klass) {
    rb_raise(rb_eRuntimeError, "frames cannot be allocated manually");
    return Qnil;
}

static VALUE frame_profile_frame(VALUE self) {
    frame_t *frame;
    TypedData_Get_Struct(self, frame_t, &frame_data_type, frame);
    return stack_buffer_profile_frame(frame->buffer, frame->index);
}

static VALUE frame_lineno(VALUE self) {
    frame_t *frame;
    TypedData_Get_Struct(self, frame_t, &frame_data_type, frame);
    return INT2NUM(stack_buffer_frame_lineno(frame->buffer, frame->index));
}

#define DEFINE_FRAME_ACCESSOR(func_name) \
    static VALUE frame_##func_name(VALUE self) { \
        return rb_profile_frame_##func_name(frame_profile_frame(self)); \
    }

DEFINE_FRAME_ACCESSOR(path)
DEFINE_FRAME_ACCESSOR(absolute_path)
DEFINE_FRAME_ACCESSOR(label)
DEFINE_FRAME_ACCESSOR(base_label)
DEFINE_FRAME_ACCESSOR(full_label)
DEFINE_FRAME_ACCESSOR(first_lineno)
DEFINE_FRAME_ACCESSOR(classpath)
DEFINE_FRAME_ACCESSOR(singleton_method_p)
DEFINE_FRAME_ACCESSOR(method_name)
DEFINE_FRAME_ACCESSOR(qualified_method_name)

void stack_frame_define(VALUE mStackFrames) {
    cFrame = rb_define_class_under(mStackFrames, "Frame", rb_cObject);
    rb_global_variable(&cFrame);

    rb_define_alloc_func(cFrame, frame_allocate);
    rb_define_method(cFrame, "lineno", frame_lineno, 0);
    rb_define_method(cFrame, "path", frame_path, 0);
    rb_define_method(cFrame, "absolute_path", frame_absolute_path, 0);
    rb_define_method(cFrame, "label", frame_label, 0);
    rb_define_method(cFrame, "base_label", frame_base_label, 0);
    rb_define_method(cFrame, "full_label", frame_full_label, 0);
    rb_define_method(cFrame, "first_lineno", frame_first_lineno, 0);
    rb_define_method(cFrame, "classpath", frame_classpath, 0);
    rb_define_method(cFrame, "singleton_method?", frame_singleton_method_p, 0);
    rb_define_method(cFrame, "method_name", frame_method_name, 0);
    rb_define_method(cFrame, "qualified_method_name", frame_qualified_method_name, 0);
}