#include "ruby.h"
#include "caml/callback.h"
     
void initcaml(void) {
  char *dummy_argv[] = {0};

  caml_startup(dummy_argv);
}


VALUE OcamlMatrix = Qnil; // set up the Ruby namespace
    
void Init_ocamlmatrix();

VALUE singleton_matrix_new(VALUE class, VALUE width, VALUE height);
void method_assign(VALUE self, VALUE x, VALUE y, VALUE item);
VALUE method_retrieve(VALUE self, VALUE x, VALUE y);
VALUE method_matrix_mul(VALUE self, VALUE b);

void Init_ocamlmatrix() {
  OcamlMatrix = rb_define_module("OcamlMatrix");
  rb_define_singleton_method(OcamlMatrix, "w_new_matrix", singleton_matrix_new, 2);
  rb_define_method(OcamlMatrix, "w_matrix_assign", method_assign, 3);
  rb_define_method(OcamlMatrix, "w_matrix_retrieve", method_retrieve, 2);
  rb_define_method(OcamlMatrix, "w_matrix_mul", method_matrix_mul, 1);

  initcaml();
}


VALUE singleton_matrix_new(VALUE class, VALUE width, VALUE height) {
  CAMLparam0 ();
  CAMLlocal1 (ocaml_m);
  ocaml_m = ocaml_new_matrix(Val_long(NUM2LONG(width)), Val_long(NUM2LONG(height)));

  VALUE my_m = Data_Wrap_Struct(class, 0, 0, ocaml_m);
  rb_obj_call_init(my_m, 0, NULL);

  CAMLreturnT (VALUE, my_m);
}


void method_assign(VALUE self, VALUE x, VALUE y, VALUE item) {
  CAMLparam0 ();
  CAMLlocal1 (ocaml_m);

  Data_Get_Struct(self, value, ocaml_m);

  ocaml_matrix_assign(ocaml_m, Val_long(NUM2LONG(x)), Val_long(NUM2LONG(y)), Val_long(NUM2LONG(item)));

  CAMLreturn0;
}


VALUE method_retrieve(VALUE self, VALUE x, VALUE y) {
  CAMLparam0 ();
  CAMLlocal1 (ocaml_m);

  Data_Get_Struct(self, value, ocaml_m);

  VALUE retval = LONG2NUM(Long_val(ocaml_matrix_retrieve(ocaml_m,
                                                         Val_long(NUM2LONG(x)),
                                                         Val_long(NUM2LONG(y)))
                                   ));

  CAMLreturnT (VALUE, retval);
}


VALUE method_matrix_mul(VALUE self, VALUE b) {
  CAMLparam0 ();
  CAMLlocal3 (ocaml_m1, ocaml_m2, ocaml_m3);

  Data_Get_Struct(self, value, ocaml_m1);
  Data_Get_Struct(b, value, ocaml_m2);

  ocaml_m3 = ocaml_matrix_mul(ocaml_m1, ocaml_m2);

  VALUE my_m = Data_Wrap_Struct(class, 0, 0, ocaml_m);
  rb_obj_call_init(my_m, 0, NULL);

  CAMLreturnT (VALUE, my_m);
}

