#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>

#include "vncserv.h"
#include "proto.h"
#include "vncauth.h"
#include "pixtrans.h"
#include "hextile.h"
#include "areas.h"
#include "debug.h"
#include "profiling.h"
#include "zrle.h"
#include "server.h"
#include "proto2.h"
#include "raw.h"
#include "cursor.h"
#include "dirtyrect.h"
#include "task.h"


#define IDLE_TIMEOUT 1000

#define DEFAULTBUFFERSIZE     2048

#define ASYNC_CURSOR_UPDATE 1
#define ASYNC_COLOURMAP 2
#define ASYNC_SCREEN 4

typedef struct {
  send_rect_state rect;
  uint8_t encoding;
  uint8_t flags;
  const pointer_shape *cursor;
  dirtyrect_buffer rects;
} async_state;

typedef struct {
  vncserv pub; // n.b. assumed to be first entry by to_public / to_private

  // password sutff
  char challenge[16];

  // input buffer
  char *buffer;
  int buffersize, bytesinbuffer;

  // client stuff
  dirtyrect_buffer dirty;
  area clientbox; // Area the client is interested in (from FramebufferUpdateRequest). If not empty we're clear to send a framebuffer update any time we want.

  int client_confirmed_format;
  int statetimeout;
  int started;
  int sharedflag;
  int errorhasoccured;
  bool flushedsomething;

  // Async framebuffer update state
  async_state async;
  uint8_t next_async;
  const pointer_shape *pending_cursor;

  // Async cuttext state
  clipboard_text *async_text;
  int async_text_offset;
} vncserv_private;


static inline vncserv *to_public(vncserv_private *serv)
{
  return (vncserv *) serv;
}

static inline vncserv_private *to_private(vncserv *serv)
{
  return (vncserv_private *) serv;
}





static void serv_error(vncserv_private *serv, const char *msg);
static void set_state(vncserv_private *serv, int state, int timeout_in_seconds);
static void fill_buffer(vncserv_private *serv, int max);
static void flush_buffer(vncserv_private *serv, int n);
static void read_set_pixel_format(vncserv_private *serv);
static void read_fix_colour_map_entries(vncserv_private *serv);
static void read_set_encodings(vncserv_private *serv);
static void read_framebuffer_update_request(vncserv_private *serv);
static void read_key_event(vncserv_private *serv);
static void read_pointer_event(vncserv_private *serv);
static void read_client_cut_text(vncserv_private *serv);
static void write_data(vncserv_private *serv, const void *bytes, int n);
static void async_queue(vncserv_private *serv, int flag);
static void async_send_update_poll(vncserv_private *serv);
static void async_send_text_poll(vncserv_private *serv);
static void serv_setcolourmapentries(vncserv_private *serv);



vncserv *vncserver_create(const server_config_t *config, vncservio *io, void *meta) {
  // create new vnc server

  vncserv_private *serv;

  if (!io)    return NULL;

  if (!io->write)
  {
    if (io->error)
      io->error(NULL, "io.write() not specified", NULL);
    return NULL;
  }

  serv = (vncserv_private *)malloc(sizeof(vncserv_private));
  if (!serv) {
    if (io->error) {
      char t[64];
      sprintf(t, "Failed to malloc() %d bytes", sizeof(vncserv_private));
      io->error(NULL, t, NULL);
    }
    return NULL;
  }
  memset(serv, 0, sizeof(vncserv_private));
  memcpy(&serv->pub.config, config, sizeof(server_config_t));
  memcpy(&serv->pub.io, io, sizeof(vncservio));
  serv->pub.meta = meta;
  serv->bytesinbuffer = 0;
  serv->buffersize = DEFAULTBUFFERSIZE;
  serv->buffer = malloc(serv->buffersize);
  if (!serv->buffer) {
    free(serv);
    if (io->error) {
      char t[64];
      sprintf(t, "Failed to malloc() %d bytes", serv->buffersize);
      io->error(NULL, t, NULL);
    }
    return NULL;
  }
  dirtyrect_init(&serv->dirty);
  reset_area(&serv->clientbox);
  serv->pub.encodings = (supported_encodings) { 0 };
  serv->pub.state = VNCSERVSTATE_IDLE;
  serv->statetimeout = 0;
  serv->started = 0;

  return to_public(serv);
}

int vncserver_set_buffer_size(vncserv *serv_public, int size) {
// set buffer size
  char *p;
  vncserv_private *serv = to_private(serv_public);

  // never decrease buffer size
  if (serv->buffersize >= size)      return 0;
  // get new buffer
  p = malloc(size);
  if (!p) {
    char t[64];
    sprintf(t, "Failed to malloc() %d bytes", size);
    serv_error(serv, t);
    return 1;
  }
  // copy buffer contents to new buffer
  if (serv->bytesinbuffer)   memcpy(p, serv->buffer, serv->bytesinbuffer);
  // free old buffer
  free(serv->buffer);
  serv->buffer = p;
  return 0;
}


void vncserv_set_8bpp_palette(vncserv *serv, const unsigned int *palette) {
  if (!memcmp(serv->pal8bpp, palette, 4*256))
    return;

  memcpy(serv->pal8bpp, palette, 4*256);

  async_queue(to_private(serv), ASYNC_COLOURMAP);
}

void vncserv_started(vncserv *serv_public) {
  vncserv_private *serv = to_private(serv_public);
  // tell the server that the connection is open
  serv->started = 1;
  write_data(serv, (void *)"RFB 003.003\n", 12);
  set_state(serv, VNCSERVSTATE_WAITING_FOR_PROTOCOL_VERSION, 60);
}

int vncserv_poll(vncserv *serv_public, int timeout_in_centiseconds) {
  vncserv_private *serv = to_private(serv_public);
  // poll the server, should be called as often as possible

  if (!serv->started)   return VNCSERV_OK;
  clock_t starttime = clock();
  do {

    if (clock() - serv->statetimeout > 0) {
      serv_error(serv, "Timeout whilst waiting for client data");
      return VNCSERV_FATAL_ERROR;
    }
    // Keep looping until timeout is reached or we fail to pull any data from the receive buffer
    serv->flushedsomething = false; 

    switch (serv->pub.state) {
    case VNCSERVSTATE_IDLE:
      break;

    case VNCSERVSTATE_WAITING_FOR_PROTOCOL_VERSION:
      if (serv->bytesinbuffer < 12)   fill_buffer(serv, 12-serv->bytesinbuffer);
      if (serv->bytesinbuffer > 12) {
        serv_error(serv, "Too much data received from client");
        return VNCSERV_FATAL_ERROR;
      }
      if (serv->bytesinbuffer == 12) {
        // validate protocol version
        // HBP - somethings missing here!
        // remove the message
        flush_buffer(serv, -1);
        if (serv->pub.config.password[0]) {
          int i;
          CARD32 authtype = Swap32IfLE(2);
          // send auth message
          write_data(serv, (void *)&authtype, 4);
          // generate random challenge
          for (i = 0; i < 16; i++) {
            int c;
            c = rand();
            c ^= clock();
            c ^= ((int)serv)>>i;
            serv->challenge[i] = c &255;
          }
          write_data(serv, serv->challenge, 16);
          set_state(serv, VNCSERVSTATE_WAITING_FOR_AUTH, 60);
        } else {
          CARD32 authtype = Swap32IfLE(1);
          // send auth message
          write_data(serv, (void *)&authtype, 4);
          set_state(serv, VNCSERVSTATE_WAITING_FOR_CLIENT_INIT, 60);
        }
      }
      break;

    case VNCSERVSTATE_WAITING_FOR_AUTH:
      if (serv->bytesinbuffer < 16)   fill_buffer(serv, 16-serv->bytesinbuffer);
      if (serv->bytesinbuffer > 16) {
        serv_error(serv, "Too much data received from client");
        return VNCSERV_FATAL_ERROR;
      }
      if (serv->bytesinbuffer == 16) {
        // validate encoded string
        vncEncryptBytes((unsigned char *)serv->challenge, serv->pub.config.password);
        if (memcmp(serv->challenge, serv->buffer, 16)) {
          CARD32 failed = Swap32IfLE(1);
          write_data(serv, (void *)&failed, 4);
          serv_error(serv, "Authentication failed");
          return VNCSERV_FATAL_ERROR;
        } else {
          CARD32 ok = Swap32IfLE(0);
          write_data(serv, (void *)&ok, 4);
          set_state(serv, VNCSERVSTATE_WAITING_FOR_CLIENT_INIT, 60);
        }
        flush_buffer(serv, -1);
      }
      break;

    case VNCSERVSTATE_WAITING_FOR_CLIENT_INIT:
      if (serv->bytesinbuffer < 1)   fill_buffer(serv, 1-serv->bytesinbuffer);
      if (serv->bytesinbuffer > 1) {
        serv_error(serv, "Too much data received from client");
        return VNCSERV_FATAL_ERROR;
      }
      if (serv->bytesinbuffer == 1) {
        CARD32 len;
        CARD16 x, y;
        PIXEL_FORMAT data;

        serv->sharedflag = serv->buffer[0];
        flush_buffer(serv, -1);

        if (!serv->sharedflag)
        {
          serv->pub.io.exclusive(&serv->pub, serv->pub.meta);
        }

        // send server init
        // send size
        x = Swap16IfLE(serv->pub.screen.width);
        y = Swap16IfLE(serv->pub.screen.height);
        write_data(serv, &x, 2);
        write_data(serv, &y, 2);
        // send pixel format
        serv->pub.clientformat = serv->pub.screen.desiredformat;
        /* Try and avoid the server asking for <8bpp depth, by limiting our min
           depth to 8bpp
           (currently we don't properly cope with the client depth < server
            depth <= 8bpp, and even if the client & server depths currently
            match, it's not ideal to be stuck at <8bpp if the user changes to
            an 8bpp mode) */
        if (!serv->pub.clientformat.true_colour_flag)
        {
          serv->pub.clientformat.depth = 8;
        }
        serv->client_confirmed_format = 0;
        data = serv->pub.clientformat;
        data.red_max = Swap16IfLE(data.red_max);
        data.green_max = Swap16IfLE(data.green_max);
        data.blue_max = Swap16IfLE(data.blue_max);
        memset(data.padding,0,sizeof(data.padding));
        write_data(serv, &data, 16);
        // send namelength and name
        len = Swap32IfLE(strlen(serv->pub.config.name));
        write_data(serv, &len, 4);
        write_data(serv, serv->pub.config.name, strlen(serv->pub.config.name));
        set_state(serv, VNCSERVSTATE_READY, IDLE_TIMEOUT);

        pixtrans_build(&serv->pub);

        serv_setcolourmapentries(serv);
      }
      break;

    case VNCSERVSTATE_READY:
      fill_buffer(serv, serv->buffersize-serv->bytesinbuffer);
      if (serv->bytesinbuffer >= 1)
        switch (serv->buffer[0]) {
        case C2S_SetPixelFormat:
          read_set_pixel_format(serv);
          break;
        case C2S_FixColourMapEntries:
          read_fix_colour_map_entries(serv);
          break;
        case C2S_SetEncodings:
          read_set_encodings(serv);
          break;
        case C2S_FramebufferUpdateRequest:
          read_framebuffer_update_request(serv);
          break;
        case C2S_KeyEvent:
          read_key_event(serv);
          break;
        case C2S_PointerEvent:
          read_pointer_event(serv);
          break;
        case C2S_ClientCutText:
          read_client_cut_text(serv);
          break;
        default:
          serv_error(serv, "Unexpected message type sent by client");
          return VNCSERV_FATAL_ERROR;
        }
      async_queue(serv, 0);
      break;

    case VNCSERVSTATE_SENDING_UPDATE:
      fill_buffer(serv, serv->buffersize-serv->bytesinbuffer);
      if (serv->bytesinbuffer >= 1)
        switch (serv->buffer[0]) {
        case C2S_SetPixelFormat:
          /* Not while we're sending */
          break;
        case C2S_FixColourMapEntries:
          read_fix_colour_map_entries(serv);
          break;
        case C2S_SetEncodings:
          /* Not while we're sending */
          break;
        case C2S_FramebufferUpdateRequest:
          /* TODO - Merge in if possible */
          break;
        case C2S_KeyEvent:
          read_key_event(serv);
          break;
        case C2S_PointerEvent:
          read_pointer_event(serv);
          break;
        case C2S_ClientCutText:
          read_client_cut_text(serv);
          break;
        default:
          serv_error(serv, "Unexpected message type sent by client");
          return VNCSERV_FATAL_ERROR;
        }
      async_send_update_poll(serv);
      break;

    case VNCSERVSTATE_SENDING_TEXT:
      fill_buffer(serv, serv->buffersize-serv->bytesinbuffer);
      if (serv->bytesinbuffer >= 1)
        switch (serv->buffer[0]) {
        case C2S_SetPixelFormat:
          read_set_pixel_format(serv);
          break;
        case C2S_FixColourMapEntries:
          read_fix_colour_map_entries(serv);
          break;
        case C2S_SetEncodings:
          read_set_encodings(serv);
          break;
        case C2S_FramebufferUpdateRequest:
          read_framebuffer_update_request(serv);
          break;
        case C2S_KeyEvent:
          read_key_event(serv);
          break;
        case C2S_PointerEvent:
          read_pointer_event(serv);
          break;
        case C2S_ClientCutText:
          read_client_cut_text(serv);
          break;
        default:
          serv_error(serv, "Unexpected message type sent by client");
          return VNCSERV_FATAL_ERROR;
        }
      async_send_text_poll(serv);
      break;
    }
  } while (serv->flushedsomething && (clock()-starttime < timeout_in_centiseconds) && !serv->errorhasoccured);
  return serv->errorhasoccured;
}

void vncserv_framebuffer_area_moved(vncserv *serv, area *from, area *to) {
  area both;
  areas_union(from, to, &both);
  vncserv_framebuffer_changed(serv, &both);
}

void vncserv_framebuffer_changed(vncserv *serv_public, area *serverarea) {
  vncserv_private *serv = to_private(serv_public);
  // called whenever part of the screen has changed
  area ar;

  copy_area(&ar, serverarea);
  // validate area
  if (ar.x <  0 || ar.y <  0)   return;
  if (ar.w <= 0 || ar.h <= 0)   return;
  if (ar.x + ar.w > serv->pub.screen.width || ar.y + ar.h > serv->pub.screen.height)   return;

  dirtyrect_add(&serv->dirty, &ar);

  if (areas_intersect(&ar, &serv->clientbox))
  {
    async_queue(serv, ASYNC_SCREEN);
  }
}

void vncserv_closedown(vncserv *serv_public) {
  vncserv_private *serv = to_private(serv_public);
  // close down the server
  if (serv->pub.zrle)
  {
    zrle_destroy(serv->pub.zrle);
  }
  if (serv->pub.zlib)
  {
    zrle_destroy(serv->pub.zlib);
  }
  if (serv->buffer)
  {
    free(serv->buffer);
  }
  clipboard_deref(&serv->async_text);
  free(serv);
}

int vncserv_cut_text(vncserv *serv_public, clipboard_text *text) {
  vncserv_private *serv = to_private(serv_public);
  // send string to client
  cuttext_header data;
  if (!serv->started || ((serv->pub.state != VNCSERVSTATE_READY) && (serv->pub.state != VNCSERVSTATE_SENDING_UPDATE)))
  {
    dprintf("vncserv_cut_text: Wrong state (%d)\n",serv->pub.state);
    return VNCSERV_FAILED_TO_WRITE;
  }

  clipboard_deref(&serv->async_text);

  /* Make it an async op if there isn't enough buffer space, or if we're in the middle of a framebuffer update (ensures clipboard text will make it through) */
  if ((serv->pub.state != VNCSERVSTATE_READY) || (vncserv_get_tx_space(serv_public) < text->len+sizeof(cuttext_header)))
  {
    serv->async_text = clipboard_addref(&text);
    serv->async_text_offset = 0;
    async_queue(serv, 0);
    return VNCSERV_OK;
  }

  data.type = S2C_ServerCutText;
  data.pad[0] = data.pad[1] = data.pad[2] = 0;
  data.len = Swap32IfLE(text->len);
  write_data(serv, &data, sizeof(cuttext_header));
  write_data(serv, text->text, text->len);
  return VNCSERV_OK;
}

void vncserv_bell(vncserv *serv_public) {
  vncserv_private *serv = to_private(serv_public);
  // ring the bell on the client
  CARD8 type;
  if (!serv->started || (serv->pub.state != VNCSERVSTATE_READY) || !vncserv_get_tx_space(serv_public))   return;
  type = S2C_Bell;
  write_data(serv, &type, 1);
}

bool vncserv_can_desktopsize(vncserv *serv_public)
{
  /* Return true if we're in a state where vncserv_desktopsize can be called
     (disregarding whether we actually support the encoding or not) */
  vncserv_private *serv = to_private(serv_public);
  
  if (serv->pub.state > VNCSERVSTATE_READY) /* != ? */
  {
    dprintf("vncserv_can_desktopsize: Wrong state (%d)\n",serv->pub.state);
    return false;
  }

  if (vncserv_get_tx_space(serv_public) < 2048)
  {
    dprintf("vncserv_can_desktopsize: Insufficient TX buffer space\n");
    return false;
  }

  if ((serv->pub.state == VNCSERVSTATE_READY) && area_is_empty(&serv->clientbox))
  {
    dprintf("vncserv_can_desktopsize: Client not waiting for framebuffer update\n");
    return false;
  }

  return true;
}

int vncserv_desktopsize(vncserv *serv_public, servscreen *screen)
{
  vncserv_private *serv = to_private(serv_public);

  /* vncserv_can_desktopsize assumed to be true */

  if (!memcmp(&serv->pub.screen,screen,sizeof(servscreen)))
    return VNCSERV_OK;

  bool rebuildpixtrans = memcmp(&serv->pub.screen.format,&screen->format,sizeof(PIXEL_FORMAT));
  bool resize = (serv->pub.screen.width != screen->width) || (serv->pub.screen.height != screen->height);

  /* Copy over the changes */
  serv->pub.screen = *screen;

  /* Communicate changes to client if necessary */
  if (serv->pub.state == VNCSERVSTATE_READY)
  {
    if (resize)
    {
      if (!serv->pub.encodings.desktopsize)
      {
        serv_error(serv, "Screen mode has been changed");
        return VNCSERV_FATAL_ERROR;
      }
      else
      {
        struct {
          CARD8 type;
          CARD8 pad1;
          CARD16 n;
        } data;
        struct {
          CARD16 x;
          CARD16 y;
          CARD16 w;
          CARD16 h;
          CARD32 coding;
        } rect;
      
        // can be in the temp buffer in one go...
        data.type = S2C_FramebufferUpdate;
        data.pad1 = 0;
        data.n    = Swap16IfLE(1);
        write_data(serv, &data, 4);
      
        rect.x = 0;
        rect.y = 0;
        rect.w = Swap16IfLE(screen->width);
        rect.h = Swap16IfLE(screen->height);
        rect.coding = Swap32IfLE(DESKTOPSIZE_ENCODING);
        write_data(serv, &rect, 12);

        serv_setcolourmapentries(serv);
      }
    }

    if (rebuildpixtrans)
    {
      pixtrans_build(&serv->pub);
    }
  }

  // Clear any pending rectangles
  dirtyrect_init(&serv->dirty);
  reset_area(&serv->clientbox);
  
  return VNCSERV_OK;
}

void vncserv_update_cursor(vncserv *serv_public, const pointer_shape *cursor)
{
  if (cursor_will_send(serv_public, cursor))
  {
    vncserv_private *serv = to_private(serv_public);
    serv->pending_cursor = cursor;
    async_queue(serv, ASYNC_CURSOR_UPDATE);
  }
}

void vncserv_async_send_poll(vncserv *serv_public)
{
  if (serv_public->state == VNCSERVSTATE_SENDING_UPDATE)
  {
    async_send_update_poll(to_private(serv_public));
  }
  else if (serv_public->state == VNCSERVSTATE_SENDING_TEXT)
  {
    async_send_text_poll(to_private(serv_public));
  }
}

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

// report an error to the frontend
void serv_error(vncserv_private *serv, const char *msg) {
  if (serv->pub.io.error)   serv->pub.io.error(&serv->pub, msg, serv->pub.meta);
}

void set_state(vncserv_private *serv, int state, int timeout_in_seconds) {
  serv->pub.state = state;
  serv->statetimeout = clock() + timeout_in_seconds*100;
}

// try to fill the buffer with 'max' bytes from the frontend
void fill_buffer(vncserv_private *serv, int max) {
  int n;
  if (max <= 0)         return;
  if (!serv->pub.io.read)   return;
  n = serv->pub.io.read(&serv->pub, serv->buffer+serv->bytesinbuffer, max, serv->pub.meta);
  if (n > 0)  serv->bytesinbuffer += n;
  if (n < 0)  serv->errorhasoccured = n;
}

// remove 'n' bytes from the buffer
void flush_buffer(vncserv_private *serv, int n) {
  if (n < 0)               serv->bytesinbuffer = 0;
  if (n <= 0)              return;
  if (n > serv->bytesinbuffer)  n = serv->bytesinbuffer;
  if (n < serv->bytesinbuffer)  memmove(serv->buffer, serv->buffer+n, serv->bytesinbuffer-n);
  serv->bytesinbuffer -= n;
  if (serv->pub.state >= VNCSERVSTATE_READY)
  {
    /* Reset idle disconnect timeout */
    serv->statetimeout = clock() + IDLE_TIMEOUT*100;
  }
  serv->flushedsomething = true;
}

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

// client sent a 'set pixel format' message, so read and process it
void read_set_pixel_format(vncserv_private *serv) {
  struct {
    CARD8 type;
    CARD8 pad1, pad2, pad3;
    CARD8 bpp;
    CARD8 depth;
    CARD8 bigendian;
    CARD8 truecolour;
    CARD16 redmax;
    CARD16 greenmax;
    CARD16 bluemax;
    CARD8 redshift;
    CARD8 greenshift;
    CARD8 blueshift;
    CARD8 pad17, pad18, pad19;
  } data;

  // make sure we have enough data in the buffer
  if (serv->bytesinbuffer < 20)           return;
  memcpy(&data, serv->buffer, 20);
  flush_buffer(serv, 20);

  dprintf("SetPixelFormat received\n");

  // process the message
  serv->pub.clientformat.bits_per_pixel = data.bpp;
  serv->pub.clientformat.depth = data.depth;
  serv->pub.clientformat.big_endian_flag = data.bigendian;
  serv->pub.clientformat.true_colour_flag = data.truecolour;
  serv->pub.clientformat.red_max = Swap16IfLE(data.redmax);
  serv->pub.clientformat.green_max = Swap16IfLE(data.greenmax);
  serv->pub.clientformat.blue_max = Swap16IfLE(data.bluemax);
  serv->pub.clientformat.red_shift= data.redshift;
  serv->pub.clientformat.green_shift= data.greenshift;
  serv->pub.clientformat.blue_shift= data.blueshift;

  serv->client_confirmed_format = 1;

  pixtrans_build(&serv->pub);

  /* Ensure colour map is re-sent if appropriate */
  serv_setcolourmapentries(serv);
}

// client sent 'fix colour map entries'
// just read and ignore it
void read_fix_colour_map_entries(vncserv_private *serv) {
  struct {
    CARD8 type;
    CARD8 pad1;
    CARD16 col1;
    CARD16 numcol;
  } data;
  int n;

  if (serv->bytesinbuffer < 4)           return;
  memcpy(&data, serv->buffer, 4);
  n = Swap16IfLE(data.numcol);
  if (serv->bytesinbuffer < 4 + 6*n)     return;
  flush_buffer(serv, 4 + 6*n);
}

// client has specified which encodings to use
void read_set_encodings(vncserv_private *serv) {
  struct {
    CARD8 type;
    CARD8 pad1;
    CARD16 numencodings;
  } data;
  int num, i;

  if (serv->bytesinbuffer < 4)           return;
  memcpy(&data, serv->buffer, 4);
  num = Swap16IfLE(data.numencodings);
  // don't process anything until all encodings have been read
  if (serv->bytesinbuffer < 4 + num*4)   return;

  supported_encodings encodings = {0};

  for (i = 0; i < num; i++) {
    CARD32 enc;
    memcpy(&enc, serv->buffer + 4 + 4*i, 4);
    enc = Swap32IfLE(enc);
    dprintf("Encoding %d: %08x\n",i,enc);
    // HBP some work on encoding priorities is needed...
    switch (enc) {
    case HEXTILE_ENCODING:
      encodings.hextile = 1;
      break;
    case HEXTILEDITHER8_ENCODING:
      encodings.hextiledithering = 1;
      break;
    case COPY_ENCODING:
      encodings.copy = 1;
      break;
    case ZRLE_ENCODING:
      encodings.zrle = 1;
      break;
    case ZLIB_ENCODING:
      encodings.zlib = 1;
      break;
    case DESKTOPSIZE_ENCODING:
      encodings.desktopsize = 1;
      break;
    case CURSOR_ENCODING:
      encodings.cursor = 1;
      break;
    case XCURSOR_ENCODING:
      encodings.xcursor = 1;
      break;
    }
  }
  flush_buffer(serv, 4 + 4*num);

  serv->pub.encodings = encodings;

  /* We prefer zrle over zlib (ignore whatever priority the client indicates)
     Try and avoid creating multiple encoders */
  int level = serv->pub.config.zlib_level;
  if (serv->pub.encodings.zrle && (level >= 0))
  {
    if (!serv->pub.zrle)
    {
      serv->pub.zrle = zrle_init(level);
    }
  }
  else if (serv->pub.encodings.zlib && (level > 0))
  {
    if (!serv->pub.zlib)
    {
      serv->pub.zlib = zrle_init(level);
    }
  }
}

// client sent update request
void read_framebuffer_update_request(vncserv_private *serv) {
  struct {
    CARD8 type;
    CARD8 incremental;
    CARD16 minx;
    CARD16 miny;
    CARD16 width;
    CARD16 height;
  } data;
  int x, y, w, h, x1, y1;
  area client;

  if (serv->bytesinbuffer < 10)          return;
  memcpy(&data, serv->buffer, 10);
  flush_buffer(serv, 10);
  x = Swap16IfLE(data.minx);
  y = Swap16IfLE(data.miny);
  w = Swap16IfLE(data.width);
  h = Swap16IfLE(data.height);

  dprintf("FramebufferUpdateRequest: %d (%d,%d) (%d,%d)\n",data.incremental,x,y,w,h);

  x1 = x + w;
  y1 = y + h;

  // make sure the area is valid
  if ((x < 0) || (y < 0) || (x1 > serv->pub.screen.width) || (y1 > serv->pub.screen.height) || (x > x1) || (y > y1))
  {
    char temp[128];
    sprintf(temp,"Bad update rectangle from client (%d,%d) (%d,%d)",x,y,x1,y1);
    serv_error(serv,temp);
    return;
  }
  set_area(&client, x, y, x1-x, y1-y);

  if (data.incremental) {
    // send any changed area within the new client area
    if (w && h) {
      if (area_is_empty(&serv->clientbox)) {
        // if there's no changed area, and no old client area...
        copy_area(&serv->clientbox, &client);
  
      } else {
        // if there's no changed area, make client area from union of
        // old client area and new client area
        areas_union(&client, &serv->clientbox, &serv->clientbox);
      }

      if (dirtyrect_intersects(&serv->dirty, &client))
      {
        async_queue(serv, ASYNC_SCREEN);
      }
    }
  } else {
    /* TODO - A client which constantly spams non-incremental update requests will prevent us from ever sending a desktopsize update. Could potentially fix by calling through to server to check mode_valid and not adding the dirtyrect/async_queue; but if client keeps spamming messages then there's a danger we'll gobble them all up and only produce the one response (protocol violation!) - so maybe we should call server_checkmode here, to allow it to respond immediately? (and to just this one message) */
    dirtyrect_add(&serv->dirty, &client);
    copy_area(&serv->clientbox, &client);
    async_queue(serv, ASYNC_SCREEN);
  }
}

// client sent key event
void read_key_event(vncserv_private *serv) {
  struct {
    CARD8 type;
    CARD8 downflag;
    CARD8 pad2, pad3;
    CARD32 key;
  } data;
  int key;

  if (serv->bytesinbuffer < 8)          return;
  memcpy(&data, serv->buffer, 8);
  flush_buffer(serv, 8);
  key = Swap32IfLE(data.key);
  if (serv->pub.io.keypress)
    serv->pub.io.keypress(&serv->pub, key, !!data.downflag, serv->pub.meta);
}

// client sent pointer event
void read_pointer_event(vncserv_private *serv) {
  struct {
    CARD8 type;
    CARD8 buttons;
    CARD16 x, y;
  } data;
  int x, y;

  if (serv->bytesinbuffer < 6)          return;
  memcpy(&data, serv->buffer, 6);
  flush_buffer(serv, 6);
  x = Swap16IfLE(data.x);
  y = Swap16IfLE(data.y);
  serv->pub.pointer_active = true;
  if (serv->pub.io.pointer)
    serv->pub.io.pointer(&serv->pub, x, y, data.buttons, serv->pub.meta);
}

// client sent text
void read_client_cut_text(vncserv_private *serv) {
  cuttext_header data;
  int len;

  if (serv->bytesinbuffer < sizeof(cuttext_header))          return;
  memcpy(&data, serv->buffer, sizeof(cuttext_header));
  len = Swap32IfLE(data.len);
  if ((serv->bytesinbuffer == serv->buffersize) && (serv->buffersize < sizeof(cuttext_header)+len))
  {
    /* Text too long to fit in the buffer - transfer in chunks */
    int truncate = serv->bytesinbuffer-sizeof(cuttext_header);
    if (serv->pub.io.cuttext) serv->pub.io.cuttext(&serv->pub, serv->buffer+sizeof(cuttext_header), truncate, len-truncate, serv->pub.meta);
    serv->bytesinbuffer = sizeof(cuttext_header); /* Discard everything after the header (safe, we know there shouldn't be any other packets in there) */
    len -= truncate; /* Update length by how much we've thrown away */
    data.len = Swap32IfLE(((CARD32)len));
    memcpy(serv->buffer, &data, sizeof(cuttext_header)); /* Write new length back to buffer */
    return;
  }
  if (serv->bytesinbuffer < sizeof(cuttext_header)+len)      return;

  if (serv->pub.io.cuttext)    serv->pub.io.cuttext(&serv->pub, serv->buffer+sizeof(cuttext_header), len, 0, serv->pub.meta);

  flush_buffer(serv, sizeof(cuttext_header)+len);
}

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

void serv_setcolourmapentries(vncserv_private *serv)
{
  if ((serv->pub.state != VNCSERVSTATE_READY) || serv->pub.clientformat.true_colour_flag || !serv->client_confirmed_format)
  {
    dprintf("serv_setcolourmapentries: Wrong state (%d %d %d)\n",serv->pub.state, serv->pub.clientformat.true_colour_flag, serv->client_confirmed_format);
    return;
  }
  /* TODO optimise */
  struct {
    CARD8 messagetype;
    CARD8 padding;
    CARD16 first;
    CARD16 number;
  } header;
  int number = 1<<serv->pub.clientformat.depth;
  int i;
  header.messagetype = S2C_SetColourMapEntries;
  header.padding = 0;
  header.first = Swap16IfLE(0);
  header.number = Swap16IfLE(number);
  write_data(serv, &header, 2+4);
  CARD16 pal[256*3];
  for (i=0;i<number;i++)
  {
    pal[i*3] = ((serv->pub.pal8bpp[i] & 0xff00)>>8) * 0x101;
    pal[i*3+1] = ((serv->pub.pal8bpp[i] & 0xff0000)>>16) * 0x101;
    pal[i*3+2] = ((serv->pub.pal8bpp[i] & 0xff000000)>>24) * 0x101;
  }
  write_data(serv, pal, sizeof(CARD16)*number*3);
}

// Async update handling

static union
{
  raw_temp_buffer raw;
  hextile_temp_buffer hextile;
  zrle_temp_buffer zrle;
  cursor_temp_buffer cursor;
} shared_temp;

/* TODO allow negative for error */
static int async_num_rects(vncserv *serv, const async_state *state, int encoding)
{
  switch(encoding)
  {
  case RAW_ENCODING:
    return raw_num_rects(serv, &state->rect.rect);
  case HEXTILE_ENCODING:
    return hextile_num_rects(serv, &state->rect.rect);
  case ZLIB_ENCODING:
  case ZRLE_ENCODING:
    return zrle_num_rects(serv, &state->rect.rect);
  case CURSOR_ENCODING:
  case XCURSOR_ENCODING:
    return (cursor_will_send(serv, state->cursor) ? 1 : 0);
  default:
    dprintf("async_num_rects: Unknown encoding %d\n",encoding);
    return 0;
  }
}

static bool async_encode_and_send(vncserv *serv, async_state *state, int encoding)
{
  switch(encoding)
  {
  case RAW_ENCODING:
    return raw_send(serv, &state->rect, &shared_temp.raw);
  case HEXTILE_ENCODING:
    return hextile_send(serv, &state->rect, &shared_temp.hextile);
  case ZLIB_ENCODING:
    return zlib_send(serv, &state->rect, &shared_temp.zrle);
  case ZRLE_ENCODING:
    return zrle_send(serv, &state->rect, &shared_temp.zrle);
  case CURSOR_ENCODING:
  case XCURSOR_ENCODING:
    return cursor_send(serv, state->cursor, &shared_temp.cursor);
  default:
    dprintf("async_encode_and_send: Unknown encoding %d\n",encoding);
    return true;
  }
}

void write_data(vncserv_private *serv, const void *data, int n) {
  if (serv->pub.io.write) {
    int err;
    err = serv->pub.io.write(&serv->pub, data, n, serv->pub.meta);
    if (err != VNCSERV_OK)   serv->errorhasoccured = err;
  }
}

static void begin_framebuffer_update(vncserv *serv, int count)
{
  if (serv->state != VNCSERVSTATE_SENDING_UPDATE)
  {
    dprintf("vncserv_begin_framebuffer_update: Wrong state (%d)\n",serv->state);
    serv_error(to_private(serv),"bad");
    return;
  }
  struct {
    CARD8 type;
    CARD8 pad1;
    CARD16 n;
  } data;
  data.type = S2C_FramebufferUpdate;
  data.pad1 = 0;
  data.n    = Swap16IfLE(count);
  write_data(to_private(serv), &data, 4);
}

void async_queue(vncserv_private *serv, int flag)
{
  serv->next_async |= flag;
  if (serv->pub.state != VNCSERVSTATE_READY)
  {
    return;
  }
  /* Prioritise clipboard data over framebuffer */
  if (serv->async_text)
  {
//    dprintf("async_queue: Doing async text\n");
    serv->pub.state = VNCSERVSTATE_SENDING_TEXT;
    return;
  }
  if (!serv->next_async)
  {
    return;
  }
#if 0
  dprintf("async_queue: Doing async%s%s%s\n",
         (serv->next_async & ASYNC_COLOURMAP) ? " colourmap" : "",
         (serv->next_async & ASYNC_CURSOR_UPDATE) ? " cursor" : "",
         (serv->next_async & ASYNC_SCREEN) ? " screen" : "");
#endif
  dirtyrect_cut(&serv->dirty, &serv->clientbox, &serv->async.rects);
  serv->async.rect.x = serv->async.rect.y = -1;
  serv->async.flags = serv->next_async;
  serv->async.cursor = serv->pending_cursor; /* XXX possible to send cursor when it's not actually supported? */

  reset_area(&serv->clientbox); /* Can't send any more until next FramebufferUpdateRequest */
  serv->next_async = 0;
  serv->pending_cursor = NULL;

  serv->async.encoding = RAW_ENCODING;
  if (serv->async.flags & ASYNC_SCREEN)
  {
    if (serv->pub.encodings.zrle && serv->pub.zrle)
      serv->async.encoding = ZRLE_ENCODING;
    else if (serv->pub.encodings.zlib && serv->pub.zlib)
      serv->async.encoding = ZLIB_ENCODING;
    else if (serv->pub.encodings.hextile)
      serv->async.encoding = HEXTILE_ENCODING;
  }

  /* Send colour map update first */
  if (serv->async.flags & ASYNC_COLOURMAP)
  {
    serv_setcolourmapentries(serv);
    serv->async.flags &= ~ASYNC_COLOURMAP;
  }

  /* Count number of rectangles needed for the remainder */
  int rects = 0;
  if (serv->async.flags & ASYNC_CURSOR_UPDATE)
  {
    int r = async_num_rects(to_public(serv), &serv->async, CURSOR_ENCODING);
    if (!r)
    {
      serv->async.flags &= ~ASYNC_CURSOR_UPDATE;
    }
    rects += r;
  }
  int dirty_rects = 0;
  if (serv->async.flags & ASYNC_SCREEN)
  {
    dirtyrect_buffer temp = serv->async.rects;
    dirtyrect_next(&temp, &serv->async.rect.rect);
    while (!area_is_empty(&serv->async.rect.rect))
    {
//      dprintf("  rect (%d,%d) (%d,%d)\n",serv->async.rect.rect.x,serv->async.rect.rect.y,serv->async.rect.rect.w,serv->async.rect.rect.h);
      dirty_rects++;
      rects += async_num_rects(to_public(serv), &serv->async, serv->async.encoding);
      dirtyrect_next(&temp, &serv->async.rect.rect);
    }
    if (!dirty_rects)
    {
      serv_error(serv, "Attempt to send empty rect");
      return;
    }
    /* Grab the first real rect into serv->async */
    dirtyrect_next(&serv->async.rects, &serv->async.rect.rect);
  }
//  dprintf("%d rects (%d dirty) with encoding %d\n",rects,dirty_rects,serv->async.encoding);
  if (rects > 65535)
  {
    serv_error(serv, "too many rects");
    return;
  }

  if (!serv->async.flags)
  {
    return;
  }

  serv->pub.state = VNCSERVSTATE_SENDING_UPDATE;

  /* Send the header */
  begin_framebuffer_update(to_public(serv), rects);
}

void async_send_update_poll(vncserv_private *serv)
{
  int t = clock();
  bool did_something;
  do
  {
    did_something = false;
    if (serv->async.flags & ASYNC_CURSOR_UPDATE)
    {
      did_something = async_encode_and_send(to_public(serv), &serv->async, CURSOR_ENCODING);
      if (did_something)
      {
        serv->async.flags &= ~ASYNC_CURSOR_UPDATE;
      }
    }
    else if (serv->async.flags & ASYNC_SCREEN)
    {
      did_something = async_encode_and_send(to_public(serv), &serv->async, serv->async.encoding);
      if (send_rect_is_complete(&serv->async.rect))
      {
        dirtyrect_next(&serv->async.rects, &serv->async.rect.rect);
        if (area_is_empty(&serv->async.rect.rect))
        {
          serv->async.flags &= ~ASYNC_SCREEN;
        }
        else
        {
          serv->async.rect.x = serv->async.rect.y = -1;
        }
      }
    }
  } while (did_something && serv->async.flags && (clock() == t));
  if (!serv->async.flags)
  {
//    dprintf("async done\n");
    serv->pub.state = VNCSERVSTATE_READY;
    async_queue(serv, 0);
  }
}

void async_send_text_poll(vncserv_private *serv)
{
  int tx_space = vncserv_get_tx_space(&serv->pub); 
  if (!serv->async_text_offset)
  {
    if (tx_space <= sizeof(cuttext_header)) /* Ensure space for at least 1 char after the header, so async_text_offset will become non-zero */
    {
      return;
    }
    /* Send the header */
    cuttext_header data;
    data.type = S2C_ServerCutText;
    data.pad[0] = data.pad[1] = data.pad[2] = 0;
    data.len = Swap32IfLE(serv->async_text->len);
    write_data(serv, &data, sizeof(cuttext_header));
    tx_space -= sizeof(cuttext_header);
  }

  /* Since sending text is just a memcpy operation, only fill the buffer once before we quit out (socket won't have emptied much by the time that we're done) */
  if (tx_space > (serv->async_text->len - serv->async_text_offset))
  {
    tx_space = serv->async_text->len - serv->async_text_offset;
  }
  write_data(serv, &serv->async_text->text[serv->async_text_offset], tx_space);
  serv->async_text_offset += tx_space;

  if (serv->async_text_offset == serv->async_text->len)
  {
//    dprintf("async text done\n");
    clipboard_deref(&serv->async_text);
    serv->pub.state = VNCSERVSTATE_READY;
    async_queue(serv, 0);
  }
}
