#include "config.h"

#ifdef USE_FRAMEBUF
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef USE_CURSES
#include <curses.h>
#endif

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

#include "const.h"
#include "nlllib.h"
#include "key.h"
#include "framebuf.h"
#include "term.h"

static struct linebuf {
  unsigned char chara[FRAMEBUF_MAXWIDTH];
  unsigned int attr[FRAMEBUF_MAXWIDTH];
} linebuf[FRAMEBUF_MAXHEIGHT];

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

  struct {
    int x;
    int y;
    unsigned int attr;
  } cursor;

  struct frameline {
    unsigned int flags;
#define FRAMEBUF_CONTEXT_FRAME_FLAG_DIRTY (1 <<  0)
    struct linebuf *buffer;
  } line[FRAMEBUF_MAXHEIGHT];
} *framebuf_context_t;

static struct framebuf_context _context;
static framebuf_context_t context = NULL;

#define DEFAULT_CHARA  ' '
#define DEFAULT_EFFECT NLL_A_RESET
#define DEFAULT_COLOR  NLL_C_RESET

#define EFFECT_SHIFT  0
#define EFFECT_MASK   0xf
#define FGCOLOR_SHIFT 4
#define FGCOLOR_MASK  0xf0
#define BGCOLOR_SHIFT 8
#define BGCOLOR_MASK  0xf00

#define GET_EFFECT(attr)  (((attr) &  EFFECT_MASK) >> EFFECT_SHIFT)
#define GET_FGCOLOR(attr) (((attr) & FGCOLOR_MASK) >> FGCOLOR_SHIFT)
#define GET_BGCOLOR(attr) (((attr) & BGCOLOR_MASK) >> BGCOLOR_SHIFT)

#define GET_ATTR(effect, fgcolor, bgcolor) \
	((((effect)  <<  EFFECT_SHIFT) &  EFFECT_MASK) | \
	 (((fgcolor) << FGCOLOR_SHIFT) & FGCOLOR_MASK) | \
	 (((bgcolor) << BGCOLOR_SHIFT) & BGCOLOR_MASK))

#define DEFAULT_ATTR GET_ATTR(DEFAULT_EFFECT, DEFAULT_COLOR, DEFAULT_COLOR)

int framebuf_get_screen_flags(void)
{
  return context ? context->flags : NLL_F_DISABLE;
}

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

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

int framebuf_get_cursor_x(void)
{
  return context ? context->cursor.x : -1;
}

int framebuf_get_cursor_y(void)
{
  return context ? context->cursor.y : -1;
}

int framebuf_get_cursor_effect(void)
{
  return context ? GET_EFFECT(context->cursor.attr) : -1;
}

int framebuf_get_cursor_fgcolor(void)
{
  return context ? GET_FGCOLOR(context->cursor.attr) : -1;
}

int framebuf_get_cursor_bgcolor(void)
{
  return context ? GET_BGCOLOR(context->cursor.attr) : -1;
}

int framebuf_init(void)
{
  int r;
  if ((r = nll_term_init()) < 0)
    return r;
  framebuf_enable(1);
  r = framebuf_setscreen(NLL_F_DISABLE, 0, 0);
  framebuf_enable(0);
  if (r < 0)
    return r;
  return 0;
}

int framebuf_done(void)
{
  int r;
  framebuf_enable(1);
  r = framebuf_setscreen(NLL_F_DISABLE, 0, 0);
  framebuf_enable(0);
  if (r < 0)
    return r;
  if ((r = nll_term_done()) < 0)
    return r;
  return 0;
}

static void _flush(FILE *fp)
{
  fflush(fp);
}

static void _clear(FILE *fp)
{
  fprintf(fp, "\x1b[2J"); /* Clear */
  fprintf(fp, "\x1b[1;1H"); /* Home */
}

static void _wait(FILE *fp)
{
  nll_wait_output(fp);
}

static void _cursor_on(FILE *fp)
{
  fprintf(fp, "\x1b[>5l"); /* Cursor ON */
}

static void _cursor_off(FILE *fp)
{
  fprintf(fp, "\x1b[>5h"); /* Cursor OFF */
}

static void _home(FILE *fp)
{
  fprintf(fp, "\x1b[1;1H"); /* Home */
}

static void _effect(FILE *fp, int e)
{
  fprintf(fp, "\x1b[%dm", e); /* Mode (0:Reset) */
}

static void _fgcolor(FILE *fp, int c)
{
  fprintf(fp, "\x1b[%dm", 30 + c);
}

static void _bgcolor(FILE *fp, int c)
{
  fprintf(fp, "\x1b[%dm", 40 + c);
}

static void _newline(FILE *fp)
{
#if 0
  fprintf(fp, "\x1b[1E");
#else
  fprintf(fp, "\n");
#endif
}

static void _movex(FILE *fp, int num)
{
  if (num < 0) fprintf(fp, "\x1b[%dD", -num); /* Left */
  else         fprintf(fp, "\x1b[%dC",  num); /* Right */
}

static void _movey(FILE *fp, int num)
{
  if (num < 0) fprintf(fp, "\x1b[%dA", -num); /* Up */
  else         fprintf(fp, "\x1b[%dB",  num); /* Down */
}

static void _move(FILE *fp, int x, int y)
{
  fprintf(fp, "\x1b[%d;%dH", y + 1, x + 1);
}

static void _scroll_y(FILE *fp, int num)
{
  if (num < 0) fprintf(fp, "\x1b[%dT", -num); /* Scroll Up */
  else         fprintf(fp, "\x1b[%dS",  num); /* Scroll Down */
}

static FILE *outfp(framebuf_context_t context)
{
  return nll_stdout;
}

#ifdef USE_CURSES
static int _color_num(int color)
{
  switch (color) {
  case COLOR_BLACK:   return 1;
  case COLOR_RED:     return 2;
  case COLOR_GREEN:   return 3;
  case COLOR_YELLOW:  return 4;
  case COLOR_BLUE:    return 5;
  case COLOR_MAGENTA: return 6;
  case COLOR_CYAN:    return 7;
  case COLOR_WHITE:   return 8;
  case -1:
  default:
    break;
  }
  return 0;
}

static int _color_pair(int fgcolor, int bgcolor)
{
  return _color_num(fgcolor) * 9 + _color_num(bgcolor) + 1;
}

static int _color_init(void)
{
  int fgcolor, bgcolor, n;
  static int colors[] = {
    -1,
    COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW,
    COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE
  };

  for (fgcolor = 0; fgcolor < 9; fgcolor++) {
    for (bgcolor = 0; bgcolor < 9; bgcolor++) {
      n = _color_pair(colors[fgcolor], colors[bgcolor]);
      init_pair(n, colors[fgcolor], colors[bgcolor]);
    }
  }

  return 0;
}
#endif

static int _enable(framebuf_context_t context)
{
  return nll_term_start(context ? context->flags : 0);
}

static int _disable(framebuf_context_t context)
{
  return nll_term_restore(context ? context->flags : 0);
}

static int _init(framebuf_context_t context)
{
#ifdef USE_CURSES
  if (context && (context->flags & NLL_F_CURSES)) {
    /* none */
  }
#endif

  return 0;
}

static int _done(framebuf_context_t context)
{
#ifdef USE_CURSES
  if (context && (context->flags & NLL_F_CURSES)) {
    /* none */
  }
#endif

  return 0;
}

static int flush_reset(framebuf_context_t context)
{
#ifdef USE_CURSES
  if (context && (context->flags & NLL_F_CURSES)) {
    if (has_colors()) {
      start_color();
      use_default_colors();
      _color_init();
    }
    return 0;
  }
#endif

  _effect(outfp(context), NLL_A_RESET);
  _fgcolor(outfp(context), NLL_C_RESET);
  _bgcolor(outfp(context), NLL_C_RESET);
  _flush(outfp(context));

  return 0;
}

static int flush_wait(framebuf_context_t context)
{
#ifdef USE_CURSES
  if (context && (context->flags & NLL_F_CURSES)) {
    return 0;
  }
#endif

  _wait(outfp(context));

  return 0;
}

static int flush_cursor_on(framebuf_context_t context, int on)
{
#ifdef USE_CURSES
  if (context && (context->flags & NLL_F_CURSES)) {
    curs_set(on ? 1 : 0);
    return 0;
  }
#endif

  if (on)
    _cursor_on(outfp(context));
  else
    _cursor_off(outfp(context));
  _flush(outfp(context));

  return 0;
}

static int flush_home(framebuf_context_t context)
{
#ifdef USE_CURSES
  if (context && (context->flags & NLL_F_CURSES)) {
    move(0, 0);
    return 0;
  }
#endif

  _home(outfp(context));

  return 0;
}

static int flush_effect(framebuf_context_t context, int effect)
{
#ifdef USE_CURSES
  if (context && (context->flags & NLL_F_CURSES)) {
    switch (effect) {
    case NLL_A_RESET:      attron(A_NORMAL);     break;
    case NLL_A_BOLD:       attron(A_BOLD);       break;
    case NLL_A_FAINT:      attron(A_DIM);        break;
#ifdef A_ITALIC
    case NLL_A_ITALIC:     attron(A_ITALIC);     break;
#else
    case NLL_A_ITALIC:     attron(A_BOLD);       break;
#endif
    case NLL_A_UNDERLINE:  attron(A_UNDERLINE);  break;
    case NLL_A_BLINK:      attron(A_BLINK);      break;
    case NLL_A_RAPIDBLINK: attron(A_BLINK);      break;
    case NLL_A_INVERSE:    attron(A_REVERSE);    break;
    case NLL_A_INVISIBLE:  attron(A_INVIS);      break;
#ifdef A_HORIZONTAL
    case NLL_A_CROSSOUT:   attron(A_HORIZONTAL); break;
#else
    case NLL_A_CROSSOUT:   attron(A_BOLD);       break;
#endif
    default: break;
    }
    return 0;
  }
#endif

  _effect(outfp(context), effect);

  return 0;
}

#ifdef USE_CURSES
static int curses_color(int color)
{
  switch (color) {
  case NLL_C_BLACK:   return COLOR_BLACK;
  case NLL_C_RED:     return COLOR_RED;
  case NLL_C_GREEN:   return COLOR_GREEN;
  case NLL_C_YELLOW:  return COLOR_YELLOW;
  case NLL_C_BLUE:    return COLOR_BLUE;
  case NLL_C_MAGENTA: return COLOR_MAGENTA;
  case NLL_C_CYAN:    return COLOR_CYAN;
  case NLL_C_WHITE:   return COLOR_WHITE;
  case NLL_C_RESET:
  default:
    break;
  }
  return -1;
}
#endif

static int flush_color(framebuf_context_t context, int fgcolor, int bgcolor)
{
#ifdef USE_CURSES
  if (context && (context->flags & NLL_F_CURSES)) {
    if (has_colors()) {
      fgcolor = curses_color(fgcolor);
      bgcolor = curses_color(bgcolor);
      attron(COLOR_PAIR(_color_pair(fgcolor, bgcolor)));
    }
    return 0;
  }
#endif

  _fgcolor(outfp(context), fgcolor);
  _bgcolor(outfp(context), bgcolor);

  return 0;
}

static int flush_write(framebuf_context_t context, unsigned char *p, int size)
{
#ifdef USE_CURSES
  if (context && (context->flags & NLL_F_CURSES)) {
    addnstr((char *)p, size);
    return 0;
  }
#endif

  fwrite(p, size, 1, outfp(context));

  return 0;
}

static int flush_newline(framebuf_context_t context, int line)
{
#ifdef USE_CURSES
  if (context && (context->flags & NLL_F_CURSES)) {
    move(getcury(stdscr) + 1, 0);
    return 0;
  }
#endif

  _newline(outfp(context));

  return 0;
}

static int flush_finish(framebuf_context_t context)
{
#ifdef USE_CURSES
  if (context && (context->flags & NLL_F_CURSES)) {
    refresh();
    return 0;
  }
#endif

  fflush(outfp(context));

  return 0;
}

static int flush(framebuf_context_t context, unsigned int flags)
{
  int i, n, size;
  unsigned char *cp;
  unsigned int attr = -1, *ap;

  if (!context)
    return 0;

  if ((flags | context->flags) & NLL_F_FORCE) {
    for (i = 0; i < context->height; i++)
      context->line[i].flags |= FRAMEBUF_CONTEXT_FRAME_FLAG_DIRTY;
  }

  flush_wait(context);

  flush_home(context);

  for (i = 0; i < context->height; i++) {
    if (context->line[i].flags & FRAMEBUF_CONTEXT_FRAME_FLAG_DIRTY) {
      n = 0;
      cp = context->line[i].buffer->chara;
      ap = context->line[i].buffer->attr;

      flush_wait(context);

      while (n < context->width) {
	if (attr != *ap) {
	  attr = *ap;
	  flush_effect(context, GET_EFFECT(attr));
	  flush_color(context, GET_FGCOLOR(attr), GET_BGCOLOR(attr));
	}
	for (size = 0; n + size < context->width; size++, ap++) {
	  if (*ap != attr)
	    break;
	}
	flush_write(context, cp, size);
	cp += size;
	n  += size;
      }
      context->line[i].flags &= ~FRAMEBUF_CONTEXT_FRAME_FLAG_DIRTY;
    }
    flush_newline(context, i);
  }

  flush_finish(context);

  return 0;
}

static int frameline_clear(struct frameline *line, int width,
			   int chara, unsigned int attr)
{
  int i;
  for (i = 0; i < width; i++) {
    line->buffer->chara[i] = chara;
    line->buffer->attr[i] = attr;
  }
  line->flags |= FRAMEBUF_CONTEXT_FRAME_FLAG_DIRTY;
  return 0;
}

static int lineclear(framebuf_context_t context, int chara, unsigned int attr)
{
  int i;
  for (i = 0; i < context->height; i++) {
    context->line[i].buffer = &linebuf[i];
    frameline_clear(&context->line[i], context->width, chara, attr);
  }
  return 0;
}

int framebuf_setscreen(int flags, int width, int height)
{
  if (flags < 0) {
    flags = framebuf_get_screen_flags();
    if (flags == NLL_F_DISABLE)
      flags = NLL_F_ENABLE;
  }

  switch (flags) {
  case NLL_F_DISABLE:
    if (context) {
      _disable(context);
      _done(context);
      context = NULL;
      _enable(context);
      flush_reset(context);
      flush_cursor_on(context, 1);
    }
    break;

  default:
    _disable(context);

    if (context) {
      width  = (width  < 0) ? context->width  : width;
      height = (height < 0) ? context->height : height;
    }

    width  = ((width  > 0) && (width  <= FRAMEBUF_MAXWIDTH ))
      ? width  : FRAMEBUF_DEFAULT_WIDTH;
    height = ((height > 0) && (height <= FRAMEBUF_MAXHEIGHT))
      ? height : FRAMEBUF_DEFAULT_HEIGHT;

    context = &_context;
    memset(context, 0, sizeof(*context));

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

    context->cursor.x = 0;
    context->cursor.y = 0;
    context->cursor.attr = DEFAULT_ATTR;

    _init(context);

    _enable(context);

    flush_reset(context);
    flush_cursor_on(context, (flags & NLL_F_CURSOR) ? 1 : 0);

    lineclear(context, DEFAULT_CHARA, DEFAULT_ATTR);
    flush(context, 0);

    break;
  }

  return 0;
}

int framebuf_enable(int enable)
{
  return enable ? _enable(context) : _disable(context);
}

int framebuf_cursor_on(void)
{
  if (!context) {
    _cursor_on(nll_stdout);
    _flush(nll_stdout);
    return 0;
  }

  flush_cursor_on(context, 1);

  return 0;
}

int framebuf_cursor_off(void)
{
  if (!context) {
    _cursor_off(nll_stdout);
    _flush(nll_stdout);
    return 0;
  }

  flush_cursor_on(context, 0);

  return 0;
}

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

  flush(context, flags);

  return 0;
}

int framebuf_seteffect(int effect)
{
  if (!context) {
    if ((effect < 0) || (effect > 9))
      ;
    else {
      _effect(nll_stdout, effect);
      _flush(nll_stdout);
    }
    return 0;
  }

  if ((effect < 0) || (effect > 9))
    effect = GET_EFFECT(context->cursor.attr);

  context->cursor.attr =
    (context->cursor.attr & ~EFFECT_MASK) | GET_ATTR(effect, 0, 0);

  return 0;
}

int framebuf_setfgcolor(int color)
{
  if (!context) {
    if ((color < 0) || (color > 9))
      ;
    else {
      _fgcolor(nll_stdout, color);
      _flush(nll_stdout);
    }
    return 0;
  }

  if ((color < 0) || (color > 9))
    color = GET_FGCOLOR(context->cursor.attr);

  context->cursor.attr =
    (context->cursor.attr & ~FGCOLOR_MASK) | GET_ATTR(0, color, 0);

  return 0;
}

int framebuf_setbgcolor(int color)
{
  if (!context) {
    if ((color < 0) || (color > 9))
      ;
    else {
      _bgcolor(nll_stdout, color);
      _flush(nll_stdout);
    }
    return 0;
  }

  if ((color < 0) || (color > 9))
    color = GET_BGCOLOR(context->cursor.attr);

  context->cursor.attr =
    (context->cursor.attr & ~BGCOLOR_MASK) | GET_ATTR(0, 0, color);

  return 0;
}

int framebuf_move_cursor(int x, int y)
{
  if (!context) {
    _move(nll_stdout, x, y);
    _flush(nll_stdout);
    return 0;
  }

  x = ((x < 0) || (x > context->width  - 1)) ? context->cursor.x : x;
  y = ((y < 0) || (y > context->height - 1)) ? context->cursor.y : y;

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

  return 0;
}

int framebuf_move_left(int num)
{
  if (!context) {
    _movex(nll_stdout, -num);
    _flush(nll_stdout);
    return 0;
  }

  if (num > context->cursor.x)
    num = context->cursor.x;

  context->cursor.x -= num;

  return 0;
}

int framebuf_move_right(int num)
{
  if (!context) {
    _movex(nll_stdout, num);
    _flush(nll_stdout);
    return 0;
  }

  if (num > context->width - 1 - context->cursor.x)
    num = context->width - 1 - context->cursor.x;

  context->cursor.x += num;

  return 0;
}

int framebuf_move_up(int num)
{
  if (!context) {
    _movey(nll_stdout, -num);
    _flush(nll_stdout);
    return 0;
  }

  if (num > context->cursor.y)
    num = context->cursor.y;

  context->cursor.y -= num;

  return 0;
}

int framebuf_move_down(int num)
{
  if (!context) {
    _movey(nll_stdout, num);
    _flush(nll_stdout);
    return 0;
  }

  if (num > context->height - 1 - context->cursor.y)
    num = context->height - 1 - context->cursor.y;

  context->cursor.y += num;

  return 0;
}

int framebuf_getch(int x, int y)
{
  if (!context) {
    return -1;
  }

  x = ((x < 0) || (x > context->width  - 1)) ? context->cursor.x : x;
  y = ((y < 0) || (y > context->height - 1)) ? context->cursor.y : y;

  return context->line[y].buffer->chara[x];
}

int framebuf_clear(int chara, int flags)
{
  if (!context) {
    if (chara >= 0) {
      _clear(nll_stdout);
      _flush(nll_stdout);
    }
    return 0;
  }

  chara = (chara < 0) ? DEFAULT_CHARA : chara;

  lineclear(context, chara, context->cursor.attr);

  if ((flags | context->flags) & NLL_F_FLUSH)
    flush(context, flags);

  return 0;
}

static int _scroll_x(int num, int chara)
{
  int i, x, n;
  struct linebuf *buffer;

  if (!context) {
    return 0;
  }

  for (i = 0; i < context->height; i++) {
    buffer = context->line[i].buffer;
    for (x = 0; x < context->width; x++) {
      n = (num < 0) ? context->width - x - 1 : x;
      if ((n + num < 0) || (n + num > context->width - 1)) {
	buffer->chara[n] = chara;
	buffer->attr[n]  = context->cursor.attr;
      } else {
	buffer->chara[n] = buffer->chara[n + num];
	buffer->attr[n]  = buffer->attr[n + num];
      }
    }
    context->line[i].flags |= FRAMEBUF_CONTEXT_FRAME_FLAG_DIRTY;
  }

  return 0;
}

static int _scroll_up(int num, int chara)
{
  int i;
  struct linebuf *buffer;

  if (!context) {
    _scroll_y(nll_stdout, -num);
    _flush(nll_stdout);
    return 0;
  }

  for (i = context->height - 1; i >= 0; i--) {
    if (i >= num) {
      buffer = context->line[i].buffer;
      context->line[i].buffer = context->line[i - num].buffer;
      context->line[i - num].buffer = buffer;
    } else {
      frameline_clear(&context->line[i], context->width, chara, context->cursor.attr);
    }
    context->line[i].flags |= FRAMEBUF_CONTEXT_FRAME_FLAG_DIRTY;
  }

  return 0;
}

static int _scroll_down(int num, int chara)
{
  int i;
  struct linebuf *buffer;

  if (!context) {
    _scroll_y(nll_stdout, num);
    _flush(nll_stdout);
    return 0;
  }

  for (i = 0; i < context->height; i++) {
    if (i + num < context->height) {
      buffer = context->line[i].buffer;
      context->line[i].buffer = context->line[i + num].buffer;
      context->line[i + num].buffer = buffer;
    } else {
      frameline_clear(&context->line[i], context->width, chara, context->cursor.attr);
    }
    context->line[i].flags |= FRAMEBUF_CONTEXT_FRAME_FLAG_DIRTY;
  }

  return 0;
}

int framebuf_scroll(int x, int y, int chara)
{
  chara = (chara < 0) ? DEFAULT_CHARA : chara;

  if      (x)     _scroll_x(x, chara);
  if      (y < 0) _scroll_up(-y, chara);
  else if (y > 0) _scroll_down(y, chara);

  if (context->flags & NLL_F_FLUSH)
    flush(context, 0);

  return 0;
}

int framebuf_puts(const char *str)
{
  int len, i;

  if (!context) {
    nll_wait_output(nll_stdout);
    fputs(str, nll_stdout);
    _flush(nll_stdout);
    return 0;
  }

  len = strlen(str);

  for (i = 0; i < len; i++) {
    switch (str[i]) {
    case '\n':
      goto newline;

    default:
      if (context->cursor.x < context->width) {
	context->line[context->cursor.y].buffer->chara[context->cursor.x] = str[i];
	context->line[context->cursor.y].buffer->attr[context->cursor.x] = context->cursor.attr;
	context->line[context->cursor.y].flags |= FRAMEBUF_CONTEXT_FRAME_FLAG_DIRTY;
	if (++context->cursor.x >= context->width)
	  goto newline;
      }
      break;

    newline:
      context->cursor.x = 0;
      if (context->cursor.y < context->height - 1)
	context->cursor.y++;
      else
	_scroll_down(1, DEFAULT_CHARA);
      break;
    }
  }

  if (context->flags & NLL_F_FLUSH)
    flush(context, 0);

  return 0;
}

static int _cursorpos(int *x, int *y)
{
  if (!context) {
    *x = (*x == NLL_F_CURSORPOS) ? 0 : *x;
    *y = (*y == NLL_F_CURSORPOS) ? 0 : *y;
    return 0;
  }

  *x = (*x == NLL_F_CURSORPOS) ? context->cursor.x : *x;
  *y = (*y == NLL_F_CURSORPOS) ? context->cursor.y : *y;

  return 0;
}

static int _put(int x, int y, const char *str)
{
  if ((x < 0) || (y < 0))
    return -1;
  if (context) {
    if ((x > context->width - 1) || (y > context->height - 1))
      return -1;
  }
  framebuf_move_cursor(x, y);
  framebuf_puts(str);
  return 0;
}

int framebuf_line(int x0, int y0, int x1, int y1, const char *str)
{
  int nx, ny, n, d, dx, dy;

  _cursorpos(&x0, &y0);
  _cursorpos(&x1, &y1);

  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);
      }
    }
    _put(dx, dy, str);
  }

  return 0;
}

int framebuf_box(int x0, int y0, int x1, int y1, const char *str, int flags)
{
  int x, y;

  _cursorpos(&x0, &y0);
  _cursorpos(&x1, &y1);

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

  if (flags & NLL_F_FILL) {
    for (y = y0; y <= y1; y++) {
      for (x = x0; x <= x1; x++) {
	_put(x, y, str);
      }
    }
  } else {
    for (x = x0; x <= x1; x++)
      _put(x, y0, str);
    if (y0 < y1) {
      for (x = x0; x <= x1; x++)
	_put(x, y1, str);
    }
    if (y0 + 1 < y1) {
      for (y = y0 + 1; y <= y1 - 1; y++)
	_put(x0, y, str);
      if (x0 < x1) {
	for (y = y0 + 1; y <= y1 - 1; y++)
	  _put(x1, y, str);
      }
    }
  }

  return 0;
}

key_code_t framebuf_getkey(void)
{
  int key;
  key = nll_term_key(context ? context->flags : 0);

  switch (key) {
  case NLL_TERM_KEY_END:      return KEY_CODE_END;
  case NLL_TERM_KEY_UP:       return KEY_CODE_UP;
  case NLL_TERM_KEY_DOWN:     return KEY_CODE_DOWN;
  case NLL_TERM_KEY_RIGHT:    return KEY_CODE_RIGHT;
  case NLL_TERM_KEY_LEFT:     return KEY_CODE_LEFT;
  case NLL_TERM_KEY_PAGEUP:   return KEY_CODE_PAGEUP;
  case NLL_TERM_KEY_PAGEDOWN: return KEY_CODE_PAGEDOWN;
  default:
    break;
  }

  return key;
}
#endif
