#include "mruby_pm_bridge.h"

#include "mruby/array.h"
#include "mruby/hash.h"
#include "mruby/string.h"
#include "mruby/error.h"

#define hv_foreach(hv, entry, block) {  \
  HV* hash = hv;                        \
  hv_iterinit(hash);                    \
  HE* entry;                            \
  while ((entry = hv_iternext(hash))) { \
    block;                              \
  }                                     \
}


SV * mruby_pm_bridge_value2sv(pTHX_ mrb_state *mrb, const mrb_value v) {
    switch (mrb_type(v)) {
    case MRB_TT_UNDEF:
      return &PL_sv_undef;
    case MRB_TT_FALSE: {
      if (mrb_fixnum(v)) {
        return sv_bless(newRV_inc(sv_2mortal(newSVsv(&PL_sv_undef))), gv_stashpv("mRuby::Bool::False", TRUE));
      }
      else {
        return &PL_sv_undef;
      }
    }
    case MRB_TT_TRUE:
      return sv_bless(newRV_inc(sv_2mortal(newSViv(1))), gv_stashpv("mRuby::Bool::True", TRUE));
    case MRB_TT_FIXNUM:
      return newSViv(mrb_fixnum(v));
    case MRB_TT_FLOAT:
      return newSVnv(mrb_float(v));
    case MRB_TT_STRING:
      return newSVpvn((char*)RSTRING_PTR(v), (STRLEN)RSTRING_LEN(v));
    case MRB_TT_SYMBOL: {
      mrb_int len;
      const char *name = mrb_sym2name_len(mrb, mrb_symbol(v), &len);
      return sv_bless(newRV_inc(sv_2mortal(newSVpvn((char*)name, (STRLEN)len))), gv_stashpv("mRuby::Symbol", TRUE));
    }
    case MRB_TT_HASH: {
      const mrb_value  keys = mrb_hash_keys(mrb, v);
      const mrb_value *ptr  = RARRAY_PTR(keys);
      const int        len  = RARRAY_LEN(keys);

      HV * ret = newHV_mortal();

      int i;
      for (i=0; LIKELY(i<len); i++) {
        const mrb_value kk = ptr[i];
        const mrb_value vv = mrb_hash_get(mrb, v, kk);

        SV * key_sv = sv_2mortal(mruby_pm_bridge_value2sv(aTHX_ mrb, kk));
        SV * val_sv = mruby_pm_bridge_value2sv(aTHX_ mrb, vv);
        hv_store_ent(ret, key_sv, SvROK(val_sv) ? SvREFCNT_inc(sv_2mortal(val_sv)) : val_sv, 0);
      }

      return newRV_inc((SV*)ret);
    }
    case MRB_TT_ARRAY: {
      const mrb_value *ptr = RARRAY_PTR(v);
      const int        len = RARRAY_LEN(v);

      AV * ret = newAV_mortal();

      int i;
      for (i=0; LIKELY(i<len); i++) {
        SV * val_sv = mruby_pm_bridge_value2sv(aTHX_ mrb, ptr[i]);
        av_push(ret, SvROK(val_sv) ? SvREFCNT_inc(sv_2mortal(val_sv)) : val_sv);
      }

      return newRV_inc((SV*)ret);
    }
    case MRB_TT_EXCEPTION: {
      mrb_value bt = mrb_exc_backtrace(mrb, v);
      return sv_bless(SvREFCNT_inc(sv_2mortal(mruby_pm_bridge_value2sv(aTHX_ mrb, bt))), gv_stashpv("mRuby::Exception", TRUE));
    }
    default:
      croak("This type of ruby value is not supported yet: %d", mrb_type(v));
    }
    abort();
}

static mrb_value mruby_pm_bridge_av2value(pTHX_ mrb_state *mrb, AV *av) {
  const I32 size = av_len(av) + 1;
  mrb_value ary = mrb_ary_new_capa(mrb, (mrb_int)size);

  I32 i;
  for (i=0; LIKELY(i<size); i++) {
    SV** v = av_fetch(av, i, 0);
    mrb_ary_set(mrb, ary, i, mruby_pm_bridge_sv2value(aTHX_ mrb, *v));
  }

  return ary;
}

static mrb_value mruby_pm_bridge_hv2value(pTHX_ mrb_state *mrb, HV *hv) {
  static const int BUF_SIZE = 32;

  int bufsize = BUF_SIZE;
  int keysize = 0;
  SV **keys_buf; Newxc(keys_buf, bufsize, SV*, SV*);
  SV **vals_buf; Newxc(vals_buf, bufsize, SV*, SV*);
  hv_foreach(hv, ent, {
    keys_buf[keysize] = hv_iterkeysv(ent);
    vals_buf[keysize] = HeVAL(ent);
    if (++keysize == bufsize) {
      bufsize *= 2;
      Renewc(keys_buf, bufsize, SV*, SV*);
      Renewc(vals_buf, bufsize, SV*, SV*);
    }
  });

  mrb_value hash = mrb_hash_new_capa(mrb, (mrb_int)keysize);

  int i;
  for (i=0; LIKELY(i<keysize); i++) {
    const mrb_value key = mruby_pm_bridge_sv2value(aTHX_ mrb, keys_buf[i]);
    const mrb_value val = mruby_pm_bridge_sv2value(aTHX_ mrb, vals_buf[i]);
    mrb_hash_set(mrb, hash, key, val);
  }

  Safefree(keys_buf);
  Safefree(vals_buf);
  return hash;
}

mrb_value mruby_pm_bridge_sv2value(pTHX_ mrb_state *mrb, SV *sv) {
  if (!SvOK(sv)) {
    return mrb_nil_value();
  }
  else if (sv_isobject(sv)) {
    if (sv_derived_from(sv, "mRuby::Symbol")) {
      STRLEN len;
      const char* sym = SvPV(SvRV(sv), len);
      return mrb_symbol_value(mrb_intern(mrb, sym, (size_t)len));
    }
    else if (sv_isobject(sv) && sv_derived_from(sv, "mRuby::Bool::True")) {
      return mrb_true_value();
    }
    else if (sv_isobject(sv) && sv_derived_from(sv, "mRuby::Bool::False")) {
      return mrb_false_value();
    }
    return mrb_nil_value();
  }
  else if (SvROK(sv)) {
    switch (SvTYPE(SvRV(sv))) {
      case SVt_PVAV:
        return mruby_pm_bridge_av2value(aTHX_ mrb, (AV*)SvRV(sv));
      case SVt_PVHV:
        return mruby_pm_bridge_hv2value(aTHX_ mrb, (HV*)SvRV(sv));
      default:
        return mruby_pm_bridge_sv2value(aTHX_ mrb, (SV*)SvRV(sv));
    }
  }
  else if (SvIOK(sv)) {
    return mrb_fixnum_value((mrb_int)SvIV(sv));
  }
  else if (SvNOK(sv)) {
    return mrb_float_value(mrb, (mrb_float)SvNV(sv));
  }
  else {
    STRLEN len;
    const char *p = SvPV(sv, len);
    return mrb_str_new(mrb, p, (size_t)len);
  }
}