#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/osmodule.h>
#include <oslib/osword.h>
#include <oslib/taskmanager.h>
#include "mcode.h"

#include "server.h"
#include "header.h"
#include "debug.h"
#include "profiling.h"
#include "frontend.h"
#include "mcode.h"
#include "task.h"
#include "ipservfe.h"
#include "filter.h"

void *private_word = NULL;
int taskhandle = 0; /* 0 if task not started, -1 if starting, else actual task handle */

os_state_t os_state;

static int frontend_start(void *pw);
static int frontend_stop(void *pw);
static void frontend_status();
static servscreen frontend_readvduvars();

extern void __module_header(void); /* Not an actual function */

_kernel_oserror err_bad_arguments_0 = { 1, "Syntax: *vncserv_start <port> <password>" };
_kernel_oserror err_failed_start = { 1, "Failed to start server" };


// ---------------------------------------------------------------------
// module api


_kernel_oserror *module_init(const char *tail, int base, void *pw) {
  private_word = pw;

  os_state.pointer_shapes[0].w = os_state.pointer_shapes[0].h = 1; /* Empty pointer image for when we need to disable it */
  os_state.myscreen = frontend_readvduvars(); /* Assume we're not redirected to a sprite on startup */
  POINTER_BBOX_RESET(&os_state.pointer_bbox);

  debug_init();
  profile_init();
  os_error *err;
  err = xos_claim(WordV,osword_entry,pw);
  if(!err) err = xos_claim(ByteV,osbyte_entry,pw);
  if(!err) err = xos_claim(PaletteV,palettev_entry,pw);
  return (_kernel_oserror *) err;
}



_kernel_oserror *module_finalise(int fatal, int base, void *pw) {
  os_error *err;
  frontend_stop(pw);
  err = task_shutdown();
  if(!err) err = xos_release(WordV,osword_entry,pw);
  if(!err) err = xos_release(ByteV,osbyte_entry,pw);
  if(!err) err = xos_release(PaletteV,palettev_entry,pw);
  if(!err) debug_finalise();
  return (_kernel_oserror *) err;
}

void module_service(int service, _kernel_swi_regs *r, void *pw) {
  switch (service) {
  case Service_Memory:
    if (r->r[2] == (void*)__module_header)
    {
      r->r[1] = 0;
    }
    break;
  case Service_ModeChange:
    POINTER_BBOX_RESET(&os_state.pointer_bbox);
    /* Update our idea of what the OS screen mode is */
    os_state.myscreen = frontend_readvduvars();
    FOR_EACH_CLIENT(client)
    {
      server_checkmode(client);
    }
    break;
  case Service_SwitchingOutputToSprite:
    os_state.sprite_redirection = (r->r[2+2] != 0); /* Redirected to sprite if OS_SpriteOp R2 != 0 */
    break;
  }
}

static void toggle_task()
{
  if (!global_config.enable_clipboard)
  {
    task_clipboard_release(NULL);
  }
  if (global_config.enable_clipboard || global_config.enable_filter)
  {
    task_start();
  }
  else
  {
    task_shutdown();
  }
}

_kernel_oserror *command_handler(const char *args, int argc, int cmdno, void *pw) {
  os_error *err;

  switch (cmdno) {
  case CMD_vncserv_start: // *vncserv_start
    frontend_stop(pw);
    if (frontend_start(pw))
    {
      return &err_failed_start;
    }
    return NULL;

  case CMD_vncserv_stop: // *vncserv_stop
    frontend_stop(pw);
    break;

  case CMD_vncserv_status: // *vncserv_status
    frontend_status();
    break;

  case CMD_vncserv_start_task:
    if (taskhandle == 0)
    {
      taskhandle = -1;
    }
    else if (taskhandle != -1)
    {
      return NULL;
    }
    if (!task_should_run())
    {
      printf("Wimp task not needed - clipboard and filter manager integration are disabled\n");
      taskhandle = 0;
      return NULL;
    }
    err = xosmodule_enter(Module_Title, "");
    if (err)
    {
      taskhandle = 0;
      return (_kernel_oserror *) err;
    }
    break;

  case CMD_vncserv_config: // *vncserv_config <file>
    err = (os_error *) config_load(args);
    if (err)
    {
      return (_kernel_oserror *) err;
    }
    if (global_config.enable_input)
    {
      /* Make sure the kernel's keyboard driver is configured, otherwise many
         special keys won't work correctly (this case is only likely to be hit
         if the machine has booted without a keyboard connected) */
      int keyboardtype;
      if (!_swix(OS_InstallKeyHandler,_IN(0)|_OUT(0),1,&keyboardtype) && (keyboardtype == 255))
      {
        /* Pretend there's a standard PC keyboard present, should be good enough until a real keyboard is connected */
        _swix(OS_CallAVector,_INR(0,2)|_IN(9),KeyV_Present,2,0x4e6f4b64,KeyV);
      }
    }
    if (ipservfe_haveclients())
    {
      if (global_config.enable_filter)
      {
        filter_init();
      }
      else
      {
        filter_shutdown();
      }
    }
    toggle_task();
    if (ipservfe_running())
    {
      ipservfe_reconfigure();
    }
    
    break;
  }
  return NULL;      // or pointer to error-block
}


// ---------------------------------------------------------------------
// handlers and stuff

int osword_handler(_kernel_swi_regs *r, void *pw) {
  if (r->r[0] != OSWord_Pointer)
  {
    return VECTOR_PASSON;
  }
  /* n.b. can't use OSLib type here due to unaligned pointer in struct */
  typedef struct {
    unsigned char type;
    unsigned char shape;
    unsigned char w,h,x,y;
    unsigned char p0,p1,p2,p3;
  } pointer_block;
  pointer_block *p = (pointer_block *) r->r[1];
  if (p->type == oswordpointer_OP_DEFINE)
  {
    if ((p->shape < 1) || (p->shape > 4))
    {
      return VECTOR_PASSON;
    }
    /* Remember mouse data */
    char *dat;
    dat = (char *) (p->p0 | (p->p1 << 8) | (p->p2 << 16) | (p->p3 << 24));
    pointer_shape *s = &os_state.pointer_shapes[p->shape];
    s->w = p->w;
    s->h = p->h;
    s->x = p->x;
    s->y = p->y;
    if(dat)
      memcpy(s->data,dat,p->w*p->h);
    if(os_state.current_pointer == p->shape)
    {
      FOR_EACH_CLIENT(client)
      {
        server_set_cursor(client, s);
      }
    }
  }
  else if (p->type == oswordpointer_OP_SET_BBOX)
  {
    /* Remember bounding box */
    pointer_bbox_t box;
    memcpy(&box, (&p->type)+1, sizeof(pointer_bbox_t));
//    dprintf("bbox: %d,%d %d,%d\n",box.left,box.bottom,box.right,box.top);
    if ((box.left <= box.right) && (box.bottom <= box.top))
    {
      os_state.pointer_bbox = box;
    }
  }
  return VECTOR_PASSON;
}

int osbyte_handler(_kernel_swi_regs *r, void *pw) {
  if(r->r[0] != osbyte_SELECT_POINTER)
    return VECTOR_PASSON;
  int p = r->r[1] & 7;
  if(p > 4)
    return VECTOR_PASSON;
  if(p != os_state.current_pointer)
  {
    os_state.current_pointer = p;
    pointer_shape *s = &os_state.pointer_shapes[p];
    FOR_EACH_CLIENT(client)
    {
      server_set_cursor(client, s);
    }
  }
  return VECTOR_PASSON;
}

int palettev_handler(_kernel_swi_regs *r, void *pw) {
  if(r->r[4] == 2)
    os_state.palette_dirty = true;
  return VECTOR_PASSON;
}

// ---------------------------------------------------------------------
// frontend

int frontend_start(void *pw) {
  if (!ipservfe_create())
  {
    return 1;
  }
  task_start();
  return 0;
}


int frontend_stop(void *pw) {
  ipservfe_stop();
  return 0;
}

void print_format(const PIXEL_FORMAT *pf,const char *prefix)
{
  printf("%s%u BPP %s (%u used bits, %s-endian)\n",prefix,pf->bits_per_pixel,(pf->true_colour_flag?"true colour":"paletted"),pf->depth,(pf->big_endian_flag?"big":"little"));
  if (pf->true_colour_flag)
  {
    printf("%sRed max %u shift %u\n",prefix,pf->red_max,pf->red_shift);
    printf("%sGreen max %u shift %u\n",prefix,pf->green_max,pf->green_shift);
    printf("%sBlue max %u shift %u\n",prefix,pf->blue_max,pf->blue_shift);
  }
}

void print_screen(const servscreen *scr,const char *prefix)
{
  printf("%sFramebuffer at %p\n",prefix,scr->framebuffer);
  printf("%sWidth %d height %d\n",prefix,scr->width,scr->height);
  printf("%sBPP %d BPL %d stride %d\n",prefix,scr->format.bits_per_pixel,scr->bpl,scr->stride);
  printf("%sxeig %d yeig %d\n",prefix,scr->xeig,scr->yeig);
}

void frontend_status() {
  printf("Configuration:\n");
  printf("  Nudge mouse: %s\n",YN(!global_config.dont_nudge_mouse));
  printf("  Stretch key presses: %s\n",YN(global_config.stretch_keys));
  printf("  Use filter manager: %s\n",YN(global_config.enable_filter));
  printf("  Multiple connections: %s\n",YN(global_config.multiple_connections));
  printf("  Broadcast clipboard: %s\n",YN(global_config.broadcast_clipboard));
  int count;
  const server_config_t *configs = configs_get(&count);
  for(int i=0;i<count;i++)
  {
    printf("  Server:\n");
    printf("    Port: %d\n",configs[i].port);
    printf("    Password: %s\n",YN(configs[i].password[0]));
    printf("    Name: %s\n",configs[i].name);
    printf("    Mouse support: %s\n",EN(configs[i].enable_mouse));
    printf("    Keyboard support: %s\n",EN(configs[i].enable_kbd));
    printf("    Swap Menu/Adjust: %s\n",YN(configs[i].swap_adjust_menu));
    printf("    Preferred cursor encoding: %s\n",(configs[i].prefer_xcursor < 0 ? "Cursor" : (configs[i].prefer_xcursor ? "XCursor" : "Auto")));
    printf("    Clipboard support: %s\n",YN(configs[i].enable_clipboard));
    printf("    zlib compression level: %d\n",configs[i].zlib_level);
    printf("    Allow exclusive: %s\n",YN(configs[i].allow_exclusive));
  }
  if (!count)
  {
    printf("  No servers configured\n");
  }

  printf("\nOS state:\n");
  printf("  Mouse: %d,%d buttons %d\n",os_state.mouse_x,os_state.mouse_y,os_state.mouse_buttons);
  printf("  BBox: %d,%d %d,%d\n",os_state.pointer_bbox.left,os_state.pointer_bbox.bottom,os_state.pointer_bbox.right,os_state.pointer_bbox.top);
  print_screen(&os_state.myscreen,"  ");
  printf("  Screen redirected to sprite: %s\n",YN(os_state.sprite_redirection));

  printf("\nWimp task status:\n");
  if (taskhandle == 0)
  {
    printf("  Not running\n");
  }
  else if (taskhandle == -1)
  {
    printf("  Starting\n");
  }
  else
  {
    printf("  Running, handle %08x\n",taskhandle);
  }

  ipservfe_status();
}

servscreen frontend_readvduvars()
{
  /* Read current VDU vars and translate into a servscreen struct */
  int vars[10], vals[10];
  vars[0] = os_MODEVAR_MODE_FLAGS;
  vars[1] = os_MODEVAR_LOG2_BPP;
  vars[2] = os_MODEVAR_NCOLOUR;
  vars[3] = os_MODEVAR_LINE_LENGTH;
  vars[4] = os_MODEVAR_XWIND_LIMIT;
  vars[5] = os_MODEVAR_YWIND_LIMIT;
  vars[6] = os_VDUVAR_DISPLAY_START;
  vars[7] = os_MODEVAR_XEIG_FACTOR;
  vars[8] = os_MODEVAR_YEIG_FACTOR;
  vars[9] = -1;
  xos_read_vdu_variables((os_vdu_var_list *)vars, vals);

  servscreen newscreen;
  memset(&newscreen,0,sizeof(newscreen));
  newscreen.framebuffer = (void *) vals[6];
  newscreen.width = vals[4]+1;
  newscreen.height = vals[5]+1;
  newscreen.bpl = ((newscreen.width << vals[1]) + 7) >> 3;
  newscreen.stride = vals[3];
  newscreen.xeig = vals[7];
  newscreen.yeig = vals[8];

  /* Deduce pixel format:
     https://www.riscosopen.org/wiki/documentation/show/Valid%20Mode%20Variable%20Combinations
   */

  newscreen.format.bits_per_pixel = 1<<vals[1];
  newscreen.format.depth = 1<<vals[1];
  newscreen.format.true_colour_flag = (vals[1] >= 4);

  switch(vals[1])
  {
  case 0:
  case 1:
  case 2:
  case 3:
    /* Paletted */
    break;
  case 4:
    if (vals[2] == 4095)
    {
      /* 4K */
      newscreen.format.depth = 12;
      newscreen.format.red_max = newscreen.format.green_max = newscreen.format.blue_max = 15;
      newscreen.format.red_shift = 0;
      newscreen.format.green_shift = 4;
      newscreen.format.blue_shift = 8;
    }
    else if (vals[0] & (1<<7))
    {
      /* 64K */
      newscreen.format.red_max = newscreen.format.blue_max = 31;
      newscreen.format.green_max = 63;
      newscreen.format.red_shift = 0;
      newscreen.format.green_shift = 5;
      newscreen.format.blue_shift = 11;
    }
    else
    {
      /* 32K */
      newscreen.format.depth = 15;
      newscreen.format.red_max = newscreen.format.green_max = newscreen.format.blue_max = 31;
      newscreen.format.red_shift = 0;
      newscreen.format.green_shift = 5;
      newscreen.format.blue_shift = 10;
    }
    break;
  case 5:
    /* 16M */
    newscreen.format.depth = 24;
    newscreen.format.red_max = newscreen.format.green_max = newscreen.format.blue_max = 255;
    newscreen.format.red_shift = 0;
    newscreen.format.green_shift = 8;
    newscreen.format.blue_shift = 16;
    break;
  }
  if (vals[0] & (1<<14))
  {
    /* Red/blue swapped mode */
    newscreen.format.red_shift = newscreen.format.blue_shift;
    newscreen.format.blue_shift = 0;
  }

  /* Now work out the desired format for the client to use. This is basically the current format, except paletted modes will be bumped up to 8bpp */
  newscreen.desiredformat = newscreen.format;
  if (newscreen.desiredformat.bits_per_pixel < 8)
    newscreen.desiredformat.bits_per_pixel = 8;

  return newscreen;
}
