#include "config.h"

#ifdef USE_GRAPHIC
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifdef USE_SDL1
#include <SDL/SDL.h>
#endif
#ifdef USE_SDL2
#include <SDL2/SDL.h>
#endif

#ifdef USE_NLLIBC
#include <nllibc.h>
#endif

#include "const.h"
#include "nlllib.h"
#include "memory.h"
#include "key.h"
#include "mouse.h"
#include "graphic.h"
#include "sdl.h"
#include "image.h"
#include "picture.h"
#include "charset.h"

typedef enum {
  EVENTREQ_CODE_UNKNOWN = 0,
  EVENTREQ_CODE_SYNC,
  EVENTREQ_CODE_ENABLE,
  EVENTREQ_CODE_RESIZE,
  EVENTREQ_CODE_FLUSH,
  EVENTREQ_CODE_ALLFLUSH,
  EVENTREQ_CODE_MAINIMG,
  EVENTREQ_CODE_RANGE,
  EVENTREQ_CODE_OFFSET,
  EVENTREQ_CODE_SCALE,
  EVENTREQ_CODE_DOTOFFSET,
  EVENTREQ_CODE_DOTSIZE,
  EVENTREQ_CODE_COLOR,
  EVENTREQ_CODE_POSITION,
  EVENTREQ_CODE_KEYDOWN,
  EVENTREQ_CODE_KEYUP,
  EVENTREQ_CODE_MOUSEMOTION,
  EVENTREQ_CODE_MOUSEBUTTONDOWN,
  EVENTREQ_CODE_MOUSEBUTTONUP,
  EVENTREQ_CODE_CLEAR,
  EVENTREQ_CODE_SCROLL,
  EVENTREQ_CODE_DOT,
  EVENTREQ_CODE_LINE,
  EVENTREQ_CODE_BOX,
  EVENTREQ_CODE_CIRCLE,
  EVENTREQ_CODE_PAINT,
  EVENTREQ_CODE_COPY,
  EVENTREQ_CODE_ROTATE,
} eventreq_code_t;

struct eventreq_point {
  int x;
  int y;
};

typedef struct eventreq {
  struct eventreq *next;
  eventreq_code_t code;
  image_t image;
  image_draw_count_t count;
  int color;
  unsigned int flags;
  union {
    struct eventreq_point point;
    struct {
      int enable;
    } enable;
    struct {
      struct eventreq_point point;
      struct eventreq_point size;
      struct eventreq_point mul;
    } range;
    struct {
      struct eventreq_point mul;
      struct eventreq_point div;
    } scale;
    struct {
      int fg;
      int bg;
      int from;
      int to;
    } color;
    struct {
      key_code_t code;
    } key;
    struct {
      mouse_code_t code;
      int x;
      int y;
    } mouse;
    struct {
      struct eventreq_point point0;
      struct eventreq_point point1;
    } rect;
    struct {
      struct eventreq_point point;
      struct eventreq_point radius;
    } circle;
    struct {
      struct eventreq_point point;
      int border;
    } paint;
    struct {
      struct eventreq_point point;
      struct eventreq_point size;
      struct eventreq_point source;
      struct eventreq_point ssize;
      image_t simage;
    } copy;
    struct {
      struct eventreq_point point;
      struct eventreq_point size;
      struct eventreq_point center;
      int num;
    } rotate;
  } u;
} *eventreq_t;

#define USE_CCACHE

#ifdef USE_CCACHE
#define CCACHE_RECT_NUM  128
#define CCACHE_POINT_NUM 128
typedef struct ccache {
  int r;
  int g;
  int b;
  struct ccache_rects {
    int num;
#ifdef USE_SDL
    SDL_Rect rect[CCACHE_RECT_NUM];
#endif
  } rects;
#ifdef USE_SDL_RENDERER
  struct ccache_points {
    int num;
#ifdef USE_SDL
    SDL_Point point[CCACHE_POINT_NUM];
#endif
  } points;
#endif
} *ccache_t;
#endif

typedef struct graphic_context {
  unsigned int flags;
  int width;
  int height;

  volatile int active;
  volatile int discard;
  volatile int sync;
  volatile int disable;

#ifdef USE_SDL
  SDL_Thread *thread;
#endif

#ifdef USE_SDL2
  SDL_Window *window;
#endif

#ifdef USE_SDL
  SDL_Surface *surface;
#endif
#ifdef USE_SDL_RENDERER
  SDL_Renderer *renderer;
#endif

  image_t main_image;

#define KEY_BUFFER_NUM 1024
  struct {
    struct {
      key_code_t code;
    } buffer[KEY_BUFFER_NUM];
    int rindex;
    int windex;
    int status[KEY_CODE_NUM];
  } key;

#define MOUSE_BUFFER_NUM 256
  struct {
    int x;
    int y;

    struct {
      mouse_code_t code;
      int x;
      int y;
    } buffer[MOUSE_BUFFER_NUM];
    int rindex;
    int windex;
    int status[MOUSE_CODE_NUM];
  } mouse;

  struct {
    int event;
    eventreq_t head;
    eventreq_t tail;

#define REQ_BUFFER_NUM 256
    struct {
      volatile int send_index;
      volatile int recv_index;
      struct eventreq req[REQ_BUFFER_NUM];
    } buffer;
  } req;

#ifdef USE_CCACHE
#define PRIM_COLOR_NUM 8
#define COLOR_HASH_NUM 64
#define COLOR_ROUGH  16
#define COLOR_HROUGH 64
#define COLOR_ALIGN(c, s) (((c) < (s)) ? 0 : ((c) / (s) + 1) * (s) - 1)
  struct ccache prim_colors[PRIM_COLOR_NUM];
  struct ccache colors_hash[COLOR_HASH_NUM];
#endif
} *graphic_context_t;

static unsigned int color_hash_func(int r, int g, int b, unsigned int flags)
{
  unsigned int n;
  if (flags & NLL_G_HROUGH) {
    n = (r >> 2) ^ (g >> 4) ^ (b >> 6);
  } else if (flags & NLL_G_ROUGH) {
    n = (r >> 2) ^ (g >> 3) ^ (b >> 4);
  } else {
    n = r ^ g ^ b;
  }
  return n % COLOR_HASH_NUM;
}

static graphic_context_t context = NULL;

int graphic_color(int red, int green, int blue)
{
  red   = (red   < 0)    ? 0    : red;
  green = (green < 0)    ? 0    : green;
  blue  = (blue  < 0)    ? 0    : blue;
  red   = (red   > 0xff) ? 0xff : red;
  green = (green > 0xff) ? 0xff : green;
  blue  = (blue  > 0xff) ? 0xff : blue;
  return ((red & 0xff) << 16) | ((green & 0xff) << 8) | (blue & 0xff);
}

int graphic_rgb(int color, int *redp, int *greenp, int *bluep)
{
  if (redp)   *redp   = (color >> 16) & 0xff;
  if (greenp) *greenp = (color >>  8) & 0xff;
  if (bluep)  *bluep  = (color >>  0) & 0xff;
  return color;
}

static int graphic_color_align(int color, int *redp, int *greenp, int *bluep, unsigned int flags)
{
  int r, g, b;

  graphic_rgb(color, &r, &g, &b);

  if (flags & NLL_G_HROUGH) {
    r = COLOR_ALIGN(r, COLOR_HROUGH);
    g = COLOR_ALIGN(g, COLOR_HROUGH);
    b = COLOR_ALIGN(b, COLOR_HROUGH);
  } else if (flags & NLL_G_ROUGH) {
    r = COLOR_ALIGN(r, COLOR_ROUGH);
    g = COLOR_ALIGN(g, COLOR_ROUGH);
    b = COLOR_ALIGN(b, COLOR_ROUGH);
  }

  if (redp)   *redp   = r;
  if (greenp) *greenp = g;
  if (bluep)  *bluep  = b;

  return graphic_color(r, g, b);
}

int graphic_get_screen_flags(void)
{
  return (context && context->active) ? context->flags : NLL_G_DISABLE;
}

int graphic_get_screen_width(void)
{
  return context ? context->width : -1;
}

int graphic_get_screen_height(void)
{
  return context ? context->height : -1;
}

int graphic_get_screen_image(void)
{
  return context ? image_get_index(context->main_image) : -1;
}

int graphic_check(void)
{
  return 0;
}

int graphic_init(void)
{
#ifdef GRAPHIC_EXTERNAL_DRIVER
  ext_graphic_init();
#endif
  return graphic_setscreen(NLL_G_DISABLE, 0, 0, 0);
}

int graphic_done(void)
{
  int r;
  r = graphic_setscreen(NLL_G_DISABLE, 0, 0, 0);
#ifdef GRAPHIC_EXTERNAL_DRIVER
  ext_graphic_done();
#endif
  return r;
}

static int flush_clear(graphic_context_t context)
{
#ifdef USE_SDL
  SDL_Rect rect;

#ifdef USE_SDL_RENDERER
  if (context->flags & NLL_G_RENDERER) {
    SDL_SetRenderDrawColor(context->renderer, 0, 0, 0, 0);
    SDL_RenderClear(context->renderer);
    return 0;
  }
#endif

  rect.x = 0;
  rect.y = 0;
  rect.w = context->width;
  rect.h = context->height;
  SDL_FillRect(context->surface, &rect,
	       SDL_MapRGB(context->surface->format, 0, 0, 0));
#endif

  return 0;
}

#ifndef USE_CCACHE
static int flush_line(graphic_context_t context, int x, int y, int w,
		      int r, int g, int b)
{
#ifdef USE_SDL
  SDL_Rect rect;

  rect.x = x;
  rect.y = y;
  rect.w = w;
  rect.h = 1;

#ifdef USE_SDL_RENDERER
  if (context->flags & NLL_G_RENDERER) {
    SDL_SetRenderDrawColor(context->renderer, r, g, b, 255);
    if (w == 1)
      SDL_RenderDrawPoint(context->renderer, x, y);
    else
      SDL_RenderDrawRect(context->renderer, &rect);
    return 0;
  }
#endif

  SDL_FillRect(context->surface, &rect,
	       SDL_MapRGB(context->surface->format, r, g, b));
#endif

  return 0;
}
#else
static int flush_ccache_rects(graphic_context_t context, struct ccache_rects *rects,
			      int r, int g, int b)
{
#ifdef USE_SDL
  Uint32 color;
#ifdef USE_SDL1
  int i;
#endif

#ifdef USE_SDL_RENDERER
  if (context->flags & NLL_G_RENDERER) {
    SDL_SetRenderDrawColor(context->renderer, r, g, b, 255);
    SDL_RenderDrawRects(context->renderer, rects->rect, rects->num);
    rects->num = 0;
    return 0;
  }
#endif

  color = SDL_MapRGB(context->surface->format, r, g, b);

#ifdef USE_SDL1
  for (i = 0; i < rects->num; i++) {
    SDL_FillRect(context->surface, &rects->rect[i], color);
  }
#endif
#ifdef USE_SDL2
  SDL_FillRects(context->surface, rects->rect, rects->num, color);
#endif
#endif

  rects->num = 0;

  return 0;
}

static int set_ccache_rects(graphic_context_t context, struct ccache_rects *rects,
			    int x, int y, int w, int r, int g, int b)
{
#ifdef USE_SDL
  int n;
#endif

  if (rects->num == CCACHE_RECT_NUM)
    flush_ccache_rects(context, rects, r, g, b);

#ifdef USE_SDL
  n = rects->num;
  rects->rect[n].x = x;
  rects->rect[n].y = y;
  rects->rect[n].w = w;
  rects->rect[n].h = 1;
#endif
  rects->num++;

  return 0;
}

#ifdef USE_SDL_RENDERER
static int flush_ccache_points(graphic_context_t context, struct ccache_points *points,
			       int r, int g, int b)
{
  SDL_SetRenderDrawColor(context->renderer, r, g, b, 255);
  SDL_RenderDrawPoints(context->renderer, points->point, points->num);

  points->num = 0;

  return 0;
}

static int set_ccache_points(graphic_context_t context, struct ccache_points *points,
			     int x, int y, int r, int g, int b)
{
  int n;

  if (points->num == CCACHE_POINT_NUM)
    flush_ccache_points(context, points, r, g, b);

  n = points->num;
  points->point[n].x = x;
  points->point[n].y = y;
  points->num++;

  return 0;
}
#endif

static int flush_ccache(graphic_context_t context, struct ccache *ccache)
{
  int r, g, b;

  r = ccache->r;
  g = ccache->g;
  b = ccache->b;

  if (ccache->rects.num > 0)
    flush_ccache_rects(context, &ccache->rects, r, g, b);
#ifdef USE_SDL_RENDERER
  if (ccache->points.num > 0)
    flush_ccache_points(context, &ccache->points, r, g, b);
#endif

  return 0;
}

static int set_ccache(graphic_context_t context, struct ccache *ccache,
		      int x, int y, int w)
{
  int r, g, b;

  r = ccache->r;
  g = ccache->g;
  b = ccache->b;

#ifdef USE_SDL_RENDERER
  if (context->flags & NLL_G_RENDERER) {
    if (w == 1)
      return set_ccache_points(context, &ccache->points, x, y, r, g, b);
  }
#endif
  return set_ccache_rects(context, &ccache->rects, x, y, w, r, g, b);
}
#endif

static int flush_finish(graphic_context_t context)
{
#ifdef USE_SDL_RENDERER
  if (context->flags & NLL_G_RENDERER) {
    SDL_RenderPresent(context->renderer);
    return 0;
  }
#endif

#ifdef USE_SDL1
  SDL_Flip(context->surface);
#endif
#ifdef USE_SDL2
  SDL_UpdateWindowSurface(context->window);
#endif

  return 0;
}

static int _flush(graphic_context_t context, image_t image, image_draw_count_t count, unsigned int flags)
{
  int width, height, x, y, w, r, g, b, force = 0;
  int c, next;
#ifdef USE_CCACHE
  int i;
  ccache_t prim_color;
  ccache_t color_hash;
#endif

  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  width  = (context->width  < image->width)  ? context->width  : image->width;
  height = (context->height < image->height) ? context->height : image->height;

#ifdef USE_CCACHE
  for (i = 0; i < PRIM_COLOR_NUM; i++) {
    prim_color = &context->prim_colors[i];
    prim_color->r = (i & 4) ? 255 : 0;
    prim_color->g = (i & 2) ? 255 : 0;
    prim_color->b = (i & 1) ? 255 : 0;
    prim_color->rects.num = 0;
#ifdef USE_SDL_RENDERER
    prim_color->points.num = 0;
#endif
  }

  for (i = 0; i < COLOR_HASH_NUM; i++) {
    color_hash = &context->colors_hash[i];
    color_hash->r = 0;
    color_hash->g = 0;
    color_hash->b = 0;
    color_hash->rects.num = 0;
#ifdef USE_SDL_RENDERER
    color_hash->points.num = 0;
#endif
  }
#endif

  if ((flags | context->flags) & NLL_G_FORCE)
    force = 1;

  if (force)
    flush_clear(context);

  for (y = 0; y < height; y++) {
    if (!force && !image_is_draw_line(image, count, y))
      continue;

    c = next = -1;

    for (x = 0, w = 0; x + w <= width; w++) {
      if (x + w <= width - 1) {
	if (!force && (c < 0) && !((x + w) % image->draw.column_per)) {
	  if (!image_is_draw_column(image, count, x + w, y)) {
	    w += image->draw.column_per - 1;
	    continue;
	  }
	}
	if (!force && !image_is_draw_pixel(image, count, x + w, y))
	  next = -1;
	else
	  next = image_get_color(image, x + w, y);
	if (x + w == 0)
	  c = next;
	if (c == next)
	  continue;
      }

      if (!(force && c == 0) && (c >= 0)) {
	graphic_color_align(c, &r, &g, &b, flags | context->flags);

#ifdef USE_CCACHE
	if (((r == 0) || (r == 255)) && ((g == 0) || (g == 255)) &&
	    ((b == 0) || (b == 255))) {
	  i = 0;
	  if (r == 255) i += 4;
	  if (g == 255) i += 2;
	  if (b == 255) i += 1;
	  prim_color = &context->prim_colors[i];
	  set_ccache(context, prim_color, x, y, w);
	} else {
	  i = color_hash_func(r, g, b, flags | context->flags);
	  color_hash = &context->colors_hash[i];
	  if ((color_hash->r != r) || (color_hash->g != g) ||
	      (color_hash->b != b)) {
	    flush_ccache(context, color_hash);
	    color_hash->r = r;
	    color_hash->g = g;
	    color_hash->b = b;
	  }
	  set_ccache(context, color_hash, x, y, w);
	}
#else
	flush_line(context, x, y, w, r, g, b);
#endif
      }

      c = next;
      x += w;
      w = 0;
    }
  }

#ifdef USE_CCACHE
  for (i = 0; i < PRIM_COLOR_NUM; i++) {
    prim_color = &context->prim_colors[i];
    flush_ccache(context, prim_color);
  }

  for (i = 0; i < COLOR_HASH_NUM; i++) {
    color_hash = &context->colors_hash[i];
    flush_ccache(context, color_hash);
  }
#endif

  flush_finish(context);

  return 0;
}

static int flush(graphic_context_t context, image_t image, image_draw_count_t count, unsigned int flags)
{
  if ((count == 0) && !(context->flags & NLL_G_DIRECT))
    image_reset_draw(image);
  return _flush(context, image, count, flags);
}

static int allflush(graphic_context_t context, image_t image)
{
  return _flush(context, image, 0, NLL_G_FORCE);
}

static int proc_range(image_t image, int x, int y, int width, int height, int mulx, int muly)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  image->range.x = x;
  image->range.y = y;
  image->range.width  = width;
  image->range.height = height;
  image->range.mulx = mulx;
  image->range.muly = muly;

  return 0;
}

static int proc_offset(image_t image, int x, int y)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  image->offset.x = x;
  image->offset.y = y;

  return 0;
}

static int proc_scale(image_t image, int mulx, int muly, int divx, int divy)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  image->scale.mul.x = mulx;
  image->scale.mul.y = muly;
  image->scale.div.x = divx;
  image->scale.div.y = divy;

  return 0;
}

static int proc_dotoffset(image_t image, int x, int y)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  image->dot.x = x;
  image->dot.y = y;

  return 0;
}

static int proc_dotsize(image_t image, int width, int height)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  image->dot.width  = width;
  image->dot.height = height;

  return 0;
}

static int _color(image_t image, int color, unsigned int flags)
{
  if (color == NLL_G_DEFAULT)
    color = image->current.color;

  if (color == image->color.from)
    color = image->color.to;

  if (color < 0) {
    /* transparent */
  } else {
    if (flags & (NLL_G_HROUGH | NLL_G_ROUGH))
      color = graphic_color_align(color, NULL, NULL, NULL, flags);
  }

  return color;
}

static int proc_color(image_t image, int color, int fgcolor, int bgcolor, int fromcolor, int tocolor, unsigned int flags)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  if (color     != NLL_G_DEFAULT) image->current.color = color;
  if (fgcolor   != NLL_G_DEFAULT) image->color.fg      = fgcolor;
  if (bgcolor   != NLL_G_DEFAULT) image->color.bg      = bgcolor;
  if (fromcolor != NLL_G_DEFAULT) image->color.from    = fromcolor;
  if (tocolor   != NLL_G_DEFAULT) image->color.to      = tocolor;

  return 0;
}

static int currentpos(image_t image, int *x, int *y, unsigned int flags)
{
  *x = (*x == NLL_G_DEFAULT) ? image->current.x : *x;
  *y = (*y == NLL_G_DEFAULT) ? image->current.y : *y;
  if (!(flags & NLL_G_NUPDATE)) {
    image->current.x = *x;
    image->current.y = *y;
  }
  return 0;
}

static int proc_position(image_t image, int x, int y, unsigned int flags)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  if (flags & NLL_G_OFFSET) {
    x += image->current.x;
    y += image->current.y;
  }

  currentpos(image, &x, &y, flags);

  return 0;
}

static int scaling(image_t image, int *x, int *y)
{
  if (!image)
    return -1;
  if (image->scale.mul.x) *x *= image->scale.mul.x;
  if (image->scale.mul.y) *y *= image->scale.mul.y;
  if (image->scale.div.x) *x /= image->scale.div.x;
  if (image->scale.div.y) *y /= image->scale.div.y;
  return 0;
}

static int rscaling(image_t image, int *x, int *y)
{
  if (!image)
    return -1;
  if (image->scale.div.x) *x *= image->scale.div.x;
  if (image->scale.div.y) *y *= image->scale.div.y;
  if (image->scale.mul.x) *x /= image->scale.mul.x;
  if (image->scale.mul.y) *y /= image->scale.mul.y;
  return 0;
}

static int offseting(image_t image, int *x, int *y)
{
  if (!image)
    return -1;
  if (image->offset.x) *x += image->offset.x;
  if (image->offset.y) *y += image->offset.y;
  return 0;
}

static int roffseting(image_t image, int *x, int *y)
{
  if (!image)
    return -1;
  if (image->offset.x) *x -= image->offset.x;
  if (image->offset.y) *y -= image->offset.y;
  return 0;
}

static int dotoffset(image_t image, int *x, int *y)
{
  *x = image->dot.x;
  *y = image->dot.y;
  return 0;
}

static int dotsize(image_t image, int *width, int *height)
{
  *width  = image->dot.width  ? image->dot.width  : 1;
  *height = image->dot.height ? image->dot.height : 1;
  return 0;
}

static int inrange(image_t image, int *xp, int *yp, int *mxp, int *myp)
{
  int x, y, w, h, mx, my;

  if (!image)
    return -1;

  x = *xp; mx = image->range.mulx; mx = mx ? mx : 1;
  y = *yp; my = image->range.muly; my = my ? my : 1;

  if (image->range.width) {
    w = image->range.width;
    if (w < 0) w += (image->width - image->range.x);
    if (x > w - 1)
      return -1;
  }
  if (image->range.height) {
    h = image->range.height;
    if (h < 0) h += (image->height - image->range.y);
    if (y > h - 1)
      return -1;
  }

  if (image->range.x) {
    if (x < 0)
      return -1;
    x = image->range.x + x * mx;
  }

  if (image->range.y) {
    if (y < 0)
      return -1;
    y = image->range.y + y * my;
  }

  *xp = x; *mxp = mx;
  *yp = y; *myp = my;

  return 0;
}

static int outrange(image_t image, int *xp, int *yp)
{
  int x, y, mx, my;

  if (!image)
    return -1;

  x = *xp; mx = image->range.mulx; mx = mx ? mx : 1;
  y = *yp; my = image->range.muly; my = my ? my : 1;

  if (image->range.x) x = (x - image->range.x) / mx;
  if (image->range.y) y = (y - image->range.y) / my;

  *xp = x;
  *yp = y;

  return 0;
}

static int _pget(image_t image, int x, int y)
{
  return image_get_color(image, x, y);
}

static int rpget(image_t image, int x, int y)
{
  int mx, my, sx = 0, sy = 0;

  if (inrange(image, &x, &y, &mx, &my) < 0)
    return -1;

  if (mx < 0) { sx = -1; }
  if (my < 0) { sy = -1; }

  return _pget(image, x + sx, y + sy);
}

static int pget(image_t image, int x, int y)
{
  int dotx, doty, dotw, doth;

  dotoffset(image, &dotx, &doty);
  dotsize(image, &dotw, &doth);

  if (dotw < 0) { x += dotw; dotw = -dotw; }
  if (doth < 0) { y += doth; doth = -doth; }
  x += dotx;
  y += doty;

  return rpget(image, x, y);
}

static int proc_pget(image_t image, int x, int y)
{
  scaling(image, &x, &y);
  offseting(image, &x, &y);
  return pget(image, x, y);
}

static int _pset(image_t image, image_draw_count_t count,
		 int x, int y, int color, unsigned int flags)
{
  if (image_set_color(image, x, y, color, flags) < 0)
    return -1;

  image_set_draw_line(image, count, y);
  image_set_draw_column(image, count, x, y);
  image_set_draw_pixel(image, count, x, y);

  return 0;
}

static int rpset(image_t image, image_draw_count_t count,
		 int x, int y, int color, unsigned int flags)
{
  int mx, my, x0, y0, cx, cy;
  int sx = 0, sy = 0, dx = 1, dy = 1, px, py;

  if (inrange(image, &x, &y, &mx, &my) < 0)
    return -1;

  cx = mx; if (mx < 0) { cx = -cx; dx = -dx; sx = dx; }
  cy = my; if (my < 0) { cy = -cy; dy = -dy; sy = dy; }

  for (y0 = 0, py = sy; y0 < cy; y0++, py += dy) {
    for (x0 = 0, px = sx; x0 < cx; x0++, px += dx) {
      _pset(image, count, x + px, y + py, color, flags);
    }
  }

  return 0;
}

static int pset(image_t image, image_draw_count_t count,
		int x, int y, int color, unsigned int flags)
{
  int dotx, doty, dotw, doth;
  int x0, y0;

  if (color < 0) /* transparent */
    return 0;

  dotoffset(image, &dotx, &doty);
  dotsize(image, &dotw, &doth);

  if (dotw < 0) { x += dotw; dotw = -dotw; }
  if (doth < 0) { y += doth; doth = -doth; }
  x += dotx;
  y += doty;

  for (x0 = 0; x0 < dotw; x0++) {
    for (y0 = 0; y0 < doth; y0++) {
      if (rpset(image, count, x + x0, y + y0, color, flags) < 0)
	return -1;
    }
  }

  return 0;
}

static int _pcget(image_t image, int x, int y)
{
  return image_copy_get_color(image, x, y);
}

static int rpcget(image_t image, int x, int y)
{
  int mx, my, sx = 0, sy = 0;

  if (inrange(image, &x, &y, &mx, &my) < 0)
    return -1;

  if (mx < 0) { sx = -1; }
  if (my < 0) { sy = -1; }

  return _pcget(image, x + sx, y + sy);
}

static int pcget(image_t image, int x, int y)
{
  int dotx, doty, dotw, doth;

  dotoffset(image, &dotx, &doty);
  dotsize(image, &dotw, &doth);

  if (dotw < 0) { x += dotw; dotw = -dotw; }
  if (doth < 0) { y += doth; doth = -doth; }
  x += dotx;
  y += doty;

  return rpcget(image, x, y);
}

static int _pcsave(image_t image, int x, int y)
{
  return image_copy_save_color(image, x, y);
}

static int rpcsave(image_t image, int x, int y)
{
  int mx, my, x0, y0, cx, cy;
  int sx = 0, sy = 0, dx = 1, dy = 1, px, py;

  if (inrange(image, &x, &y, &mx, &my) < 0)
    return -1;

  cx = mx; if (mx < 0) { cx = -cx; dx = -dx; sx = dx; }
  cy = my; if (my < 0) { cy = -cy; dy = -dy; sy = dy; }

  for (y0 = 0, py = sy; y0 < cy; y0++, py += dy) {
    for (x0 = 0, px = sx; x0 < cx; x0++, px += dx) {
      _pcsave(image, x + px, y + py);
    }
  }

  return 0;
}

static int pcsave(image_t image, int x, int y)
{
  int dotx, doty, dotw, doth;

  dotoffset(image, &dotx, &doty);
  dotsize(image, &dotw, &doth);

  if (dotw < 0) { x += dotw; dotw = -dotw; }
  if (doth < 0) { y += doth; doth = -doth; }
  x += dotx;
  y += doty;

  return rpcsave(image, x, y);
}

static int _clear(image_t image, image_draw_count_t count, int color, unsigned int flags)
{
  int x, y;

  for (y = 0; y < image->height; y++) {
    for (x = 0; x < image->width; x++) {
      rpset(image, count, x, y, color, flags);
    }
  }

  return 0;
}

static int proc_clear(image_t image, image_draw_count_t count, int color, unsigned int flags)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  color = _color(image, color, flags);
  image->current.color = color;

  return _clear(image, count, color, flags);
}

static int _scroll(image_t image, image_draw_count_t count,
		   int x, int y, int color, unsigned int flags)
{
  int dx, dy, sx, sy, c;

  for (dy = 0; dy < image->height; dy++) {
    for (dx = 0; dx < image->width; dx++) {
      sx = (x < 0) ? image->width  - dx - 1 : dx;
      sy = (y < 0) ? image->height - dy - 1 : dy;
      if ((x + sx < 0) || (x + sx > image->width  - 1) ||
	  (y + sy < 0) || (y + sy > image->height - 1))
	c = color;
      else
	c = rpget(image, x + sx, y + sy);
      rpset(image, count, sx, sy, c, flags);
    }
  }

  return 0;
}

static int proc_scroll(image_t image, image_draw_count_t count,
		       int x, int y, int color, unsigned int flags)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  color = _color(image, color, flags);
  image->current.color = color;

  scaling(image, &x, &y);

  return _scroll(image, count, x, y, color, flags);
}

static int _dot(image_t image, image_draw_count_t count,
		int x, int y, int color, unsigned int flags)
{
  return pset(image, count, x, y, color, flags);
}

static int proc_dot(image_t image, image_draw_count_t count,
		    int x, int y, int color, unsigned int flags)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  color = _color(image, color, flags);
  image->current.color = color;

  currentpos(image, &x, &y, flags);
  scaling(image, &x, &y);
  offseting(image, &x, &y);

  return _dot(image, count, x, y, color, flags);
}

static int _line(image_t image, image_draw_count_t count,
		 int x0, int y0, int x1, int y1,
		 int color, unsigned int flags)
{
  int nx, ny, n, d, dx, dy;

  nx = (x0 < x1) ? x1 - x0 : x0 - x1;
  ny = (y0 < y1) ? y1 - y0 : y0 - y1;

  n = (nx > ny) ? nx : ny;

  for (d = 0; d <= n; d++) {
    if (!n) {
      dx = x0;
      dy = y0;
    } else {
      if (nx > ny) {
	dx = (x0 < x1) ? (x0 + d) : (x0 - d);
	dy = (y0 < y1) ? (y0 + d * ny / n) : (y0 - d * ny / n);
      } else {
	dx = (x0 < x1) ? (x0 + d * nx / n) : (x0 - d * nx / n);
	dy = (y0 < y1) ? (y0 + d) : (y0 - d);
      }
    }
    _dot(image, count, dx, dy, color, flags);
  }

  return 0;
}

static int proc_line(image_t image, image_draw_count_t count,
		     int x0, int y0, int x1, int y1, int color, unsigned int flags)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  color = _color(image, color, flags);
  image->current.color = color;

  currentpos(image, &x0, &y0, flags | NLL_G_NUPDATE);
  currentpos(image, &x1, &y1, flags);
  scaling(image, &x0, &y0); offseting(image, &x0, &y0);
  scaling(image, &x1, &y1); offseting(image, &x1, &y1);

  return _line(image, count, x0, y0, x1, y1, color, flags);
}

static int _box(image_t image, image_draw_count_t count,
		int x0, int y0, int x1, int y1,
		int color, unsigned int flags)
{
  int x, y;

  if (x0 > x1) { x = x0; x0 = x1; x1 = x; }
  if (y0 > y1) { y = y0; y0 = y1; y1 = y; }

  if (flags & NLL_G_FILL) {
    for (y = y0; y <= y1; y++) {
      for (x = x0; x <= x1; x++) {
	_dot(image, count, x, y, color, flags);
      }
    }
  } else {
    for (x = x0; x <= x1; x++)
      _dot(image, count, x, y0, color, flags);
    if (y0 < y1) {
      for (x = x0; x <= x1; x++)
	_dot(image, count, x, y1, color, flags);
    }
    if (y0 + 1 < y1) {
      for (y = y0 + 1; y <= y1 - 1; y++)
	_dot(image, count, x0, y, color, flags);
      if (x0 < x1) {
	for (y = y0 + 1; y <= y1 - 1; y++)
	  _dot(image, count, x1, y, color, flags);
      }
    }
  }

  return 0;
}

static int proc_box(image_t image, image_draw_count_t count,
		    int x0, int y0, int x1, int y1, int color, unsigned int flags)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  color = _color(image, color, flags);
  image->current.color = color;

  currentpos(image, &x0, &y0, flags | NLL_G_NUPDATE);
  currentpos(image, &x1, &y1, flags);
  scaling(image, &x0, &y0); offseting(image, &x0, &y0);
  scaling(image, &x1, &y1); offseting(image, &x1, &y1);

  return _box(image, count, x0, y0, x1, y1, color, flags);
}

#define SINNUM 64

static const int sin_val[SINNUM + 1] = {
  0,     245,  491,  736,  980, 1224, 1467, 1710,
  1951, 2191, 2430, 2667, 2903, 3137, 3369, 3599,
  3827, 4052, 4276, 4496, 4714, 4929, 5141, 5350,
  5556, 5758, 5957, 6152, 6344, 6532, 6716, 6895,
  7071, 7242, 7410, 7572, 7730, 7883, 8032, 8176,
  8315, 8449, 8577, 8701, 8819, 8932, 9040, 9142,
  9239, 9330, 9415, 9495, 9569, 9638, 9700, 9757,
  9808, 9853, 9892, 9925, 9952, 9973, 9988, 9997, 10000
};

static int _circle(image_t image, image_draw_count_t count,
		   int x, int y, int rx, int ry,
		   int color, unsigned int flags)
{
  int i, x0, y0, x1, y1, s0, s1, c0, c1, s, m;
  int nx, ny, n, d, dx, dy, bx, by, px[2], py[2];

  m = 0;
  if (flags & NLL_G_ODDPOS)
    m = 0;
  else if (flags & NLL_G_EVENPOS)
    m = 1;

  rx = (rx < 0) ? -rx : rx;
  ry = (ry < 0) ? -ry : ry;

  if (m) {
    if (!rx || !ry)
      return 0;
    rx--;
    ry--;
  }

  if      ((rx ==  0) && (ry ==  0)) s = SINNUM;
  else if ((rx <=  8) && (ry <=  8)) s = 8;
  else if ((rx <= 16) && (ry <= 16)) s = 4;
  else if ((rx <= 64) && (ry <= 64)) s = 2;
  else                               s = 1;

  bx = -1;
  by = -1;

  for (i = 0; i < SINNUM; i += s) {
    s0 = sin_val[i];
    c0 = sin_val[SINNUM - i];
    s1 = sin_val[i + s];
    c1 = sin_val[SINNUM - (i + s)];

    x0 = rx * c0 / 10000;
    y0 = ry * s0 / 10000;
    x1 = rx * c1 / 10000;
    y1 = ry * s1 / 10000;

    nx = (x0 < x1) ? x1 - x0 : x0 - x1;
    ny = (y0 < y1) ? y1 - y0 : y0 - y1;

    n = (nx > ny) ? nx : ny;

    for (d = 0; d <= n; d++) {
      if (!n) {
	dx = x0;
	dy = y0;
      } else {
	if (nx > ny) {
	  dx = (x0 < x1) ? (x0 + d) : (x0 - d);
	  dy = (y0 < y1) ? (y0 + d * ny / n) : (y0 - d * ny / n);
	} else {
	  dx = (x0 < x1) ? (x0 + d * nx / n) : (x0 - d * nx / n);
	  dy = (y0 < y1) ? (y0 + d) : (y0 - d);
	}
      }

      px[0] = x - dx - m;
      py[0] = y - dy - m;
      px[1] = x + dx;
      py[1] = y + dy;

      if (flags & NLL_G_FILL) {
	if (dy != by)
	  _line(image, count, px[0], py[1], px[1], py[1], color, flags);
	if ((dy != by) && (m || (dy > 0)))
	  _line(image, count, px[0], py[0], px[1], py[0], color, flags);
      } else {
	if ((dx != bx) || (dy != by)) {
	  _dot(image, count, px[1], py[1], color, flags);
	  if (m || (dy > 0))
	    _dot(image, count, px[1], py[0], color, flags);
	}
	if (((dx != bx) || (dy != by)) && (m || (dx > 0))) {
	  _dot(image, count, px[0], py[1], color, flags);
	  if (m || (dy > 0))
	    _dot(image, count, px[0], py[0], color, flags);
	}
      }

      bx = dx;
      by = dy;
    }
  }

  return 0;
}

static int proc_circle(image_t image, image_draw_count_t count,
		       int x, int y, int rx, int ry, int color, unsigned int flags)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  color = _color(image, color, flags);
  image->current.color = color;

  currentpos(image, &x, &y, flags);
  scaling(image, &x, &y); offseting(image, &x, &y);
  scaling(image, &rx, &ry);

  return _circle(image, count, x, y, rx, ry, color, flags);
}

static int _paint(image_t image, image_draw_count_t count,
		  int x, int y, int color, int border, unsigned int flags)
{
  int x0, x1, c;

  for (x0 = x; x0 >= 0; x0--) {
    c = rpget(image, x0, y);
    if ((c < 0) || (c == color))
      break;
    if (!(flags & NLL_G_FILL)) {
      if (c == border)
	break;
    } else {
      if (c != border)
	break;
    }
  }
  for (x1 = x; x1 < image->width; x1++) {
    c = rpget(image, x1, y);
    if ((c < 0) || (c == color))
      break;
    if (!(flags & NLL_G_FILL)) {
      if (c == border)
	break;
    } else {
      if (c != border)
	break;
    }
  }
  x0++;
  x1--;

  for (x = x0; x <= x1; x++)
    rpset(image, count, x, y, color, flags);

  for (x = x0; x <= x1; x++) {
    if (y > 0)
      _paint(image, count, x, y - 1, color, border, flags);
    if (y < image->height - 1)
      _paint(image, count, x, y + 1, color, border, flags);
  }

  return 0;
}

static int proc_paint(image_t image, image_draw_count_t count,
		      int x, int y, int color, int border, unsigned int flags)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  color = _color(image, color, flags);
  image->current.color = color;

  flags = (flags & ~NLL_G_SET_MASK) | NLL_G_SET;

  currentpos(image, &x, &y, flags);
  scaling(image, &x, &y);
  offseting(image, &x, &y);

  if (border == NLL_G_DEFAULT) {
    border = rpget(image, x, y);
    flags |= NLL_G_FILL;
  } else {
    border = _color(image, border, flags);
  }

  return _paint(image, count, x, y, color, border, flags);
}

static int _cmp(image_t image,
		int x0, int y0, int width0, int height0,
		int x1, int y1, int width1, int height1,
		image_t simage, unsigned int flags)
{
  int x, y, w, h;
  int dx, dy, w0, h0;
  int sx, sy, w1, h1;
  int color;

  if (width1  <= 0) width1  += (simage->width  - x1);
  if (height1 <= 0) height1 += (simage->height - y1);
  if (!width1 ) width1  = simage->width;
  if (!height1) height1 = simage->height;

  width0  = width0  ? width0  : width1;
  height0 = height0 ? height0 : height1;

  w0 = width0;
  h0 = height0;
  w1 = width1;
  h1 = height1;

  if (width0  < 0) { w0 = -w0; x0 += w0; }
  if (height0 < 0) { h0 = -h0; y0 += h0; }
  if (width1  < 0) { w1 = -w1; x1 += w1; }
  if (height1 < 0) { h1 = -h1; y1 += h1; }

  /* dot size zero is auto scale */
  w = image->dot.width  ? w1 : w0;
  h = image->dot.height ? h1 : h0;

  for (y = 0; y < h; y++) {
    sy = (height1 < 0) ? -(y+1) : y;
    dy = (height0 < 0) ? -(y+1) : y;
    sy =  image->dot.height ? sy : (sy * h1) / h0;
    dy = !image->dot.height ? dy : (dy * h0) / h1;
    for (x = 0; x < w; x++) {
      sx = (width1 < 0) ? -(x+1) : x;
      dx = (width0 < 0) ? -(x+1) : x;
      sx =  image->dot.width ? sx : (sx * w1) / w0;
      dx = !image->dot.width ? dx : (dx * w0) / w1;
      color = pget(simage, x1 + sx, y1 + sy);
      if (color != pget(image, x0 + dx, y0 + dy))
	return 1;
    }
  }

  return 0;
}

static int proc_cmp(image_t image,
		    int x0, int y0, int width0, int height0,
		    int x1, int y1, int width1, int height1,
		    image_t simage, unsigned int flags)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  if (!simage || simage->status != IMAGE_STATUS_ACTIVE)
    return 0;

  currentpos( image, &x0, &y0, flags);
  currentpos(simage, &x1, &y1, flags);
  scaling( image, &x0, &y0); offseting( image, &x0, &y0);
  scaling(simage, &x1, &y1); offseting(simage, &x1, &y1);
  scaling( image, &width0, &height0);
  scaling(simage, &width1, &height1);

  return _cmp(image, x0, y0, width0, height0,
	      x1, y1, width1, height1, simage, flags);
}

static int _copy(image_t image, image_draw_count_t count,
		 int x0, int y0, int width0, int height0,
		 int x1, int y1, int width1, int height1,
		 image_t simage, unsigned int flags)
{
  int x, y, w, h;
  int dx, dy, w0, h0;
  int sx, sy, w1, h1;
  int color;

  if (width1  <= 0) width1  += (simage->width  - x1);
  if (height1 <= 0) height1 += (simage->height - y1);
  if (!width1 ) width1  = simage->width;
  if (!height1) height1 = simage->height;

  width0  = width0  ? width0  : width1;
  height0 = height0 ? height0 : height1;

  w0 = width0;
  h0 = height0;
  w1 = width1;
  h1 = height1;

  if (width0  < 0) { w0 = -w0; x0 += w0; }
  if (height0 < 0) { h0 = -h0; y0 += h0; }
  if (width1  < 0) { w1 = -w1; x1 += w1; }
  if (height1 < 0) { h1 = -h1; y1 += h1; }

  /* dot size zero is auto scale */
  w = image->dot.width  ? w1 : w0;
  h = image->dot.height ? h1 : h0;

  for (y = 0; y < h; y++) {
    sy = (height1 < 0) ? -(y+1) : y;
    sy =  image->dot.height ? sy : (sy * h1) / h0;
    for (x = 0; x < w; x++) {
      sx = (width1 < 0) ? -(x+1) : x;
      sx =  image->dot.width ? sx : (sx * w1) / w0;
      pcsave(simage, x1 + sx, y1 + sy);
    }
  }

  for (y = 0; y < h; y++) {
    sy = (height1 < 0) ? -(y+1) : y;
    dy = (height0 < 0) ? -(y+1) : y;
    sy =  image->dot.height ? sy : (sy * h1) / h0;
    dy = !image->dot.height ? dy : (dy * h0) / h1;
    for (x = 0; x < w; x++) {
      sx = (width1 < 0) ? -(x+1) : x;
      dx = (width0 < 0) ? -(x+1) : x;
      sx =  image->dot.width ? sx : (sx * w1) / w0;
      dx = !image->dot.width ? dx : (dx * w0) / w1;
      color = pcget(simage, x1 + sx, y1 + sy);
      color = _color(image, color, flags);
      pset(image, count, x0 + dx, y0 + dy, color, flags);
    }
  }

  return 0;
}

static int proc_copy(image_t image, image_draw_count_t count,
		     int x0, int y0, int width0, int height0,
		     int x1, int y1, int width1, int height1,
		     image_t simage, unsigned int flags)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  if (!simage || simage->status != IMAGE_STATUS_ACTIVE)
    return 0;

  currentpos( image, &x0, &y0, flags);
  currentpos(simage, &x1, &y1, flags);
  scaling( image, &x0, &y0); offseting( image, &x0, &y0);
  scaling(simage, &x1, &y1); offseting(simage, &x1, &y1);
  scaling( image, &width0, &height0);
  scaling(simage, &width1, &height1);

  return _copy(image, count, x0, y0, width0, height0,
	       x1, y1, width1, height1, simage, flags);
}

static int _rotate(image_t image, image_draw_count_t count,
		   int x, int y, int width, int height, int cx, int cy,
		   int num, int color, unsigned int flags)
{
  int sx, sy, dx, dy, m;
  int px[4], py[4];
  int c;

  m = 1;
  if (flags & NLL_G_ODDPOS)
    m = 0;
  else if (flags & NLL_G_EVENPOS)
    m = 1;

  if (width  <= 0) width  += (image->width  - x);
  if (height <= 0) height += (image->height - y);

  num = num % 4;
  if (num < 0) num += 4;

  for (sy = 0; sy < height; sy++) {
    for (sx = 0; sx < width; sx++) {
      pcsave(image, x + sx, y + sy);
      pset(image, count, x + sx, y + sy, color, flags);
    }
  }

  for (sy = 0; sy < height; sy++) {
    for (sx = 0; sx < width; sx++) {
      dx = sx - cx;
      dy = sy - cy;
      px[0] = cx + dx;     py[0] = cy + dy;
      px[1] = cx - dy - m; py[1] = cy + dx;
      px[2] = cx - dx - m; py[2] = cy - dy - m;
      px[3] = cx + dy;     py[3] = cy - dx - m;
      c = pcget(image, x + px[0], y + py[0]);
      if (c < 0)
	c = color;
      pset(image, count, x + px[num], y + py[num], c, flags);
    }
  }

  return 0;
}

static int proc_rotate(image_t image, image_draw_count_t count,
		       int x, int y, int width, int height, int cx, int cy,
		       int num, int color, unsigned int flags)
{
  if (!image || image->status != IMAGE_STATUS_ACTIVE)
    return 0;

  color = _color(image, color, flags);
  image->current.color = color;

  currentpos(image, &x, &y, flags);
  scaling(image, &x, &y); offseting(image, &x, &y);
  scaling(image, &width, &height);
  scaling(image, &cx, &cy);

  return _rotate(image, count, x, y, width, height, cx, cy, num, color, flags);
}

#ifdef USE_SDL
static key_code_t sdl_keysym_to_keycode(int sym)
{
  key_code_t code = KEY_CODE_NONE;

  switch (sym) {
  case SDLK_RETURN:       code = KEY_CODE_ENTER;        break;
  case SDLK_ESCAPE:       code = KEY_CODE_ESCAPE;       break;
  case SDLK_BACKSPACE:    code = KEY_CODE_BACKSPACE;    break;
  case SDLK_TAB:          code = KEY_CODE_TAB;          break;
  case SDLK_SPACE:        code = KEY_CODE_SPACE;        break;
  case SDLK_EXCLAIM:      code = KEY_CODE_EXCLAIM;      break;
  case SDLK_QUOTEDBL:     code = KEY_CODE_QUOTEDBL;     break;
  case SDLK_HASH:         code = KEY_CODE_HASH;         break;
#ifdef SDLK_PERCENT
  case SDLK_PERCENT:      code = KEY_CODE_PERCENT;      break;
#endif
  case SDLK_DOLLAR:       code = KEY_CODE_DOLLAR;       break;
  case SDLK_AMPERSAND:    code = KEY_CODE_AMPERSAND;    break;
  case SDLK_QUOTE:        code = KEY_CODE_QUOTE;        break;
  case SDLK_LEFTPAREN:    code = KEY_CODE_LEFTPAREN;    break;
  case SDLK_RIGHTPAREN:   code = KEY_CODE_RIGHTPAREN;   break;
  case SDLK_ASTERISK:     code = KEY_CODE_ASTERISK;     break;
  case SDLK_PLUS:         code = KEY_CODE_PLUS;         break;
  case SDLK_COMMA:        code = KEY_CODE_COMMA;        break;
  case SDLK_MINUS:        code = KEY_CODE_MINUS;        break;
  case SDLK_PERIOD:       code = KEY_CODE_PERIOD;       break;
  case SDLK_SLASH:        code = KEY_CODE_SLASH;        break;
  case SDLK_0:            code = KEY_CODE_0;            break;
  case SDLK_1:            code = KEY_CODE_1;            break;
  case SDLK_2:            code = KEY_CODE_2;            break;
  case SDLK_3:            code = KEY_CODE_3;            break;
  case SDLK_4:            code = KEY_CODE_4;            break;
  case SDLK_5:            code = KEY_CODE_5;            break;
  case SDLK_6:            code = KEY_CODE_6;            break;
  case SDLK_7:            code = KEY_CODE_7;            break;
  case SDLK_8:            code = KEY_CODE_8;            break;
  case SDLK_9:            code = KEY_CODE_9;            break;
  case SDLK_COLON:        code = KEY_CODE_COLON;        break;
  case SDLK_SEMICOLON:    code = KEY_CODE_SEMICOLON;    break;
  case SDLK_LESS:         code = KEY_CODE_LESS;         break;
  case SDLK_EQUALS:       code = KEY_CODE_EQUALS;       break;
  case SDLK_GREATER:      code = KEY_CODE_GREATER;      break;
  case SDLK_QUESTION:     code = KEY_CODE_QUESTION;     break;
  case SDLK_AT:           code = KEY_CODE_AT;           break;

  case SDLK_LEFTBRACKET:  code = KEY_CODE_LEFTBRACKET;  break;
  case SDLK_BACKSLASH:    code = KEY_CODE_BACKSLASH;    break;
  case SDLK_RIGHTBRACKET: code = KEY_CODE_RIGHTBRACKET; break;
  case SDLK_CARET:        code = KEY_CODE_CARET;        break;
  case SDLK_UNDERSCORE:   code = KEY_CODE_UNDERSCORE;   break;
  case SDLK_BACKQUOTE:    code = KEY_CODE_BACKQUOTE;    break;
  case SDLK_a:            code = KEY_CODE_A;            break;
  case SDLK_b:            code = KEY_CODE_B;            break;
  case SDLK_c:            code = KEY_CODE_C;            break;
  case SDLK_d:            code = KEY_CODE_D;            break;
  case SDLK_e:            code = KEY_CODE_E;            break;
  case SDLK_f:            code = KEY_CODE_F;            break;
  case SDLK_g:            code = KEY_CODE_G;            break;
  case SDLK_h:            code = KEY_CODE_H;            break;
  case SDLK_i:            code = KEY_CODE_I;            break;
  case SDLK_j:            code = KEY_CODE_J;            break;
  case SDLK_k:            code = KEY_CODE_K;            break;
  case SDLK_l:            code = KEY_CODE_L;            break;
  case SDLK_m:            code = KEY_CODE_M;            break;
  case SDLK_n:            code = KEY_CODE_N;            break;
  case SDLK_o:            code = KEY_CODE_O;            break;
  case SDLK_p:            code = KEY_CODE_P;            break;
  case SDLK_q:            code = KEY_CODE_Q;            break;
  case SDLK_r:            code = KEY_CODE_R;            break;
  case SDLK_s:            code = KEY_CODE_S;            break;
  case SDLK_t:            code = KEY_CODE_T;            break;
  case SDLK_u:            code = KEY_CODE_U;            break;
  case SDLK_v:            code = KEY_CODE_V;            break;
  case SDLK_w:            code = KEY_CODE_W;            break;
  case SDLK_x:            code = KEY_CODE_X;            break;
  case SDLK_y:            code = KEY_CODE_Y;            break;
  case SDLK_z:            code = KEY_CODE_Z;            break;

  case SDLK_CAPSLOCK:     code = KEY_CODE_CAPSLOCK;     break;
  case SDLK_F1:           code = KEY_CODE_F1;           break;
  case SDLK_F2:           code = KEY_CODE_F2;           break;
  case SDLK_F3:           code = KEY_CODE_F3;           break;
  case SDLK_F4:           code = KEY_CODE_F4;           break;
  case SDLK_F5:           code = KEY_CODE_F5;           break;
  case SDLK_F6:           code = KEY_CODE_F6;           break;
  case SDLK_F7:           code = KEY_CODE_F7;           break;
  case SDLK_F8:           code = KEY_CODE_F8;           break;
  case SDLK_F9:           code = KEY_CODE_F9;           break;
  case SDLK_F10:          code = KEY_CODE_F10;          break;
  case SDLK_F11:          code = KEY_CODE_F11;          break;
  case SDLK_F12:          code = KEY_CODE_F12;          break;

#ifdef SDLK_PRINTSCREEN
  case SDLK_PRINTSCREEN:  code = KEY_CODE_PRINTSCREEN;  break;
#endif
#ifdef SDLK_SCROLLOCK
  case SDLK_SCROLLOCK:    code = KEY_CODE_SCROLLLOCK;   break;
#endif
#ifdef SDLK_SCROLLLOCK
  case SDLK_SCROLLLOCK:   code = KEY_CODE_SCROLLLOCK;   break;
#endif
  case SDLK_PAUSE:        code = KEY_CODE_PAUSE;        break;
  case SDLK_INSERT:       code = KEY_CODE_INSERT;       break;
  case SDLK_HOME:         code = KEY_CODE_HOME;         break;
  case SDLK_PAGEUP:       code = KEY_CODE_PAGEUP;       break;
  case SDLK_DELETE:       code = KEY_CODE_DELETE;       break;
  case SDLK_END:          code = KEY_CODE_END;          break;
  case SDLK_PAGEDOWN:     code = KEY_CODE_PAGEDOWN;     break;
  case SDLK_RIGHT:        code = KEY_CODE_RIGHT;        break;
  case SDLK_LEFT:         code = KEY_CODE_LEFT;         break;
  case SDLK_DOWN:         code = KEY_CODE_DOWN;         break;
  case SDLK_UP:           code = KEY_CODE_UP;           break;

  case SDLK_F13:          code = KEY_CODE_F13;          break;
  case SDLK_F14:          code = KEY_CODE_F14;          break;
  case SDLK_F15:          code = KEY_CODE_F15;          break;

  case SDLK_LCTRL:        code = KEY_CODE_LCTRL;        break;
  case SDLK_LSHIFT:       code = KEY_CODE_LSHIFT;       break;
  case SDLK_LALT:         code = KEY_CODE_LALT;         break;
  case SDLK_RCTRL:        code = KEY_CODE_RCTRL;        break;
  case SDLK_RSHIFT:       code = KEY_CODE_RSHIFT;       break;
  case SDLK_RALT:         code = KEY_CODE_RALT;         break;

  case SDLK_UNKNOWN:
  default:
    break;
  }

  return code;
}
#endif

static int key_initbuf(graphic_context_t context)
{
  memset(context->key.buffer, 0, sizeof(context->key.buffer));
  context->key.rindex = 0;
  context->key.windex = 0;
  memset(context->key.status, 0, sizeof(context->key.status));
  return 0;
}

static key_code_t key_getbuf(graphic_context_t context)
{
  key_code_t code;

  if (context->key.rindex == context->key.windex)
    return KEY_CODE_NONE;

  code = context->key.buffer[context->key.rindex].code;
  context->key.rindex = (context->key.rindex + 1) % KEY_BUFFER_NUM;

  return code;
}

static int key_putbuf(graphic_context_t context, key_code_t code)
{
  int i;

  i = (context->key.windex + 1) % KEY_BUFFER_NUM;
  if (i == context->key.rindex)
    return -1;

  context->key.buffer[context->key.windex].code = code;
  context->key.windex = i;

  return 0;
}

static int key_getstatus(graphic_context_t context, key_code_t code)
{
  return context->key.status[code];
}

static int key_setstatus(graphic_context_t context, key_code_t code, int state)
{
  context->key.status[code] = state;
  return 0;
}

#ifdef USE_SDL
static mouse_code_t sdl_mousebutton_to_mousecode(int button)
{
  mouse_code_t code = MOUSE_CODE_NONE;

  switch (button) {
  case SDL_BUTTON_RIGHT:  code = MOUSE_CODE_RIGHT;  break;
  case SDL_BUTTON_LEFT:   code = MOUSE_CODE_LEFT;   break;
  case SDL_BUTTON_MIDDLE: code = MOUSE_CODE_MIDDLE; break;

  default:
    break;
  }

  return code;
}
#endif

static int mouse_initbuf(graphic_context_t context)
{
  memset(context->mouse.buffer, 0, sizeof(context->mouse.buffer));
  context->mouse.rindex = 0;
  context->mouse.windex = 0;
  memset(context->mouse.status, 0, sizeof(context->mouse.status));
  return 0;
}

static mouse_code_t mouse_getbuf(graphic_context_t context, int *xp, int *yp)
{
  mouse_code_t code;

  if (context->mouse.rindex == context->mouse.windex)
    return MOUSE_CODE_NONE;

  code = context->mouse.buffer[context->mouse.rindex].code;
  if (xp) *xp = context->mouse.buffer[context->mouse.rindex].x;
  if (yp) *yp = context->mouse.buffer[context->mouse.rindex].y;
  context->mouse.rindex = (context->mouse.rindex + 1) % MOUSE_BUFFER_NUM;

  return code;
}

static int mouse_putbuf(graphic_context_t context, mouse_code_t code, int x, int y)
{
  int i;

  i = (context->mouse.windex + 1) % MOUSE_BUFFER_NUM;
  if (i == context->mouse.rindex)
    return -1;

  context->mouse.buffer[context->mouse.windex].code = code;
  context->mouse.buffer[context->mouse.windex].x = x;
  context->mouse.buffer[context->mouse.windex].y = y;
  context->mouse.windex = i;

  return 0;
}

static int mouse_getstatus(graphic_context_t context, mouse_code_t code)
{
  return context->mouse.status[code];
}

static int mouse_setstatus(graphic_context_t context, mouse_code_t code, int state)
{
  context->mouse.status[code] = state;
  return 0;
}

static int _delay(int msec)
{
#ifdef USE_SDL
  SDL_Delay(msec);
#else
  nll_usleep(msec * 1000);
#endif
  return 0;
}

static int req_buffer_init(graphic_context_t context)
{
  context->req.buffer.send_index = 0;
  context->req.buffer.recv_index = 0;
  return 0;
}

static eventreq_t req_buffer_alloc(graphic_context_t context)
{
  eventreq_t req;

  req = &context->req.buffer.req[context->req.buffer.send_index];
  context->req.buffer.send_index++;
  context->req.buffer.send_index %= REQ_BUFFER_NUM;

  while (1) {
    if (!context->active)
      return NULL;
    if (context->req.buffer.send_index != context->req.buffer.recv_index)
      break;
    _delay(10);
  }

  memset(req, 0, sizeof(*req));

  return req;
}

static int req_buffer_free(graphic_context_t context, eventreq_t req)
{
  if (&context->req.buffer.req[context->req.buffer.recv_index] != req)
    return -1;
  context->req.buffer.recv_index++;
  context->req.buffer.recv_index %= REQ_BUFFER_NUM;
  return 0;
}

static int req_buffer_proc(eventreq_t req)
{
  eventreq_t next;
  int x, y;
  key_code_t keycode;
  mouse_code_t mousecode;

  for (; req; req = next) {
    next = req->next;

    if (req->code == EVENTREQ_CODE_SYNC) {
      context->sync = 1;
      goto next;
    }

    if (context->discard)
      goto next;

    if (req->code == EVENTREQ_CODE_ENABLE) {
      context->disable = !req->u.enable.enable;
      goto next;
    }

    if (context->disable)
      goto next;

    switch (req->code) {
    case EVENTREQ_CODE_RESIZE:
#ifdef USE_SDL1
      SDL_SetVideoMode(context->width, context->height, 32, SDL_HWSURFACE);
      context->surface = SDL_GetVideoSurface();
#endif
#ifdef USE_SDL2
      SDL_SetWindowSize(context->window, context->width, context->height);
      context->surface = SDL_GetWindowSurface(context->window);
#endif
      break;

    case EVENTREQ_CODE_FLUSH:
      flush(context, req->image, req->count, req->flags);
      break;

    case EVENTREQ_CODE_ALLFLUSH:
      allflush(context, req->image);
      break;

    case EVENTREQ_CODE_MAINIMG:
      context->main_image = req->image;
      break;

    case EVENTREQ_CODE_RANGE:
      proc_range(req->image, req->u.range.point.x, req->u.range.point.y, req->u.range.size.x, req->u.range.size.y, req->u.range.mul.x, req->u.range.mul.y);
      break;

    case EVENTREQ_CODE_OFFSET:
      proc_offset(req->image, req->u.point.x, req->u.point.y);
      break;

    case EVENTREQ_CODE_SCALE:
      proc_scale(req->image, req->u.scale.mul.x, req->u.scale.mul.y, req->u.scale.div.x, req->u.scale.div.y);
      break;

    case EVENTREQ_CODE_DOTOFFSET:
      proc_dotoffset(req->image, req->u.point.x, req->u.point.y);
      break;

    case EVENTREQ_CODE_DOTSIZE:
      proc_dotsize(req->image, req->u.point.x, req->u.point.y);
      break;

    case EVENTREQ_CODE_COLOR:
      proc_color(req->image, req->color, req->u.color.fg, req->u.color.bg, req->u.color.from, req->u.color.to, req->flags);
      break;

    case EVENTREQ_CODE_POSITION:
      proc_position(req->image, req->u.point.x, req->u.point.y, req->flags);
      break;

    case EVENTREQ_CODE_KEYDOWN:
      keycode = req->u.key.code;
      if (keycode != KEY_CODE_NONE) {
	key_putbuf(context, keycode);
	key_setstatus(context, keycode, 1);
      }
      break;

    case EVENTREQ_CODE_KEYUP:
      keycode = req->u.key.code;
      if (keycode != KEY_CODE_NONE) {
	key_setstatus(context, keycode, 0);
      }
      break;

    case EVENTREQ_CODE_MOUSEMOTION:
      context->mouse.x = req->u.mouse.x;
      context->mouse.y = req->u.mouse.y;
      break;

    case EVENTREQ_CODE_MOUSEBUTTONDOWN:
      mousecode = req->u.mouse.code;
      x = req->u.mouse.x;
      y = req->u.mouse.y;
      if (mousecode != MOUSE_CODE_NONE) {
	mouse_putbuf(context, mousecode, x, y);
	mouse_setstatus(context, mousecode, 1);
      }
      break;

    case EVENTREQ_CODE_MOUSEBUTTONUP:
      mousecode = req->u.mouse.code;
      if (mousecode != MOUSE_CODE_NONE) {
	mouse_setstatus(context, mousecode, 0);
      }
      break;

    case EVENTREQ_CODE_CLEAR:
      proc_clear(req->image, req->count, req->color, req->flags);
      break;

    case EVENTREQ_CODE_SCROLL:
      proc_scroll(req->image, req->count, req->u.point.x, req->u.point.y, req->color, req->flags);
      break;

    case EVENTREQ_CODE_DOT:
      proc_dot(req->image, req->count, req->u.point.x, req->u.point.y, req->color, req->flags);
      break;

    case EVENTREQ_CODE_LINE:
      proc_line(req->image, req->count, req->u.rect.point0.x, req->u.rect.point0.y, req->u.rect.point1.x, req->u.rect.point1.y, req->color, req->flags);
      break;

    case EVENTREQ_CODE_BOX:
      proc_box(req->image, req->count, req->u.rect.point0.x, req->u.rect.point0.y, req->u.rect.point1.x, req->u.rect.point1.y, req->color, req->flags);
      break;

    case EVENTREQ_CODE_CIRCLE:
      proc_circle(req->image, req->count, req->u.circle.point.x, req->u.circle.point.y, req->u.circle.radius.x, req->u.circle.radius.y, req->color, req->flags);
      break;

    case EVENTREQ_CODE_PAINT:
      proc_paint(req->image, req->count, req->u.paint.point.x, req->u.paint.point.y, req->color, req->u.paint.border, req->flags);
      break;

    case EVENTREQ_CODE_COPY:
      proc_copy(req->image, req->count,
		req->u.copy.point.x, req->u.copy.point.y,
		req->u.copy.size.x, req->u.copy.size.y,
		req->u.copy.source.x, req->u.copy.source.y,
		req->u.copy.ssize.x, req->u.copy.ssize.y,
		req->u.copy.simage, req->flags);
      break;

    case EVENTREQ_CODE_ROTATE:
      proc_rotate(req->image, req->count,
		  req->u.rotate.point.x, req->u.rotate.point.y,
		  req->u.rotate.size.x, req->u.rotate.size.y,
		  req->u.rotate.center.x, req->u.rotate.center.y,
		  req->u.rotate.num, req->color, req->flags);
      break;

    default:
      break;
    }

  next:
    req_buffer_free(context, req);
  }

  return 0;
}

#ifdef USE_SDL
static int sdl_proc(graphic_context_t context, SDL_Event *event)
{
  eventreq_t req;
#ifdef USE_SDL1
  SDLKey sym;
#endif
#ifdef USE_SDL2
  SDL_Keycode sym;
#endif
  key_code_t keycode;
  int button, x, y;
  mouse_code_t mousecode;

  switch (event->type) {
#ifdef USE_SDL1
  case SDL_VIDEOEXPOSE:
#endif
#ifdef USE_SDL2
  case SDL_WINDOWEVENT:
#endif
    allflush(context, context->main_image);
    break;

  case SDL_QUIT:
    return -1;

  case SDL_KEYDOWN:
    sym = event->key.keysym.sym;
    keycode = sdl_keysym_to_keycode(sym);
    if (keycode != KEY_CODE_NONE) {
      key_putbuf(context, keycode);
      key_setstatus(context, keycode, 1);
    }
    break;

  case SDL_KEYUP:
    sym = event->key.keysym.sym;
    keycode = sdl_keysym_to_keycode(sym);
    if (keycode != KEY_CODE_NONE) {
      key_setstatus(context, keycode, 0);
    }
    break;

  case SDL_MOUSEMOTION:
    context->mouse.x = event->motion.x;
    context->mouse.y = event->motion.y;
    break;

  case SDL_MOUSEBUTTONDOWN:
    button = event->button.button;
    mousecode = sdl_mousebutton_to_mousecode(button);
    x = event->button.x;
    y = event->button.y;
    if (mousecode != MOUSE_CODE_NONE) {
      mouse_putbuf(context, mousecode, x, y);
      mouse_setstatus(context, mousecode, 1);
    }
    break;

  case SDL_MOUSEBUTTONUP:
    button = event->button.button;
    mousecode = sdl_mousebutton_to_mousecode(button);
    if (mousecode != MOUSE_CODE_NONE) {
      mouse_setstatus(context, mousecode, 0);
    }
    break;

  default:
    break;
  }

  if (event->type == context->req.event) {
    req = (eventreq_t)event->user.data1;
    req_buffer_proc(req);
  }

  return 0;
}

static int sdl_thread_main(void *p)
{
  graphic_context_t context;
  SDL_Event e;
  int r;

  context = (graphic_context_t)p;

#ifdef USE_SDL1
  SDL_InitSubSystem(SDL_INIT_VIDEO);
  SDL_WM_SetCaption("NLL", "nll");
  SDL_SetVideoMode(context->width, context->height, 32, SDL_HWSURFACE);
  context->req.event = SDL_USEREVENT;
#endif
#ifdef USE_SDL2
  SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
  context->window = SDL_CreateWindow("NLL",
				     SDL_WINDOWPOS_UNDEFINED,
				     SDL_WINDOWPOS_UNDEFINED,
				     context->width, context->height, 0);
#if 0
  context->req.event = SDL_RegisterEvents(1);
#else
  /*
   * You should originally call SDL_RegisterEvents(),
   * but to prevent the leak of the number when it was repeated.
   */
  context->req.event = SDL_USEREVENT;
#endif
#endif

#ifdef USE_SDL_RENDERER
  context->renderer = SDL_CreateRenderer(context->window, -1,
					 SDL_RENDERER_ACCELERATED);
#endif

#ifdef USE_SDL1
  context->surface = SDL_GetVideoSurface();
#endif
#ifdef USE_SDL2
  context->surface = SDL_GetWindowSurface(context->window);
#endif

  flush_clear(context);
  flush_finish(context);

  context->active = 1;

  while (1) {
    r = SDL_WaitEvent(&e);
    if (r < 1)
      break;
    if (sdl_proc(context, &e) < 0)
      break;
  }

  context->active = 0;

#if 0
  SDL_FreeSurface(context->surface);
#endif

#ifdef USE_SDL_RENDERER
  SDL_DestroyRenderer(context->renderer);
#endif

#ifdef USE_SDL1
  SDL_QuitSubSystem(SDL_INIT_VIDEO);
#endif
#ifdef USE_SDL2
  SDL_DestroyWindow(context->window);
#endif

  return 0;
}

static int sdl_send_event(graphic_context_t context, SDL_Event *event)
{
  while (SDL_PushEvent(event) < 0) {
    if (!context->active)
      return -1;
    _delay(10);
  }
  return 0;
}
#endif

static int req_quit(graphic_context_t context)
{
  int r = -1;
#ifdef USE_SDL
  SDL_Event e;

  memset(&e, 0, sizeof(e));
  e.type = SDL_QUIT;

  r = sdl_send_event(context, &e);
#else
  context->active = 0;
#endif

  return r;
}

static int req_enqueue(graphic_context_t context, eventreq_t req)
{
  req->next = NULL;

  if (!context->req.tail)
    context->req.head = req;
  else
    context->req.tail->next = req;
  context->req.tail = req;

  return 0;
}

static int req_dequeue(graphic_context_t context)
{
  int r = -1;
#ifdef USE_SDL
  SDL_Event e;
#else
  eventreq_t req;
#endif

  if (!context->req.head)
    return 0;

#ifdef USE_SDL
  memset(&e, 0, sizeof(e));
  e.type = context->req.event;
  e.user.data1 = context->req.head;

  context->req.head = NULL;
  context->req.tail = NULL;

  r = sdl_send_event(context, &e);
#else
  req = context->req.head;

  context->req.head = NULL;
  context->req.tail = NULL;

  r = req_buffer_proc(req);
#endif

  return r;
}

static eventreq_t req_buffer_get(graphic_context_t context)
{
  int send_index;

  send_index = (context->req.buffer.send_index + 1) % REQ_BUFFER_NUM;

  if (send_index == context->req.buffer.recv_index)
    req_dequeue(context);

  return req_buffer_alloc(context);
}

static int req_sync(graphic_context_t context)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_SYNC;

  req_enqueue(context, req);

  return req_dequeue(context);
}

static int req_enable(graphic_context_t context, int enable)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_ENABLE;

  req->u.enable.enable = enable;

  req_enqueue(context, req);

  return req_dequeue(context);
}

static int req_resize(graphic_context_t context)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_RESIZE;

  req_enqueue(context, req);

  return req_dequeue(context);
}

static int req_flush(graphic_context_t context, image_t image, unsigned int flags)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_FLUSH;
  req->image = image;
  req->count = image->draw.count;

  req->flags = flags;

  req_enqueue(context, req);

  image->draw.count++;

  return req_dequeue(context);
}

static int req_mainimg(image_t image)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_MAINIMG;
  req->image = image;

  return req_enqueue(context, req);
}

static int req_range(image_t image, int x, int y, int width, int height, int mulx, int muly)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_RANGE;
  req->image = image;

  req->u.range.point.x = x;
  req->u.range.point.y = y;
  req->u.range.size.x = width;
  req->u.range.size.y = height;
  req->u.range.mul.x = mulx;
  req->u.range.mul.y = muly;

  return req_enqueue(context, req);
}

static int req_offset(image_t image, int x, int y)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_OFFSET;
  req->image = image;

  req->u.point.x = x;
  req->u.point.y = y;

  return req_enqueue(context, req);
}

static int req_scale(image_t image, int mulx, int muly, int divx, int divy)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_SCALE;
  req->image = image;

  req->u.scale.mul.x = mulx;
  req->u.scale.mul.y = muly;
  req->u.scale.div.x = divx;
  req->u.scale.div.y = divy;

  return req_enqueue(context, req);
}

static int req_dotoffset(image_t image, int x, int y)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_DOTOFFSET;
  req->image = image;

  req->u.point.x = x;
  req->u.point.y = y;

  return req_enqueue(context, req);
}

static int req_dotsize(image_t image, int width, int height)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_DOTSIZE;
  req->image = image;

  req->u.point.x = width;
  req->u.point.y = height;

  return req_enqueue(context, req);
}

static int req_color(image_t image, int color, int fgcolor, int bgcolor, int fromcolor, int tocolor, unsigned int flags)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_COLOR;
  req->image = image;

  req->color = color;
  req->flags = flags;
  req->u.color.fg   = fgcolor;
  req->u.color.bg   = bgcolor;
  req->u.color.from = fromcolor;
  req->u.color.to   = tocolor;

  return req_enqueue(context, req);
}

static int req_position(image_t image, int x, int y, unsigned int flags)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_POSITION;
  req->image = image;

  req->flags = flags;
  req->u.point.x = x;
  req->u.point.y = y;

  return req_enqueue(context, req);
}

#ifndef GRAPHIC_INPUT_DIRECT
static int req_keydown(key_code_t code)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_KEYDOWN;

  req->u.key.code = code;

  return req_enqueue(context, req);
}

static int req_keyup(key_code_t code)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_KEYUP;

  req->u.key.code = code;

  return req_enqueue(context, req);
}

static int req_mousemotion(int x, int y)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_MOUSEMOTION;

  req->u.mouse.x = x;
  req->u.mouse.y = y;

  return req_enqueue(context, req);
}

static int req_mousebuttondown(mouse_code_t code, int x, int y)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_MOUSEBUTTONDOWN;

  req->u.mouse.code = code;
  req->u.mouse.x = x;
  req->u.mouse.y = y;

  return req_enqueue(context, req);
}

static int req_mousebuttonup(mouse_code_t code)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_MOUSEBUTTONUP;

  req->u.mouse.code = code;

  return req_enqueue(context, req);
}
#endif

static int req_clear(image_t image, int color, unsigned int flags)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_CLEAR;
  req->image = image;
  req->count = image->draw.count;

  req->color = color;
  req->flags = flags;

  return req_enqueue(context, req);
}

static int req_scroll(image_t image, int x, int y, int color, unsigned int flags)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_SCROLL;
  req->image = image;
  req->count = image->draw.count;

  req->color = color;
  req->flags = flags;
  req->u.point.x = x;
  req->u.point.y = y;

  return req_enqueue(context, req);
}

static int req_dot(image_t image, int x, int y, int color, unsigned int flags)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_DOT;
  req->image = image;
  req->count = image->draw.count;

  req->color = color;
  req->flags = flags;
  req->u.point.x = x;
  req->u.point.y = y;

  return req_enqueue(context, req);
}

static int req_line(image_t image, int x0, int y0, int x1, int y1, int color, unsigned int flags)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_LINE;
  req->image = image;
  req->count = image->draw.count;

  req->color = color;
  req->flags = flags;
  req->u.rect.point0.x = x0;
  req->u.rect.point0.y = y0;
  req->u.rect.point1.x = x1;
  req->u.rect.point1.y = y1;

  return req_enqueue(context, req);
}

static int req_box(image_t image, int x0, int y0, int x1, int y1, int color, unsigned int flags)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_BOX;
  req->image = image;
  req->count = image->draw.count;

  req->color = color;
  req->flags = flags;
  req->u.rect.point0.x = x0;
  req->u.rect.point0.y = y0;
  req->u.rect.point1.x = x1;
  req->u.rect.point1.y = y1;

  return req_enqueue(context, req);
}

static int req_circle(image_t image, int x, int y, int rx, int ry, int color, unsigned int flags)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_CIRCLE;
  req->image = image;
  req->count = image->draw.count;

  req->color = color;
  req->flags = flags;
  req->u.circle.point.x = x;
  req->u.circle.point.y = y;
  req->u.circle.radius.x = rx;
  req->u.circle.radius.y = ry;

  return req_enqueue(context, req);
}

static int req_paint(image_t image, int x, int y, int color, int border, unsigned int flags)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_PAINT;
  req->image = image;
  req->count = image->draw.count;

  req->color = color;
  req->flags = flags;
  req->u.paint.point.x = x;
  req->u.paint.point.y = y;
  req->u.paint.border = border;

  return req_enqueue(context, req);
}

static int req_copy(image_t image, int x0, int y0, int width0, int height0, int x, int y, int width, int height, image_t simage, unsigned int flags)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_COPY;
  req->image = image;
  req->count = image->draw.count;

  req->flags = flags;
  req->u.copy.point.x  = x0;
  req->u.copy.point.y  = y0;
  req->u.copy.size.x   = width0;
  req->u.copy.size.y   = height0;
  req->u.copy.source.x = x;
  req->u.copy.source.y = y;
  req->u.copy.ssize.x  = width;
  req->u.copy.ssize.y  = height;
  req->u.copy.simage   = simage;

  return req_enqueue(context, req);
}

static int req_rotate(image_t image, int x, int y, int width, int height, int cx, int cy, int num, int color, unsigned int flags)
{
  eventreq_t req;

  req = req_buffer_get(context);
  if (!req)
    return -1;

  req->code = EVENTREQ_CODE_ROTATE;
  req->image = image;
  req->count = image->draw.count;

  req->color = color;
  req->flags = flags;
  req->u.rotate.point.x  = x;
  req->u.rotate.point.y  = y;
  req->u.rotate.size.x   = width;
  req->u.rotate.size.y   = height;
  req->u.rotate.center.x = cx;
  req->u.rotate.center.y = cy;
  req->u.rotate.num      = num;

  return req_enqueue(context, req);
}

int graphic_setscreen(int flags, int width, int height, int index)
{
  image_t image, oldimage;
#ifdef USE_SDL
  int status;
#endif

  if (flags < 0) {
    flags = graphic_get_screen_flags();
    if (flags == NLL_G_DISABLE)
      flags = NLL_G_ENABLE;
  }

  if ((flags != NLL_G_DISABLE) && !(flags & NLL_G_MODE_MASK))
    flags |= NLL_G_ENABLE;

#ifdef GRAPHIC_EXTERNAL_DRIVER
  ext_graphic_stop();
#endif

  switch (flags) {
  case NLL_G_DISABLE:
    if (context) {
      if (context->active) {
	graphic_flush(NLL_G_SYNC);
	req_quit(context);
	context->discard = 1;
	while (context->active)
	  _delay(10);
      }
#ifdef USE_SDL
      SDL_WaitThread(context->thread, &status);
#if 0 /* Done at exit from nll */
      sdl_done();
#endif
#endif
    }
    goto disable;

  default:
    if (context && !context->active)
      graphic_setscreen(NLL_G_DISABLE, 0, 0, 0);

    width  = (width  > GRAPHIC_MAXWIDTH ) ? GRAPHIC_MAXWIDTH  : width;
    height = (height > GRAPHIC_MAXHEIGHT) ? GRAPHIC_MAXHEIGHT : height;

    if (context) {
      graphic_flush(NLL_G_SYNC);

      if (index < 0) {
	width  = (width  > 0) ? width  : context->width;
	height = (height > 0) ? height : context->height;
	image = image_get(width, height, NULL, IMAGE_TYPE_NONE, flags);
	if (!image)
	  return -1;
	if (context->main_image->status == IMAGE_STATUS_ACTIVE) {
	  proc_copy(image, 0, 0, 0, 0, 0, 0, 0, width, height, context->main_image, 0);
	}
      } else {
	image = image_get_image(index);
	if (!image || (image->status != IMAGE_STATUS_ACTIVE))
	  return -1;
	width  = (width  > 0) ? width  : image->width;
	height = (height > 0) ? height : image->height;
      }

      oldimage = context->main_image;
      req_mainimg(image);
      graphic_sync();
      image_destroy(oldimage);

      context->flags  = flags;
      context->width  = width;
      context->height = height;

      req_resize(context);
    } else {
      context = memory_alloc(sizeof(*context));
      if (!context)
	goto disable;
      memset(context, 0, sizeof(*context));

      if (index < 0) {
	width  = (width  > 0) ? width  : GRAPHIC_DEFAULT_WIDTH;
	height = (height > 0) ? height : GRAPHIC_DEFAULT_HEIGHT;
	image = image_get(width, height, NULL, IMAGE_TYPE_NONE, flags);
	if (!image)
	  goto disable;
      } else {
	image = image_get_image(index);
	if (!image || (image->status != IMAGE_STATUS_ACTIVE))
	  goto disable;
	width  = (width  > 0) ? width  : image->width;
	height = (height > 0) ? height : image->height;
      }

      context->main_image = image;

      context->flags  = flags;
      context->width  = width;
      context->height = height;

      key_initbuf(context);
      mouse_initbuf(context);

      req_buffer_init(context);

#ifdef USE_SDL
      sdl_start();
#else
      context->active = 1;
#endif
#ifdef USE_SDL1
      context->thread = SDL_CreateThread(sdl_thread_main, context);
#endif
#ifdef USE_SDL2
      context->thread = SDL_CreateThread(sdl_thread_main, "SDL GRAPHIC thread", context);
#endif

      while (!context->active)
	_delay(10);
    }

    graphic_flush(NLL_G_SYNC);

    break;
  }

#ifdef GRAPHIC_EXTERNAL_DRIVER
  ext_graphic_start(image_get_index(context->main_image));
#endif

  return 0;

disable:
  if (context) {
    if (context->main_image)
      image_destroy(context->main_image);
    memory_free(context);
    context = NULL;
  }
  return -1;
}

int graphic_sync(void)
{
  if (!context) {
    return 0;
  }

  context->sync = 0;
  req_sync(context);
  while (!context->sync) {
    if (!context->active)
      return -1;
    _delay(10);
  }

  return 0;
}

int graphic_enable(int enable)
{
  if (!context) {
    return 0;
  }

  req_enable(context, enable);

  return 0;
}

int graphic_flush(int flags)
{
  if (!context) {
    return 0;
  }

  if ((context->main_image->draw.count == 0) && (context->flags & NLL_G_DIRECT))
    image_reset_draw(context->main_image);

  req_flush(context, context->main_image, flags);

  if (flags & NLL_G_SYNC)
    graphic_sync();

  return 0;
}

static int _sync(void)
{
  while (context && context->active) {
    if (context->req.buffer.send_index == context->req.buffer.recv_index)
      break;
    _delay(10);
  }
  return 0;
}

int graphic_getmainimage(void)
{
  image_t image;

  if (!context) {
    return -1;
  }

  _sync();
  image = context->main_image;

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return -1;

  return image_get_index(image);
}

int graphic_setmainimage(int index)
{
  image_t image;

  if (!context) {
    return -1;
  }

  image = (index < 0) ? context->main_image : image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return -1;

  req_mainimg(image);
  graphic_sync();

  return index;
}

int graphic_getwidth(int index)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  _sync();

  return image->width;
}

int graphic_getheight(int index)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  _sync();

  return image->height;
}

int graphic_getrange(int index, int *xp, int *yp, int *widthp, int *heightp, int *mulxp, int *mulyp)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return -1;

  _sync();

  *xp = image->range.x;
  *yp = image->range.y;
  *widthp  = image->range.width;
  *heightp = image->range.height;
  *mulxp = image->range.mulx;
  *mulyp = image->range.muly;

  return 0;
}

int graphic_setrange(int index, int x, int y, int width, int height, int mulx, int muly)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if (!context || (context->flags & NLL_G_DIRECT))
    proc_range(image, x, y, width, height, mulx, muly);
  else if (context->flags & NLL_G_BUFFER)
    req_range(image, x, y, width, height, mulx, muly);

  return 0;
}

int graphic_getoffset(int index, int *xp, int *yp)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return -1;

  _sync();

  *xp = image->offset.x;
  *yp = image->offset.y;

  return 0;
}

int graphic_setoffset(int index, int x, int y)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if (!context || (context->flags & NLL_G_DIRECT))
    proc_offset(image, x, y);
  else if (context->flags & NLL_G_BUFFER)
    req_offset(image, x, y);

  return 0;
}

int graphic_getscale(int index, int *mulxp, int *mulyp, int *divxp, int *divyp)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return -1;

  _sync();

  *mulxp = image->scale.mul.x;
  *mulyp = image->scale.mul.y;
  *divxp = image->scale.div.x;
  *divyp = image->scale.div.y;

  return 0;
}

int graphic_setscale(int index, int mulx, int muly, int divx, int divy)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if (!context || (context->flags & NLL_G_DIRECT))
    proc_scale(image, mulx, muly, divx, divy);
  else if (context->flags & NLL_G_BUFFER)
    req_scale(image, mulx, muly, divx, divy);

  return 0;
}

int graphic_getdotoffset(int index, int *xp, int *yp)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return -1;

  _sync();

  *xp = image->dot.x;
  *yp = image->dot.y;

  return 0;
}

int graphic_setdotoffset(int index, int x, int y)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if (!context || (context->flags & NLL_G_DIRECT))
    proc_dotoffset(image, x, y);
  else if (context->flags & NLL_G_BUFFER)
    req_dotoffset(image, x, y);

  return 0;
}

int graphic_getdotsize(int index, int *widthp, int *heightp)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return -1;

  _sync();

  *widthp  = image->dot.width;
  *heightp = image->dot.height;

  return 0;
}

int graphic_setdotsize(int index, int width, int height)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if (!context || (context->flags & NLL_G_DIRECT))
    proc_dotsize(image, width, height);
  else if (context->flags & NLL_G_BUFFER)
    req_dotsize(image, width, height);

  return 0;
}

int graphic_getcolor(int index, int *colorp, int *fgcolorp, int *bgcolorp, int *fromcolorp, int *tocolorp)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  _sync();

  if (colorp)     *colorp     = image->current.color;
  if (fgcolorp)   *fgcolorp   = image->color.fg;
  if (bgcolorp)   *bgcolorp   = image->color.bg;
  if (fromcolorp) *fromcolorp = image->color.from;
  if (tocolorp)   *tocolorp   = image->color.to;

  return 0;
}

int graphic_setcolor(int index, int color, int fgcolor, int bgcolor, int fromcolor, int tocolor, int flags)
{
  int old_color;
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  _sync();
  old_color = image->current.color;

  if (!context || (context->flags & NLL_G_DIRECT))
    proc_color(image, color, fgcolor, bgcolor, fromcolor, tocolor, flags);
  else if (context->flags & NLL_G_BUFFER)
    req_color(image, color, fgcolor, bgcolor, fromcolor, tocolor, flags);

  return old_color;
}

int graphic_getpixel(int index, int x, int y)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  _sync();

  return proc_pget(image, x, y);
}

int graphic_keydown(key_code_t code)
{
  if (context) {
#ifndef GRAPHIC_INPUT_DIRECT
    req_keydown(code);
    req_dequeue(context);
#else
    if (code != KEY_CODE_NONE) {
      key_putbuf(context, code);
      key_setstatus(context, code, 1);
    }
#endif
  }

  return 0;
}

int graphic_keyup(key_code_t code)
{
  if (context) {
#ifndef GRAPHIC_INPUT_DIRECT
    req_keyup(code);
    req_dequeue(context);
#else
    if (code != KEY_CODE_NONE) {
      key_setstatus(context, code, 0);
    }
#endif
  }

  return 0;
}

int graphic_mousemotion(int x, int y)
{
  if (context) {
#ifndef GRAPHIC_INPUT_DIRECT
    req_mousemotion(x, y);
    req_dequeue(context);
#else
    context->mouse.x = x;
    context->mouse.y = y;
#endif
  }

  return 0;
}

int graphic_mousebuttondown(mouse_code_t code, int x, int y)
{
  if (context) {
#ifndef GRAPHIC_INPUT_DIRECT
    req_mousebuttondown(code, x, y);
    req_dequeue(context);
#else
    if (code != MOUSE_CODE_NONE) {
      mouse_putbuf(context, code, x, y);
      mouse_setstatus(context, code, 1);
    }
#endif
  }

  return 0;
}

int graphic_mousebuttonup(mouse_code_t code)
{
  if (context) {
#ifndef GRAPHIC_INPUT_DIRECT
    req_mousebuttonup(code);
    req_dequeue(context);
#else
    if (code != MOUSE_CODE_NONE) {
      mouse_setstatus(context, code, 0);
    }
#endif
  }

  return 0;
}

int graphic_clear(int index, int color, int flags)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if (!context || (context->flags & NLL_G_DIRECT))
    proc_clear(image, image->draw.count, color, flags);
  else if (context->flags & NLL_G_BUFFER)
    req_clear(image, color, flags);

  if (context && (image == context->main_image)) {
    if ((flags | context->flags) & NLL_G_FLUSH)
      graphic_flush(0);
  }

  return 0;
}

int graphic_scroll(int index, int x, int y, int color, int flags)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if (!context || (context->flags & NLL_G_DIRECT))
    proc_scroll(image, image->draw.count, x, y, color, flags);
  else if (context->flags & NLL_G_BUFFER)
    req_scroll(image, x, y, color, flags);

  if (context && (image == context->main_image)) {
    if ((flags | context->flags) & NLL_G_FLUSH)
      graphic_flush(0);
  }

  return 0;
}

int graphic_dot(int index, int x, int y, int color, int flags)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if (!context || (context->flags & NLL_G_DIRECT))
    proc_dot(image, image->draw.count, x, y, color, flags);
  else if (context->flags & NLL_G_BUFFER)
    req_dot(image, x, y, color, flags);

  if (context && (image == context->main_image)) {
    if ((flags | context->flags) & NLL_G_FLUSH)
      graphic_flush(0);
  }

  return 0;
}

int graphic_line(int index, int x0, int y0, int x1, int y1, int color, int flags)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if (!context || (context->flags & NLL_G_DIRECT))
    proc_line(image, image->draw.count, x0, y0, x1, y1, color, flags);
  else if (context->flags & NLL_G_BUFFER)
    req_line(image, x0, y0, x1, y1, color, flags);

  if (context && (image == context->main_image)) {
    if ((flags | context->flags) & NLL_G_FLUSH)
      graphic_flush(0);
  }

  return 0;
}

int graphic_box(int index, int x0, int y0, int x1, int y1, int color, int flags)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if (!context || (context->flags & NLL_G_DIRECT))
    proc_box(image, image->draw.count, x0, y0, x1, y1, color, flags);
  else if (context->flags & NLL_G_BUFFER)
    req_box(image, x0, y0, x1, y1, color, flags);

  if (context && (image == context->main_image)) {
    if ((flags | context->flags) & NLL_G_FLUSH)
      graphic_flush(0);
  }

  return 0;
}

int graphic_circle(int index, int x, int y, int rx, int ry, int color, int flags)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if (!context || (context->flags & NLL_G_DIRECT))
    proc_circle(image, image->draw.count, x, y, rx, ry, color, flags);
  else if (context->flags & NLL_G_BUFFER)
    req_circle(image, x, y, rx, ry, color, flags);

  if (context && (image == context->main_image)) {
    if ((flags | context->flags) & NLL_G_FLUSH)
      graphic_flush(0);
  }

  return 0;
}

int graphic_paint(int index, int x, int y, int color, int border, int flags)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if (!context || (context->flags & NLL_G_DIRECT))
    proc_paint(image, image->draw.count, x, y, color, border, flags);
  else if (context->flags & NLL_G_BUFFER)
    req_paint(image, x, y, color, border, flags);

  if (context && (image == context->main_image)) {
    if ((flags | context->flags) & NLL_G_FLUSH)
      graphic_flush(0);
  }

  return 0;
}

int graphic_copy(int index, int x0, int y0, int width0, int height0, int source, int x, int y, int width, int height, int flags)
{
  image_t image, simage;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if (source < 0)
    simage = context ? context->main_image : NULL;
  else
    simage = image_get_image(source);

  if (!simage || (simage->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if (!context || (context->flags & NLL_G_DIRECT))
    proc_copy(image, image->draw.count, x0, y0, width0, height0, x, y, width, height, simage, flags);
  else if (context->flags & NLL_G_BUFFER)
    req_copy(image, x0, y0, width0, height0, x, y, width, height, simage, flags);

  if (context && (image == context->main_image)) {
    if ((flags | context->flags) & NLL_G_FLUSH)
      graphic_flush(0);
  }

  return 0;
}

int graphic_rotate(int index, int x, int y, int width, int height, int cx, int cy, int num, int color, int flags)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if (!context || (context->flags & NLL_G_DIRECT))
    proc_rotate(image, image->draw.count, x, y, width, height, cx, cy, num, color, flags);
  else if (context->flags & NLL_G_BUFFER)
    req_rotate(image, x, y, width, height, cx, cy, num, color, flags);

  if (context && (image == context->main_image)) {
    if ((flags | context->flags) & NLL_G_FLUSH)
      graphic_flush(0);
  }

  return 0;
}

int graphic_char(int index, int x, int y, int width, int height, int ch, int type, int flags)
{
  image_t image;
  struct charsets *cs;
  const struct charset *c;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if ((type < 0) || (type >= charsets_num))
    return 0;

  cs = &charsets[type];
  c = cs->charset;

  if (!cs->image) {
    if (graphic_charset(type, NULL, NULL, NULL, flags) < 0)
      return 0;
  }

  if (!context || (context->flags & NLL_G_DIRECT)) {
    proc_copy(image, image->draw.count, x, y, width, height,
	      0, c->height * ch, c->width, c->height, cs->image, flags);
  } else if (context->flags & NLL_G_BUFFER) {
    req_copy(image, x, y, width, height,
	     0, c->height * ch, c->width, c->height, cs->image, flags);
  }

  if (context && (image == context->main_image)) {
    if ((flags | context->flags) & NLL_G_FLUSH)
      graphic_flush(0);
  }

  return 0;
}

int graphic_print(int index, int x, int y, int width, int height, const char *string, int length, int type, int xgrid, int ygrid, int flags)
{
  image_t image;
  struct charsets *cs;
  const struct charset *c;
  int i, ch;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if ((type < 0) || (type >= charsets_num))
    return 0;

  cs = &charsets[type];
  c = cs->charset;

  if (!cs->image) {
    if (graphic_charset(type, NULL, NULL, NULL, flags) < 0)
      return 0;
  }

  if (xgrid == NLL_G_DEFAULT) xgrid = width ? width : c->width;
  if (ygrid == NLL_G_DEFAULT) ygrid = 0;

  if (!context || (context->flags & NLL_G_DIRECT)) {
    proc_position(image, x, y, flags);
  } else if (context->flags & NLL_G_BUFFER) {
    req_position(image, x, y, flags);
  }

  x = NLL_G_DEFAULT;
  y = NLL_G_DEFAULT;

  for (i = 0; i < length; i++) {
    ch = string[i];
    if (!context || (context->flags & NLL_G_DIRECT)) {
      proc_copy(image, image->draw.count, x, y, width, height,
		0, c->height * ch, c->width, c->height, cs->image, flags);
      proc_position(image, xgrid, ygrid, flags | NLL_G_OFFSET);
    } else if (context->flags & NLL_G_BUFFER) {
      req_copy(image, x, y, width, height,
	       0, c->height * ch, c->width, c->height, cs->image, flags);
      req_position(image, xgrid, ygrid, flags | NLL_G_OFFSET);
    }
  }

  if (context && (image == context->main_image)) {
    if ((flags | context->flags) & NLL_G_FLUSH)
      graphic_flush(0);
  }

  return 0;
}

key_code_t graphic_getkeybuf(void)
{
  if (!context) {
    return KEY_CODE_NONE;
  }
  return key_getbuf(context);
}

int graphic_getkeystatus(key_code_t code)
{
  if (!context) {
    return 0;
  }

  if (((code < 0) || (code >= KEY_CODE_NUM)) ||
      (code == KEY_CODE_NONE))
    return 0;

  return key_getstatus(context, code);
}

int graphic_getmousepos(int *xp, int *yp)
{
  int x, y;

  if (!context) {
    return -1;
  }

  x = context->mouse.x;
  y = context->mouse.y;

  outrange(context->main_image, &x, &y);
  roffseting(context->main_image, &x, &y);
  rscaling(context->main_image, &x, &y);

  if (xp) *xp = x;
  if (yp) *yp = y;

  return 0;
}

mouse_code_t graphic_getmousebuf(int *xp, int *yp)
{
  int x, y;
  mouse_code_t code;

  if (!context) {
    return MOUSE_CODE_NONE;
  }

  if ((code = mouse_getbuf(context, &x, &y)) == MOUSE_CODE_NONE)
    return code;

  outrange(context->main_image, &x, &y);
  roffseting(context->main_image, &x, &y);
  rscaling(context->main_image, &x, &y);

  if (xp) *xp = x;
  if (yp) *yp = y;

  return code;
}

int graphic_getmousestatus(mouse_code_t code)
{
  if (!context) {
    return 0;
  }

  if (((code < 0) || (code >= MOUSE_CODE_NUM)) ||
      (code == MOUSE_CODE_NONE))
    return 0;

  return mouse_getstatus(context, code);
}

int graphic_allocimage(int width, int height, unsigned int flags)
{
  image_t image;

  width  = (width  > GRAPHIC_MAXWIDTH ) ? GRAPHIC_MAXWIDTH  : width;
  height = (height > GRAPHIC_MAXHEIGHT) ? GRAPHIC_MAXHEIGHT : height;

  if (width  <= 0) width  = context ? context->width  : GRAPHIC_DEFAULT_WIDTH;
  if (height <= 0) height = context ? context->height : GRAPHIC_DEFAULT_HEIGHT;

  image = image_get(width, height, NULL, IMAGE_TYPE_NONE, flags);
  if (!image)
    return -1;

  return image_get_index(image);
}

int graphic_freeimage(int index)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return -1;

  if (context) {
    graphic_sync();
    if (image == context->main_image)
      return -1;
  }

  image_destroy(image);

  return index;
}

int graphic_loadimage(char *filename, int *widthp, int *heightp, unsigned int flags)
{
  image_t image;

  image = picture_load(filename, flags);
  if (!image)
    return -1;

  if (widthp ) *widthp  = image->width;
  if (heightp) *heightp = image->height;

  return image_get_index(image);
}

int graphic_saveimage(char *filename, int index, int x, int y, int width, int height, unsigned int flags)
{
  image_t image;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  return picture_save(filename, image, x, y, width, height, flags);
}

int graphic_cmpimage(int index, int x0, int y0, int width0, int height0, int source, int x, int y, int width, int height, int flags)
{
  image_t image, simage;

  if (index < 0)
    image = context ? context->main_image : NULL;
  else
    image = image_get_image(index);

  if (!image || (image->status != IMAGE_STATUS_ACTIVE))
    return 0;

  if (source < 0)
    simage = context ? context->main_image : NULL;
  else
    simage = image_get_image(source);

  if (!simage || (simage->status != IMAGE_STATUS_ACTIVE))
    return 0;

  _sync();

  return proc_cmp(image, x0, y0, width0, height0, x, y, width, height, simage, flags);
}

int graphic_charset(int type, int *widthp, int *heightp, int *nump, int flags)
{
  struct charsets *cs;
  const struct charset *c;
  void *bitmap;

  if ((type < 0) || (type >= charsets_num))
    return -1;

  cs = &charsets[type];
  c = cs->charset;

  if (!cs->image) {
    /* cast for avoid warning of const by GRAPHIC_CHARACTER_PRESET_RDONLY */
    bitmap = (void *)c->bitmap;

    flags |= (c->flags | NLL_G_CONST);
#ifdef GRAPHIC_CHARACTER_PRESET_RDONLY
    flags |= NLL_G_RDONLY;
#endif

    cs->image = image_get(c->width, c->height * c->num, bitmap, c->type, flags);
    if (!cs->image)
      return -1;
  }

  if (widthp ) *widthp  = c->width;
  if (heightp) *heightp = c->height;
  if (nump   ) *nump    = c->num;

  return image_get_index(cs->image);
}
#endif
