#include <Python.h>
#include <xdo.h>

typedef struct {
  PyObject_HEAD
  xdo_t* xdo;
} xdo_XdoObject;

static void
pyxdo_dealloc(xdo_XdoObject* self)
{
  if (self->xdo != NULL)
    xdo_free(self->xdo);
  self->ob_type->tp_free((PyObject*)self);
}

static PyObject *
pyxdo_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    xdo_XdoObject *self = NULL;
    const char* display = NULL;
    
    if (!PyArg_ParseTuple(args, "|s", &display))
      return NULL;

    self = (xdo_XdoObject *)type->tp_alloc(type, 0);
    if (self != NULL) {
      self->xdo = xdo_new(display);
      if (self->xdo == NULL) {
        pyxdo_dealloc(self);
        PyErr_SetString(PyExc_SystemError, "Could not initialize libxdo");
        return NULL;
      }
    }

    return (PyObject *)self;
}

static PyObject*
pyxdo_str(xdo_XdoObject *self) {
  return PyString_FromFormat("libxdo context object (%p)", self->xdo);
}

static PyObject *
pyxdo_get_focused_window(xdo_XdoObject* self)
{
  Window wout = 0;
  int ret = xdo_get_focused_window(self->xdo, &wout);
  if (ret == XDO_ERROR)
    return NULL;
  else
    return PyInt_FromLong(wout);
}

static PyObject *
pyxdo_focus_window(xdo_XdoObject* self, PyObject *args)
{
  Window win = 0;
  if (!PyArg_ParseTuple(args, "i", &win))
    return NULL;

  if (xdo_focus_window(self->xdo, win) == XDO_ERROR) {
    PyErr_SetString(PyExc_SystemError, "libxdo: failed to focus window"); 
    return NULL;
  }

  Py_RETURN_NONE;
}

static PyObject *
pyxdo_wait_for_window_focus(xdo_XdoObject* self, PyObject *args)
{
  Window win = 0;
  PyObject* focustype = NULL;
  int f = 1;
  if (!PyArg_ParseTuple(args, "i|O", &win, &focustype))
    return NULL;

  if (focustype != NULL)
    f = PyObject_IsTrue(focustype);
  if (xdo_wait_for_window_focus(self->xdo, win, f) == XDO_ERROR) {
    PyErr_SetString(PyExc_SystemError, "libxdo: failed to wait for focus on window"); 
    return NULL;
  }

  Py_RETURN_NONE;
}

/* int xdo_type(const xdo_t *xdo, Window window, const char *string, useconds_t delay); */
static PyObject *
pyxdo_type(xdo_XdoObject* self, PyObject *args, PyObject *kwargs)
{
  /* FIXME: python delay arg should accept some sort of high-resolution native time type, not just microseconds as an int */

  Window win = CURRENTWINDOW;
  const char* string;
  static char *kwlist[] = {"string", "clearmodifiers", "delay", "window", NULL};
  int delay = 12000;
  PyObject* c = NULL;
  int clearmods = 1;
  charcodemap_t *active_mods = NULL;
  int active_mods_n;


  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|Oii", kwlist, &string, &c, &delay, &win))
    return NULL;
  if (c != NULL)
    clearmods = PyObject_IsTrue(c);

  if (clearmods) {
    /* clear modifier keys */
    xdo_get_active_modifiers(self->xdo, &active_mods, &active_mods_n);
    xdo_clear_active_modifiers(self->xdo, win, active_mods, active_mods_n);
  }

  if (xdo_enter_text_window(self->xdo, win, string, delay) == XDO_ERROR) {
    PyErr_SetString(PyExc_SystemError, "libxdo: Failed to type.");
    return NULL;
  }

  if (clearmods) {
    /* restore modifier keys */
    xdo_set_active_modifiers(self->xdo, win, active_mods, active_mods_n);
    free(active_mods);
  }

  Py_RETURN_NONE;
}


static PyObject *
pyxdo_version(PyObject *self)
{
    return Py_BuildValue("s", xdo_version());
}

static PyMethodDef XdoTypeMethods[] = {
    {"version",  (PyCFunction)pyxdo_version, METH_NOARGS, "Return (as a string) the version of libxdo"},
    {"get_focused_window", (PyCFunction)pyxdo_get_focused_window, METH_NOARGS, "return the numeric ID of the focused window"},
    {"focus_window", (PyCFunction)pyxdo_focus_window, METH_VARARGS, "Focus the given window"},
    {"wait_for_window_focus", (PyCFunction)pyxdo_wait_for_window_focus, METH_VARARGS, "Wait for the given window to get focus (or to lose focus if the optional last argument is non-True"},
    {"type", (PyCFunction)pyxdo_type, METH_VARARGS | METH_KEYWORDS, "Type the given string.  Optional keyword arguments are:\
  window -- select the window (by numeric ID) to send text to.  If supplied, we send the keystrokes via XSendEvent (which may not be accepted by all applications); if not supplied we send the keystrokes via XTEST.\
  clearmodifiers -- clears any current modifier keys before sending the text (defaults to True)\
  delay -- the number of microseconds to wait between each keystroke (defaults to 12000)"},
    {NULL}        /* Sentinel */
};

static PyTypeObject xdo_XdoType = {
    PyObject_HEAD_INIT(NULL)
    .tp_name = "xdo.xdo",
    .tp_basicsize = sizeof(xdo_XdoObject),
    .tp_dealloc = (destructor)pyxdo_dealloc, 
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_doc = "xdo context object",
    .tp_new = pyxdo_new,
    .tp_str = (reprfunc)pyxdo_str,
    .tp_methods = XdoTypeMethods
};


/* xdo_type() */

static PyMethodDef XdoMethods[] = {
    {"version",  (PyCFunction)pyxdo_version, METH_NOARGS,
     "Return (as a string) the version of libxdo"},
    {NULL}        /* Sentinel */
};


#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initxdo(void)
{
    PyObject* m;

    if (PyType_Ready(&xdo_XdoType) < 0)
        return;

    m = Py_InitModule3("xdo", XdoMethods,
                       "bindings for libxdo -- simulate X11 keyboard/mouse input");

    Py_INCREF(&xdo_XdoType);
    PyModule_AddObject(m, "xdo", (PyObject *)&xdo_XdoType);
}
