#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "kernel.h"
// oslib
#include "oslib/os.h"
#include "oslib/osbyte.h"
#include "oslib/osword.h"
#include "oslib/osspriteop.h"
#include "oslib/colourtrans.h"

#include "sys/errno.h"
#include "sys/socket.h"

#include "vncserv.h"
#include "vnckeys.h"
#include "ipserv.h"
#include "mcode.h"
#include "server.h"


#define CONNECT_WAIT          0
#define CONNECT_OK            1
#define CONNECT_ERR           2

static vncserv *server         = NULL;
static int connected           = CONNECT_WAIT;
static int vncsocket           = -1;
static ipserv *ipserver        = NULL;
static int buffered            = 0;
static int buffersize          = 400000;
static int buffermark          = 0;
static char *buffer            = NULL;
static int nexty               = 0;
static int slice_y             = 128;
static int flag_support_mouse  = 0;
static int flag_support_kbd    = 0;
static int flag_swap_buttons   = 0;
static int fatal_error_occured = 0;
static int port                = 5901;
static char password[9]        = "";
static char *screenstart;
static char *prevscreen        = NULL;
static int bpp;
static int pressedkeys[128];
static int bytesread, bytessent, pixelssent;
static int check_connection_time;

// externs
int mouse_buttons = 0, mouse_x = -10000, mouse_y = -10000;
int screensize_x, screensize_y;


static int check_area(int y0, int y1);
static void poll_sending(void);
static void call_key_vector(int keyno, int down);

static int io_read(const vncserv *serv, void *buffer, int bytes, void *meta);
static int io_write(const vncserv *serv, void *buf, int bytes, void *meta);
static int io_setmark(const vncserv *serv, void *meta);
static int io_getmark(const vncserv *serv, 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_error(const vncserv *serv, const char *msg, void *meta);

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

int server_start(int prt, const char *psswrd) {
  vncservio io;
  int vars[8], vals[8];

  port = prt;
  strncpy(password, psswrd, 8);
  password[8] = '\0';

  bytesread = bytessent = pixelssent = 0;

  // fill in the vncservio structure
  memset(&io, 0, sizeof(vncservio));
  io.read = io_read;
  io.write = io_write;
  io.setmark = io_setmark;
  io.getmark = io_getmark;
  io.pointer = io_pointer;
  io.keypress = io_keypress;
  io.error = io_error;

  // create socket to listen on
  ipserver = ipserv_create(port);
  if (!ipserver)    return 1;

  vars[0] = os_MODEVAR_LOG2_BPP;
  vars[1] = os_MODEVAR_XWIND_LIMIT;
  vars[2] = os_MODEVAR_YWIND_LIMIT;
  vars[3] = os_VDUVAR_SCREEN_START;
  vars[4] = -1;
  xos_read_vdu_variables((os_vdu_var_list *)vars, vals);

  bpp = 1<<vals[0];
  screensize_x = vals[1]+1;
  screensize_y = vals[2]+1;
  screenstart = (char *)vals[3];

  buffersize = 10000+2*(screensize_x*screensize_y*bpp)/8;
  buffer = malloc(buffersize);
  if (!buffer) {
    ipserv_close(ipserver);
    return 2;
  }

  prevscreen = malloc((screensize_x*screensize_y*bpp)/8);
  if (!prevscreen) {
    free(buffer);
    ipserv_close(ipserver);
    return 3;
  }
  memcpy(prevscreen, screenstart, (screensize_x*screensize_y*bpp)/8);

  // create vncserver - notice that we could call vncserver_create() more than once with
  // the same framebuffer, this would allow us to serv the same bitmap to multiple clients
  server = vncserver_create(prevscreen, screensize_x, screensize_y, bpp, (screensize_x*bpp)/8,
                            "RISCOS desktop", &io, NULL, password);
  if (!server) {
    free(buffer);
    free(prevscreen);
    ipserv_close(ipserver);
    return 4;
  }

  if (bpp == 8) {
    unsigned int palette[256];
    int i;
    xcolourtrans_read_palette((osspriteop_area *)-1, (osspriteop_id)-1,
                              (os_palette *)palette, 1024, 0, NULL);
    for (i = 0; i < 256; i++)      palette[i] = palette[i]>>8;
    vncserv_set_8bpp_palette(server, palette);
  }

  memset(pressedkeys, 0, 128);

  buffered = 0;
  connected = CONNECT_WAIT;
  nexty = 0;

  return 0;
}


void server_stop() {
  int keyno;

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

  if (server)            vncserv_closedown(server);
  server = NULL;
  if (ipserver)          ipserv_close(ipserver);
  ipserver = NULL;
  if (vncsocket != -1)   close(vncsocket);
  vncsocket = -1;
  if (buffer)            free(buffer);
  buffer = NULL;
  if (prevscreen)        free(prevscreen);
  prevscreen = NULL;
}


int server_poll() {

  int y;
  int err_poll;

  if (connected == CONNECT_OK)
    if (clock() > check_connection_time) {
//      struct sockaddr addr;
//      int len;
//      if (getpeername(vncsocket, &addr, &len))
//        if (errno)  fatal_error_occured = 1;
      check_connection_time = clock() + 50;
    }

  if (fatal_error_occured) {
    fatal_error_occured = 0;
#ifndef BUILD_MODULE
    fprintf(stderr, "[%d] Fatal error, restarting\n", clock());
#endif
    server_stop();
    server_start(port, password);
  }

  if (connected == CONNECT_WAIT || connected == CONNECT_OK) {
    // check for an incoming connection
    if (!ipserv_accept(ipserver, &vncsocket)) {
      //ipserv_close(ipserver); // don't listen anymore
      //ipserver = NULL;
      connected = CONNECT_OK;
      vncserv_started(server);
      check_connection_time = clock() + 50;
    }
  }
  if (connected != CONNECT_OK)   return 0;

  err_poll = vncserv_poll(server, 0);

  if (err_poll != VNCSERV_OK) {
#ifndef BUILD_MODULE
      fprintf(stderr, "[%d] Polling returned error %d\n", clock(), err_poll);
#endif
    // error, so close and wait for new client
    fatal_error_occured = 1;

  } else if (buffered) {
    poll_sending();

    // make sure there's room for all that we could possible want to send
    if (buffersize - buffered < 10000 + (screensize_x*slice_y*bpp)/8) {
#ifndef BUILD_MODULE
      fprintf(stderr, "[%d] Buffer almost full (%d of %d free)\n",
                       clock(), buffersize-buffered, buffersize);
#endif
      server_stop();
      return 0;
    }
  }

  y = nexty + slice_y;
  if (y > screensize_y)  y = screensize_y;
  check_area(nexty, y);
  nexty = y;
  if (nexty == screensize_y)   nexty = 0;

  return 0;
}


int server_set_flag(int flag, int state) {
  int oldstate, newstate;

  switch (flag) {
  case SERVER_FLAG_SUPPORT_MOUSE:
    oldstate = flag_support_mouse;
    break;
  case SERVER_FLAG_SUPPORT_KBD:
    oldstate = flag_support_kbd;
    break;
  case SERVER_FLAG_SWAP_MENU_ADJUST:
    oldstate = flag_swap_buttons;
    break;
  }
  switch (state) {
  case SERVER_FLAG_SET:
    newstate = 1;
    break;
  case SERVER_FLAG_CLEAR:
    newstate = 0;
    break;
  case SERVER_FLAG_TOGGLE:
    newstate = !oldstate;
    break;
  }
  switch (flag) {
  case SERVER_FLAG_SUPPORT_MOUSE:
    flag_support_mouse = newstate;
    break;
  case SERVER_FLAG_SUPPORT_KBD:
    flag_support_kbd = newstate;
    break;
  case SERVER_FLAG_SWAP_MENU_ADJUST:
    flag_swap_buttons = newstate;
    break;
  }
  return oldstate;
}


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

// -------------------------------------------------------------
// 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;
  n = recv(vncsocket, buffer, bytes, 0);
  if (n >= 0) {
    bytesread += n;
    return n;
  }
  errnox = errno &~IYONIX_SOCKET_ERR;
  if (errnox == 0)     return VNCSERV_OK;
  if (errnox == EWOULDBLOCK || errnox == EAGAIN)   return VNCSERV_OK;
#ifndef BUILD_MODULE
    fprintf(stderr, "[%d] Error in io_read, error %d\n", clock(), errnox);
#endif
  fatal_error_occured = 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, void *buf, int bytes, void *meta) {

  if (buffersize - buffered < bytes)   return VNCSERV_FAILED_TO_WRITE;
  memcpy(buffer+buffered, buf, bytes);
  buffered += bytes;
  poll_sending();
  return VNCSERV_OK;
}

// io_setmark() is called by the server to mark the current end of the send buffer
int io_setmark(const vncserv *serv, void *meta) {
  buffermark = buffered;
  return VNCSERV_OK;
}

// io_getmark() is called by the server to get the current location of the mark in the send buffer
int io_getmark(const vncserv *serv, void *meta) {
  return buffermark;
}

// 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) {
  int keyno;

  if (!flag_support_kbd)   return VNCSERV_OK;
  keyno = -1;

  switch (key) {
  case VNCKEY_BACKSPACE:
    keyno = 0x1e;
    break;
  case VNCKEY_TAB:
    keyno = 0x26;
    break;
  case VNCKEY_ENTER:
    keyno = 0x67;
    break;
  case VNCKEY_ESCAPE:
    keyno = 0x00;
    break;
  case VNCKEY_LEFT:
    keyno = 0x62;
    break;
  case VNCKEY_UP:
    keyno = 0x59;
    break;
  case VNCKEY_RIGHT:
    keyno = 0x64;
    break;
  case VNCKEY_DOWN:
    keyno = 0x63;
    break;
  case VNCKEY_PAGEUP:
    keyno = 0x21;
    break;
  case VNCKEY_PAGEDOWN:
    keyno = 0x36;
    break;
  case VNCKEY_INSERT:
    keyno = 0x1f;
    break;
  case VNCKEY_DELETE:
    keyno = 0x34;
    break;
  case VNCKEY_F1:
    keyno = 0x01;
    break;
  case VNCKEY_F2:
    keyno = 0x02;
    break;
  case VNCKEY_F3:
    keyno = 0x03;
    break;
  case VNCKEY_F4:
    keyno = 0x04;
    break;
  case VNCKEY_F5:
    keyno = 0x05;
    break;
  case VNCKEY_F6:
    keyno = 0x06;
    break;
  case VNCKEY_F7:
    keyno = 0x07;
    break;
  case VNCKEY_F8:
    keyno = 0x08;
    break;
  case VNCKEY_F9:
    keyno = 0x09;
    break;
  case VNCKEY_F10:
    keyno = 0x0a;
    break;
  case VNCKEY_F11:
    keyno = 0x0b;
    break;
  case VNCKEY_F12:
    keyno = 0x0c;
    break;
  case VNCKEY_SHIFT:
    keyno = 0x4c;
    break;
  case VNCKEY_CTRL:
    keyno = 0x3b;
    break;
  case VNCKEY_ALT:
    keyno = 0x5e;
    break;
  case VNCKEY_SHIFT_R:
    keyno = 0x58;
    break;
  case VNCKEY_CTRL_R:
    keyno = 0x61;
    break;
  case VNCKEY_ALT_R:
    keyno = 0x60;
    break;
  default:
    // handle the rest by inserting codes into
    // the kbd buffer when key is pressed
    if (!down)    return VNCSERV_OK;
//    if (key >= 'A' && key <= 'Z' && (pressedkeys[0x61] || pressedkeys[0x3b]))
//      keyno = key - 'A' + 1;
//    else if (key >= 'a' && key <= 'z' && (pressedkeys[0x61] || pressedkeys[0x3b]))
//      keyno = key - 'a' + 1;
    if (key >= ' ' && key <= '~')   // 32 - 126
      keyno = key;
    else if (key >= 128 && key <= 255)
      keyno = key;
    // CTRL modifies character code
    if (pressedkeys[0x3b])
    {
      keyno = keyno&31;
      xos_byte(osbyte_TYPE_CHAR, 0, 0, NULL, NULL);
    }
    if (keyno >= 0) {
#ifndef BUILD_MODULE
      fprintf(stderr, "[%d] Inserting key %d (%x) into kbd buffer\n", clock(), keyno, keyno);
#endif
      xos_byte(osbyte_TYPE_CHAR, 0, keyno, NULL, NULL);
    } else {
#ifndef BUILD_MODULE
      fprintf(stderr, "[%d] Unsupport key received: %d (%x)\n", clock(), key, key);
#endif
    }
    return VNCSERV_OK;
    break;
  }
  if (keyno >= 0)     call_key_vector(keyno, down);
  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) {
  if (!flag_support_mouse)   return VNCSERV_OK;
  if (x != mouse_x || y != mouse_y) {
    oswordpointer_position_block pos;

    pos.op = oswordpointer_OP_SET_POSITION;
    pos.x = 2*x;
    pos.y = 2*(screensize_y - y);
    xoswordpointer_set_position(&pos);
    mouse_x = x;
    mouse_y = y;
  }
  if (buttons != mouse_buttons) {
#ifndef BUILD_MODULE
    fprintf(stderr, "[%d] Mouse buttons changed from %x to %x\n", clock(), mouse_buttons, buttons);
#endif
    if ((buttons & 4) != (mouse_buttons & 4)) {
      if (flag_swap_buttons)
        call_key_vector(0x71, buttons & 4);
      else
        call_key_vector(0x72, buttons & 4);
    }
    if ((buttons & 2) != (mouse_buttons & 2)) {
      if (flag_swap_buttons)
        call_key_vector(0x72, buttons & 2);
      else
        call_key_vector(0x71, buttons & 2);
    }
    if ((buttons & 1) != (mouse_buttons & 1))
      call_key_vector(0x70, buttons & 1);
    mouse_buttons = buttons;
  }
  return VNCSERV_OK;
}


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

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

void call_key_vector(int keyno, int down) {
  down = !!down;
  if (pressedkeys[keyno] == down) {
#ifndef BUILD_MODULE
    fprintf(stderr, "[%d] Key (scancode %x) already %s\n", clock(), keyno, down? "down" : "up");
#endif
    return;
  }
  pressedkeys[keyno] = down;
  press_release_key(keyno, down);
#ifndef BUILD_MODULE
  fprintf(stderr, "[%d] %s key (scancode %x)\n", clock(), down?"Pressing":"Releasing", keyno);
#endif
}


int check_area(int y0, int y1) {
  int xc0, xc1, yc0, yc1, y, bpl;
  area bbox;

#define NOT_YET_SET      10000

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

  bpl = (screensize_x*bpp)/8;
  for (y = y0; y < y1; y++) {
    char *scr, *prev;
    int xd0, xd1;
    scr  = screenstart + y*bpl;
    prev = prevscreen + y*bpl;
    compare_screens(&xd0, &xd1, scr, prev, bpl);
    if (xd0 >= 0) {
      xd0 = (xd0*8)/bpp;      // convert from bytes to pixels
      xd1 = (xd1*8)/bpp;
      if (xd0 < xc0)   xc0 = xd0;
      if (xd1 > xc1)   xc1 = xd1;
      if (yc0 == NOT_YET_SET)
        yc0 = yc1 = y;
      else
        yc1 = y;
    }
  }
  if (xc0 == NOT_YET_SET)  return 0;
  if (xc0 < 0)   xc0 = 0;
  if (yc0 < 0)   yc0 = 0;
  if (xc1 > screensize_x)   xc1 = screensize_x;
  if (yc1 > screensize_y)   yc1 = screensize_y;
  bbox.x = xc0;
  bbox.y = yc0;
  bbox.w = xc1-xc0;
  bbox.h = yc1-yc0+1;
  if (bbox.h > screensize_y)   bbox.h = screensize_y;
  pixelssent += bbox.w*bbox.h;
  vncserv_framebuffer_changed(server, &bbox);
  return 0;
}


void poll_sending() {

  int written, thistime, errnox;

  if (buffered == 0)   return;
  thistime = buffered;
  if (thistime > 50000)  thistime = 50000;
  written = send(vncsocket, buffer, thistime, 0);

  if (written < 0) {
    errnox = errno &~IYONIX_SOCKET_ERR;
    if ((errnox != 0) && (errnox != EWOULDBLOCK) && (errnox != EAGAIN)) {
      fatal_error_occured = 1;
    }
#ifndef BUILD_MODULE
    fprintf(stderr, "[%d] Failed to write anything of the %d bytes (error number %d)\n", clock(), thistime, errnox);
#endif
    return;
  }
  bytessent += written;
  buffered -= written;
  buffermark -= written;
  if (buffermark < 0) buffermark = 0;
  if (buffered)    memmove(buffer, buffer+written, buffered);
}
