#ifndef __PY_KERNWIN__
#define __PY_KERNWIN__


//------------------------------------------------------------------------
//<decls(py_kernwin)>
//------------------------------------------------------------------------

//-------------------------------------------------------------------------
// Context structure used by add|del_idc_hotkey()
struct py_idchotkey_ctx_t
{
  qstring action_name;
  ref_t pyfunc;

  py_idchotkey_ctx_t(
          const char *_action_name,
          PyObject *_pyfunc)
    : action_name(_action_name),
      pyfunc(borref_t(_pyfunc)) {}
};

static ref_t py_colorizer;

static void py_snapshot_restore_callback(const char *err_msg, void *userdata);

//------------------------------------------------------------------------
//</decls(py_kernwin)>
//------------------------------------------------------------------------

//------------------------------------------------------------------------
//<inline(py_kernwin)>
//------------------------------------------------------------------------

//------------------------------------------------------------------------
static PyObject *py_register_timer(int interval, PyObject *callback)
{
  PYW_GIL_CHECK_LOCKED_SCOPE();

  if ( callback == nullptr || !PyCallable_Check(callback) )
    Py_RETURN_NONE;

  // An inner class hosting the callback method
  struct tmr_t
  {
    static int idaapi callback(void *ud)
    {
      PYW_GIL_GET;
      py_timer_ctx_t *ctx = (py_timer_ctx_t *)ud;
      newref_t py_result(PyObject_CallFunctionObjArgs(ctx->pyfunc.o, nullptr));
      int ret = -1;
      if ( PyErr_Occurred() != nullptr )
      {
        msg("Exception in timer callback. This timer will be unregistered.\n");
        PyErr_Print();
      }
      else if ( py_result )
      {
        ret = PyLong_AsLong(py_result.o);
      }

      // Timer has been unregistered?
      if ( ret == -1 )
        python_timer_del(ctx);
      return ret;
    };
  };

  py_timer_ctx_t *ctx = python_timer_new(callback);
  ctx->timer_id = register_timer(
          interval,
          tmr_t::callback,
          ctx);

  if ( ctx->timer_id != nullptr )
  {
    return PyCapsule_New(ctx, VALID_CAPSULE_NAME, nullptr);
  }
  else
  {
    python_timer_del(ctx);
    Py_RETURN_NONE;
  }
}

//------------------------------------------------------------------------
static bool py_unregister_timer(PyObject *timer_obj)
{
  PYW_GIL_CHECK_LOCKED_SCOPE();

  if ( timer_obj == nullptr || !PyCapsule_IsValid(timer_obj, VALID_CAPSULE_NAME) )
    return false;

  py_timer_ctx_t *ctx = (py_timer_ctx_t *) PyCapsule_GetPointer(timer_obj, VALID_CAPSULE_NAME);
  if ( ctx == nullptr || !unregister_timer(ctx->timer_id) )
    return false;

  python_timer_del(ctx);
  // invalidate capsule; make sure we don't try and delete twice
  PyCapsule_SetName(timer_obj, INVALID_CAPSULE_NAME);
  return true;
}

//------------------------------------------------------------------------
static PyObject *py_choose_idasgn()
{
  char *name = choose_idasgn();
  PYW_GIL_CHECK_LOCKED_SCOPE();
  if ( name == nullptr )
  {
    Py_RETURN_NONE;
  }
  else
  {
    PyObject *py_str = PyUnicode_FromString(name);
    qfree(name);
    return py_str;
  }
}

//------------------------------------------------------------------------
static PyObject *py_get_highlight(TWidget *v, uint32 flags=0)
{
  qstring buf;
  uint32 lflags;
  bool ok = get_highlight(&buf, v, &lflags, flags);
  PYW_GIL_CHECK_LOCKED_SCOPE();
  if ( !ok )
    Py_RETURN_NONE;
  return Py_BuildValue("(sk)", buf.c_str(), lflags);
}

//------------------------------------------------------------------------
static int py_load_custom_icon_fn(const char *filename)
{
  return load_custom_icon(filename);
}

//------------------------------------------------------------------------
static int py_load_custom_icon_data(PyObject *data, const char *format)
{
  Py_ssize_t len;
  char *s;
  PYW_GIL_CHECK_LOCKED_SCOPE();
  if ( PyBytes_AsStringAndSize(data, &s, &len) == -1 )
    return 0;
  else
    return load_custom_icon(s, len, format);
}

//------------------------------------------------------------------------
static int py_msg(const char *message)
{
  int rc = 0;
  if ( message != nullptr )
  {
    SWIG_PYTHON_THREAD_BEGIN_ALLOW;
    rc = msg("%s", message);
    SWIG_PYTHON_THREAD_END_ALLOW;
  }
  return rc;
}

//------------------------------------------------------------------------
static int py_warning(const char *message)
{
  int rc = 0;
  if ( message != nullptr )
  {
    SWIG_PYTHON_THREAD_BEGIN_ALLOW;
    rc = warning("%s", message);
    SWIG_PYTHON_THREAD_END_ALLOW;
  }
  return rc;
}

//------------------------------------------------------------------------
static void py_error(const char *message)
{
  if ( message != nullptr )
  {
    SWIG_PYTHON_THREAD_BEGIN_ALLOW;
    error("%s", message);
  }
}

//------------------------------------------------------------------------
PyObject *py_ask_text(size_t max_size, const char *defval, const char *prompt)
{
  PYW_GIL_CHECK_LOCKED_SCOPE();

  qstring qbuf;

  PyObject *py_ret;
  if ( ask_text(&qbuf, max_size, defval, "%s", prompt) )
  {
    py_ret = PyUnicode_FromStringAndSize(qbuf.begin(), qbuf.length());
  }
  else
  {
    py_ret = Py_None;
    Py_INCREF(py_ret);
  }
  return py_ret;
}

//------------------------------------------------------------------------
PyObject *py_ask_str(qstring *defval, int hist, const char *prompt)
{
  PYW_GIL_CHECK_LOCKED_SCOPE();

  PyObject *py_ret;
  if ( ask_str(defval, hist, "%s", prompt) )
  {
    py_ret = PyUnicode_FromStringAndSize(defval->begin(), defval->length());
  }
  else
  {
    py_ret = Py_None;
    Py_INCREF(py_ret);
  }
  return py_ret;
}

//------------------------------------------------------------------------
static bool py_process_ui_action(const char *name, int flags = 0)
{
  return process_ui_action(name, flags, nullptr);
}

//------------------------------------------------------------------------
bool py_del_hotkey(PyObject *ctx)
{
  PYW_GIL_CHECK_LOCKED_SCOPE();
  if ( !PyCapsule_IsValid(ctx, VALID_CAPSULE_NAME) )
    return false;

  py_idchotkey_ctx_t *_ctx = (py_idchotkey_ctx_t *) PyCapsule_GetPointer(ctx, VALID_CAPSULE_NAME);
  if ( _ctx == nullptr || !unregister_action(_ctx->action_name.c_str()) )
    return false;

  delete _ctx;

  // invalidate capsule; make sure we don't try and delete twice
  PyCapsule_SetName(ctx, INVALID_CAPSULE_NAME);
  return true;
}

//------------------------------------------------------------------------
PyObject *py_add_hotkey(const char *hotkey, PyObject *callable)
{
  PYW_GIL_CHECK_LOCKED_SCOPE();
  // Make sure a callable was passed
  if ( !PyCallable_Check(callable) )
    return nullptr;

  // Form the function name
  qstring idc_func_name;
  idc_func_name.sprnt("py_hotkeycb_%p", callable);

  // Can add the hotkey?
  if ( add_idc_hotkey(hotkey, idc_func_name.c_str()) == IDCHK_OK )
  {
    do
    {
      // Generate global variable name
      qstring idc_gvarname;
      idc_gvarname.sprnt("_g_pyhotkey_ref_%p", callable);

      // Now add the global variable
      idc_value_t *gvar = add_idc_gvar(idc_gvarname.c_str());
      if ( gvar == nullptr )
        break;

      // The function body will call a registered IDC function that
      // will take a global variable that wraps a PyCallable as a pvoid
      qstring idc_func;
      idc_func.sprnt("static %s() { %s(%s); }",
        idc_func_name.c_str(),
        S_PYINVOKE0,
        idc_gvarname.c_str());

      // Compile the IDC condition
      qstring errbuf;
      if ( !compile_idc_text(idc_func.c_str(), &errbuf) )
        break;

      // Create new context
      py_idchotkey_ctx_t *ctx = new py_idchotkey_ctx_t(idc_func_name.c_str(), callable);

      // Bind IDC variable w/ the PyCallable
      gvar->set_pvoid(callable);

      // Return the context
      return PyCapsule_New(ctx, VALID_CAPSULE_NAME, nullptr);
    } while (false);
  }
  // Cleanup
  unregister_action(idc_func_name.c_str());
  Py_RETURN_NONE;
}

//------------------------------------------------------------------------
static PyObject *py_take_database_snapshot(snapshot_t *snapshot)
{
  PYW_GIL_CHECK_LOCKED_SCOPE();

  qstring err_msg;

  bool b = take_database_snapshot(snapshot, &err_msg);

  // Return (b, err_msg)
  return Py_BuildValue("(Ns)", PyBool_FromLong(b), err_msg.empty() ? nullptr : err_msg.c_str());
}

//-------------------------------------------------------------------------
static PyObject *py_restore_database_snapshot(
        const snapshot_t *snapshot,
        PyObject *callback,
        PyObject *userdata)
{
  PYW_GIL_CHECK_LOCKED_SCOPE();

  // If there is no callback, just call the function directly
  if ( callback == Py_None )
    return PyBool_FromLong(restore_database_snapshot(snapshot, nullptr, nullptr));

  // Create a new tuple or increase reference to userdata
  if ( userdata == Py_None )
  {
    userdata = PyTuple_New(0);
    if ( userdata == nullptr )
      return nullptr;
  }
  else
  {
    Py_INCREF(userdata);
  }

  // Create callback data tuple (use 'N' for userdata, since its
  // reference has already been incremented)
  PyObject *cb_data = Py_BuildValue("(ON)", callback, userdata);

  bool b = restore_database_snapshot(snapshot, py_snapshot_restore_callback, (void *) cb_data);

  if ( !b )
    Py_DECREF(cb_data);

  return PyBool_FromLong(b);
}

//------------------------------------------------------------------------
static ssize_t py_execute_sync(PyObject *callable, int reqf)
{
  PYW_GIL_CHECK_LOCKED_SCOPE();
  ssize_t rc = -1;
  // Callable?
  if ( PyCallable_Check(callable) )
  {
    struct py_exec_request_t : exec_request_t
    {
      ref_t py_callable;
      virtual ssize_t idaapi execute() override
      {
        PYW_GIL_GET;
        newref_t py_result(PyObject_CallFunctionObjArgs(py_callable.o, nullptr));
        ssize_t ret = !py_result || !PyLong_Check(py_result.o)
                    ? -1
                    : PyLong_AsLong(py_result.o);
        // if the requesting thread decided not to wait for the request to
        // complete, we have to self-destroy, nobody else will do it
        if ( (code & MFF_NOWAIT) != 0 )
          delete this;
        return ret;
      }
      py_exec_request_t(PyObject *pyc)
      {
        // No need to GIL-ensure here, since this is created
        // within the py_execute_sync() scope.
        py_callable = borref_t(pyc);
      }
      virtual ~py_exec_request_t()
      {
        // Need to GIL-ensure here, since this might be called
        // from the main thread.
        PYW_GIL_GET;
        py_callable = ref_t(); // Release callable
      }
    };
    py_exec_request_t *req = new py_exec_request_t(callable);

    // Release GIL before executing, or if this is running in the
    // non-main thread, this will wait on the req.sem, while the main
    // thread might be waiting for the GIL to be available.
    SWIG_PYTHON_THREAD_BEGIN_ALLOW;
    rc = execute_sync(*req, reqf);
    SWIG_PYTHON_THREAD_END_ALLOW;
    // destroy the request once it is finished. exception: NOWAIT requests
    // will be handled in the future, so do not destroy them yet!
    if ( (reqf & MFF_NOWAIT) == 0 )
      delete req;
  }
  return rc;
}

//------------------------------------------------------------------------
static bool py_execute_ui_requests(PyObject *callable_list)
{
  struct py_ui_request_t: public ui_request_t
  {
  private:
    ref_vec_t py_callables;
    size_t py_callable_idx;

    static int idaapi s_callable_list_walk_cb(
        const ref_t &py_item,
        Py_ssize_t /*index*/,
        void *ud)
    {
      PYW_GIL_CHECK_LOCKED_SCOPE();
      // Not callable? Terminate iteration
      if ( !PyCallable_Check(py_item.o) )
        return CIP_FAILED;

      // Append this callable and increment its reference
      py_ui_request_t *_this = (py_ui_request_t *)ud;
      _this->py_callables.push_back(py_item);
      return CIP_OK;
    }
  public:
    py_ui_request_t(): py_callable_idx(0)
    {
    }

    virtual bool idaapi run() override
    {
      PYW_GIL_GET;

      // Get callable
      ref_t py_callable = py_callables.at(py_callable_idx);
      bool reschedule;
      newref_t py_result(PyObject_CallFunctionObjArgs(py_callable.o, nullptr));
      // execute_ui_requests() will cause this run() code to be executed
      // asynchronously. This means that, should an exception have been raised
      // in the Python code we just executed, it won't have the possibility of
      // trickling all the way up to the Python runtime.
      // Since it would interfere with subsequent Python code execution, we
      // want to report it right away.
      if ( PyErr_Occurred() != nullptr )
      {
        PyErr_Print();
        return false;
      }
      reschedule = py_result != nullptr && PyObject_IsTrue(py_result.o);

      // No rescheduling? Then advance to the next callable
      if ( !reschedule )
        ++py_callable_idx;

      // Reschedule this C callback only if there are more callables
      return py_callable_idx < py_callables.size();
    }

    // Walk the list and extract all callables
    bool init(PyObject *callable_list)
    {
      Py_ssize_t count = pyvar_walk_seq(
              callable_list,
              s_callable_list_walk_cb,
              this);
      return count > 0;
    }

    virtual idaapi ~py_ui_request_t()
    {
      PYW_GIL_GET;
      py_callables.clear();
    }
  };

  py_ui_request_t *req = new py_ui_request_t();
  if ( !req->init(callable_list) )
  {
    delete req;
    return false;
  }
  execute_ui_requests(req, nullptr);
  return true;
}

//------------------------------------------------------------------------
struct jobj_wrapper_t
{
private:
  const jobj_t *o;

public:
  jobj_wrapper_t(const jobj_t *_o) : o(_o) {}

  PyObject *get_dict()
  {
    return PyW_from_jobj_t(*o);
  }

  static bool fill_jobj_from_dict(jobj_t *out, PyObject *dict)
  {
    return PyW_to_jobj_t(out, dict);
  }
};

//---------------------------------------------------------------------------
// UI hooks
//---------------------------------------------------------------------------
ssize_t idaapi UI_Callback(void *ud, int notification_code, va_list va);
struct UI_Hooks : public hooks_base_t
{
  // hookgenUI:methodsinfo_decl

  UI_Hooks(uint32 _flags=0, uint32 _hkcb_flags=HKCB_GLOBAL)
    : hooks_base_t("ida_kernwin.UI_Hooks", UI_Callback, HT_UI, _flags, _hkcb_flags) {}

  bool hook() { return hooks_base_t::hook(); }
  bool unhook() { return hooks_base_t::unhook(); }
#ifdef TESTABLE_BUILD
  PyObject *dump_state(bool assert_all_reimplemented=false) { return hooks_base_t::dump_state(mappings, mappings_size, assert_all_reimplemented); }
#endif

  // hookgenUI:methods

  ssize_t dispatch(int code, va_list va)
  {
    ssize_t ret = 0;
    switch ( code )
    {
      // hookgenUI:notifications
    }
    return ret;
  }

private:
  static ssize_t handle_get_ea_hint_output(PyObject *o, qstring *buf, ea_t)
  {
    ssize_t rc = 0;
    if ( o != nullptr && PyUnicode_Check(o) && PyUnicode_as_qstring(buf, o) )
      rc = 1;
    Py_XDECREF(o);
    return rc;
  }

  static ssize_t handle_hint_output(PyObject *o, qstring *hint, int *important_lines)
  {
    if ( o != nullptr && PyTuple_Check(o) && PyTuple_Size(o) == 2 )
    {
      borref_t el0(PyTuple_GetItem(o, 0));
      qstring plug_hint;
      if ( el0
        && PyUnicode_Check(el0.o)
        && PyUnicode_as_qstring(&plug_hint, el0.o)
        && !plug_hint.empty() )
      {
        borref_t el1(PyTuple_GetItem(o, 1));
        if ( el1 && PyLong_Check(el1.o) )
        {
          long lns = PyLong_AsLong(el1.o);
          if ( lns > 0 )
          {
            if ( !hint->empty() && hint->last() != '\n' )
              hint->append('\n');
            hint->append(plug_hint);
            *important_lines += lns;
          }
        }
      }
    }
    return 0;
  }

  static ssize_t handle_hint_output(PyObject *o, qstring *hint, ea_t, int, int *important_lines)
  {
    return handle_hint_output(o, hint, important_lines);
  }

  static ssize_t handle_hint_output(PyObject *o, qstring *hint, TWidget *, place_t *, int *important_lines)
  {
    return handle_hint_output(o, hint, important_lines);
  }

  static jobj_wrapper_t wrap_widget_cfg(const jobj_t *jobj)
  {
    return jobj_wrapper_t(jobj);
  }

  static ssize_t handle_create_desktop_widget_output(PyObject *o)
  {
    if ( o == Py_None )
      return 0;
    TWidget *widget = nullptr;
    int cvt = SWIG_ConvertPtr(o, (void **) &widget, SWIGTYPE_p_TWidget, 0);
    if ( !SWIG_IsOK(cvt) || widget == nullptr )
      return 0;
    return ssize_t(widget);
  }

  static ssize_t handle_widget_cfg_output(PyObject *o, const TWidget *, jobj_t *cfg)
  {
    return jobj_wrapper_t::fill_jobj_from_dict(cfg, o);
  }
};

//-------------------------------------------------------------------------
bool py_register_action(action_desc_t *desc)
{
  bool ok = desc != nullptr;
  if ( ok )
  {
    desc->flags |= ADF_OWN_HANDLER;
    ok = register_action(*desc);
    if ( ok )
    {
      // Let's set this to nullptr, so when the wrapping Python action_desc_t
      // instance is deleted, it doesn't try to delete the handler (See
      // kernwin.i's action_desc_t::~action_desc_t()).
      desc->handler = nullptr;
    }
  }
  return ok;
}

//-------------------------------------------------------------------------
PyObject *py_get_registered_actions()
{
  PYW_GIL_CHECK_LOCKED_SCOPE();
  qstrvec_t actions;
  get_registered_actions(&actions);
  return qstrvec2pylist(actions);
}

//-------------------------------------------------------------------------
bool py_attach_dynamic_action_to_popup(
        TWidget *unused,
        TPopupMenu *popup_handle,
        action_desc_t *desc,
        const char *popuppath = nullptr,
        int flags = 0)
{
  qnotused(unused);
  if ( popup_handle == nullptr || desc == nullptr )
    return false;
  bool ok = attach_dynamic_action_to_popup(
          nullptr, popup_handle, *desc, popuppath, flags);
  // If attaching
  //  * succeeded: the action (and its handler) will be deleted after
  //    the popup is dismissed,
  //  * fails: the action (and its handler) will be deleted right away
  // Therefore, we must always drop ownership.
  desc->handler = nullptr;
  return ok;
}

// This is similar to a twinline_t, with improved memory management:
// twinline_t has a dummy destructor, that performs no cleanup.
struct disasm_line_t
{
  disasm_line_t() : at(nullptr) {}
  ~disasm_line_t() { qfree(at); }
  disasm_line_t(const disasm_line_t &other) { *this = other; }
  disasm_line_t &operator=(const disasm_line_t &other)
  {
    qfree(at);
    at = other.at == nullptr ? nullptr : other.at->clone();
    return *this;
  }
  place_t *at;
  qstring line;
  color_t prefix_color;
  bgcolor_t bg_color;
  bool is_default;
};
DECLARE_TYPE_AS_MOVABLE(disasm_line_t);
typedef qvector<disasm_line_t> disasm_text_t;

//-------------------------------------------------------------------------
// tuple(fields, icon, attr)
static PyObject *py_chooser_base_t_get_row(
        const chooser_base_t *chobj,
        size_t n)
{
  if ( chobj == nullptr )
  {
    PyErr_SetString(PyExc_ValueError, "A valid chooser_base_t pointer is expected as first argument");
    return nullptr;
  }
  qstrvec_t fields;
  fields.resize(chobj->columns);
  chooser_item_attrs_t *attrs = new chooser_item_attrs_t;
  int icon;
  chobj->get_row(&fields, &icon, attrs, n);

  PyObject *tuple = PyTuple_New(3);
  PyTuple_SetItem(tuple, 0, qstrvec2pylist(fields));
  PyTuple_SetItem(tuple, 1, PyLong_FromLong(icon));
  PyObject *py_attrs = SWIG_NewPointerObj(
          SWIG_as_voidptr(attrs),
          SWIGTYPE_p_chooser_item_attrs_t,
          SWIG_POINTER_OWN);
  PyTuple_SetItem(tuple, 2, py_attrs);
  return tuple;
}

//-------------------------------------------------------------------------
void py_gen_disasm_text(disasm_text_t &text, ea_t ea1, ea_t ea2, bool truncate_lines)
{
  text_t _text;
  gen_disasm_text(_text, ea1, ea2, truncate_lines);
  for ( size_t i = 0, n = _text.size(); i < n; ++i )
  {
    twinline_t &tl = _text[i];
    disasm_line_t &dl = text.push_back();
    dl.at = tl.at;           // Transfer ownership
    dl.line.swap(tl.line);   // Transfer ownership
  }
}

//-------------------------------------------------------------------------
PyObject *py_set_nav_colorizer(PyObject *callback)
{
  struct ida_local lambda_t
  {
    static uint32 idaapi call_py_colorizer(ea_t ea, asize_t nbytes, void *)
    {
      PYW_GIL_GET;

      if ( !py_colorizer ) // Shouldn't happen.
        return 0;
      newref_t pyres(PyObject_CallFunction(
                             py_colorizer.o, "KK",
                             (unsigned long long) ea,
                             (unsigned long long) nbytes));
      PyW_ShowCbErr("nav_colorizer");
      uint32 rc = 0;
      bool ok = pyres && PyLong_Check(pyres.o);
      if ( ok )
      {
        int overflow = 0;
        const long l = PyLong_AsLongAndOverflow(pyres.o, &overflow);
        ok = PyErr_Occurred() == nullptr;
        if ( ok )
        {
          if ( l == -1 && overflow != 0 )
            ok = false;
          else
            rc = uint32(l);
        }
        else
        {
          PyErr_Print();
        }
      }

      if ( !ok )
      {
        static bool warned = false;
        if ( !warned )
        {
          msg("WARNING: set_nav_colorizer() callback must return an "
              "unsigned 'long', that can be converted into a 32-bit "
              "unsigned integer.\n");
          warned = true;
        }
      }
      return rc;
    }
  };

  // Always perform the call to set_nav_colorizer(): that has side-effects
  // (e.g., updating the legend.)
  bool first_install = py_colorizer == nullptr;
  py_colorizer = borref_t(callback);
  nav_colorizer_t *was_fun = nullptr;
  void *was_ud = nullptr;
  set_nav_colorizer(&was_fun, &was_ud, lambda_t::call_py_colorizer, nullptr);
  if ( !first_install )
    Py_RETURN_NONE;
  PyObject *was_fun_ptr = PyCapsule_New((void *) was_fun, VALID_CAPSULE_NAME, nullptr);
  PyObject *was_ud_ptr = PyCapsule_New(was_ud, VALID_CAPSULE_NAME, nullptr);
  PyObject *dict = PyDict_New();
  PyDict_SetItemString(dict, "fun", was_fun_ptr);
  PyDict_SetItemString(dict, "ud", was_ud_ptr);
  return dict;
}

//-------------------------------------------------------------------------
uint32 py_call_nav_colorizer(
        PyObject *colorizer,
        ea_t ea,
        asize_t nbytes)
{
  if ( !PyDict_Check(colorizer) )
    return 0;
  borref_t py_fun(PyDict_GetItemString(colorizer, "fun"));
  borref_t py_ud(PyDict_GetItemString(colorizer, "ud"));
  if ( !py_fun
    || !PyCapsule_IsValid(py_fun.o, VALID_CAPSULE_NAME)
    || !PyCapsule_IsValid(py_ud.o, VALID_CAPSULE_NAME) )
  {
    return 0;
  }
  nav_colorizer_t *fun = (nav_colorizer_t *) PyCapsule_GetPointer(py_fun.o, VALID_CAPSULE_NAME);
  void *ud = PyCapsule_GetPointer(py_ud.o, VALID_CAPSULE_NAME);
  if ( fun == nullptr )
    return 0;
  return fun(ea, nbytes, ud);
}

//-------------------------------------------------------------------------
PyObject *py_msg_get_lines(int count=-1)
{
  qstrvec_t lines;
  msg_get_lines(&lines, count);
  return qstrvec2pylist(lines);
}

//-------------------------------------------------------------------------
static TWidget *TWidget__from_ptrval__(size_t ptrval)
{
  return (TWidget *) ptrval;
}

// we limit the the number of spaces that can be added to 512k
#define MAX_SPACES_ADDED 524288
//-------------------------------------------------------------------------
static qstring py_add_spaces(const char *s, size_t len)
{
  qstring qbuf(s);
  const size_t slen = tag_strlen(qbuf.c_str());
  const size_t nlen = qbuf.length() - slen + len;
  if ( len > slen && nlen < MAX_SPACES_ADDED )
  {
    qbuf.resize(nlen);
  }
  else
  {
    if ( s == nullptr )
      qbuf.resize(1);
  }
  // we use the actual 'size' because we know that
  // 'add_spaces()' will add a terminating zero anyway
  add_spaces(qbuf.begin(), qbuf.size(), len);
  return qbuf;
}

//-------------------------------------------------------------------------
THREAD_SAFE void py_show_wait_box(const char *message)
{
  idapython_show_wait_box(/*internal=*/ false, message);
}

//-------------------------------------------------------------------------
void py_hide_wait_box()
{
  idapython_hide_wait_box();
}

//</inline(py_kernwin)>

//---------------------------------------------------------------------------
//<code(py_kernwin)>

// hookgenUI:methodsinfo_def

//---------------------------------------------------------------------------
ssize_t idaapi UI_Callback(void *ud, int code, va_list va)
{
  // hookgenUI:safecall=UI_Hooks
}

//------------------------------------------------------------------------
bool idaapi py_menu_item_callback(void *userdata)
{
  PYW_GIL_GET;

  // userdata is a tuple of ( func, args )
  // func and args are borrowed references from userdata

  PyObject *o = (PyObject *) userdata;
  if ( !PyTuple_Check(o) )
    return false;

  PyObject *func = PyTuple_GetItem(o, 0);
  PyObject *args = PyTuple_GetItem(o, 1);

  // Call the python function
  newref_t result(PyObject_Call(func, args, nullptr));

  // We cannot raise an exception in the callback, just print it.
  if ( result == nullptr )
  {
    PyErr_Print();
    return false;
  }

  return PyObject_IsTrue(result.o) != 0;
}

//-------------------------------------------------------------------------
static void ida_kernwin_init(void) {}

//-------------------------------------------------------------------------
static void ida_kernwin_term(void)
{
  py_colorizer = ref_t();
}

//-------------------------------------------------------------------------
static void ida_kernwin_closebase(void) {}

//------------------------------------------------------------------------
static void py_snapshot_restore_callback(const char *err_msg, void *userdata)
{
  PYW_GIL_GET;

  // userdata is a tuple of ( func, args )
  // func and args are borrowed references from userdata

  PyObject *o = (PyObject *) userdata;
  if ( o == nullptr || !PyTuple_Check(o) )
    return;

  PyObject *func = PyTuple_GetItem(o, 0);
  PyObject *args = PyTuple_GetItem(o, 1);

  // Create arguments tuple for python function
  PyObject *cb_args = Py_BuildValue("(sO)", err_msg, args);

  // Call the python function
  newref_t result(PyObject_Call(func, cb_args, nullptr));

  // Free cb_args and userdata
  Py_DECREF(cb_args);
  Py_DECREF(o);

  // We cannot raise an exception in the callback, just print it.
  if ( !result )
    PyErr_Print();
}
//</code(py_kernwin)>

#endif
