#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <time.h>
#include <kernel.h>
#include <swis.h>
// oslib
#include <oslib/os.h>
#include <oslib/osbyte.h>
#include <oslib/osword.h>
#include <oslib/osspriteop.h>
#include <oslib/colourtrans.h>
#include <oslib/socket.h>
#include <oslib/wimp.h>

#include "vncserv.h"
#include "mcode.h"
#include "server.h"
#include "proto.h"
#include "pixtrans.h"
#include "debug.h"
#include "profiling.h"
#include "circular.h"
#include "frontend.h"
#include "task.h"
#include "sockevent.h"
#include "ipserv.h"
#include "cursor.h"
#include "hextile.h"
#include "raw.h"
#include "zrle.h"
#include "ipservfe.h"
#include "header.h"
#include "filter.h"
#include "keys.h"

#define NOT_YET_SET      65536

#define KEY_STRETCH_TIME 3

/* VNC connection/client state */
struct vnc_client_s
{
  socket_s socket;
  struct vncserv_s *server;
  bool mode_valid; // True if the server has accepted myscreen, false if myscreen is invalid or not accepted
  socket_event_status_t socket_event;
  const pointer_shape *pending_cursor;
  circular_buffer buffer;
  bool fatal_error_occurred;
  bool use_scroll_event;
  int nexty;
  int y_offset;
  unsigned int screen_update_tick;
  unsigned int last_tick_callback_time; /* Timestamp of the first unfulfilled tick callback request; gets reset to 0 when the callback is received. We only track the time when we're actually expecting a callback, so that we won't get confused if we block ourselves somehow (e.g. waiting for long async op) */
  char *prevscreen;
  uint8_t pressedkeys[128];
  clipboard_text *client_clipboard; /* Text being received from client */
  clipboard_text *app_clipboard; /* Text that was last sent to client */
};

static const int slice_y = 64;

static int checkscreenmode(vnc_client_t *client);
static int check_area(vnc_client_t *client, int y0, int y1);
static void poll_sending(vnc_client_t *client);
static void server_socket_event(void *param);
static void server_updatepalette(vnc_client_t *client);
static void call_key_vector(vnc_client_t *client, int keyno, int down);

static int io_read(const vncserv *serv, void *buffer, int bytes, void *meta);
static int io_write(const vncserv *serv, const void *buf, int bytes, void *meta);
static int io_keypress(const vncserv *serv, int key, int down, void *meta);
static int io_pointer(const vncserv *serv, int x, int y, int buttons, void *meta);
static int io_cuttext(const vncserv *serv, const char *text, int len, int remain, void *meta);
static int io_error(const vncserv *serv, const char *msg, void *meta);
static int io_get_tx_space(const vncserv *serv, void *meta);
static void io_exclusive(const vncserv *serv, void *meta);

// ---------------------------------------------------------------------
// api

vnc_client_t *server_create(socket_s socket, const server_config_t *config)
{
  vnc_client_t *client = malloc(sizeof(vnc_client_t));
  if (!client)
  {
    dprintf("client_create(): malloc failed\n");
    xsocket_close(socket);
    return NULL;
  }

  memset(client, 0, sizeof(vnc_client_t));
  client->socket = socket;
  dprintf("client->socket = %d\n",client->socket);

  client->pending_cursor = &os_state.pointer_shapes[os_state.current_pointer];

  /* TX buffer size is based around the size of the largest amount we write in one 'packet' */
  int buffersize = MAX(MAX(CURSOR_MAX_PACKET, HEXTILE_MAX_PACKET), MAX(RAW_MAX_PACKET, ZRLE_MAX_PACKET));
  /* Double the size so that there's enough space for a full packet and a half-sent packet */
  buffersize += buffersize;

  if (!circular_init(&client->buffer, buffersize))
  {
    server_close(client);
    return NULL;
  }

  /* Check if the WindowScroll module is present (to try and work out
     whether Event_PointerScroll is likely to do something) */
  client->use_scroll_event = !xos_cli("RMEnsure WindowScroll 0.01");

  vncservio io;

  // fill in the vncservio structure
  memset(&io, 0, sizeof(vncservio));
  io.read = io_read;
  io.write = io_write;
  io.keypress = io_keypress;
  io.pointer = io_pointer;
  io.cuttext = io_cuttext;
  io.error = io_error;
  io.get_tx_space = io_get_tx_space;
  io.exclusive = io_exclusive;

  client->server = vncserver_create(config, &io, client);
  if (!client->server) {
    server_close(client);
    return NULL;
  }

  socket_event_init(&client->socket_event, client->socket, client, server_socket_event);

  checkscreenmode(client);

  vncserv_started(client->server);

  return client;
}

void server_close(vnc_client_t *client)
{
  if (!client)
  {
    return;
  }

  int keyno;

  // release all keys
  for (keyno = 0; keyno < 128; keyno++)
    if (client->pressedkeys[keyno])
      press_release_key(keyno, 0);

  if (client->server)            vncserv_closedown(client->server);
  client->server = NULL;

  socket_event_stop(&client->socket_event);

  circular_free(&client->buffer);

  clipboard_deref(&client->client_clipboard);
  clipboard_deref(&client->app_clipboard);
  task_clipboard_release(client);

  if (client->prevscreen)        free(client->prevscreen);
  client->prevscreen = NULL;

  dprintf("close socket %d\n",client->socket);
  xsocket_close(client->socket);

  free(client);
}

void server_update(vnc_client_t *client)
{
  int err_poll;
  unsigned int now = clock();

  /* Deal with key stretching */
  for (int i=0;i<128;i++)
  {
    int k = client->pressedkeys[i];
    if (!(k & 127))
      continue;
    k--;
    if (!k)
    {
      /* Release stretched key */
      dprintf("Releasing stretched key (scancode %x)\n",i);
      press_release_key(i, 0);
    }
    client->pressedkeys[i] = k;
  }

  err_poll = vncserv_poll(client->server, 1);

  if (err_poll != VNCSERV_OK) {
    dprintf("Polling returned error %d\n", err_poll);
    // error, so close and wait for new client
    client->fatal_error_occurred = true;
    return;

  } else if (client->buffer.used) {
    poll_sending(client);

    // make sure there's room for something
    if (client->buffer.size - client->buffer.used < 10000) {
      dprintf("[%d] Buffer almost full (%d of %d free)\n",
                       now, client->buffer.size-client->buffer.used, client->buffer.size);
      return;
    }
  }

  /* Disable screen update checks when we're sending an update. TODO - Can re-enable once the server is smart enough to not re-flag areas as dirty if we've updated them while they're still queued for send. May also need to consider impact on DesktopSize encoding. */
  if (client->fatal_error_occurred || (client->server->state != VNCSERVSTATE_READY))
  {
    return;
  }

  const pointer_shape *pending_cursor = client->pending_cursor;
  if (pending_cursor && client->mode_valid && cursor_will_send(client->server, pending_cursor))
  {
    /* Clear flag before processing, so that any update from IRQ while we're processing will get caught next time round */
    client->pending_cursor = NULL;

    vncserv_update_cursor(client->server, pending_cursor);
  }

  clipboard_text *server_text = task_clipboard_get();
  if (client->server->config.enable_clipboard /* Enabled */
  && (server_text != client->app_clipboard) /* And different */
  && (!server_text || (server_text->source != client))) /* And not bouncing it back to the client we got it from */
  {
    if (!server_text || (vncserv_cut_text(client->server, server_text) == VNCSERV_OK))
    {
      /* Text sent OK (or queued for async send), remember that we've sent it */
      clipboard_deref(&client->app_clipboard);
      client->app_clipboard = clipboard_addref(&server_text);
    }
  }

  if (checkscreenmode(client))
  {
    dprintf("Screen mode changed, restarting\n");
    client->fatal_error_occurred = true;
    return;
  }

  /* Prevent screen updates if server hasn't accepted screen yet (e.g. waiting for async desktopsize) */
  if (!client->mode_valid)
  {
    dprintf("server_update: !mode_valid\n");
    return;
  }

  bool scan_screen = !filter_is_init();
  if (!scan_screen)
  {
    /* Limit frequency of screen updates - helps avoid flickering and high CPU/network usage if one area is being redrawn frequently */
    if (client->screen_update_tick >= 7)
    {
      /* Kick the task into giving us a callback
         We use this as (a) a heartbeat that the filter manager/wimp is actually running and (b) to ensure we aren't in the middle of a redraw op when we scan the screen for changes (avoids wasting CPU/bandwidth when dealing with windows which can get stuck in a refresh loop, e.g. task manager) */
      task_tick();

      /* If it's been too long since the last callback, fall back to manual screen scanning */
      if (!client->last_tick_callback_time)
      {
        client->last_tick_callback_time = now;
      }
      else if ((now - client->last_tick_callback_time) > 15)
      {
        if (client->screen_update_tick == 7)
        {
          dprintf("Wimp task not responding, falling back to manual screen scanning\n");
          client->screen_update_tick = 8;
        }
        scan_screen = true;
      }
    }
    else
    {
      client->screen_update_tick++;
    }
  }

  if (scan_screen)
  {
    int y = client->nexty + slice_y;
    if (y > os_state.myscreen.height)
      y = os_state.myscreen.height;
    check_area(client, client->nexty, y);
    client->nexty = y;
    if (client->nexty == os_state.myscreen.height)
    {
      client->nexty = client->y_offset;
      client->y_offset = (client->y_offset+1) & 3;
    }
  }

  if (os_state.palette_dirty)
  {
    server_updatepalette(client);
    os_state.palette_dirty = false;
  }
}

int tick_callback_handler(_kernel_swi_regs *r, void *pw)
{
  FOR_EACH_CLIENT(client)
  {
    client->last_tick_callback_time = 0;
    if (client->fatal_error_occurred || (client->server->state != VNCSERVSTATE_READY) || (client->buffer.size - client->buffer.used < 10000) || (client->screen_update_tick < 7))
    {
      continue;
    }
    filter_update(client);
  }
  return 1;
}

bool server_has_fatal_error_occurred(vnc_client_t *client)
{
  return (client && client->fatal_error_occurred);
}

void server_release_key(vnc_client_t *client, int key) {
  call_key_vector(client, key, 0);
}

void server_checkmode(vnc_client_t *client) {
  if (!client)
  {
    return;
  }
  /* Force change to take place now, since we know the mode vars are valid */
  checkscreenmode(client);
}

int checkscreenmode(vnc_client_t *client)
{
  if (!os_state.myscreen.framebuffer)
  {
    dprintf("checkscreenmode: OS mode unknown!\n");
    client->fatal_error_occurred = true;
    client->mode_valid = false;
    return VNCSERV_FATAL_ERROR;
  }
  if (!client->server)
  {
    dprintf("checkscreenmode: No server!\n");
    client->fatal_error_occurred = true;
    client->mode_valid = false;
    return VNCSERV_FATAL_ERROR;
  }

  if (!os_state.sprite_redirection)
  {
    /* Update VDU start addr */
    int vars[2];
    vars[0] = os_VDUVAR_DISPLAY_START;
    vars[1] = -1;
    if (!xos_read_vdu_variables((os_vdu_var_list *)vars, vars))
    {
      os_state.myscreen.framebuffer = (void *) vars[0];
    }
  }

  servscreen newscreen = os_state.myscreen;
  newscreen.framebuffer = client->server->screen.framebuffer;
  /* Ensure stride is word multiple */
  newscreen.stride = (newscreen.bpl+3)&~3;
  if (!memcmp(&newscreen,&client->server->screen,sizeof(servscreen)))
  {
    /* No change in screen mode */
    client->mode_valid = true;
    return VNCSERV_OK;
  }

  dprintf("checkscreenmode: new screen %dx%dx%d @ %08x\n",newscreen.width,newscreen.height,newscreen.format.bits_per_pixel,newscreen.framebuffer);

  /* OS screen must have word multiple stride, and be word aligned (OS probably guarantees this for us, but better to be safe than sorry) */
  if ((((int) os_state.myscreen.framebuffer) & 3) || (os_state.myscreen.stride & 3))
  {
    dprintf("OS screen unaligned!\n");
    client->fatal_error_occurred = true;
    client->mode_valid = false;
    return VNCSERV_FATAL_ERROR;
  }

  /* If the width or height has changed and desktopsize isn't supported then we might as well stop now */
  bool desktopsize = client->server->screen.framebuffer && ((client->server->screen.width != newscreen.width) || (client->server->screen.height != newscreen.height));
  if (desktopsize && !client->server->encodings.desktopsize)
  {
    dprintf("Screen size changed but desktopsize not supported\n");
    client->fatal_error_occurred = false;
    client->mode_valid = false;
    return VNCSERV_FATAL_ERROR;
  }

  /* Check if the server can accept a mode change at this point in time */
  if (!vncserv_can_desktopsize(client->server))
  {
    client->mode_valid = false; /* Postpone our shadow buffer update logic until the server can accept the new mode */
    return VNCSERV_OK;
  }

  /* Resize screen buffer */
  int current_prevscreensize = client->server->screen.bpl*client->server->screen.height;
  int new_prevscreensize = newscreen.bpl*newscreen.height;

  dprintf("current prevscreen size %d\n",current_prevscreensize);

  if ((current_prevscreensize != new_prevscreensize) && client->prevscreen)
  {
    free(client->prevscreen);
    client->prevscreen = NULL;
    client->server->screen.framebuffer = NULL;
  }

  bool refresh = false;

  if (!client->prevscreen)
  {
    client->prevscreen = malloc(new_prevscreensize);
    if (!client->prevscreen)
    {
      dprintf("Failed to allocate prevscreen\n");
      client->fatal_error_occurred = true;
      client->mode_valid = false;
      return VNCSERV_FATAL_ERROR;
    }
    for (int y=0;y<os_state.myscreen.height;y++)
    {
      const char *src = ((const char *) os_state.myscreen.framebuffer) + y*os_state.myscreen.stride;
      char *dest = client->prevscreen + y*newscreen.stride;
      memcpy(dest, src, newscreen.bpl);
    }
    refresh = true;
  }

  newscreen.framebuffer = client->prevscreen;
  if (desktopsize)
  {
    client->nexty = 0;
  }

  /* Let the server know about the change */
  int ret = vncserv_desktopsize(client->server, &newscreen); /* Updates client->server->screen */
  if (ret != VNCSERV_OK)
  {
    dprintf("vncserv_desktopsize failed\n");
    client->mode_valid = false;
    return ret;
  }
  client->mode_valid = true;

  if (!os_state.myscreen.format.true_colour_flag) /* TODO be more sensible with palette updates */
  {
    server_updatepalette(client);
  }

  if (refresh)
  {
    area bbox;
    bbox.x = 0;
    bbox.y = 0;
    bbox.w = os_state.myscreen.width;
    bbox.h = os_state.myscreen.height;
    vncserv_framebuffer_changed(client->server, &bbox);
  }

  dprintf("desktopsize %d refresh %d ptr %08x\n",desktopsize,refresh,client->server->screen);

  return VNCSERV_OK;
}

void server_set_cursor(vnc_client_t *client, const pointer_shape *shape) {
  if (client)
  {
    client->pending_cursor = shape;
  }
}

void server_status(vnc_client_t *client)
{
  char temp[256];
  char *status = "Connected?";
  socket_sockaddr addr;
  int namelen = sizeof(socket_sockaddr);
  if (!xsocket_getpeername(client->socket,&addr,&namelen))
  {
    if (addr.sockaddr_in.af == socket_AF_INET)
    {
      sprintf(temp,"Connected to client %d.%d.%d.%d:%d",addr.sockaddr_in.addr&255,(addr.sockaddr_in.addr>>8)&255,(addr.sockaddr_in.addr>>16)&255,(addr.sockaddr_in.addr>>24)&255,htons(addr.sockaddr_in.port));
      status = temp;
    }
  }
  printf("  %s\n",status);
  printf("  Server port: %d\n",client->server->config.port);

  status = "Unknown";

  switch(client->server->state)
  {
  case VNCSERVSTATE_IDLE:
    status = "Idle";
    break;
  case VNCSERVSTATE_WAITING_FOR_PROTOCOL_VERSION:
    status = "Waiting for protocol version";
    break;
  case VNCSERVSTATE_WAITING_FOR_AUTH:
    status = "Waiting for auth";
    break;
  case VNCSERVSTATE_WAITING_FOR_CLIENT_INIT:
    status = "Waiting for client init";
    break;
  case VNCSERVSTATE_READY:
    status = "Running";
    break;
  case VNCSERVSTATE_SENDING_UPDATE:
    status = "Sending framebuffer update";
    break;
  case VNCSERVSTATE_SENDING_TEXT:
    status = "Sending clipboard text";
    break;
  }
  printf("  Protocol state: %s\n",status);

  printf("  Screen mode valid: %s\n",YN(client->mode_valid));
  printf("  TX buffer: %d/%d hwm %d\n",client->buffer.used,client->buffer.size,client->buffer.hwm);
  client->buffer.hwm = 0;

  if (client->server->state < VNCSERVSTATE_READY)
  {
    return;
  }

  printf("  ZRLE encoder created: %s\n",YN(client->server->zrle));
  printf("  zlib encoder created: %s\n",YN(client->server->zlib));

  printf("  Recognised encodings supported by client:\n");
  if (client->server->encodings.hextile)
    printf("    Hextile\n");
  if (client->server->encodings.copy)
    printf("    CopyRect\n");
  if (client->server->encodings.hextiledithering)
    printf("    Hextile (dithered)\n");
  if (client->server->encodings.zrle)
    printf("    ZRLE\n");
  if (client->server->encodings.zlib)
    printf("    zlib\n");
  if (client->server->encodings.desktopsize)
    printf("    DesktopSize\n");
  if (client->server->encodings.cursor)
    printf("    Cursor\n");
  if (client->server->encodings.xcursor)
    printf("    XCursor\n");

  printf("  Server pixel format:\n");
  print_format(&client->server->screen.format,"    ");

  printf("  Client pixel format:\n");
  print_format(&client->server->clientformat,"    ");

  printf("  Server screen mode:\n");
  print_screen(&client->server->screen,"    ");
}

void server_updatepalette(vnc_client_t *client)
{
  if (!client || !client->mode_valid || os_state.myscreen.format.true_colour_flag)
    return;

  unsigned int palette[256];
  xcolourtrans_read_palette((osspriteop_area *)-1, (osspriteop_id)-1,
                            (os_palette *)palette, 1024, 0, NULL);

  /* Include pointer colours if we have the space */
  if (os_state.myscreen.format.depth < 8)
  {
    int start = 1<<os_state.myscreen.format.depth;
    xos_read_palette(1,25,&palette[start],NULL);
    xos_read_palette(2,25,&palette[start+1],NULL);
    xos_read_palette(3,25,&palette[start+2],NULL);
  }
  vncserv_set_8bpp_palette(client->server, palette);
}

static inline int mylog2(int val)
{
  /* Assumes 'val' is a power of 2! */
  return 31-__builtin_clz(val);
}

void server_framebuffer_changed(vnc_client_t *client, const area *a, bool unlikely)
{
  if (!client->mode_valid)
  {
    return;
  }
  if (!unlikely)
  {
    /* Reset screen update tick (should really be in server_update!) */
    client->screen_update_tick = 0;
  }

  /* Essentially, check_area, but bound to a rectangle */
  int xc0, xc1, yc0, yc1, y, bpl, os_stride, my_stride;
  area bbox;

  PROFILE_FUNC_BEGIN

  xc0 =  NOT_YET_SET;
  xc1 = -NOT_YET_SET;
  yc0 =  NOT_YET_SET;
  yc1 = -NOT_YET_SET;

  int l2bpp = mylog2(os_state.myscreen.format.bits_per_pixel);

  bpl = os_state.myscreen.bpl;
  os_stride = os_state.myscreen.stride;
  my_stride = client->server->screen.stride; /* Safe??? */

  /* calculate start and end offsets in bits */
  int line_start_offset = a->x << l2bpp;
  int line_end_offset = (a->x+a->w) << l2bpp;
  /* ensure start is word-aligned */
  line_start_offset &= ~31;
  /* convert to bytes */
  line_start_offset >>= 3;
  line_end_offset = (line_end_offset+7) >> 3;
  if (line_start_offset < 0)
  {
    line_start_offset = 0;
  }
  if (line_end_offset > bpl)
  {
    line_end_offset = bpl;
  }
  if (line_start_offset >= line_end_offset)
  {
    return;
  }

  for (y=a->y;y<a->y+a->h;y++) {
    if ((y < 0) || (y >= os_state.myscreen.height)) {
      continue;
    }
    char *scr, *prev;
    int xd0, xd1;
    scr  = ((char*)os_state.myscreen.framebuffer) + y*os_stride + line_start_offset;
    prev = client->prevscreen + y*my_stride + line_start_offset;
    compare_screens(&xd0, &xd1, scr, prev, line_end_offset - line_start_offset);
    if (xd0 >= 0) {
      xd0 += line_start_offset;
      xd1 += line_start_offset;
      xd0 = (xd0*8) >> l2bpp;      // convert from bytes to pixels
      xd1 = (xd1*8) >> l2bpp;
      if (xd0 < xc0)   xc0 = xd0;
      if (xd1 > xc1)   xc1 = xd1;
      if (yc0 == NOT_YET_SET)
      {
        yc0 = yc1 = y;
      }
      else
        yc1 = y;
    }
  }

  PROFILE_FUNC_END
  PROFILE_FUNC_METRIC(a->w*a->h)

  if (xc0 == NOT_YET_SET)  return;
  if (xc0 < 0)   xc0 = 0;
  if (yc0 < 0)   yc0 = 0;
  if (xc1 > os_state.myscreen.width)   xc1 = os_state.myscreen.width;
  if (yc1 > os_state.myscreen.height)   yc1 = os_state.myscreen.height;
  bbox.x = xc0;
  bbox.y = yc0;
  bbox.w = xc1-xc0;
  bbox.h = yc1-yc0+1;
  if (bbox.h > os_state.myscreen.height)   bbox.h = os_state.myscreen.height;
  vncserv_framebuffer_changed(client->server, &bbox);
  return;
}

// -------------------------------------------------------------
// io functions



// io_read() is called by the server to read data from the client
int io_read(const vncserv *serv, void *buffer, int bytes, void *meta) {
  int n;
  int errnox;
  os_error *err;
  vnc_client_t *client = (vnc_client_t *) meta;
  PROFILE_FUNC_BEGIN
// dprintf("xsocket_recv %d %x %d\n", client->socket, buffer, bytes);
  err = xsocket_recv(client->socket, buffer, bytes, 0, &n);
// dprintf("-> %d %08x %s\n",bytes,(err?err->errnum:0),(err?err->errmess:""));
  PROFILE_FUNC_END
  if ((!err) && (n >= 0)) {
    PROFILE_FUNC_METRIC(n)
    return n;
  }
  errnox = err->errnum &~IYONIX_SOCKET_ERR;
  if (errnox == 0)     return VNCSERV_OK;
  if (errnox == socket_EWOULDBLOCK || errnox == socket_EAGAIN)   return VNCSERV_OK;
  dprintf("Error in io_read, error %d\n", errnox);
  client->fatal_error_occurred = 1;
  return VNCSERV_FAILED_TO_READ;
}

// io_write() is called by the server to write data to the client
int io_write(const vncserv *serv, const void *buf, int bytes, void *meta) {

  vnc_client_t *client = (vnc_client_t *) meta;
  PROFILE_FUNC_BEGIN
  if (!circular_write(&client->buffer, (const char *) buf, bytes))
  {
    dprintf("io_write: Buffer overflow (%d %d %d)\n",client->buffer.size,client->buffer.used,bytes);
    PROFILE_FUNC_END
    return VNCSERV_FAILED_TO_WRITE;
  }
  PROFILE_FUNC_END
  PROFILE_FUNC_METRIC(bytes)
  poll_sending(client);
  return VNCSERV_OK;
}

// io_keypress() is called by the server when the user presses/releases a key
int io_keypress(const vncserv *serv, int key, int down, void *meta) {
  vnc_client_t *client = (vnc_client_t *) meta;
  int keyno;

  if (!serv->config.enable_kbd)   return VNCSERV_OK;

//  dprintf("key %d down %d\n",key,down);

  /* Check if it's a special key */
  keyno = x11_to_special_llkey(key);
  if (keyno != -1)
  {
    /* Use KeyV */
    call_key_vector(client, keyno, down);
    task_clipboard_tick();
    return VNCSERV_OK;
  }

  /* Try converting to the current alphabet */
  int unicode = x11_to_unicode(key);
  if (unicode == -1)
  {
    dprintf("Unsupported X11 key received: %d (%x)\n", key, key);
    return VNCSERV_OK;
  }

//  dprintf("X11 %x -> unicode %x\n", key, unicode);

  /* Some software detects Ctrl or Shift-Ctrl hotkeys via INKEY, so if ctrl is held prefer to generate KeyV events rather than inserting bytes into the keyboard buffer */
  int ctrl = (client->pressedkeys[os_TRANSITION_KEY_LEFT_CONTROL] | client->pressedkeys[os_TRANSITION_KEY_RIGHT_CONTROL]) & 128;
  int llkey = unicode_to_llkey(unicode);
  if (llkey != -1)
  {
    if (!down || ctrl)
    {
      /* Always try KeyV if key going up - to avoid key being stuck down if Ctrl was released before the character key */
      call_key_vector(client, llkey, down);
      task_clipboard_tick();
      return VNCSERV_OK;
    }
  }
  else if (!down)
  {
    return VNCSERV_OK;
  }
  if (ctrl)
  {
    /* Unhandled ctrl-key, use old behaviour of inserting (modified) ASCII code into buffer */
    insert_character(unicode & 31, false);
  }
  else
  {
    const int *alphabet = get_alphabet();
    if (alphabet)
    {
      int c = unicode_to_alphabet(unicode, alphabet);
      if (c == -1)
      {
        dprintf("No mapping in alphabet for X11 key: %d (%x)\n", key, key);
      }
      else
      {
//        dprintf("unicode %x -> alpha %x via %x\n", unicode, c, alphabet);
        insert_character(c, false);
      }
    }
    else
    {
      insert_character(unicode, true);
    }
  }
  return VNCSERV_OK;
}


// io_pointer() is called by the server when the user moves/clicks the mouse
int io_pointer(const vncserv *serv, int x, int y, int buttons, void *meta) {
  vnc_client_t *client = (vnc_client_t *) meta;
  if (!serv->config.enable_mouse)   return VNCSERV_OK;
  if (x != os_state.mouse_x || y != os_state.mouse_y) {
    oswordpointer_position_block pos;

    pos.op = oswordpointer_OP_SET_POSITION;
    pos.x = x<<os_state.myscreen.xeig;
    pos.y = (os_state.myscreen.height - y)<<os_state.myscreen.yeig;
    /* Manually clamp pointer to lie within bounding box (OS will ignore call if it's outside) */
    if (pos.x < os_state.pointer_bbox.left)
      pos.x = os_state.pointer_bbox.left;
    else if (pos.x > os_state.pointer_bbox.right)
      pos.x = os_state.pointer_bbox.right;
    if (pos.y < os_state.pointer_bbox.bottom)
      pos.y = os_state.pointer_bbox.bottom;
    else if (pos.y > os_state.pointer_bbox.top)
      pos.y = os_state.pointer_bbox.top;
    xoswordpointer_set_position(&pos);
    os_state.mouse_x = x;
    os_state.mouse_y = y;
  }
  int delta = buttons ^ os_state.mouse_buttons;
  if (delta) {
//    dprintf("Mouse buttons changed from %x to %x\n", os_state.mouse_buttons, buttons);
    if (delta & 4) {
      if (serv->config.swap_adjust_menu)
        call_key_vector(client, 0x71, buttons & 4);
      else
        call_key_vector(client, 0x72, buttons & 4);
    }
    if (delta & 2) {
      if (serv->config.swap_adjust_menu)
        call_key_vector(client, 0x72, buttons & 2);
      else
        call_key_vector(client, 0x71, buttons & 2);
    }
    if (delta & 1)
      call_key_vector(client, 0x70, buttons & 1);
    int scroll = delta & buttons;
    int scroll_x = ((scroll>>6)&1) - ((scroll>>5)&1);
    int scroll_y = ((scroll>>3)&1) - ((scroll>>4)&1);
    if (scroll_x | scroll_y)
    {
      /* Scroll wheel input. Send it via Event_Expansion 4 if WindowScroll is
         present. Otherwise, fall back to the old RISC OS 5 method - send a 
         manual scroll request to the window, and if that fails, send it to
         PointerV. */
      if (client->use_scroll_event)
      {
        _swix(OS_GenerateEvent, _INR(0,3), 21, Event_PointerScroll, scroll_x, scroll_y);
      }
      else
      {
        wimp_pointer ptr;
        /* Find the window currently under the mouse */
        if (!xwimp_get_pointer_info(&ptr))
        {
          wimp_window_state state;
          state.w = ptr.w;
          if ((state.w != wimp_BACKGROUND) && !xwimp_get_window_state(&state))
          {
            /* Does the window support scrolling, and does it have a scroll bar? */
            if (state.flags & (wimp_WINDOW_SCROLL_REPEAT | wimp_WINDOW_SCROLL))
            {
              if (state.flags & wimp_WINDOW_NEW_FORMAT)
              {
                if (!(state.flags & wimp_WINDOW_HSCROLL))
                  scroll_x = 0;
                if (!(state.flags & wimp_WINDOW_VSCROLL))
                  scroll_y = 0;
              }
              else
              {
                if (!(state.flags & (1<<3)))
                  scroll_x = 0;
                if (!(state.flags & (1<<2)))
                  scroll_y = 0;
              }
              if (scroll_x | scroll_y)
              {
                wimp_scroll scr;
                scr.w = state.w;
                scr.visible = state.visible;
                scr.xscroll = state.xscroll;
                scr.yscroll = state.yscroll;
                scr.next = state.next;
                scr.xmin = scroll_x;
                scr.ymin = scroll_y;
                scr.i = ptr.i;
                xwimp_send_message_to_window(wimp_SCROLL_REQUEST, (wimp_message *) &scr, ptr.w, ptr.i, NULL);
              }
            }
            scroll_x = scroll_y = 0;
          }
        }
        if (scroll_x | scroll_y)
        {
          /* Try PointerV. No OSLib support for this reason code! */
          _swix(OS_CallAVector, _INR(0,3) | _IN(9), 9, -scroll_y, 0, scroll_x, PointerV);
        }
      }
    }
    os_state.mouse_buttons = buttons;
    if (delta & 7)
    {
      task_clipboard_tick();
    }
  }
  return VNCSERV_OK;
}

int io_cuttext(const vncserv *serv, const char *text, int len, int remain, void *meta) {
  dprintf("io_cuttext: %d bytes received from client, %d to go\n",len,remain);
  vnc_client_t *client = (vnc_client_t *) meta;
  if (serv->config.enable_clipboard)
  {
    task_clipboard_put(&client->client_clipboard, client, text, len, remain);
  }
  return VNCSERV_OK;
}

int io_error(const vncserv *serv, const char *msg, void *meta) {
  // may be called with serv=NULL during vncserver_create()
  dprintf ("%s\n", msg);
  return VNCSERV_OK;
}

int io_get_tx_space(const vncserv *serv, void *meta) {
  vnc_client_t *client = (vnc_client_t *) meta;
  return client->buffer.size - client->buffer.used;
}

void io_exclusive(const vncserv *serv, void *meta) {
  vnc_client_t *client = (vnc_client_t *) meta;
  if (serv->config.allow_exclusive)
  {
    ipservfe_disconnect_all_but(client);
  }
}

// ---------------------------------------------------------------------

void call_key_vector(vnc_client_t *client, int keyno, int down) {
  if (!client)
  {
    return;
  }

  down = (down ? 128 : 0);
  if ((client->pressedkeys[keyno] & 128) == down) {
    dprintf("Key (scancode %x) already %s\n", keyno, down? "down" : "up");
    return;
  }

  /* Deal with kernel key debouncing, and clients which send rapid-fire up/down events, by implementing our own key-down stretching logic
     We want to apply it for anything the OS (technically, InternationalKeyboard) considers to be a 'shifting key' */
  if (down)
  {
    bool shifting = true;
    /* Query the OS for the current key handler */
    os_key_handler *h = NULL;
    if (global_config.stretch_keys
     && !xos_install_key_handler(NULL, &h) && h)
    {
      /* Check the table of shifting keys */
      shifting = false;
      const uint32_t *han = (const uint32_t *) h;
      if (han[1] & 0x80000000)
      {
        /* Wide key handler */
        const uint16_t *table = (const uint16_t *) (han[3] + (uint32_t) h);
        int count = *table++;
        while (count--)
        {
          if (*table++ == keyno)
          {
            shifting = true;
            break;
          }
        }
      }
      else
      {
        /* Narrow key handler */
        const uint8_t *table = (const uint8_t *) (han[3] + (uint32_t) h);
        int count = *table++;
        while (count--)
        {
          if (*table++ == keyno)
          {
            shifting = true;
            break;
          }
        }
      }
      /* Shifting keys are immune to debounce */
      if (!shifting)
      {
        down += KEY_STRETCH_TIME;
      }
    }
    /* Press it if it's not down/stretching */
    if (!client->pressedkeys[keyno])
    {
      dprintf("Pressing %skey (scancode %x)\n", shifting?"shifting ":"", keyno);
      press_release_key(keyno, 1);
      client->pressedkeys[keyno] = down;
    }
    else
    {
      dprintf("Re-pressing %skey (scancode %x)\n", shifting?"shifting ":"", keyno);
      /* Just set the down flag */
      client->pressedkeys[keyno] |= 128;
    }
  }
  else
  {
    int state = client->pressedkeys[keyno] & ~128;
    /* Release immediately if stretch timer has been reached */
    if (!state)
    {
      dprintf("Releasing key (scancode %x)\n", keyno);
      press_release_key(keyno, 0);
    }
    else
    {
      dprintf("Stretching key (scancode %x)\n", keyno);
    }
    client->pressedkeys[keyno] = state;
  }
}

int check_area(vnc_client_t *client, int y0, int y1) {
  int xc0, xc1, yc0, yc1, y, bpl, os_stride, my_stride, ystep;
  area bbox;

  PROFILE_FUNC_BEGIN

  xc0 =  NOT_YET_SET;
  xc1 = -NOT_YET_SET;
  yc0 =  NOT_YET_SET;
  yc1 = -NOT_YET_SET;

  int l2bpp = mylog2(os_state.myscreen.format.bits_per_pixel);

  bpl = os_state.myscreen.bpl;
  os_stride = os_state.myscreen.stride;
  my_stride = client->server->screen.stride; /* only safe because we've checked mode_valid earlier */
  ystep = 4;
  for (y=y0;y<y1;y+=ystep) {
    char *scr, *prev;
    int xd0, xd1;
    scr  = ((char*)os_state.myscreen.framebuffer) + y*os_stride;
    prev = client->prevscreen + y*my_stride;
    compare_screens(&xd0, &xd1, scr, prev, bpl);
    if (xd0 >= 0) {
      xd0 = (xd0*8) >> l2bpp;      // convert from bytes to pixels
      xd1 = (xd1*8) >> l2bpp;
      if (xd0 < xc0)   xc0 = xd0;
      if (xd1 > xc1)   xc1 = xd1;
      if (yc0 == NOT_YET_SET)
      {
        yc0 = yc1 = y;
        ystep = 1;
      }
      else
        yc1 = y;
    }
  }

  PROFILE_FUNC_END
  PROFILE_FUNC_METRIC((y1-y0)*os_state.myscreen.width)

  if (xc0 == NOT_YET_SET)  return 0;
  if (xc0 < 0)   xc0 = 0;
  if (yc0 < 0)   yc0 = 0;
  if (xc1 > os_state.myscreen.width)   xc1 = os_state.myscreen.width;
  if (yc1 > os_state.myscreen.height)   yc1 = os_state.myscreen.height;
  bbox.x = xc0;
  bbox.y = yc0;
  bbox.w = xc1-xc0;
  bbox.h = yc1-yc0+1;
  if (bbox.h > os_state.myscreen.height)   bbox.h = os_state.myscreen.height;
  vncserv_framebuffer_changed(client->server, &bbox);
  return 0;
}


void poll_sending(vnc_client_t *client) {
  static int busy;
  if (busy++)
  {
    dprintf("poll_sending busy!\n");
    busy--;
    return;
  }

  int written = 0, thistime, errnox;
  os_error *err = NULL;
  const int chunksize = 50000;

  PROFILE_FUNC_BEGIN

  do
  {
    int old_used = client->buffer.used;
    err = circular_tx(&client->buffer, client->socket, chunksize);
    thistime = old_used - client->buffer.used;
    written += thistime;
  }
  while (!err && (thistime > 0));

  if (written)
  {
//    dprintf("poll_sending: %d\n",written);
    PROFILE_FUNC_METRIC(written)
  }

  PROFILE_FUNC_END

  if (err) {
    errnox = err->errnum &~IYONIX_SOCKET_ERR;
    if ((errnox != 0) && (errnox != socket_EWOULDBLOCK) && (errnox != socket_EAGAIN) && (errnox != socket_ENOBUFS)) {
      dprintf("poll_sending: error %08x %s\n",err->errnum,err->errmess);
      client->fatal_error_occurred = 1;
    }
    busy--;
    return;
  }
  busy--;
}

void server_socket_event(void *param)
{
  vnc_client_t *client = param;
  /* Ignore the event if it's been too long since last callback (don't want to starve main callback) */
  unsigned int delta = ipservfe_last_callback_delta();
  if (delta > 10)
  {
    return;
  }
  if (!client->fatal_error_occurred)
  {
    if (client->buffer.used)
    {
      poll_sending(client);
    }
    vncserv_async_send_poll(client->server);
  }
}
