/* xscreensaver, Copyright (c) 2016 Jamie Zawinski <jwz@jwz.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or 
 * implied warranty.
 *
 * This file is three related things:
 *
 *   - It is the Android-specific C companion to jwxyz-gl.c;
 *   - It is how C calls into Java to do things that OpenGL does not
 *     have access to without Java-based APIs;
 *   - It is how the jwxyz.java class calls into C to run the hacks.
 */

#include "config.h"

#ifdef HAVE_ANDROID /* whole file */

#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <setjmp.h>

#include <GLES/gl.h>
#include <jni.h>
#include <android/log.h>
#include <pthread.h>

#include "screenhackI.h"
#include "jwxyzI.h"
#include "jwzglesI.h"
#include "jwxyz-android.h"
#include "textclient.h"
#include "grabscreen.h"


#define countof(x) (sizeof(x)/sizeof(*(x)))

extern struct xscreensaver_function_table *xscreensaver_function_table;

struct function_table_entry {
  const char *progname;
  struct xscreensaver_function_table *xsft;
};

#include "gen/function-table.h"

struct event_queue {
  XEvent event;
  struct event_queue *next;
};

static void send_queued_events (struct running_hack *rh);

const char *progname;
const char *progclass;
int mono_p = 0;

static JavaVM *global_jvm;
static jmp_buf jmp_target;

static double current_rotation = 0;

extern void check_gl_error (const char *type);

void
do_logv(int prio, const char *fmt, va_list args)
{
  __android_log_vprint(prio, "xscreensaver", fmt, args);

  /* The idea here is that if the device/emulator dies shortly after a log
     message, then waiting here for a short while should increase the odds
     that adb logcat can pick up the message before everything blows up. Of
     course, doing this means dumping a ton of messages will slow things down
     significantly.
  */
# if 0
  struct timespec ts;
  ts.tv_sec = 0;
  ts.tv_nsec = 25 * 1000000;
  nanosleep(&ts, NULL);
# endif
}

void Log(const char *fmt, ...)
{
  va_list args;
  va_start (args, fmt);
  Logv(fmt, args);
  va_end (args);
}

/* Handle an abort on Android
   TODO: Test that Android handles aborts properly
 */
void
jwxyz_abort (const char *fmt, ...)
{
  /* Send error to Android device log */
  if (!fmt || !*fmt)
    fmt = "abort";

  va_list args;
  va_start (args, fmt);
  do_logv(ANDROID_LOG_ERROR, fmt, args);
  va_end (args);

  char buf[10240];
  va_start (args, fmt);
  vsprintf (buf, fmt, args);
  va_end (args);

  JNIEnv *env;
  (*global_jvm)->AttachCurrentThread (global_jvm, &env, NULL);

  if (! (*env)->ExceptionOccurred(env)) {
    // If there's already an exception queued, let's just go with that one.
    // Else, queue a Java exception to be thrown.
    (*env)->ThrowNew (env, (*env)->FindClass(env, "java/lang/RuntimeException"),
                      buf);
  }

  // Nonlocal exit out of the jwxyz code.
  longjmp (jmp_target, 1);
}


/* We get to keep live references to Java classes in use because the VM can
   unload a class that isn't being used, which invalidates field and method
   IDs.
   https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp17074
*/


// #### only need one var I think
static size_t classRefCount = 0;
static jobject globalRefjwxyz, globalRefIterable, globalRefIterator,
    globalRefMapEntry;

static jfieldID runningHackField;
static jmethodID iterableIterator, iteratorHasNext, iteratorNext;
static jmethodID entryGetKey, entryGetValue;

static pthread_mutex_t mutg = PTHREAD_MUTEX_INITIALIZER;

static void screenhack_do_fps (Display *, Window, fps_state *, void *);


// Initialized OpenGL and runs the screenhack's init function.
//
static void
doinit (jobject jwxyz_obj, struct running_hack *rh, JNIEnv *env,
        const struct function_table_entry *chosen, jint api, 
        jobject defaults, jint w, jint h)
{
  if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.

  progname = chosen->progname;
  rh->xsft = chosen->xsft;
  rh->api = api;
  rh->jni_env = env;
  rh->jobject = jwxyz_obj;  // update this every time we call into C

  (*env)->GetJavaVM (env, &global_jvm);

# undef ya_rand_init  // This is the one and only place it is allowed
  ya_rand_init (0);

  Window wnd = (Window) calloc(1, sizeof(*wnd));
  wnd->window.rh = rh;
  wnd->frame.width = w;
  wnd->frame.height = h;
  wnd->type = WINDOW;

  rh->egl_window_ctx = eglGetCurrentContext();
  Assert(rh->egl_window_ctx != EGL_NO_CONTEXT, "doinit: EGL_NO_CONTEXT");

  wnd->egl_surface = eglGetCurrentSurface(EGL_DRAW);
  Assert(eglGetCurrentSurface(EGL_READ) == wnd->egl_surface,
         "doinit: EGL_READ != EGL_DRAW");

  rh->egl_display = eglGetCurrentDisplay();
  Assert(rh->egl_display != EGL_NO_DISPLAY, "doinit: EGL_NO_DISPLAY");

  EGLint config_attribs[3];
  config_attribs[0] = EGL_CONFIG_ID;
  eglQueryContext(rh->egl_display, rh->egl_window_ctx, EGL_CONFIG_ID,
                  &config_attribs[1]);
  config_attribs[2] = EGL_NONE;

  EGLint num_config;
  eglChooseConfig(rh->egl_display, config_attribs,
                  &rh->egl_config, 1, &num_config);
  Assert(num_config == 1, "no EGL config chosen");

  rh->egl_xlib_ctx = eglCreateContext(rh->egl_display, rh->egl_config,
                                      EGL_NO_CONTEXT, NULL);
  Assert(rh->egl_xlib_ctx != EGL_NO_CONTEXT, "doinit: EGL_NO_CONTEXT");
  Assert(rh->egl_xlib_ctx != rh->egl_window_ctx, "Only one context here?!");

  rh->window = wnd;
  rh->dpy = jwxyz_make_display(wnd);
  Assert(wnd == XRootWindow(rh->dpy, 0), "Wrong root window.");
  // TODO: Zero looks right, but double-check that is the right number

  progclass = rh->xsft->progclass;

  if ((*env)->ExceptionOccurred(env)) abort();
  jwzgles_reset();

  // This has to come before resource processing. It does not do graphics.
  if (rh->xsft->setup_cb)
    rh->xsft->setup_cb(rh->xsft, rh->xsft->setup_arg);

  if ((*env)->ExceptionOccurred(env)) abort();

  // Load the defaults.
  // Unceremoniously stolen from [PrefsReader defaultsToDict:].

  jclass     c = (*env)->GetObjectClass (env, defaults);
  jmethodID  m = (*env)->GetMethodID (env, c, "put",
                 "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
  if (! m) abort();
  if ((*env)->ExceptionOccurred(env)) abort();

  const struct { const char *key, *val; } default_defaults[] = {
    { "doubleBuffer", "false" },
    { "multiSample",  "false" },
    { "texFontCacheSize", "30" },
    { "textMode", "date" },
    { "textURL",
      "https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss" },
    { "grabDesktopImages",  "true" },
    { "chooseRandomImages", "true" },
  };

  for (int i = 0; i < countof(default_defaults); i++) {
    const char *key = default_defaults[i].key;
    const char *val = default_defaults[i].val;
    char *key2 = malloc (strlen(progname) + strlen(key) + 2);
    strcpy (key2, progname);
    strcat (key2, "_");
    strcat (key2, key);

    // defaults.put(key2, val);
    jstring jkey = (*env)->NewStringUTF (env, key2);
    jstring jval = (*env)->NewStringUTF (env, val);
    (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
    (*env)->DeleteLocalRef (env, jkey);
    (*env)->DeleteLocalRef (env, jval);
    // Log ("default0: \"%s\" = \"%s\"", key2, val);
    free (key2);
  }

  const char *const *defs = rh->xsft->defaults;
  while (*defs) {
    char *line = strdup (*defs);
    char *key, *val;
    key = line;
    while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t')
      key++;
    val = key;
    while (*val && *val != ':')
      val++;
    if (*val != ':') abort();
    *val++ = 0;
    while (*val == ' ' || *val == '\t')
      val++;

    unsigned long L = strlen(val);
    while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t'))
      val[--L] = 0;

    char *key2 = malloc (strlen(progname) + strlen(key) + 2);
    strcpy (key2, progname);
    strcat (key2, "_");
    strcat (key2, key);

    // defaults.put(key2, val);
    jstring jkey = (*env)->NewStringUTF (env, key2);
    jstring jval = (*env)->NewStringUTF (env, val);
    (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
    (*env)->DeleteLocalRef (env, jkey);
    (*env)->DeleteLocalRef (env, jval);
    // Log ("default: \"%s\" = \"%s\"", key2, val);
    free (key2);
    free (line);
    defs++;
  }

  (*env)->DeleteLocalRef (env, c);
  if ((*env)->ExceptionOccurred(env)) abort();

 END: ;
}


#undef DEBUG_FPS

// Animates a single frame of the current hack.
//
static void
drawXScreenSaver (JNIEnv *env, struct running_hack *rh)
{
  double now = 0;
# ifdef DEBUG_FPS
  double fps0=0, fps1=0, fps2=0, fps3=0, fps4=0;
# endif

  if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.

  /* There is some kind of weird redisplay race condition between Settings
     and the launching hack: e.g., Abstractile does XClearWindow at init,
     but the screen is getting filled with random bits.  So let's wait a
     few frames before really starting up.
   */
  if (++rh->frame_count < 8)
    goto END;

  /* Some of the screen hacks want to delay for long periods, and letting
     the framework run the update function at 30 FPS when it really wanted
     half a minute between frames would be bad.  So instead, we assume that
     the framework's animation timer might fire whenever, but we only invoke
     the screen hack's "draw frame" method when enough time has expired.
  */
  struct timeval tv;
  gettimeofday (&tv, 0);
  now = tv.tv_sec + (tv.tv_usec / 1000000.0);
# ifdef DEBUG_FPS
  fps0 = fps1 = fps2 = fps3 = fps4 = now;
#endif
  if (now < rh->next_frame_time) goto END;

  prepare_context(rh);

# ifdef DEBUG_FPS
  gettimeofday (&tv, 0);
  fps1 = tv.tv_sec + (tv.tv_usec / 1000000.0);
# endif

  // The init function might do graphics (e.g. XClearWindow) so it has
  // to be run from inside onDrawFrame, not onSurfaceChanged.

  if (! rh->initted_p) {

    void *(*init_cb) (Display *, Window, void *) =
      (void *(*)(Display *, Window, void *)) rh->xsft->init_cb;

    unsigned int bg =
      get_pixel_resource (rh->dpy, 0, "background", "Background");
    XSetWindowBackground (rh->dpy, rh->window, bg);
    XClearWindow (rh->dpy, rh->window);

    rh->closure = init_cb (rh->dpy, rh->window, rh->xsft->setup_arg);
    rh->initted_p = True;

    rh->ignore_rotation_p =
      (rh->api == API_XLIB &&
       get_boolean_resource (rh->dpy, "ignoreRotation", "IgnoreRotation"));

    if (get_boolean_resource (rh->dpy, "doFPS", "DoFPS")) {
      rh->fpst = fps_init (rh->dpy, rh->window);
      if (! rh->xsft->fps_cb) rh->xsft->fps_cb = screenhack_do_fps;
    } else {
      rh->fpst = NULL;
      rh->xsft->fps_cb = 0;
    }

    if ((*env)->ExceptionOccurred(env)) abort();
  }

# ifdef DEBUG_FPS
  gettimeofday (&tv, 0);
  fps2 = tv.tv_sec + (tv.tv_usec / 1000000.0);
# endif

  // Apparently events don't come in on the drawing thread, and JNI flips
  // out.  So we queue them there and run them here.
  send_queued_events (rh);

# ifdef DEBUG_FPS
  gettimeofday (&tv, 0);
  fps3 = tv.tv_sec + (tv.tv_usec / 1000000.0);
# endif

  unsigned long delay = rh->xsft->draw_cb(rh->dpy, rh->window, rh->closure);

# ifdef __arm__
  /* #### Until we work out why eglMakeCurrent is so slow on ARM. */
  if (delay <= 40000) delay = 0;
# endif


# ifdef DEBUG_FPS
  gettimeofday (&tv, 0);
  fps4 = tv.tv_sec + (tv.tv_usec / 1000000.0);
# endif
  if (rh->fpst && rh->xsft->fps_cb)
    rh->xsft->fps_cb (rh->dpy, rh->window, rh->fpst, rh->closure);

  gettimeofday (&tv, 0);
  now = tv.tv_sec + (tv.tv_usec / 1000000.0);
  rh->next_frame_time = now + (delay / 1000000.0);

 END: ;

# ifdef DEBUG_FPS
  Log("## FPS prep = %-6d init = %-6d events = %-6d draw = %-6d fps = %-6d\n",
      (int) ((fps1-fps0)*1000000),
      (int) ((fps2-fps1)*1000000),
      (int) ((fps3-fps2)*1000000),
      (int) ((fps4-fps3)*1000000),
      (int) ( (now-fps4)*1000000));
# endif
}


// Extracts the C structure that is stored in the jwxyz Java object.
static struct running_hack *
getRunningHack (JNIEnv *env, jobject thiz)
{
  jlong result = (*env)->GetLongField (env, thiz, runningHackField);
  struct running_hack *rh = (struct running_hack *)(intptr_t)result;
  if (rh)
    rh->jobject = thiz;  // update this every time we call into C
  return rh;
}

// Look up a class and mark it global in the provided variable.
static jclass
acquireClass (JNIEnv *env, const char *className, jobject *globalRef)
{
  jclass clazz = (*env)->FindClass(env, className);
  *globalRef = (*env)->NewGlobalRef(env, clazz);
  return clazz;
}


/* Note: to find signature strings for native methods:
   cd ./project/xscreensaver/build/intermediates/classes/debug/
   javap -s -p org.jwz.xscreensaver.jwxyz
 */


// Implementation of jwxyz's nativeInit Java method.
//
JNIEXPORT void JNICALL
Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz,
                                            jstring jhack, jint api,
                                            jobject defaults,
                                            jint w, jint h)
{
  pthread_mutex_lock(&mutg);

  struct running_hack *rh = calloc(1, sizeof(struct running_hack));

  if ((*env)->ExceptionOccurred(env)) abort();

  // #### simplify
  if (!classRefCount) {
    jclass classjwxyz = (*env)->GetObjectClass(env, thiz);
    globalRefjwxyz = (*env)->NewGlobalRef(env, classjwxyz);
    runningHackField = (*env)->GetFieldID
      (env, classjwxyz, "nativeRunningHackPtr", "J");
    if ((*env)->ExceptionOccurred(env)) abort();

    jclass classIterable =
      acquireClass(env, "java/lang/Iterable", &globalRefIterable);
    iterableIterator = (*env)->GetMethodID
      (env, classIterable, "iterator", "()Ljava/util/Iterator;");
    if ((*env)->ExceptionOccurred(env)) abort();

    jclass classIterator =
      acquireClass(env, "java/util/Iterator", &globalRefIterator);
    iteratorHasNext = (*env)->GetMethodID
      (env, classIterator, "hasNext", "()Z");
    iteratorNext = (*env)->GetMethodID
      (env, classIterator, "next", "()Ljava/lang/Object;");
    if ((*env)->ExceptionOccurred(env)) abort();

    jclass classMapEntry =
      acquireClass(env, "java/util/Map$Entry", &globalRefMapEntry);
    entryGetKey = (*env)->GetMethodID
      (env, classMapEntry, "getKey", "()Ljava/lang/Object;");
    entryGetValue = (*env)->GetMethodID
      (env, classMapEntry, "getValue", "()Ljava/lang/Object;");
    if ((*env)->ExceptionOccurred(env)) abort();
  }

  ++classRefCount;

  // Store the C struct into the Java object.
  (*env)->SetLongField(env, thiz, runningHackField, (jlong)(intptr_t)rh);

  // TODO: Sort the list so binary search works.
  const char *hack =(*env)->GetStringUTFChars(env, jhack, NULL);

  int chosen = 0;
  for (;;) {
    if (!chosen == countof(function_table)) {
      Log ("Hack not found: %s", hack);
      abort();
    }
    if (!strcmp(function_table[chosen].progname, hack))
      break;
    chosen++;
  }

  (*env)->ReleaseStringUTFChars(env, jhack, hack);

  doinit (thiz, rh, env, &function_table[chosen], api, defaults, w, h);

  pthread_mutex_unlock(&mutg);
}


JNIEXPORT void JNICALL
Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz,
                                              jint w, jint h, jdouble rot)
{
  pthread_mutex_lock(&mutg);
  if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.

  current_rotation = rot;

  Log ("native rotation: %f", current_rotation);

  struct running_hack *rh = getRunningHack(env, thiz);

  Window wnd = rh->window;
  wnd->frame.x = 0;
  wnd->frame.y = 0;
  wnd->frame.width  = w;
  wnd->frame.height = h;

  glViewport (0, 0, w, h);

  if (ignore_rotation_p(rh->dpy) &&
      rot != 0 && rot != 180 && rot != -180) {
    int swap = w;
    w = h;
    h = swap;
    wnd->frame.width  = w;
    wnd->frame.height = h;
  }

  jwxyz_window_resized (rh->dpy);
  if (rh->initted_p)
    rh->xsft->reshape_cb (rh->dpy, rh->window, rh->closure,
                          wnd->frame.width, wnd->frame.height);

  if (rh->api == API_GL) {
    glMatrixMode (GL_PROJECTION);
    glRotatef (-rot, 0, 0, 1);
    glMatrixMode (GL_MODELVIEW);
  }

 END:
  pthread_mutex_unlock(&mutg);
}


JNIEXPORT void JNICALL
Java_org_jwz_xscreensaver_jwxyz_nativeRender (JNIEnv *env, jobject thiz)
{
  pthread_mutex_lock(&mutg);
  struct running_hack *rh = getRunningHack(env, thiz);
  drawXScreenSaver(env, rh);
  pthread_mutex_unlock(&mutg);
}


// TODO: Check Java side is calling this properly
JNIEXPORT void JNICALL
Java_org_jwz_xscreensaver_jwxyz_nativeDone (JNIEnv *env, jobject thiz)
{
  pthread_mutex_lock(&mutg);
  if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.

  struct running_hack *rh = getRunningHack(env, thiz);

  prepare_context (rh);

  if (rh->initted_p)
    rh->xsft->free_cb (rh->dpy, rh->window, rh->closure);
  jwxyz_free_display(rh->dpy);

  free(rh);
  (*env)->SetLongField(env, thiz, runningHackField, 0);

  --classRefCount;
  if (!classRefCount) {
    (*env)->DeleteGlobalRef(env, globalRefjwxyz);
    (*env)->DeleteGlobalRef(env, globalRefIterable);
    (*env)->DeleteGlobalRef(env, globalRefIterator);
    (*env)->DeleteGlobalRef(env, globalRefMapEntry);
  }

 END:
  pthread_mutex_unlock(&mutg);
}


static int
send_event (struct running_hack *rh, XEvent *e)
{
  // Assumes mutex is locked and context is prepared

  int *xP = 0, *yP = 0;
  switch (e->xany.type) {
  case ButtonPress: case ButtonRelease:
    xP = &e->xbutton.x;
    yP = &e->xbutton.y;
    break;
  case MotionNotify:
    xP = &e->xmotion.x;
    yP = &e->xmotion.y;
    break;
  }

  // Rotate the coordinates in the events to match the pixels.
  if (xP) {
    if (ignore_rotation_p (rh->dpy)) {
      Window win = XRootWindow (rh->dpy, 0);
      int w = win->frame.width;
      int h = win->frame.height;
      int swap;
      switch ((int) current_rotation) {
      case 180: case -180:				// #### untested
        *xP = w - *xP;
        *yP = h - *yP;
        break;
      case 90: case -270:
        swap = *xP; *xP = *yP; *yP = swap;
        *yP = h - *yP;
        break;
      case -90: case 270:				// #### untested
        swap = *xP; *xP = *yP; *yP = swap;
        *xP = w - *xP;
        break;
      }
    }

    rh->window->window.last_mouse_x = *xP;
    rh->window->window.last_mouse_y = *yP;
  }

  return (rh->xsft->event_cb
          ? rh->xsft->event_cb (rh->dpy, rh->window, rh->closure, e)
          : 0);
}


static void
send_queued_events (struct running_hack *rh)
{
  struct event_queue *event, *next;
  if (! rh->event_queue) return;
  for (event = rh->event_queue, next = event->next;
       event;
       event = next, next = (event ? event->next : 0)) {
    if (! send_event (rh, &event->event)) {
      // #### flash the screen or something
    }
    free (event);
  }
  rh->event_queue = 0;
}


static void
queue_event (JNIEnv *env, jobject thiz, XEvent *e)
{
  pthread_mutex_lock (&mutg);
  struct running_hack *rh = getRunningHack (env, thiz);
  struct event_queue *q = (struct event_queue *) malloc (sizeof(*q));
  memcpy (&q->event, e, sizeof(*e));
  q->next = 0;

  // Put it at the end.
  struct event_queue *oq;
  for (oq = rh->event_queue; oq && oq->next; oq = oq->next)
    ;
  if (oq)
    oq->next = q;
  else
    rh->event_queue = q;

  pthread_mutex_unlock (&mutg);
}


JNIEXPORT void JNICALL
Java_org_jwz_xscreensaver_jwxyz_sendButtonEvent (JNIEnv *env, jobject thiz,
                                                 int x, int y, jboolean down)
{
  XEvent e;
  memset (&e, 0, sizeof(e));
  e.xany.type = (down ? ButtonPress : ButtonRelease);
  e.xbutton.button = Button1;
  e.xbutton.x = x;
  e.xbutton.y = y;
  queue_event (env, thiz, &e);
}

JNIEXPORT void JNICALL
Java_org_jwz_xscreensaver_jwxyz_sendMotionEvent (JNIEnv *env, jobject thiz,
                                                 int x, int y)
{
  XEvent e;
  memset (&e, 0, sizeof(e));
  e.xany.type = MotionNotify;
  e.xmotion.x = x;
  e.xmotion.y = y;
  queue_event (env, thiz, &e);
}

JNIEXPORT void JNICALL
Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent (JNIEnv *env, jobject thiz,
                                              jboolean down_p, 
                                              int code, int mods)
{
  XEvent e;
  memset (&e, 0, sizeof(e));
  e.xkey.keycode = code;
  e.xkey.state = code;
  e.xany.type = (down_p ? KeyPress : KeyRelease);
  queue_event (env, thiz, &e);
  e.xany.type = KeyRelease;
  queue_event (env, thiz, &e);
}



/***************************************************************************
  Backend functions for jwxyz-gl.c
 */

void
prepare_context (struct running_hack *rh)
{
  Window w = rh->window;
  eglMakeCurrent (rh->egl_display, w->egl_surface, w->egl_surface,
                  rh->egl_window_ctx);
  rh->current_drawable = rh->window;
}

void
jwxyz_bind_drawable (Display *dpy, Window w, Drawable d)
{
  struct running_hack *rh = w->window.rh;
  JNIEnv *env = w->window.rh->jni_env;
  if ((*env)->ExceptionOccurred(env)) abort();
  if (rh->current_drawable != d) {
    EGLContext ctx = d == w ? rh->egl_window_ctx : rh->egl_xlib_ctx;
    eglMakeCurrent (rh->egl_display, d->egl_surface, d->egl_surface, ctx);
    // Log("%p %p %p %p %p", rh->egl_display, d->egl_surface, ctx, w, d);
    rh->current_drawable = d;
    jwxyz_assert_gl ();

    if (d != w) {
      glViewport (0, 0, d->frame.width, d->frame.height);
      jwxyz_set_matrices (dpy, d->frame.width, d->frame.height, False);
    }
  }
}


const XRectangle *
jwxyz_frame (Drawable d)
{
  return &d->frame;
}


unsigned int
jwxyz_drawable_depth (Drawable d)
{
  return (d->type == WINDOW
          ? visual_depth (NULL, NULL)
          : d->pixmap.depth);
}


void
jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
{
  xvpos->x = 0;
  xvpos->y = 0;

  if (xp) {
    xp->x = w->window.last_mouse_x;
    xp->y = w->window.last_mouse_y;
  }
}


static void
screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
{
  fps_compute (fpst, 0, -1);
  fps_draw (fpst);
}


void
jwxyz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
                 int src_x, int src_y, unsigned int width, unsigned int height,
                 int dst_x, int dst_y)
{
#if 0
  // Hilarious display corruption ahoy!
  jwxyz_gl_copy_area_copy_tex_image (dpy, src, dst, gc, src_x, src_y,
                                     width, height, dst_x, dst_y);
#else
  jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, src_x, src_y,
                                  width, height, dst_x, dst_y);
#endif
  jwxyz_assert_gl ();
}


void
jwxyz_assert_drawable (Window main_window, Drawable d)
{
  check_gl_error("jwxyz_assert_drawable");
}


void
jwxyz_assert_gl (void)
{
  check_gl_error("jwxyz_assert_gl");
}


Pixmap
XCreatePixmap (Display *dpy, Drawable d,
               unsigned int width, unsigned int height, unsigned int depth)
{
  // See also:
  // https://web.archive.org/web/20140213220709/http://blog.vlad1.com/2010/07/01/how-to-go-mad-while-trying-to-render-to-a-texture/
  // https://software.intel.com/en-us/articles/using-opengl-es-to-accelerate-apps-with-legacy-2d-guis
  // https://www.khronos.org/registry/egl/extensions/ANDROID/EGL_ANDROID_image_native_buffer.txt

  Window win = XRootWindow(dpy, 0);

  Pixmap p = malloc(sizeof(*p));
  p->type = PIXMAP;
  p->frame.x = 0;
  p->frame.y = 0;
  p->frame.width = width;
  p->frame.height = height;

  Assert(depth == 1 || depth == visual_depth(NULL, NULL),
         "XCreatePixmap: bad depth");
  p->pixmap.depth = depth;

  // Native EGL is Android 2.3/API 9. EGL in Java is available from API 1.
  struct running_hack *rh = win->window.rh;
  EGLint attribs[5];
  attribs[0] = EGL_WIDTH;
  attribs[1] = width;
  attribs[2] = EGL_HEIGHT;
  attribs[3] = height;
  attribs[4] = EGL_NONE;
  p->egl_surface = eglCreatePbufferSurface(rh->egl_display, rh->egl_config,
                                           attribs);
  Assert(p->egl_surface != EGL_NO_SURFACE,
         "XCreatePixmap: got EGL_NO_SURFACE");

  jwxyz_bind_drawable (dpy, win, p);
  glClearColor (frand(1), frand(1), frand(1), 0);
  glClear (GL_COLOR_BUFFER_BIT);

  return p;
}


int
XFreePixmap (Display *d, Pixmap p)
{
  struct running_hack *rh = XRootWindow(d, 0)->window.rh;
  if (rh->current_drawable == p)
    rh->current_drawable = NULL;
  eglDestroySurface(rh->egl_display, p->egl_surface);
  free (p);
  return 0;
}


double
current_device_rotation (void)
{
  return current_rotation;
}

Bool
ignore_rotation_p (Display *dpy)
{
  struct running_hack *rh = XRootWindow(dpy, 0)->window.rh;
  return rh->ignore_rotation_p;
}


char *
get_string_resource (Display *dpy, char *name, char *class)
{
  Window window = RootWindow (dpy, 0);
  JNIEnv *env = window->window.rh->jni_env;
  jobject obj = window->window.rh->jobject;

  if ((*env)->ExceptionOccurred(env)) abort();
  jstring jstr = (*env)->NewStringUTF (env, name);
  jclass     c = (*env)->GetObjectClass (env, obj);
  jmethodID  m = (*env)->GetMethodID (env, c, "getStringResource",
                           "(Ljava/lang/String;)Ljava/lang/String;");
  if ((*env)->ExceptionOccurred(env)) abort();

  jstring jvalue = (m
                  ? (*env)->CallObjectMethod (env, obj, m, jstr)
                  : NULL);
  (*env)->DeleteLocalRef (env, c);
  (*env)->DeleteLocalRef (env, jstr);
  char *ret = 0;
  if (jvalue) {
    const char *cvalue = (*env)->GetStringUTFChars (env, jvalue, 0);
    ret = strdup (cvalue);
    (*env)->ReleaseStringUTFChars (env, jvalue, cvalue);
  }

  Log("pref %s = %s", name, (ret ? ret : "(null)"));
  return ret;
}


/* Returns the contents of the URL. */
char *
textclient_mobile_url_string (Display *dpy, const char *url)
{
  Window window = RootWindow (dpy, 0);
  JNIEnv *env = window->window.rh->jni_env;
  jobject obj = window->window.rh->jobject;

  jstring jstr  = (*env)->NewStringUTF (env, url);
  jclass      c = (*env)->GetObjectClass (env, obj);
  jmethodID   m = (*env)->GetMethodID (env, c, "loadURL",
                            "(Ljava/lang/String;)Ljava/nio/ByteBuffer;");
  if ((*env)->ExceptionOccurred(env)) abort();
  jobject buf = (m
                 ? (*env)->CallObjectMethod (env, obj, m, jstr)
                 : NULL);
  (*env)->DeleteLocalRef (env, c);
  (*env)->DeleteLocalRef (env, jstr);

  char *body = (char *) (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
  char *body2;
  if (body) {
    int L = (*env)->GetDirectBufferCapacity (env, buf);
    body2 = malloc (L + 1);
    memcpy (body2, body, L);
    body2[L] = 0;
  } else {
    body2 = strdup ("ERROR");
  }

  if (buf)
    (*env)->DeleteLocalRef (env, buf);

  return body2;
}


void *
jwxyz_load_native_font (Display *dpy, const char *name,
                        char **native_name_ret, float *size_ret,
                        int *ascent_ret, int *descent_ret)
{
  Window window = RootWindow (dpy, 0);
  JNIEnv *env = window->window.rh->jni_env;
  jobject obj = window->window.rh->jobject;

  jstring jstr = (*env)->NewStringUTF (env, name);
  jclass     c = (*env)->GetObjectClass (env, obj);
  jmethodID  m = (*env)->GetMethodID (env, c, "loadFont",
                           "(Ljava/lang/String;)[Ljava/lang/Object;");
  if ((*env)->ExceptionOccurred(env)) abort();

  jobjectArray array = (m
                        ? (*env)->CallObjectMethod (env, obj, m, jstr)
                        : NULL);
  (*env)->DeleteLocalRef (env, c);
  (*env)->DeleteLocalRef (env, jstr);
  jstr = 0;

  if (array) {
    jobject font = (*env)->GetObjectArrayElement (env, array, 0);
    jobject name = (jstring) ((*env)->GetObjectArrayElement (env, array, 1));
    jobject size = (*env)->GetObjectArrayElement (env, array, 2);
    jobject asc  = (*env)->GetObjectArrayElement (env, array, 3);
    jobject desc = (*env)->GetObjectArrayElement (env, array, 4);
    if ((*env)->ExceptionOccurred(env)) abort();

    const char *cname = (*env)->GetStringUTFChars (env, name, 0);
    *native_name_ret = strdup (cname);
    (*env)->ReleaseStringUTFChars (env, name, cname);

    c = (*env)->GetObjectClass(env, font);
    m = (*env)->GetMethodID (env, c, "longValue", "()J");
    long font_id = (*env)->CallLongMethod (env, font, m);
    if ((*env)->ExceptionOccurred(env)) abort();

    c = (*env)->GetObjectClass(env, size);
    m = (*env)->GetMethodID (env, c, "floatValue", "()F");
    if ((*env)->ExceptionOccurred(env)) abort();

    *size_ret    =       (*env)->CallFloatMethod (env, size, m);
    *ascent_ret  = (int) (*env)->CallFloatMethod (env, asc,  m);
    *descent_ret = (int) (*env)->CallFloatMethod (env, desc, m);

    return (void *) font_id;
  } else {
    return 0;
  }
}


void
jwxyz_release_native_font (Display *dpy, void *native_font)
{
  Window window = RootWindow (dpy, 0);
  JNIEnv *env = window->window.rh->jni_env;
  jobject obj = window->window.rh->jobject;
  if ((*env)->ExceptionOccurred(env)) abort();
  jclass    c = (*env)->GetObjectClass (env, obj);
  jmethodID m = (*env)->GetMethodID (env, c, "releaseFont", "(J)V");
  (*env)->CallVoidMethod (env, obj, m, (jobject) native_font);
  if ((*env)->ExceptionOccurred(env)) abort();
}


/* If the local reference table fills up, use this to figure out where
   you missed a call to DeleteLocalRef. */
/*
static void dump_reference_tables(JNIEnv *env)
{
  jclass c = (*env)->FindClass(env, "dalvik/system/VMDebug");
  jmethodID m = (*env)->GetStaticMethodID (env, c, "dumpReferenceTables",
                                           "()V");
  (*env)->CallStaticVoidMethod (env, c, m);
  (*env)->DeleteLocalRef (env, c);
}
*/


// Returns the metrics of the multi-character, single-line UTF8 or Latin1
// string.  If pixmap_ret is provided, also renders the text.
//
void
jwxyz_render_text (Display *dpy, void *native_font,
                   const char *str, size_t len, int utf8,
                   XCharStruct *cs, char **pixmap_ret)
{
  Window window = RootWindow (dpy, 0);
  JNIEnv *env = window->window.rh->jni_env;
  jobject obj = window->window.rh->jobject;

  char *s2;

  if (utf8) {
    s2 = malloc (len + 1);
    memcpy (s2, str, len);
    s2[len] = 0;
  } else {	// Convert Latin1 to UTF8
    s2 = malloc (len * 2 + 1);
    unsigned char *s3 = (unsigned char *) s2;
    int i;
    for (i = 0; i < len; i++) {
      unsigned char c = ((unsigned char *) str)[i];
      if (! (c & 0x80)) {
        *s3++ = c;
      } else {
        *s3++ = (0xC0 | (0x03 & (c >> 6)));
        *s3++ = (0x80 | (0x3F & c));
      }
    }
    *s3 = 0;
  }

  jstring jstr  = (*env)->NewStringUTF (env, s2);
  jclass      c = (*env)->GetObjectClass (env, obj);
  jmethodID   m = (*env)->GetMethodID (env, c, "renderText",
                            "(JLjava/lang/String;Z)Ljava/nio/ByteBuffer;");
  if ((*env)->ExceptionOccurred(env)) abort();
  jobject buf =
    (m
     ? (*env)->CallObjectMethod (env, obj, m,
                                 (jlong) (long) native_font,
                                 jstr,
                                 (pixmap_ret ? JNI_TRUE : JNI_FALSE))
     : NULL);
  (*env)->DeleteLocalRef (env, c);
  (*env)->DeleteLocalRef (env, jstr);
  free (s2);

  if ((*env)->ExceptionOccurred(env)) abort();
  unsigned char *bits = (unsigned char *)
    (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
  if (bits) {
    int i = 0;
    int L = (*env)->GetDirectBufferCapacity (env, buf);
    if (L < 10) abort();
    cs->lbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
    cs->rbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
    cs->width    = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
    cs->ascent   = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
    cs->descent  = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;

    if (pixmap_ret) {
      char *pix = malloc (L - i);
      if (! pix) abort();
      memcpy (pix, bits + i, L - i);
      *pixmap_ret = pix;
    }
  } else {
    memset (cs, 0, sizeof(*cs));
    if (pixmap_ret)
      *pixmap_ret = 0;
  }

  if (buf)
    (*env)->DeleteLocalRef (env, buf);
}


/* Called from utils/grabclient.c */
char *
jwxyz_load_random_image (Display *dpy,
                         int *width_ret, int *height_ret,
                         char **name_ret)
{
  Window window = RootWindow (dpy, 0);
  struct running_hack *rh = window->window.rh;
  JNIEnv *env = rh->jni_env;
  jobject obj = rh->jobject;

  Bool images_p =
    get_boolean_resource (rh->dpy, "chooseRandomImages", "ChooseRandomImages");
  Bool grab_p =
    get_boolean_resource (rh->dpy, "grabDesktopImages", "GrabDesktopImages");
  Bool rotate_p =
    get_boolean_resource (rh->dpy, "rotateImages", "RotateImages");

  if (!images_p && !grab_p)
    return 0;

  if (grab_p && images_p) {
    grab_p = !(random() & 5);    /* if both, screenshot 1/5th of the time */
    images_p = !grab_p;
  }

  jclass      c = (*env)->GetObjectClass (env, obj);
  jmethodID   m = (*env)->GetMethodID (env, c, 
                                       (grab_p
                                        ? "getScreenshot"
                                        : "loadRandomImage"),
                                       "(IIZ)Ljava/nio/ByteBuffer;");
  if ((*env)->ExceptionOccurred(env)) abort();
  jobject buf = (m
                 ? (*env)->CallObjectMethod (env, obj, m,
                                             window->frame.width,
                                             window->frame.height,
                                             (rotate_p ? JNI_TRUE : JNI_FALSE))
                 : NULL);
  (*env)->DeleteLocalRef (env, c);

  unsigned char *bits = (unsigned char *)
    (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);

  if (bits) {
    int i = 0;
    int L = (*env)->GetDirectBufferCapacity (env, buf);
    if (L < 100) abort();
    int width  = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
    int height = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
    char *name = (char *) bits + i;
    int L2 = strlen (name);
    i += L2 + 1;
    if (width * height * 4 != L - i) abort();
    char *pix = malloc (L - i);
    if (! pix) abort();
    memcpy (pix, bits + i, L - i);
    *width_ret  = width;
    *height_ret = height;
    *name_ret   = strdup (name);
    return (char *) pix;
  }

  if (buf)
    (*env)->DeleteLocalRef (env, buf);

  return 0;
}

#endif /* HAVE_ANDROID */
