#include "builtin/data.hpp"
#include "builtin/class.hpp"
#include "capi/capi.hpp"
#include "object_memory.hpp"
#include "object_utils.hpp"
#include "ontology.hpp"
#include "on_stack.hpp"

namespace rubinius {

  void Data::init(STATE) {
    GO(data).set(ontology::new_class(state, "Data", G(object)));
    G(data)->set_object_type(state, DataType);
  }

  Data* Data::create(STATE, void* data_ptr, Data::MarkFunctor mark, Data::FreeFunctor free) {
    Data* data;

    data = state->new_object<Data>(G(data));
    data->freed_ = false;

    // Data is just a heap alias for the handle, so go ahead and create
    // the handle and populate it as an RData now.
    capi::Handle* handle = data->handle(state);

    assert(!handle && "can't already have a handle, it's brand new!");

    handle = state->memory()->add_capi_handle(state, data);

    // Don't call ->ref() on handle! We don't want the handle to keep the object
    // alive by default. The handle needs to have the lifetime of the object.

    RDataShadow* rdata = reinterpret_cast<RDataShadow*>(handle->as_rdata(0));

    rdata->data = data_ptr;
    rdata->d.untyped.dmark = mark;
    rdata->d.untyped.dfree = free;

    data->internal_ = rdata;

    if(mark || free) {
      state->memory()->needs_finalization(data, (FinalizerFunction)&Data::finalize);
    }

    return data;
  }

  Data* Data::create_typed(STATE, void* data_ptr, const struct rb_data_type_struct_shadow* type) {
    Data* data;

    data = state->new_object<Data>(G(data));
    data->freed_ = false;

    // Data is just a heap alias for the handle, so go ahead and create
    // the handle and populate it as an RData now.
    capi::Handle* handle = data->handle(state);

    assert(!handle && "can't already have a handle, it's brand new!");

    handle = state->memory()->add_capi_handle(state, data);

    // Don't call ->ref() on handle! We don't want the handle to keep the object
    // alive by default. The handle needs to have the lifetime of the object.

    RDataShadow* rdata = reinterpret_cast<RDataShadow*>(handle->as_rtypeddata(0));

    rdata->data = data_ptr;
    rdata->d.typed.typed = 1;
    rdata->d.typed.type = type;

    data->internal_ = rdata;

    if(type->function.dmark || type->function.dfree) {
      state->memory()->needs_finalization(data, (FinalizerFunction)&Data::finalize);
    }

    return data;
  }

  void* Data::data() {
    return rdata()->data;
  }

  Data::FreeFunctor Data::free() {
    RDataShadow* data = rdata();
    if(typed()) {
      return data->d.typed.type->function.dfree;
    } else {
      return data->d.untyped.dfree;
    }
  }

  Data::MarkFunctor Data::mark() {
    RDataShadow* data = rdata();
    if(typed()) {
      return data->d.typed.type->function.dmark;
    } else {
      return data->d.untyped.dmark;
    }
  }

  void Data::finalize(STATE, Data* data) {
    capi::Handle* handle = data->handle(state);

    if(!handle->valid_p()) {
      std::cerr << "Data::finalize: object has invalid handle!" << std::endl;
      return;
    }

    if(handle->object() != data) {
      std::cerr << "Data::finalize: handle does not reference object!" << std::endl;
      return;
    }

    if(data->freed_p()) {
      // TODO: Fix the issue of finalizer ordering.
      // std::cerr << "Data::finalize called for already freed object" << std::endl;
      return;
    }
    data->set_freed();

    // MRI only calls free if the data_ptr is not NULL.
    if(void* data_ptr = data->data()) {
      Data::FreeFunctor f = data->free();
      if(f) {
        // If the user specifies -1, then we call free. We check here rather
        // than when Data_Make_Struct is called because the user is allowed to
        // change dfree.
        if(reinterpret_cast<intptr_t>(f) == -1) {
          ::free(data_ptr);
        } else {
          f(data_ptr);
        }
      }
    }
  }

  void Data::Info::mark(Object* t, ObjectMark& mark) {
    auto_mark(t, mark);

    Data* data = force_as<Data>(t);

    if(mark.mature_gc_in_progress()) {
      // Don't scan objects concurrently since this might
      // not be thread safe. The C library in use here
      // might be in the process of freeing up malloc'ed
      // resources so we would see objects in an invalid
      // state and scan wrong pointers etc.
      return;
    }

    if(data->freed_p()) {
      // TODO: Fix the issue of finalizer ordering.
      // std::cerr << "Data::Info::mark called for already freed object" << std::endl;
      return;
    }

    Data::MarkFunctor marker = data->mark();

    if(marker) {
      ObjectMark* cur = capi::current_mark();
      capi::set_current_mark(&mark);

      (*marker)(data->data());

      capi::set_current_mark(cur);
    }
  }

}
