#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <oslib/os.h>
#include <oslib/osword.h>
#include "debug.h"
#include "ipserv.h"
#include "proto2.h"
#include "ipservfe.h"
#include "header.h"
#include "server.h"
#include "profiling.h"
#include "task.h"
#include "filter.h"

#define MOUSE_NUDGE_INTERVAL 25

typedef struct
{
  int count;
  ipserv *servers[0];
} ipserver_servers_t;

static ipserver_servers_t no_servers;

typedef struct
{
  ipserver_servers_t *servers;

  bool callback_waiting;
  int last_callback_time;

  /* These two must only be written with IRQs disabled, to protect accesses made by tickerv_handler */
  int num_clients;
  vnc_client_t **clients;
} ipserver_state_t;

static ipserver_state_t state;

static void stop_servers(ipserver_servers_t *servers)
{
  if (!servers || servers == &no_servers)
  {
    return;
  }
  while(servers->count)
  {
    int i = --servers->count;
    ipserv_close(servers->servers[i]);
  }
  free(servers);
}

static ipserver_servers_t *create_servers()
{
  int num_configs;
  const server_config_t *configs = configs_get(&num_configs);

  if (!num_configs)
  {
    dprintf("ipservfe_create(): No configs\n");
    return NULL;
  }

  ipserver_servers_t *servers = (ipserver_servers_t *) malloc(sizeof(ipserver_servers_t) + (sizeof(ipserv*) * num_configs));
  servers->count = 0;
  while(servers->count < num_configs)
  {
    servers->servers[servers->count] = ipserv_create(&configs[servers->count]);
    if (!servers->servers[servers->count])
    {
      stop_servers(servers);
      return NULL;
    }
    servers->count++;
  }

  return servers;
}

bool ipservfe_create()
{
  if (state.servers)
  {
    dprintf("ipservfe_create(): Already active\n");
    return false;
  }

  state.servers = create_servers();
  if (!state.servers)
  {
    ipservfe_stop();
    return false;
  }

  state.last_callback_time = clock();

  if (xos_claim(TickerV, tickerv_entry, private_word))
  {
    dprintf("ipservfe_create(): Failed to register with TickerV\n");
    ipservfe_stop();
    return false;
  }

  return true;
}

static void destroy_client(int i)
{
  vnc_client_t *client = state.clients[i];
  _kernel_irqs_off();
  state.clients[i] = state.clients[--state.num_clients];
  _kernel_irqs_on();
  server_close(client);
  if (!state.num_clients)
  {
    filter_shutdown();
  }
}

void ipservfe_stop()
{
  if (state.clients)
  {
    while (state.num_clients > 0)
    {
      destroy_client(state.num_clients-1);
    }
    free(state.clients);
    state.clients = NULL;
    state.num_clients = 0;
  }

  xos_release(TickerV, tickerv_entry, private_word);
  if (state.callback_waiting)
  {
    xos_remove_call_back((void *) callback, private_word);
    state.callback_waiting = false;
  }

  stop_servers(state.servers);
  state.servers = NULL;
}

void ipservfe_reconfigure()
{
  /* Take the easy route and just destroy & recreate the listener sockets */
  stop_servers(state.servers);
  state.servers = create_servers();
  if (!state.servers)
  {
    /* Set to dummy server list so that we still consider the server to be active (TickerV will still be running, and there may still be clients) */
    state.servers = &no_servers;
    printf("Warning: Failed to reinitialise server\n");
  }
}

bool ipservfe_running()
{
  return (state.servers != NULL);
}

bool ipservfe_haveclients()
{
  return (state.num_clients > 0);
}

vnc_client_t *ipservfe_clients(vnc_client_t ***iter)
{
  if (!state.num_clients)
  {
    return NULL;
  }
  if (*iter == NULL)
  {
    *iter = state.clients;
    return state.clients[0];
  }
  unsigned int i = (*iter) - state.clients;
  i++;
  if (i >= state.num_clients)
  {
    return NULL;
  }
  *iter = state.clients + i;
  return state.clients[i];
}

void ipservfe_disconnect_all_but(vnc_client_t *client)
{
  for(int i=state.num_clients-1;i>=0;i--)
  {
    if (state.clients[i] != client)
    {
      destroy_client(i);
    }
  }
}

unsigned int ipservfe_last_callback_delta()
{
  int now = clock();
  unsigned int delta = now - state.last_callback_time;
  return delta;
}

void ipservfe_status()
{
  if (state.servers == &no_servers)
  {
    printf("\nIP server is broken\n");
  }
  else
  {
    printf("\nIP server running: %s\n",YN(state.servers));
  }
  if (!state.num_clients)
  {
    printf("  No client connections\n");
  }
  for(int i=0;i<state.num_clients;i++)
  {
    printf("\nClient %p:\n",state.clients[i]);
    server_status(state.clients[i]);
  }
}

int tickerv_handler(_kernel_swi_regs *r, void *pw) {
  int now = clock();
  unsigned int delta = now - state.last_callback_time;
  if ((delta > MOUSE_NUDGE_INTERVAL) && !global_config.dont_nudge_mouse && state.num_clients) {
    oswordpointer_position_block pos;
    for(int i=0;i<state.num_clients;i++)
    {
      // release mouse keys
      server_release_key(state.clients[i], 0x70);
      server_release_key(state.clients[i], 0x71);
      server_release_key(state.clients[i], 0x72);
      // release shift (OS trying to scroll screen while shift+ctrl held by VNC will block callbacks)
      server_release_key(state.clients[i], os_TRANSITION_KEY_LEFT_SHIFT);
      server_release_key(state.clients[i], os_TRANSITION_KEY_RIGHT_SHIFT);
    }

    pos.op = oswordpointer_OP_READ_POSITION;
    xoswordpointer_read_position(&pos);
    pos.op = oswordpointer_OP_SET_POSITION;
    pos.y += 1<<os_state.myscreen.yeig;
    xoswordpointer_set_position(&pos);
    pos.op = oswordpointer_OP_SET_POSITION;
    pos.y -= 1<<os_state.myscreen.yeig;
    xoswordpointer_set_position(&pos);
    state.last_callback_time = now;
  }
  if (!state.callback_waiting)
  {
    if (!xos_add_call_back((void *)callback, pw))
    {
      state.callback_waiting = true;
    }
  }
  return VECTOR_PASSON;
}

void accept_connections(ipserver_servers_t *servers) {
  if (!servers)
  {
    return;
  }

  /* Check for incoming connections */
  for(int i=0;i<servers->count;i++)
  {
    socket_s newsocket;
    if (!ipserv_accept(servers->servers[i], &newsocket))
    {
      while (!global_config.multiple_connections && state.num_clients)
      {
        destroy_client(state.num_clients-1);
      }

      /* Use malloc instead of realloc, to protect any array access by IRQ handlers */
      vnc_client_t **clients = (vnc_client_t **) malloc(sizeof(vnc_client_t *) * (state.num_clients+1));
      if (!clients)
      {
        dprintf("Fatal error, not enough memory for new client\n");
        xsocket_close(newsocket);
      }
      else
      {
        memcpy(clients, state.clients, sizeof(vnc_client_t *) * state.num_clients);
        clients[state.num_clients] = server_create(newsocket, ipserv_config(servers->servers[i]));
        _kernel_irqs_off();
        vnc_client_t **old = state.clients;
        state.clients = clients;
        state.num_clients++;
        _kernel_irqs_on();
        if (old)
        {
          free(old);
        }

        if (global_config.enable_filter)
        {
          filter_init();
        }
      }
    }
  }
}

int callback_handler(_kernel_swi_regs *r, void *pw) {
  state.last_callback_time = clock();
  debug_init(); // refresh debug state

  accept_connections(state.servers);

  for(int i=state.num_clients-1;i>=0;i--)
  {
    vnc_client_t *client = state.clients[i];
    if (!server_has_fatal_error_occurred(client))
    {
      server_update(client);
    }
    if (server_has_fatal_error_occurred(client))
    {
      dprintf("Fatal error, closing client connection %p\n",client);
      destroy_client(i);
    }
  }

  profile_update();
  state.last_callback_time = clock();
  state.callback_waiting = false;
  return 1;                   // always return a value != 0 !!!!!
}
